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