github.com/blixtra/rkt@v0.8.1-0.20160204105720-ab0d1add1a43/pkg/tar/tar.go (about) 1 // Copyright 2014 The rkt Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package tar contains helper functions for working with tar files 16 package tar 17 18 import ( 19 "archive/tar" 20 "errors" 21 "fmt" 22 "io" 23 "io/ioutil" 24 "os" 25 "path/filepath" 26 "syscall" 27 28 "github.com/appc/spec/pkg/device" 29 "github.com/coreos/rkt/pkg/fileutil" 30 "github.com/coreos/rkt/pkg/uid" 31 "github.com/hashicorp/errwrap" 32 ) 33 34 const DEFAULT_DIR_MODE os.FileMode = 0755 35 36 var ErrNotSupportedPlatform = errors.New("platform and architecture is not supported") 37 38 // Map of paths that should be whitelisted. The paths should be relative to the 39 // root of the tar file and should be cleaned (for example using filepath.Clean) 40 type PathWhitelistMap map[string]struct{} 41 42 type FilePermissionsEditor func(string, int, int, byte, os.FileInfo) error 43 44 func NewUidShiftingFilePermEditor(uidRange *uid.UidRange) (FilePermissionsEditor, error) { 45 if os.Geteuid() != 0 { 46 return func(_ string, _, _ int, _ byte, _ os.FileInfo) error { 47 // The files are owned by the current user on creation. 48 // If we do nothing, they will remain so. 49 return nil 50 }, nil 51 } 52 53 return func(path string, uid, gid int, typ byte, fi os.FileInfo) error { 54 shiftedUid, shiftedGid, err := uidRange.ShiftRange(uint32(uid), uint32(gid)) 55 if err != nil { 56 return err 57 } 58 if err := os.Lchown(path, int(shiftedUid), int(shiftedGid)); err != nil { 59 return err 60 } 61 62 // lchown(2) says that, depending on the linux kernel version, it 63 // can change the file's mode also if executed as root. So call 64 // os.Chmod after it. 65 if typ != tar.TypeSymlink { 66 if err := os.Chmod(path, fi.Mode()); err != nil { 67 return err 68 } 69 } 70 return nil 71 }, nil 72 } 73 74 // ExtractTarInsecure extracts a tarball (from a tar.Reader) into the target 75 // directory. If pwl is not nil, only the paths in the map are extracted. If 76 // overwrite is true, existing files will be overwritten. 77 func ExtractTarInsecure(tr *tar.Reader, target string, overwrite bool, pwl PathWhitelistMap, editor FilePermissionsEditor) error { 78 um := syscall.Umask(0) 79 defer syscall.Umask(um) 80 81 var dirhdrs []*tar.Header 82 Tar: 83 for { 84 hdr, err := tr.Next() 85 switch err { 86 case io.EOF: 87 break Tar 88 case nil: 89 if pwl != nil { 90 relpath := filepath.Clean(hdr.Name) 91 if _, ok := pwl[relpath]; !ok { 92 continue 93 } 94 } 95 err = extractFile(tr, target, hdr, overwrite, editor) 96 if err != nil { 97 return errwrap.Wrap(errors.New("error extracting tarball"), err) 98 } 99 if hdr.Typeflag == tar.TypeDir { 100 dirhdrs = append(dirhdrs, hdr) 101 } 102 default: 103 return errwrap.Wrap(errors.New("error extracting tarball"), err) 104 } 105 } 106 107 // Restore dirs atime and mtime. This has to be done after extracting 108 // as a file extraction will change its parent directory's times. 109 for _, hdr := range dirhdrs { 110 p := filepath.Join(target, hdr.Name) 111 if err := syscall.UtimesNano(p, HdrToTimespec(hdr)); err != nil { 112 return err 113 } 114 } 115 return nil 116 } 117 118 // extractFile extracts the file described by hdr from the given tarball into 119 // the target directory. 120 // If overwrite is true, existing files will be overwritten. 121 func extractFile(tr *tar.Reader, target string, hdr *tar.Header, overwrite bool, editor FilePermissionsEditor) error { 122 p := filepath.Join(target, hdr.Name) 123 fi := hdr.FileInfo() 124 typ := hdr.Typeflag 125 if overwrite { 126 info, err := os.Lstat(p) 127 switch { 128 case os.IsNotExist(err): 129 case err == nil: 130 // If the old and new paths are both dirs do nothing or 131 // RemoveAll will remove all dir's contents 132 if !info.IsDir() || typ != tar.TypeDir { 133 err := os.RemoveAll(p) 134 if err != nil { 135 return err 136 } 137 } 138 default: 139 return err 140 } 141 } 142 143 // Create parent dir if it doesn't exist 144 if err := os.MkdirAll(filepath.Dir(p), DEFAULT_DIR_MODE); err != nil { 145 return err 146 } 147 switch { 148 case typ == tar.TypeReg || typ == tar.TypeRegA: 149 f, err := os.OpenFile(p, os.O_CREATE|os.O_RDWR, fi.Mode()) 150 if err != nil { 151 return err 152 } 153 _, err = io.Copy(f, tr) 154 if err != nil { 155 f.Close() 156 return err 157 } 158 f.Close() 159 case typ == tar.TypeDir: 160 if err := os.MkdirAll(p, fi.Mode()); err != nil { 161 return err 162 } 163 dir, err := os.Open(p) 164 if err != nil { 165 return err 166 } 167 if err := dir.Chmod(fi.Mode()); err != nil { 168 dir.Close() 169 return err 170 } 171 dir.Close() 172 case typ == tar.TypeLink: 173 dest := filepath.Join(target, hdr.Linkname) 174 if err := os.Link(dest, p); err != nil { 175 return err 176 } 177 case typ == tar.TypeSymlink: 178 if err := os.Symlink(hdr.Linkname, p); err != nil { 179 return err 180 } 181 case typ == tar.TypeChar: 182 dev := device.Makedev(uint(hdr.Devmajor), uint(hdr.Devminor)) 183 mode := uint32(fi.Mode()) | syscall.S_IFCHR 184 if err := syscall.Mknod(p, mode, int(dev)); err != nil { 185 return err 186 } 187 case typ == tar.TypeBlock: 188 dev := device.Makedev(uint(hdr.Devmajor), uint(hdr.Devminor)) 189 mode := uint32(fi.Mode()) | syscall.S_IFBLK 190 if err := syscall.Mknod(p, mode, int(dev)); err != nil { 191 return err 192 } 193 case typ == tar.TypeFifo: 194 if err := syscall.Mkfifo(p, uint32(fi.Mode())); err != nil { 195 return err 196 } 197 // TODO(jonboulle): implement other modes 198 default: 199 return fmt.Errorf("unsupported type: %v", typ) 200 } 201 202 if editor != nil { 203 if err := editor(p, hdr.Uid, hdr.Gid, hdr.Typeflag, fi); err != nil { 204 return err 205 } 206 } 207 208 // Restore entry atime and mtime. 209 // Use special function LUtimesNano not available on go's syscall package because we 210 // have to restore symlink's times and not the referenced file times. 211 ts := HdrToTimespec(hdr) 212 if hdr.Typeflag != tar.TypeSymlink { 213 if err := syscall.UtimesNano(p, ts); err != nil { 214 return err 215 } 216 } else { 217 if err := fileutil.LUtimesNano(p, ts); err != nil && err != ErrNotSupportedPlatform { 218 return err 219 } 220 } 221 222 return nil 223 } 224 225 // extractFileFromTar extracts a regular file from the given tar, returning its 226 // contents as a byte slice 227 func extractFileFromTar(tr *tar.Reader, file string) ([]byte, error) { 228 for { 229 hdr, err := tr.Next() 230 switch err { 231 case io.EOF: 232 return nil, fmt.Errorf("file not found") 233 case nil: 234 if filepath.Clean(hdr.Name) != filepath.Clean(file) { 235 continue 236 } 237 switch hdr.Typeflag { 238 case tar.TypeReg: 239 case tar.TypeRegA: 240 default: 241 return nil, fmt.Errorf("requested file not a regular file") 242 } 243 buf, err := ioutil.ReadAll(tr) 244 if err != nil { 245 return nil, errwrap.Wrap(errors.New("error extracting tarball"), err) 246 } 247 return buf, nil 248 default: 249 return nil, errwrap.Wrap(errors.New("error extracting tarball"), err) 250 } 251 } 252 } 253 254 func HdrToTimespec(hdr *tar.Header) []syscall.Timespec { 255 return []syscall.Timespec{fileutil.TimeToTimespec(hdr.AccessTime), fileutil.TimeToTimespec(hdr.ModTime)} 256 }