github.com/oweisse/u-root@v0.0.0-20181109060735-d005ad25fef1/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 // 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 MTime time.Time 35 SymlinkTarget string 36 } 37 38 func FromOSFileInfo(path 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(path); 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 MTime: fi.ModTime(), 58 SymlinkTarget: link, 59 } 60 } 61 62 // Name returns a printable file name. 63 func (fi FileInfo) PrintableName() string { 64 return unprintableRe.ReplaceAllLiteralString(fi.Name, "?") 65 } 66 67 // Without this cache, `ls -l` is orders of magnitude slower. 68 var ( 69 uidCache = map[uint32]string{} 70 gidCache = map[uint32]string{} 71 ) 72 73 // Convert uid to username, or return uid on error. 74 func lookupUserName(id uint32) string { 75 if s, ok := uidCache[id]; ok { 76 return s 77 } 78 s := fmt.Sprint(id) 79 if u, err := user.LookupId(s); err == nil { 80 s = u.Username 81 } 82 uidCache[id] = s 83 return s 84 } 85 86 // Convert gid to group name, or return gid on error. 87 func lookupGroupName(id uint32) string { 88 if s, ok := gidCache[id]; ok { 89 return s 90 } 91 s := fmt.Sprint(id) 92 if g, err := user.LookupGroupId(s); err == nil { 93 s = g.Name 94 } 95 gidCache[id] = s 96 return s 97 } 98 99 // Stringer provides a consistent way to format FileInfo. 100 type Stringer interface { 101 // FileString formats a FileInfo. 102 FileString(fi FileInfo) string 103 } 104 105 // NameStringer is a Stringer implementation that just prints the name. 106 type NameStringer struct{} 107 108 // FileString implements Stringer.FileString and just returns fi's name. 109 func (ns NameStringer) FileString(fi FileInfo) string { 110 return fi.PrintableName() 111 } 112 113 // QuotedStringer is a Stringer that returns the file name surrounded by qutoes 114 // with escaped control characters. 115 type QuotedStringer struct{} 116 117 // FileString returns the name surrounded by quotes with escaped control characters. 118 func (qs QuotedStringer) FileString(fi FileInfo) string { 119 return fmt.Sprintf("%#v", fi.Name) 120 } 121 122 // LongStringer is a Stringer that returns the file info formatted in `ls -l` 123 // long format. 124 type LongStringer struct { 125 Human bool 126 Name Stringer 127 } 128 129 // FileString implements Stringer.FileString. 130 func (ls LongStringer) FileString(fi FileInfo) string { 131 // Golang's FileMode.String() is almost sufficient, except we would 132 // rather use b and c for devices. 133 replacer := strings.NewReplacer("Dc", "c", "D", "b") 134 135 // Ex: crw-rw-rw- root root 1, 3 Feb 6 09:31 null 136 pattern := "%[1]s\t%[2]s\t%[3]s\t%[4]d, %[5]d\t%[7]v\t%[8]s" 137 if fi.Mode&os.ModeDevice == 0 && fi.Mode&os.ModeCharDevice == 0 { 138 // Ex: -rw-rw---- myuser myuser 1256 Feb 6 09:31 recipes.txt 139 pattern = "%[1]s\t%[2]s\t%[3]s\t%[6]s\t%[7]v\t%[8]s" 140 } 141 142 var size string 143 if ls.Human { 144 size = humanize.Bytes(uint64(fi.Size)) 145 } else { 146 size = strconv.FormatInt(fi.Size, 10) 147 } 148 149 s := fmt.Sprintf(pattern, 150 replacer.Replace(fi.Mode.String()), 151 lookupUserName(fi.UID), 152 lookupGroupName(fi.GID), 153 unix.Major(fi.Rdev), 154 unix.Minor(fi.Rdev), 155 size, 156 fi.MTime.Format("Jan _2 15:04"), 157 ls.Name.FileString(fi)) 158 159 if fi.Mode&os.ModeType == os.ModeSymlink { 160 s += fmt.Sprintf(" -> %v", fi.SymlinkTarget) 161 } 162 return s 163 }