gopkg.in/hugelgupf/u-root.v9@v9.0.0-20180831063832-3f6f1057f09b/cmds/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 main
     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  	modTime  time.Time
    35  	symlink  string
    36  }
    37  
    38  func extractImportantParts(n 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(n); 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  		modTime: fi.ModTime(),
    58  		symlink: link,
    59  	}
    60  }
    61  
    62  // Without this cache, `ls -l` is orders of magnitude slower.
    63  var (
    64  	uidCache = map[uint32]string{}
    65  	gidCache = map[uint32]string{}
    66  )
    67  
    68  // Convert uid to username, or return uid on error.
    69  func lookupUserName(id uint32) string {
    70  	if s, ok := uidCache[id]; ok {
    71  		return s
    72  	}
    73  	s := fmt.Sprint(id)
    74  	if u, err := user.LookupId(s); err == nil {
    75  		s = u.Username
    76  	}
    77  	uidCache[id] = s
    78  	return s
    79  }
    80  
    81  // Convert gid to group name, or return gid on error.
    82  func lookupGroupName(id uint32) string {
    83  	if s, ok := gidCache[id]; ok {
    84  		return s
    85  	}
    86  	s := fmt.Sprint(id)
    87  	if g, err := user.LookupGroupId(s); err == nil {
    88  		s = g.Name
    89  	}
    90  	gidCache[id] = s
    91  	return s
    92  }
    93  
    94  // The default stringer. Return only the filename. Unprintable characters are
    95  // replaced with '?'.
    96  func (fi fileInfo) String() string {
    97  	return unprintableRe.ReplaceAllLiteralString(fi.name, "?")
    98  }
    99  
   100  // Two alternative stringers
   101  type quotedStringer struct {
   102  	fileInfo
   103  }
   104  type longStringer struct {
   105  	fileInfo
   106  	comp  fmt.Stringer // decorator pattern
   107  	human bool
   108  }
   109  
   110  // Return the name surrounded by quotes with escaped control characters.
   111  func (fi quotedStringer) String() string {
   112  	return fmt.Sprintf("%#v", fi.name)
   113  }
   114  
   115  // The long and quoted stringers can be combined like so:
   116  //     longStringer{fi, quotedStringer{fi}}
   117  func (fi longStringer) String() string {
   118  	// Golang's FileMode.String() is almost sufficient, except we would
   119  	// rather use b and c for devices.
   120  	replacer := strings.NewReplacer("Dc", "c", "D", "b")
   121  
   122  	// Ex: crw-rw-rw-  root  root  1, 3  Feb 6 09:31  null
   123  	pattern := "%[1]s\t%[2]s\t%[3]s\t%[4]d, %[5]d\t%[7]v\t%[8]s"
   124  	var size string
   125  	if fi.mode&os.ModeDevice == 0 && fi.mode&os.ModeCharDevice == 0 {
   126  		// Ex: -rw-rw----  myuser  myuser  1256  Feb 6 09:31  recipes.txt
   127  		pattern = "%[1]s\t%[2]s\t%[3]s\t%[6]s\t%[7]v\t%[8]s"
   128  	}
   129  
   130  	if fi.human {
   131  		size = humanize.Bytes(uint64(fi.size))
   132  	} else {
   133  		size = strconv.FormatInt(fi.size, 10)
   134  	}
   135  
   136  	s := fmt.Sprintf(pattern,
   137  		replacer.Replace(fi.mode.String()),
   138  		lookupUserName(fi.uid),
   139  		lookupGroupName(fi.gid),
   140  		unix.Major(fi.rdev),
   141  		unix.Minor(fi.rdev),
   142  		size,
   143  		fi.modTime.Format("Jan _2 15:04"),
   144  		fi.comp.String())
   145  
   146  	if fi.mode&os.ModeType == os.ModeSymlink {
   147  		s += fmt.Sprintf(" -> %v", fi.symlink)
   148  	}
   149  	return s
   150  }