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