gopkg.in/hugelgupf/u-root.v9@v9.0.0-20180831063832-3f6f1057f09b/cmds/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 main 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 // Since `os.FileInfo` is an interface, it is difficult to tweak some of its 25 // internal values. For example, replacing the starting directory with a dot. 26 // `extractImportantParts` populates our own struct which we can modify at will 27 // before printing. 28 type fileInfo struct { 29 name string 30 mode os.FileMode 31 rdev uint64 32 uid, gid uint32 33 size int64 34 modTime time.Time 35 symlink string 36 } 37 38 func extractImportantParts(n string, fi os.FileInfo) fileInfo { 39 var link string 40 41 s := fi.Sys().(*syscall.Stat_t) 42 if fi.Mode()&os.ModeType == os.ModeSymlink { 43 if l, err := os.Readlink(n); err != nil { 44 link = err.Error() 45 } else { 46 link = l 47 } 48 } 49 50 return fileInfo{ 51 name: fi.Name(), 52 mode: fi.Mode(), 53 rdev: uint64(s.Rdev), 54 uid: s.Uid, 55 gid: s.Gid, 56 size: fi.Size(), 57 modTime: fi.ModTime(), 58 symlink: link, 59 } 60 } 61 62 // Without this cache, `ls -l` is orders of magnitude slower. 63 var ( 64 uidCache = map[uint32]string{} 65 gidCache = map[uint32]string{} 66 ) 67 68 // Convert uid to username, or return uid on error. 69 func lookupUserName(id uint32) string { 70 if s, ok := uidCache[id]; ok { 71 return s 72 } 73 s := fmt.Sprint(id) 74 if u, err := user.LookupId(s); err == nil { 75 s = u.Username 76 } 77 uidCache[id] = s 78 return s 79 } 80 81 // Convert gid to group name, or return gid on error. 82 func lookupGroupName(id uint32) string { 83 if s, ok := gidCache[id]; ok { 84 return s 85 } 86 s := fmt.Sprint(id) 87 if g, err := user.LookupGroupId(s); err == nil { 88 s = g.Name 89 } 90 gidCache[id] = s 91 return s 92 } 93 94 // The default stringer. Return only the filename. Unprintable characters are 95 // replaced with '?'. 96 func (fi fileInfo) String() string { 97 return unprintableRe.ReplaceAllLiteralString(fi.name, "?") 98 } 99 100 // Two alternative stringers 101 type quotedStringer struct { 102 fileInfo 103 } 104 type longStringer struct { 105 fileInfo 106 comp fmt.Stringer // decorator pattern 107 human bool 108 } 109 110 // Return the name surrounded by quotes with escaped control characters. 111 func (fi quotedStringer) String() string { 112 return fmt.Sprintf("%#v", fi.name) 113 } 114 115 // The long and quoted stringers can be combined like so: 116 // longStringer{fi, quotedStringer{fi}} 117 func (fi longStringer) String() string { 118 // Golang's FileMode.String() is almost sufficient, except we would 119 // rather use b and c for devices. 120 replacer := strings.NewReplacer("Dc", "c", "D", "b") 121 122 // Ex: crw-rw-rw- root root 1, 3 Feb 6 09:31 null 123 pattern := "%[1]s\t%[2]s\t%[3]s\t%[4]d, %[5]d\t%[7]v\t%[8]s" 124 var size string 125 if fi.mode&os.ModeDevice == 0 && fi.mode&os.ModeCharDevice == 0 { 126 // Ex: -rw-rw---- myuser myuser 1256 Feb 6 09:31 recipes.txt 127 pattern = "%[1]s\t%[2]s\t%[3]s\t%[6]s\t%[7]v\t%[8]s" 128 } 129 130 if fi.human { 131 size = humanize.Bytes(uint64(fi.size)) 132 } else { 133 size = strconv.FormatInt(fi.size, 10) 134 } 135 136 s := fmt.Sprintf(pattern, 137 replacer.Replace(fi.mode.String()), 138 lookupUserName(fi.uid), 139 lookupGroupName(fi.gid), 140 unix.Major(fi.rdev), 141 unix.Minor(fi.rdev), 142 size, 143 fi.modTime.Format("Jan _2 15:04"), 144 fi.comp.String()) 145 146 if fi.mode&os.ModeType == os.ModeSymlink { 147 s += fmt.Sprintf(" -> %v", fi.symlink) 148 } 149 return s 150 }