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