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