// extractFile extracts the file described by hdr from the given tarball into // the target directory. // If overwrite is true, existing files will be overwritten. func extractFile(tr *tar.Reader, target string, hdr *tar.Header, overwrite bool, editor FilePermissionsEditor) error { p := filepath.Join(target, hdr.Name) fi := hdr.FileInfo() typ := hdr.Typeflag if overwrite { info, err := os.Lstat(p) switch { case os.IsNotExist(err): case err == nil: // If the old and new paths are both dirs do nothing or // RemoveAll will remove all dir's contents if !info.IsDir() || typ != tar.TypeDir { err := os.RemoveAll(p) if err != nil { return err } } default: return err } } // Create parent dir if it doesn't exist if err := os.MkdirAll(filepath.Dir(p), DEFAULT_DIR_MODE); err != nil { return err } switch { case typ == tar.TypeReg || typ == tar.TypeRegA: f, err := os.OpenFile(p, os.O_CREATE|os.O_RDWR, fi.Mode()) if err != nil { return err } _, err = io.Copy(f, tr) if err != nil { f.Close() return err } f.Close() case typ == tar.TypeDir: if err := os.MkdirAll(p, fi.Mode()); err != nil { return err } dir, err := os.Open(p) if err != nil { return err } if err := dir.Chmod(fi.Mode()); err != nil { dir.Close() return err } dir.Close() case typ == tar.TypeLink: dest := filepath.Join(target, hdr.Linkname) if err := os.Link(dest, p); err != nil { return err } case typ == tar.TypeSymlink: if err := os.Symlink(hdr.Linkname, p); err != nil { return err } case typ == tar.TypeChar: dev := device.Makedev(uint(hdr.Devmajor), uint(hdr.Devminor)) mode := uint32(fi.Mode()) | syscall.S_IFCHR if err := syscall.Mknod(p, mode, int(dev)); err != nil { return err } case typ == tar.TypeBlock: dev := device.Makedev(uint(hdr.Devmajor), uint(hdr.Devminor)) mode := uint32(fi.Mode()) | syscall.S_IFBLK if err := syscall.Mknod(p, mode, int(dev)); err != nil { return err } case typ == tar.TypeFifo: if err := syscall.Mkfifo(p, uint32(fi.Mode())); err != nil { return err } // TODO(jonboulle): implement other modes default: return fmt.Errorf("unsupported type: %v", typ) } if editor != nil { if err := editor(p, hdr.Uid, hdr.Gid, hdr.Typeflag, fi); err != nil { return err } } // Restore entry atime and mtime. // Use special function LUtimesNano not available on go's syscall package because we // have to restore symlink's times and not the referenced file times. ts := HdrToTimespec(hdr) if hdr.Typeflag != tar.TypeSymlink { if err := syscall.UtimesNano(p, ts); err != nil { return err } } else { if err := fileutil.LUtimesNano(p, ts); err != nil && err != ErrNotSupportedPlatform { return err } } return nil }
func CopyTree(src, dest string, uidRange *uid.UidRange) error { cleanSrc := filepath.Clean(src) dirs := make(map[string][]syscall.Timespec) copyWalker := func(path string, info os.FileInfo, err error) error { if err != nil { return err } rootLess := path[len(cleanSrc):] target := filepath.Join(dest, rootLess) mode := info.Mode() switch { case mode.IsDir(): err := os.Mkdir(target, mode.Perm()) if err != nil { return err } dir, err := os.Open(target) if err != nil { return err } if err := dir.Chmod(mode); err != nil { dir.Close() return err } dir.Close() case mode.IsRegular(): if err := CopyRegularFile(path, target); err != nil { return err } case mode&os.ModeSymlink == os.ModeSymlink: if err := CopySymlink(path, target); err != nil { return err } case mode&os.ModeCharDevice == os.ModeCharDevice: stat := syscall.Stat_t{} if err := syscall.Stat(path, &stat); err != nil { return err } dev := device.Makedev(device.Major(stat.Rdev), device.Minor(stat.Rdev)) mode := uint32(mode) | syscall.S_IFCHR if err := syscall.Mknod(target, mode, int(dev)); err != nil { return err } case mode&os.ModeDevice == os.ModeDevice: stat := syscall.Stat_t{} if err := syscall.Stat(path, &stat); err != nil { return err } dev := device.Makedev(device.Major(stat.Rdev), device.Minor(stat.Rdev)) mode := uint32(mode) | syscall.S_IFBLK if err := syscall.Mknod(target, mode, int(dev)); err != nil { return err } case mode&os.ModeNamedPipe == os.ModeNamedPipe: if err := syscall.Mkfifo(target, uint32(mode)); err != nil { return err } default: return fmt.Errorf("unsupported mode: %v", mode) } var srcUid = info.Sys().(*syscall.Stat_t).Uid var srcGid = info.Sys().(*syscall.Stat_t).Gid shiftedUid, shiftedGid, err := uidRange.ShiftRange(srcUid, srcGid) if err != nil { return err } if err := os.Lchown(target, int(shiftedUid), int(shiftedGid)); err != nil { return err } // lchown(2) says that, depending on the linux kernel version, it // can change the file's mode also if executed as root. So call // os.Chmod after it. if mode&os.ModeSymlink != os.ModeSymlink { if err := os.Chmod(target, mode); err != nil { return err } } ts, err := pathToTimespec(path) if err != nil { return err } if mode.IsDir() { dirs[target] = ts } if mode&os.ModeSymlink != os.ModeSymlink { if err := syscall.UtimesNano(target, ts); err != nil { return err } } else { if err := LUtimesNano(target, ts); err != nil { return err } } return nil } if err := filepath.Walk(cleanSrc, copyWalker); err != nil { return err } // Restore dirs atime and mtime. This has to be done after copying // as a file copying will change its parent directory's times. for dirPath, ts := range dirs { if err := syscall.UtimesNano(dirPath, ts); err != nil { return err } } return nil }