github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/cmds/core/ls/ls.go (about) 1 // Copyright 2013-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 // ls prints the contents of a directory. 6 // 7 // Synopsis: 8 // 9 // ls [OPTIONS] [DIRS]... 10 // 11 // Options: 12 // 13 // -l: long form 14 // -Q: quoted 15 // -R: equivalent to findutil's find 16 // -F: append indicator (one of */=>@|) to entries 17 // 18 // Bugs: 19 // 20 // With the `-R` flag, directories are only ever printed once. 21 package main 22 23 import ( 24 "errors" 25 "fmt" 26 "io" 27 "log" 28 "os" 29 "path/filepath" 30 "sort" 31 "text/tabwriter" 32 33 flag "github.com/spf13/pflag" 34 "github.com/mvdan/u-root-coreutils/pkg/ls" 35 ) 36 37 var ( 38 all = flag.BoolP("all", "a", false, "show hidden files") 39 human = flag.BoolP("human-readable", "h", false, "human readable sizes") 40 directory = flag.BoolP("directory", "d", false, "list directories but not their contents") 41 long = flag.BoolP("long", "l", false, "long form") 42 quoted = flag.BoolP("quote-name", "Q", false, "quoted") 43 recurse = flag.BoolP("recursive", "R", false, "equivalent to findutil's find") 44 classify = flag.BoolP("classify", "F", false, "append indicator (one of */=>@|) to entries") 45 size = flag.BoolP("size", "S", false, "sort by size") 46 ) 47 48 // file describes a file, its name, attributes, and the error 49 // accessing it, if any. 50 // 51 // Any such description must take into account the inherently 52 // racy nature of a file system. Can a file which exists in one 53 // instant vanish in another instant? Yes. Can we get into situations 54 // in which ls might never terminate? Yes (seen in HPC systems). 55 // If our consumer (ls) is slow enough, and our producer (thousands of 56 // compute nodes) is fast enough, an ls can take *hours*. 57 // 58 // Hence, file must include the path name (since a file can vanish, 59 // the stat might then fail, so using the fileinfo will not work) 60 // and must include an error (since the file may cease to exist). 61 // It is possible, for example, to do 62 // ls /a /b /c 63 // and between the time the command is typed, some or all of these 64 // files might vanish. Users wish to know of this situation: 65 // $ ls /a /b /tmp 66 // ls: /a: No such file or directory 67 // ls: /b: No such file or directory 68 // ls: /c: No such file or directory 69 // ls is more complex than it appears at first. 70 // TODO: do we really need BOTH osfi and lsfi? 71 // This may be required on non-unix systems like Plan 9 but it 72 // would be nice to make sure. 73 type file struct { 74 path string 75 osfi os.FileInfo 76 lsfi ls.FileInfo 77 err error 78 } 79 80 func listName(stringer ls.Stringer, d string, w io.Writer, prefix bool) error { 81 var files []file 82 83 filepath.Walk(d, func(path string, osfi os.FileInfo, err error) error { 84 f := file{ 85 path: path, 86 osfi: osfi, 87 } 88 89 // error handling that matches standard ls is ... a real joy 90 if !errors.Is(err, os.ErrNotExist) { 91 f.lsfi = ls.FromOSFileInfo(path, osfi) 92 if err != nil && path == d { 93 f.err = err 94 } 95 } else { 96 f.err = err 97 } 98 99 files = append(files, f) 100 101 if err != nil { 102 return filepath.SkipDir 103 } 104 105 if !*recurse && path == d && *directory { 106 return filepath.SkipDir 107 } 108 109 if path != d && f.lsfi.Mode.IsDir() && !*recurse { 110 return filepath.SkipDir 111 } 112 113 return nil 114 }) 115 116 if *size { 117 sort.SliceStable(files, func(i, j int) bool { 118 return files[i].lsfi.Size > files[j].lsfi.Size 119 }) 120 } 121 122 for _, f := range files { 123 if f.err != nil { 124 printFile(w, stringer, f) 125 continue 126 } 127 if *recurse { 128 // Mimic find command 129 f.lsfi.Name = f.path 130 } else if f.path == d { 131 if *directory { 132 fmt.Fprintln(w, stringer.FileString(f.lsfi)) 133 continue 134 } 135 136 // Starting directory is a dot when non-recursive 137 if f.osfi.IsDir() { 138 f.lsfi.Name = "." 139 if prefix { 140 if *quoted { 141 fmt.Fprintf(w, "%q:\n", d) 142 } else { 143 fmt.Fprintf(w, "%v:\n", d) 144 } 145 } 146 } 147 } 148 149 printFile(w, stringer, f) 150 } 151 152 return nil 153 } 154 155 func indicator(fi ls.FileInfo) string { 156 if fi.Mode.IsRegular() && fi.Mode&0o111 != 0 { 157 return "*" 158 } 159 if fi.Mode&os.ModeDir != 0 { 160 return "/" 161 } 162 if fi.Mode&os.ModeSymlink != 0 { 163 return "@" 164 } 165 if fi.Mode&os.ModeSocket != 0 { 166 return "=" 167 } 168 if fi.Mode&os.ModeNamedPipe != 0 { 169 return "|" 170 } 171 return "" 172 } 173 174 func list(w io.Writer, names []string) error { 175 if len(names) == 0 { 176 names = []string{"."} 177 } 178 // Write output in tabular form. 179 tw := &tabwriter.Writer{} 180 tw.Init(w, 0, 0, 1, ' ', 0) 181 defer tw.Flush() 182 183 var s ls.Stringer = ls.NameStringer{} 184 if *quoted { 185 s = ls.QuotedStringer{} 186 } 187 if *long { 188 s = ls.LongStringer{Human: *human, Name: s} 189 } 190 // Is a name a directory? If so, list it in its own section. 191 prefix := len(names) > 1 192 for _, d := range names { 193 if err := listName(s, d, tw, prefix); err != nil { 194 return fmt.Errorf("error while listing %q: %w", d, err) 195 } 196 tw.Flush() 197 } 198 return nil 199 } 200 201 func main() { 202 flag.Parse() 203 if err := list(os.Stdout, flag.Args()); err != nil { 204 log.Fatal(err) 205 } 206 }