github.com/xyproto/u-root@v6.0.1-0.20200302025726-5528e0c77a3c+incompatible/pkg/ls/fileinfo_unix.go (about) 1 // Copyright 2017 the u-root Authors. All rights reserved 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package ls 6 7 import ( 8 "fmt" 9 "os" 10 "os/user" 11 "regexp" 12 "strconv" 13 "strings" 14 "syscall" 15 "time" 16 17 humanize "github.com/dustin/go-humanize" 18 "golang.org/x/sys/unix" 19 ) 20 21 // Matches characters which would interfere with ls's formatting. 22 var unprintableRe = regexp.MustCompile("[[:cntrl:]\n]") 23 24 // FileInfo holds file metadata. 25 // 26 // Since `os.FileInfo` is an interface, it is difficult to tweak some of its 27 // internal values. For example, replacing the starting directory with a dot. 28 // `extractImportantParts` populates our own struct which we can modify at will 29 // before printing. 30 type FileInfo struct { 31 Name string 32 Mode os.FileMode 33 Rdev uint64 34 UID, GID uint32 35 Size int64 36 MTime time.Time 37 SymlinkTarget string 38 } 39 40 // FromOSFileInfo converts os.FileInfo to an ls.FileInfo. 41 func FromOSFileInfo(path string, fi os.FileInfo) FileInfo { 42 var link string 43 44 s := fi.Sys().(*syscall.Stat_t) 45 if fi.Mode()&os.ModeType == os.ModeSymlink { 46 if l, err := os.Readlink(path); err != nil { 47 link = err.Error() 48 } else { 49 link = l 50 } 51 } 52 53 return FileInfo{ 54 Name: fi.Name(), 55 Mode: fi.Mode(), 56 Rdev: uint64(s.Rdev), 57 UID: s.Uid, 58 GID: s.Gid, 59 Size: fi.Size(), 60 MTime: fi.ModTime(), 61 SymlinkTarget: link, 62 } 63 } 64 65 // PrintableName returns a printable file name. 66 func (fi FileInfo) PrintableName() string { 67 return unprintableRe.ReplaceAllLiteralString(fi.Name, "?") 68 } 69 70 // Without this cache, `ls -l` is orders of magnitude slower. 71 var ( 72 uidCache = map[uint32]string{} 73 gidCache = map[uint32]string{} 74 ) 75 76 // Convert uid to username, or return uid on error. 77 func lookupUserName(id uint32) string { 78 if s, ok := uidCache[id]; ok { 79 return s 80 } 81 s := fmt.Sprint(id) 82 if u, err := user.LookupId(s); err == nil { 83 s = u.Username 84 } 85 uidCache[id] = s 86 return s 87 } 88 89 // Convert gid to group name, or return gid on error. 90 func lookupGroupName(id uint32) string { 91 if s, ok := gidCache[id]; ok { 92 return s 93 } 94 s := fmt.Sprint(id) 95 if g, err := user.LookupGroupId(s); err == nil { 96 s = g.Name 97 } 98 gidCache[id] = s 99 return s 100 } 101 102 // Stringer provides a consistent way to format FileInfo. 103 type Stringer interface { 104 // FileString formats a FileInfo. 105 FileString(fi FileInfo) string 106 } 107 108 // NameStringer is a Stringer implementation that just prints the name. 109 type NameStringer struct{} 110 111 // FileString implements Stringer.FileString and just returns fi's name. 112 func (ns NameStringer) FileString(fi FileInfo) string { 113 return fi.PrintableName() 114 } 115 116 // QuotedStringer is a Stringer that returns the file name surrounded by qutoes 117 // with escaped control characters. 118 type QuotedStringer struct{} 119 120 // FileString returns the name surrounded by quotes with escaped control characters. 121 func (qs QuotedStringer) FileString(fi FileInfo) string { 122 return fmt.Sprintf("%#v", fi.Name) 123 } 124 125 // LongStringer is a Stringer that returns the file info formatted in `ls -l` 126 // long format. 127 type LongStringer struct { 128 Human bool 129 Name Stringer 130 } 131 132 // FileString implements Stringer.FileString. 133 func (ls LongStringer) FileString(fi FileInfo) string { 134 // Golang's FileMode.String() is almost sufficient, except we would 135 // rather use b and c for devices. 136 replacer := strings.NewReplacer("Dc", "c", "D", "b") 137 138 // Ex: crw-rw-rw- root root 1, 3 Feb 6 09:31 null 139 pattern := "%[1]s\t%[2]s\t%[3]s\t%[4]d, %[5]d\t%[7]v\t%[8]s" 140 if fi.Mode&os.ModeDevice == 0 && fi.Mode&os.ModeCharDevice == 0 { 141 // Ex: -rw-rw---- myuser myuser 1256 Feb 6 09:31 recipes.txt 142 pattern = "%[1]s\t%[2]s\t%[3]s\t%[6]s\t%[7]v\t%[8]s" 143 } 144 145 var size string 146 if ls.Human { 147 size = humanize.Bytes(uint64(fi.Size)) 148 } else { 149 size = strconv.FormatInt(fi.Size, 10) 150 } 151 152 s := fmt.Sprintf(pattern, 153 replacer.Replace(fi.Mode.String()), 154 lookupUserName(fi.UID), 155 lookupGroupName(fi.GID), 156 unix.Major(fi.Rdev), 157 unix.Minor(fi.Rdev), 158 size, 159 fi.MTime.Format("Jan _2 15:04"), 160 ls.Name.FileString(fi)) 161 162 if fi.Mode&os.ModeType == os.ModeSymlink { 163 s += fmt.Sprintf(" -> %v", fi.SymlinkTarget) 164 } 165 return s 166 }