github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/pkg/cpio/fs_plan9.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  	"log"
    11  	"os"
    12  	"path/filepath"
    13  	"syscall"
    14  	"time"
    15  
    16  	"github.com/mvdan/u-root-coreutils/pkg/ls"
    17  	"github.com/mvdan/u-root-coreutils/pkg/uio"
    18  	"github.com/mvdan/u-root-coreutils/pkg/upath"
    19  )
    20  
    21  // A Recorder is a structure that contains variables used to calculate
    22  // file parameters such as inode numbers for a CPIO file. The life-time
    23  // of a Record structure is meant to be the same as the construction of a
    24  // single CPIO archive. Do not reuse between CPIOs if you don't know what
    25  // you're doing.
    26  type Recorder struct {
    27  	inumber uint64
    28  }
    29  
    30  var modeMap = map[uint64]os.FileMode{
    31  	modeFile: 0,
    32  	modeDir:  os.ModeDir,
    33  }
    34  
    35  func unixModeToFileType(m uint64) (os.FileMode, error) {
    36  	if t, ok := modeMap[m&modeTypeMask]; ok {
    37  		return t, nil
    38  	}
    39  	return 0, fmt.Errorf("invalid file type %#o", m&modeTypeMask)
    40  }
    41  
    42  func toFileMode(r Record) os.FileMode {
    43  	return os.FileMode(perm(r))
    44  }
    45  
    46  // setModes sets the modes.
    47  func setModes(r Record) error {
    48  	if err := os.Chmod(r.Name, toFileMode(r)&os.ModePerm); err != nil {
    49  		return err
    50  	}
    51  	return nil
    52  }
    53  
    54  func perm(r Record) uint32 {
    55  	return uint32(r.Mode) & modePermissions
    56  }
    57  
    58  func dev(r Record) int {
    59  	return int(r.Rmajor<<8 | r.Rminor)
    60  }
    61  
    62  // CreateFile creates a local file for f relative to the current working
    63  // directory.
    64  //
    65  // CreateFile will attempt to set all metadata for the file, including
    66  // ownership, times, and permissions.
    67  func CreateFile(f Record) error {
    68  	return CreateFileInRoot(f, ".", true)
    69  }
    70  
    71  // CreateFileInRoot creates a local file for f relative to rootDir.
    72  func CreateFileInRoot(f Record, rootDir string, forcePriv bool) error {
    73  	m, err := unixModeToFileType(f.Mode)
    74  	if err != nil {
    75  		return err
    76  	}
    77  
    78  	f.Name, err = upath.SafeFilepathJoin(rootDir, f.Name)
    79  	if err != nil {
    80  		// The behavior is to skip files which are unsafe due to
    81  		// zipslip, but continue extracting everything else.
    82  		log.Printf("Warning: Skipping file %q due to: %v", f.Name, err)
    83  		return nil
    84  	}
    85  	dir := filepath.Dir(f.Name)
    86  	// The problem: many cpio archives do not specify the directories and
    87  	// hence the permissions. They just specify the whole path.  In order
    88  	// to create files in these directories, we have to make them at least
    89  	// mode 755.
    90  	if _, err := os.Stat(dir); os.IsNotExist(err) && len(dir) > 0 {
    91  		if err := os.MkdirAll(dir, 0o755); err != nil {
    92  			return fmt.Errorf("CreateFileInRoot %q: %v", f.Name, err)
    93  		}
    94  	}
    95  
    96  	switch m {
    97  	case os.FileMode(0):
    98  		nf, err := os.Create(f.Name)
    99  		if err != nil {
   100  			return err
   101  		}
   102  		defer nf.Close()
   103  		if _, err := io.Copy(nf, uio.Reader(f)); err != nil {
   104  			return err
   105  		}
   106  
   107  	case os.ModeDir:
   108  		if err := os.MkdirAll(f.Name, toFileMode(f)); err != nil {
   109  			return err
   110  		}
   111  
   112  	default:
   113  		return fmt.Errorf("%v: Unknown type %#o", f.Name, m)
   114  	}
   115  
   116  	if err := setModes(f); err != nil && forcePriv {
   117  		return err
   118  	}
   119  	return nil
   120  }
   121  
   122  func (r *Recorder) inode(i Info) Info {
   123  	i.Ino = r.inumber
   124  	r.inumber++
   125  	return i
   126  }
   127  
   128  // GetRecord returns a cpio Record for the given path on the local file system.
   129  //
   130  // GetRecord does not follow symlinks. If path is a symlink, the record
   131  // returned will reflect that symlink.
   132  func (r *Recorder) GetRecord(path string) (Record, error) {
   133  	fi, err := os.Stat(path)
   134  	if err != nil {
   135  		return Record{}, err
   136  	}
   137  
   138  	sys := fi.Sys().(*syscall.Dir)
   139  	info := r.inode(sysInfo(path, sys))
   140  
   141  	switch fi.Mode() & os.ModeType {
   142  	case 0: // Regular file.
   143  		return Record{Info: info, ReaderAt: uio.NewLazyFile(path)}, nil
   144  	default:
   145  		return StaticRecord(nil, info), nil
   146  	}
   147  }
   148  
   149  // NewRecorder creates a new Recorder.
   150  //
   151  // A recorder is a structure that contains variables used to calculate
   152  // file parameters such as inode numbers for a CPIO file. The life-time
   153  // of a Record structure is meant to be the same as the construction of a
   154  // single CPIO archive. Do not reuse between CPIOs if you don't know what
   155  // you're doing.
   156  func NewRecorder() *Recorder {
   157  	return &Recorder{inumber: 2}
   158  }
   159  
   160  // LSInfoFromRecord converts a Record to be usable with the ls package for
   161  // listing files.
   162  func LSInfoFromRecord(rec Record) ls.FileInfo {
   163  	mode := modeFromLinux(rec.Mode)
   164  	return ls.FileInfo{
   165  		Name:  rec.Name,
   166  		Mode:  mode,
   167  		UID:   fmt.Sprintf("%d", rec.UID),
   168  		Size:  int64(rec.FileSize),
   169  		MTime: time.Unix(int64(rec.MTime), 0).UTC(),
   170  	}
   171  }