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