github.com/jlowellwofford/u-root@v1.0.0/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 func CreateFile(f Record) error { 102 return CreateFileInRoot(f, ".") 103 } 104 105 func CreateFileInRoot(f Record, rootDir string) 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 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.FileMode(0): 128 nf, err := os.Create(f.Name) 129 if err != nil { 130 return err 131 } 132 defer nf.Close() 133 if _, err := io.Copy(nf, uio.Reader(f)); err != nil { 134 return err 135 } 136 return setModes(f) 137 138 case os.ModeDir: 139 if err := os.MkdirAll(f.Name, toFileMode(f)); err != nil { 140 return err 141 } 142 return setModes(f) 143 144 case os.ModeDevice: 145 if err := syscall.Mknod(f.Name, perm(f)|syscall.S_IFBLK, dev(f)); err != nil { 146 return err 147 } 148 return setModes(f) 149 150 case os.ModeCharDevice: 151 if err := syscall.Mknod(f.Name, perm(f)|syscall.S_IFCHR, dev(f)); err != nil { 152 return err 153 } 154 return setModes(f) 155 156 case os.ModeSymlink: 157 content, err := ioutil.ReadAll(uio.Reader(f)) 158 if err != nil { 159 return err 160 } 161 return os.Symlink(string(content), f.Name) 162 163 default: 164 return fmt.Errorf("%v: Unknown type %#o", f.Name, m) 165 } 166 } 167 168 // Inumber and devnumbers are unique to Unix-like 169 // operating systems. You can not uniquely disambiguate a file in a 170 // Unix system with just an inumber, you need a device number too. 171 // To handle hard links (unique to Unix) we need to figure out if a 172 // given file has been seen before. To do this we see if a file has the 173 // same [dev,ino] tuple as one we have seen. If so, we won't bother 174 // reading it in. 175 176 type devInode struct { 177 dev uint64 178 ino uint64 179 } 180 181 var ( 182 inodeMap = map[devInode]Info{} 183 inumber uint64 184 ) 185 186 // Certain elements of the file can not be set by cpio: 187 // the Inode # 188 // the Dev 189 // maintaining these elements leaves us with a non-reproducible 190 // output stream. In this function, we figure out what inumber 191 // we need to use, and clear out anything we can. 192 // We always zero the Dev. 193 // We try to find the matching inode. If found, we use its inumber. 194 // If not, we get a new inumber for it and save the inode away. 195 // This eliminates two of the messier parts of creating reproducible 196 // output streams. 197 func inode(i Info) (Info, bool) { 198 d := devInode{dev: i.Dev, ino: i.Ino} 199 i.Dev = 0 200 201 if d, ok := inodeMap[d]; ok { 202 i.Ino = d.Ino 203 return i, true 204 } 205 206 i.Ino = inumber 207 inumber++ 208 inodeMap[d] = i 209 210 return i, false 211 } 212 213 func GetRecord(path string) (Record, error) { 214 fi, err := os.Lstat(path) 215 if err != nil { 216 return Record{}, err 217 } 218 219 sys := fi.Sys().(*syscall.Stat_t) 220 info, done := inode(sysInfo(path, sys)) 221 222 switch fi.Mode() & os.ModeType { 223 case 0: // Regular file. 224 if done { 225 return Record{Info: info}, nil 226 } 227 return Record{Info: info, ReaderAt: NewLazyFile(path)}, nil 228 229 case os.ModeSymlink: 230 linkname, err := os.Readlink(path) 231 if err != nil { 232 return Record{}, err 233 } 234 return StaticRecord([]byte(linkname), info), nil 235 236 default: 237 return StaticRecord(nil, info), nil 238 } 239 }