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  }