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