gopkg.in/hugelgupf/u-root.v2@v2.0.0-20180831055005-3f8fdb0ce09d/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  package cpio
     6  
     7  import (
     8  	"fmt"
     9  	"io"
    10  	"io/ioutil"
    11  	"os"
    12  	"path/filepath"
    13  	"syscall"
    14  
    15  	"github.com/u-root/u-root/pkg/uio"
    16  	"golang.org/x/sys/unix"
    17  )
    18  
    19  // Linux mode_t bits.
    20  const (
    21  	modeTypeMask    = 0170000
    22  	modeSocket      = 0140000
    23  	modeSymlink     = 0120000
    24  	modeFile        = 0100000
    25  	modeBlock       = 0060000
    26  	modeDir         = 0040000
    27  	modeChar        = 0020000
    28  	modeFIFO        = 0010000
    29  	modeSUID        = 0004000
    30  	modeSGID        = 0002000
    31  	modeSticky      = 0001000
    32  	modePermissions = 0000777
    33  )
    34  
    35  var modeMap = map[uint64]os.FileMode{
    36  	modeSocket:  os.ModeSocket,
    37  	modeSymlink: os.ModeSymlink,
    38  	modeFile:    0,
    39  	modeBlock:   os.ModeDevice,
    40  	modeDir:     os.ModeDir,
    41  	modeChar:    os.ModeCharDevice,
    42  	modeFIFO:    os.ModeNamedPipe,
    43  }
    44  
    45  // setModes sets the modes, changing the easy ones first and the harder ones last.
    46  // In this way, we set as much as we can before bailing out.
    47  // N.B.: if you set something with S_ISUID, then change the owner,
    48  // the kernel (Linux, OSX, etc.) clears S_ISUID (a good idea). So, the simple thing:
    49  // Do the chmod operations in order of difficulty, and give up as soon as we fail.
    50  // Set the basic permissions -- not including SUID, GUID, etc.
    51  // Set the times
    52  // Set the owner
    53  // Set ALL the mode bits, in case we need to do SUID, etc. If we could not
    54  // set the owner, we won't even try this operation of course, so we won't
    55  // have SUID incorrectly set for the wrong user.
    56  func setModes(r Record) error {
    57  	if err := os.Chmod(r.Name, toFileMode(r)&os.ModePerm); err != nil {
    58  		return err
    59  	}
    60  	/*if err := os.Chtimes(r.Name, time.Time{}, time.Unix(int64(r.MTime), 0)); err != nil {
    61  		return err
    62  	}*/
    63  	if err := os.Chown(r.Name, int(r.UID), int(r.GID)); err != nil {
    64  		return err
    65  	}
    66  	if err := os.Chmod(r.Name, toFileMode(r)); err != nil {
    67  		return err
    68  	}
    69  	return nil
    70  }
    71  
    72  func toFileMode(r Record) os.FileMode {
    73  	m := os.FileMode(perm(r))
    74  	if r.Mode&unix.S_ISUID != 0 {
    75  		m |= os.ModeSetuid
    76  	}
    77  	if r.Mode&unix.S_ISGID != 0 {
    78  		m |= os.ModeSetgid
    79  	}
    80  	if r.Mode&unix.S_ISVTX != 0 {
    81  		m |= os.ModeSticky
    82  	}
    83  	return m
    84  }
    85  
    86  func perm(r Record) uint32 {
    87  	return uint32(r.Mode) & modePermissions
    88  }
    89  
    90  func dev(r Record) int {
    91  	return int(r.Rmajor<<8 | r.Rminor)
    92  }
    93  
    94  func linuxModeToFileType(m uint64) (os.FileMode, error) {
    95  	if t, ok := modeMap[m&modeTypeMask]; ok {
    96  		return t, nil
    97  	}
    98  	return 0, fmt.Errorf("Invalid file type %#o", m&modeTypeMask)
    99  }
   100  
   101  func CreateFile(f Record) error {
   102  	return CreateFileInRoot(f, ".")
   103  }
   104  
   105  func CreateFileInRoot(f Record, rootDir string) 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 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.FileMode(0):
   128  		nf, err := os.Create(f.Name)
   129  		if err != nil {
   130  			return err
   131  		}
   132  		defer nf.Close()
   133  		if _, err := io.Copy(nf, uio.Reader(f)); err != nil {
   134  			return err
   135  		}
   136  		return setModes(f)
   137  
   138  	case os.ModeDir:
   139  		if err := os.MkdirAll(f.Name, toFileMode(f)); err != nil {
   140  			return err
   141  		}
   142  		return setModes(f)
   143  
   144  	case os.ModeDevice:
   145  		if err := syscall.Mknod(f.Name, perm(f)|syscall.S_IFBLK, dev(f)); err != nil {
   146  			return err
   147  		}
   148  		return setModes(f)
   149  
   150  	case os.ModeCharDevice:
   151  		if err := syscall.Mknod(f.Name, perm(f)|syscall.S_IFCHR, dev(f)); err != nil {
   152  			return err
   153  		}
   154  		return setModes(f)
   155  
   156  	case os.ModeSymlink:
   157  		content, err := ioutil.ReadAll(uio.Reader(f))
   158  		if err != nil {
   159  			return err
   160  		}
   161  		return os.Symlink(string(content), f.Name)
   162  
   163  	default:
   164  		return fmt.Errorf("%v: Unknown type %#o", f.Name, m)
   165  	}
   166  }
   167  
   168  // Inumber and devnumbers are unique to Unix-like
   169  // operating systems. You can not uniquely disambiguate a file in a
   170  // Unix system with just an inumber, you need a device number too.
   171  // To handle hard links (unique to Unix) we need to figure out if a
   172  // given file has been seen before. To do this we see if a file has the
   173  // same [dev,ino] tuple as one we have seen. If so, we won't bother
   174  // reading it in.
   175  
   176  type devInode struct {
   177  	dev uint64
   178  	ino uint64
   179  }
   180  
   181  var (
   182  	inodeMap = map[devInode]Info{}
   183  	inumber  uint64
   184  )
   185  
   186  // Certain elements of the file can not be set by cpio:
   187  // the Inode #
   188  // the Dev
   189  // maintaining these elements leaves us with a non-reproducible
   190  // output stream. In this function, we figure out what inumber
   191  // we need to use, and clear out anything we can.
   192  // We always zero the Dev.
   193  // We try to find the matching inode. If found, we use its inumber.
   194  // If not, we get a new inumber for it and save the inode away.
   195  // This eliminates two of the messier parts of creating reproducible
   196  // output streams.
   197  func inode(i Info) (Info, bool) {
   198  	d := devInode{dev: i.Dev, ino: i.Ino}
   199  	i.Dev = 0
   200  
   201  	if d, ok := inodeMap[d]; ok {
   202  		i.Ino = d.Ino
   203  		return i, true
   204  	}
   205  
   206  	i.Ino = inumber
   207  	inumber++
   208  	inodeMap[d] = i
   209  
   210  	return i, false
   211  }
   212  
   213  func GetRecord(path string) (Record, error) {
   214  	fi, err := os.Lstat(path)
   215  	if err != nil {
   216  		return Record{}, err
   217  	}
   218  
   219  	sys := fi.Sys().(*syscall.Stat_t)
   220  	info, done := inode(sysInfo(path, sys))
   221  
   222  	switch fi.Mode() & os.ModeType {
   223  	case 0: // Regular file.
   224  		if done {
   225  			return Record{Info: info}, nil
   226  		}
   227  		return Record{Info: info, ReaderAt: NewLazyFile(path)}, nil
   228  
   229  	case os.ModeSymlink:
   230  		linkname, err := os.Readlink(path)
   231  		if err != nil {
   232  			return Record{}, err
   233  		}
   234  		return StaticRecord([]byte(linkname), info), nil
   235  
   236  	default:
   237  		return StaticRecord(nil, info), nil
   238  	}
   239  }