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