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 }