github.com/rck/u-root@v0.0.0-20180106144920-7eb602e381bb/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  	"time"
    15  
    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  	m, err := linuxModeToFileType(f.Mode)
   103  	if err != nil {
   104  		return err
   105  	}
   106  
   107  	dir, _ := filepath.Split(f.Name)
   108  	// The problem: many cpio archives do not specify the directories
   109  	// and hence the permissions. They just specify the whole path.
   110  	// In order to create files in these directories, we have to make them at least
   111  	// mode 755.
   112  	if dir != "" {
   113  		switch m {
   114  		case os.FileMode(0),
   115  			os.ModeDevice,
   116  			os.ModeCharDevice,
   117  			os.ModeSymlink:
   118  			if err := os.MkdirAll(dir, 0755); err != nil {
   119  				return err
   120  			}
   121  		}
   122  	}
   123  
   124  	switch m {
   125  	case os.ModeSocket, os.ModeNamedPipe:
   126  		return fmt.Errorf("%q: type %v: cannot create IPC endpoints", f.Name, m)
   127  
   128  	case os.FileMode(0):
   129  		nf, err := os.Create(f.Name)
   130  		if err != nil {
   131  			return err
   132  		}
   133  		defer nf.Close()
   134  		if _, err := io.Copy(nf, f); err != nil {
   135  			return err
   136  		}
   137  		return setModes(f)
   138  
   139  	case os.ModeDir:
   140  		if err := os.MkdirAll(f.Name, toFileMode(f)); err != nil {
   141  			return err
   142  		}
   143  		return setModes(f)
   144  
   145  	case os.ModeDevice:
   146  		if err := syscall.Mknod(f.Name, perm(f)|syscall.S_IFBLK, dev(f)); err != nil {
   147  			return err
   148  		}
   149  		return setModes(f)
   150  
   151  	case os.ModeCharDevice:
   152  		if err := syscall.Mknod(f.Name, perm(f)|syscall.S_IFCHR, dev(f)); err != nil {
   153  			return err
   154  		}
   155  		return setModes(f)
   156  
   157  	case os.ModeSymlink:
   158  		content, err := ioutil.ReadAll(f)
   159  		if err != nil {
   160  			return err
   161  		}
   162  		return os.Symlink(string(content), f.Name)
   163  
   164  	default:
   165  		return fmt.Errorf("%v: Unknown type %#o", f.Name, m)
   166  	}
   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  var (
   183  	inodeMap = map[devInode]Info{}
   184  	inumber  uint64
   185  )
   186  
   187  // Certain elements of the file can not be set by cpio:
   188  // the Inode #
   189  // the Dev
   190  // maintaining these elements leaves us with a non-reproducible
   191  // output stream. In this function, we figure out what inumber
   192  // we need to use, and clear out anything we can.
   193  // We always zero the Dev.
   194  // We try to find the matching inode. If found, we use its inumber.
   195  // If not, we get a new inumber for it and save the inode away.
   196  // This eliminates two of the messier parts of creating reproducible
   197  // output streams.
   198  func inode(i Info) (Info, bool) {
   199  	d := devInode{dev: i.Dev, ino: i.Ino}
   200  	i.Dev = 0
   201  
   202  	if d, ok := inodeMap[d]; ok {
   203  		i.Ino = d.Ino
   204  		return i, true
   205  	}
   206  
   207  	i.Ino = inumber
   208  	inumber++
   209  	inodeMap[d] = i
   210  
   211  	return i, false
   212  }
   213  
   214  func GetRecord(path string) (Record, error) {
   215  	fi, err := os.Lstat(path)
   216  	if err != nil {
   217  		return Record{}, err
   218  	}
   219  
   220  	sys := fi.Sys().(*syscall.Stat_t)
   221  	info, done := inode(sysInfo(path, sys))
   222  
   223  	switch fi.Mode() & os.ModeType {
   224  	case 0: // Regular file.
   225  		if done {
   226  			return Record{Info: info}, nil
   227  		}
   228  		return Record{Info: info, ReadCloser: NewDeferReadCloser(path)}, nil
   229  
   230  	case os.ModeSymlink:
   231  		linkname, err := os.Readlink(path)
   232  		if err != nil {
   233  			return Record{}, err
   234  		}
   235  		return StaticRecord([]byte(linkname), info), nil
   236  
   237  	default:
   238  		return StaticRecord(nil, info), nil
   239  	}
   240  }