github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/pkg/cpio/fs_unix.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  // +build !plan9
     6  
     7  package cpio
     8  
     9  import (
    10  	"fmt"
    11  	"io"
    12  	"io/ioutil"
    13  	"os"
    14  	"path/filepath"
    15  	"syscall"
    16  	"time"
    17  
    18  	"github.com/u-root/u-root/pkg/ls"
    19  	"github.com/u-root/u-root/pkg/uio"
    20  	"golang.org/x/sys/unix"
    21  )
    22  
    23  var modeMap = map[uint64]os.FileMode{
    24  	modeSocket:  os.ModeSocket,
    25  	modeSymlink: os.ModeSymlink,
    26  	modeFile:    0,
    27  	modeBlock:   os.ModeDevice,
    28  	modeDir:     os.ModeDir,
    29  	modeChar:    os.ModeCharDevice,
    30  	modeFIFO:    os.ModeNamedPipe,
    31  }
    32  
    33  // setModes sets the modes, changing the easy ones first and the harder ones last.
    34  // In this way, we set as much as we can before bailing out.
    35  // N.B.: if you set something with S_ISUID, then change the owner,
    36  // the kernel (Linux, OSX, etc.) clears S_ISUID (a good idea). So, the simple thing:
    37  // Do the chmod operations in order of difficulty, and give up as soon as we fail.
    38  // Set the basic permissions -- not including SUID, GUID, etc.
    39  // Set the times
    40  // Set the owner
    41  // Set ALL the mode bits, in case we need to do SUID, etc. If we could not
    42  // set the owner, we won't even try this operation of course, so we won't
    43  // have SUID incorrectly set for the wrong user.
    44  func setModes(r Record) error {
    45  	if err := os.Chmod(r.Name, toFileMode(r)&os.ModePerm); err != nil {
    46  		return err
    47  	}
    48  	/*if err := os.Chtimes(r.Name, time.Time{}, time.Unix(int64(r.MTime), 0)); err != nil {
    49  		return err
    50  	}*/
    51  	if err := os.Chown(r.Name, int(r.UID), int(r.GID)); err != nil {
    52  		return err
    53  	}
    54  	if err := os.Chmod(r.Name, toFileMode(r)); err != nil {
    55  		return err
    56  	}
    57  	return nil
    58  }
    59  
    60  func toFileMode(r Record) os.FileMode {
    61  	m := os.FileMode(perm(r))
    62  	if r.Mode&unix.S_ISUID != 0 {
    63  		m |= os.ModeSetuid
    64  	}
    65  	if r.Mode&unix.S_ISGID != 0 {
    66  		m |= os.ModeSetgid
    67  	}
    68  	if r.Mode&unix.S_ISVTX != 0 {
    69  		m |= os.ModeSticky
    70  	}
    71  	return m
    72  }
    73  
    74  func perm(r Record) uint32 {
    75  	return uint32(r.Mode) & modePermissions
    76  }
    77  
    78  func dev(r Record) int {
    79  	return int(r.Rmajor<<8 | r.Rminor)
    80  }
    81  
    82  func linuxModeToFileType(m uint64) (os.FileMode, error) {
    83  	if t, ok := modeMap[m&modeTypeMask]; ok {
    84  		return t, nil
    85  	}
    86  	return 0, fmt.Errorf("invalid file type %#o", m&modeTypeMask)
    87  }
    88  
    89  // CreateFile creates a local file for f relative to the current working
    90  // directory.
    91  //
    92  // CreateFile will attempt to set all metadata for the file, including
    93  // ownership, times, and permissions.
    94  func CreateFile(f Record) error {
    95  	return CreateFileInRoot(f, ".", true)
    96  }
    97  
    98  // CreateFileInRoot creates a local file for f relative to rootDir.
    99  //
   100  // It will attempt to set all metadata for the file, including ownership,
   101  // times, and permissions. If these fail, it only returns an error if
   102  // forcePriv is true.
   103  //
   104  // Block and char device creation will only return error if forcePriv is true.
   105  func CreateFileInRoot(f Record, rootDir string, forcePriv bool) error {
   106  	m, err := linuxModeToFileType(f.Mode)
   107  	if err != nil {
   108  		return err
   109  	}
   110  
   111  	f.Name = filepath.Clean(filepath.Join(rootDir, f.Name))
   112  	dir := filepath.Dir(f.Name)
   113  	// The problem: many cpio archives do not specify the directories and
   114  	// hence the permissions. They just specify the whole path.  In order
   115  	// to create files in these directories, we have to make them at least
   116  	// mode 755.
   117  	if _, err := os.Stat(dir); os.IsNotExist(err) && len(dir) > 0 {
   118  		if err := os.MkdirAll(dir, 0755); err != nil {
   119  			return fmt.Errorf("CreateFileInRoot %q: %v", f.Name, err)
   120  		}
   121  	}
   122  
   123  	switch m {
   124  	case os.ModeSocket, os.ModeNamedPipe:
   125  		return fmt.Errorf("%q: type %v: cannot create IPC endpoints", f.Name, m)
   126  
   127  	case os.ModeSymlink:
   128  		content, err := ioutil.ReadAll(uio.Reader(f))
   129  		if err != nil {
   130  			return err
   131  		}
   132  		return os.Symlink(string(content), f.Name)
   133  
   134  	case os.FileMode(0):
   135  		nf, err := os.Create(f.Name)
   136  		if err != nil {
   137  			return err
   138  		}
   139  		defer nf.Close()
   140  		if _, err := io.Copy(nf, uio.Reader(f)); err != nil {
   141  			return err
   142  		}
   143  
   144  	case os.ModeDir:
   145  		if err := os.MkdirAll(f.Name, toFileMode(f)); err != nil {
   146  			return err
   147  		}
   148  
   149  	case os.ModeDevice:
   150  		if err := mknod(f.Name, perm(f)|syscall.S_IFBLK, dev(f)); err != nil && forcePriv {
   151  			return err
   152  		}
   153  
   154  	case os.ModeCharDevice:
   155  		if err := mknod(f.Name, perm(f)|syscall.S_IFCHR, dev(f)); err != nil && forcePriv {
   156  			return err
   157  		}
   158  
   159  	default:
   160  		return fmt.Errorf("%v: Unknown type %#o", f.Name, m)
   161  	}
   162  
   163  	if err := setModes(f); err != nil && forcePriv {
   164  		return err
   165  	}
   166  	return nil
   167  }
   168  
   169  // Inumber and devnumbers are unique to Unix-like
   170  // operating systems. You can not uniquely disambiguate a file in a
   171  // Unix system with just an inumber, you need a device number too.
   172  // To handle hard links (unique to Unix) we need to figure out if a
   173  // given file has been seen before. To do this we see if a file has the
   174  // same [dev,ino] tuple as one we have seen. If so, we won't bother
   175  // reading it in.
   176  
   177  type devInode struct {
   178  	dev uint64
   179  	ino uint64
   180  }
   181  
   182  // A Recorder is a structure that contains variables used to calculate
   183  // file parameters such as inode numbers for a CPIO file. The life-time
   184  // of a Record structure is meant to be the same as the construction of a
   185  // single CPIO archive. Do not reuse between CPIOs if you don't know what
   186  // you're doing.
   187  type Recorder struct {
   188  	inodeMap map[devInode]Info
   189  	inumber  uint64
   190  }
   191  
   192  // Certain elements of the file can not be set by cpio:
   193  // the Inode #
   194  // the Dev
   195  // maintaining these elements leaves us with a non-reproducible
   196  // output stream. In this function, we figure out what inumber
   197  // we need to use, and clear out anything we can.
   198  // We always zero the Dev.
   199  // We try to find the matching inode. If found, we use its inumber.
   200  // If not, we get a new inumber for it and save the inode away.
   201  // This eliminates two of the messier parts of creating reproducible
   202  // output streams.
   203  func (r *Recorder) inode(i Info) (Info, bool) {
   204  	d := devInode{dev: i.Dev, ino: i.Ino}
   205  	i.Dev = 0
   206  
   207  	if d, ok := r.inodeMap[d]; ok {
   208  		i.Ino = d.Ino
   209  		return i, true
   210  	}
   211  
   212  	i.Ino = r.inumber
   213  	r.inumber++
   214  	r.inodeMap[d] = i
   215  
   216  	return i, false
   217  }
   218  
   219  // GetRecord returns a cpio Record for the given path on the local file system.
   220  //
   221  // GetRecord does not follow symlinks. If path is a symlink, the record
   222  // returned will reflect that symlink.
   223  func (r *Recorder) GetRecord(path string) (Record, error) {
   224  	fi, err := os.Lstat(path)
   225  	if err != nil {
   226  		return Record{}, err
   227  	}
   228  
   229  	sys := fi.Sys().(*syscall.Stat_t)
   230  	info, done := r.inode(sysInfo(path, sys))
   231  
   232  	switch fi.Mode() & os.ModeType {
   233  	case 0: // Regular file.
   234  		if done {
   235  			return Record{Info: info}, nil
   236  		}
   237  		return Record{Info: info, ReaderAt: uio.NewLazyFile(path)}, nil
   238  
   239  	case os.ModeSymlink:
   240  		linkname, err := os.Readlink(path)
   241  		if err != nil {
   242  			return Record{}, err
   243  		}
   244  		return StaticRecord([]byte(linkname), info), nil
   245  
   246  	default:
   247  		return StaticRecord(nil, info), nil
   248  	}
   249  }
   250  
   251  // NewRecorder creates a new Recorder.
   252  //
   253  // A recorder is a structure that contains variables used to calculate
   254  // file parameters such as inode numbers for a CPIO file. The life-time
   255  // of a Record structure is meant to be the same as the construction of a
   256  // single CPIO archive. Do not reuse between CPIOs if you don't know what
   257  // you're doing.
   258  func NewRecorder() *Recorder {
   259  	return &Recorder{make(map[devInode]Info), 0}
   260  }
   261  
   262  // LSInfoFromRecord converts a Record to be usable with the ls package for
   263  // listing files.
   264  func LSInfoFromRecord(rec Record) ls.FileInfo {
   265  	var target string
   266  
   267  	mode := modeFromLinux(rec.Mode)
   268  	if mode&os.ModeType == os.ModeSymlink {
   269  		if l, err := uio.ReadAll(rec); err != nil {
   270  			target = err.Error()
   271  		} else {
   272  			target = string(l)
   273  		}
   274  	}
   275  
   276  	return ls.FileInfo{
   277  		Name:          rec.Name,
   278  		Mode:          mode,
   279  		Rdev:          unix.Mkdev(uint32(rec.Rmajor), uint32(rec.Rminor)),
   280  		UID:           uint32(rec.UID),
   281  		GID:           uint32(rec.GID),
   282  		Size:          int64(rec.FileSize),
   283  		MTime:         time.Unix(int64(rec.MTime), 0).UTC(),
   284  		SymlinkTarget: target,
   285  	}
   286  }