github.com/shaardie/u-root@v4.0.1-0.20190127173353-f24a1c26aa2e+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 package cpio 6 7 import ( 8 "fmt" 9 "io" 10 "io/ioutil" 11 "os" 12 "path/filepath" 13 "syscall" 14 15 "github.com/u-root/u-root/pkg/uio" 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 // CreateFile creates a local file for f relative to the current working 102 // directory. 103 // 104 // CreateFile will attempt to set all metadata for the file, including 105 // ownership, times, and permissions. 106 func CreateFile(f Record) error { 107 return CreateFileInRoot(f, ".", true) 108 } 109 110 // CreateFileInRoot creates a local file for f relative to rootDir. 111 // 112 // It will attempt to set all metadata for the file, including ownership, 113 // times, and permissions. If these fail, it only returns an error if 114 // forcePriv is true. 115 // 116 // Block and char device creation will only return error if forcePriv is true. 117 func CreateFileInRoot(f Record, rootDir string, forcePriv bool) error { 118 m, err := linuxModeToFileType(f.Mode) 119 if err != nil { 120 return err 121 } 122 123 f.Name = filepath.Clean(filepath.Join(rootDir, f.Name)) 124 dir := filepath.Dir(f.Name) 125 // The problem: many cpio archives do not specify the directories and 126 // hence the permissions. They just specify the whole path. In order 127 // to create files in these directories, we have to make them at least 128 // mode 755. 129 if _, err := os.Stat(dir); os.IsNotExist(err) && len(dir) > 0 { 130 if err := os.MkdirAll(dir, 0755); err != nil { 131 return fmt.Errorf("CreateFileInRoot %q: %v", f.Name, err) 132 } 133 } 134 135 switch m { 136 case os.ModeSocket, os.ModeNamedPipe: 137 return fmt.Errorf("%q: type %v: cannot create IPC endpoints", f.Name, m) 138 139 case os.ModeSymlink: 140 content, err := ioutil.ReadAll(uio.Reader(f)) 141 if err != nil { 142 return err 143 } 144 return os.Symlink(string(content), f.Name) 145 146 case os.FileMode(0): 147 nf, err := os.Create(f.Name) 148 if err != nil { 149 return err 150 } 151 defer nf.Close() 152 if _, err := io.Copy(nf, uio.Reader(f)); err != nil { 153 return err 154 } 155 156 case os.ModeDir: 157 if err := os.MkdirAll(f.Name, toFileMode(f)); err != nil { 158 return err 159 } 160 161 case os.ModeDevice: 162 if err := syscall.Mknod(f.Name, perm(f)|syscall.S_IFBLK, dev(f)); err != nil && forcePriv { 163 return err 164 } 165 166 case os.ModeCharDevice: 167 if err := syscall.Mknod(f.Name, perm(f)|syscall.S_IFCHR, dev(f)); err != nil && forcePriv { 168 return err 169 } 170 171 default: 172 return fmt.Errorf("%v: Unknown type %#o", f.Name, m) 173 } 174 175 if err := setModes(f); err != nil && forcePriv { 176 return err 177 } 178 return nil 179 } 180 181 // Inumber and devnumbers are unique to Unix-like 182 // operating systems. You can not uniquely disambiguate a file in a 183 // Unix system with just an inumber, you need a device number too. 184 // To handle hard links (unique to Unix) we need to figure out if a 185 // given file has been seen before. To do this we see if a file has the 186 // same [dev,ino] tuple as one we have seen. If so, we won't bother 187 // reading it in. 188 189 type devInode struct { 190 dev uint64 191 ino uint64 192 } 193 194 var ( 195 inodeMap = map[devInode]Info{} 196 inumber uint64 197 ) 198 199 // Certain elements of the file can not be set by cpio: 200 // the Inode # 201 // the Dev 202 // maintaining these elements leaves us with a non-reproducible 203 // output stream. In this function, we figure out what inumber 204 // we need to use, and clear out anything we can. 205 // We always zero the Dev. 206 // We try to find the matching inode. If found, we use its inumber. 207 // If not, we get a new inumber for it and save the inode away. 208 // This eliminates two of the messier parts of creating reproducible 209 // output streams. 210 func inode(i Info) (Info, bool) { 211 d := devInode{dev: i.Dev, ino: i.Ino} 212 i.Dev = 0 213 214 if d, ok := inodeMap[d]; ok { 215 i.Ino = d.Ino 216 return i, true 217 } 218 219 i.Ino = inumber 220 inumber++ 221 inodeMap[d] = i 222 223 return i, false 224 } 225 226 func newLazyFile(name string) io.ReaderAt { 227 return uio.NewLazyOpenerAt(func() (io.ReaderAt, error) { 228 return os.Open(name) 229 }) 230 } 231 232 // GetRecord returns a cpio Record for the given path on the local file system. 233 // 234 // GetRecord does not follow symlinks. If path is a symlink, the record 235 // returned will reflect that symlink. 236 func GetRecord(path string) (Record, error) { 237 fi, err := os.Lstat(path) 238 if err != nil { 239 return Record{}, err 240 } 241 242 sys := fi.Sys().(*syscall.Stat_t) 243 info, done := inode(sysInfo(path, sys)) 244 245 switch fi.Mode() & os.ModeType { 246 case 0: // Regular file. 247 if done { 248 return Record{Info: info}, nil 249 } 250 return Record{Info: info, ReaderAt: newLazyFile(path)}, nil 251 252 case os.ModeSymlink: 253 linkname, err := os.Readlink(path) 254 if err != nil { 255 return Record{}, err 256 } 257 return StaticRecord([]byte(linkname), info), nil 258 259 default: 260 return StaticRecord(nil, info), nil 261 } 262 }