func NewUidShiftingFilePermEditor(uidRange *user.UidRange) (FilePermissionsEditor, error) { if os.Geteuid() != 0 { return func(_ string, _, _ int, _ byte, _ os.FileInfo) error { // The files are owned by the current user on creation. // If we do nothing, they will remain so. return nil }, nil } return func(path string, uid, gid int, typ byte, fi os.FileInfo) error { shiftedUid, shiftedGid, err := uidRange.ShiftRange(uint32(uid), uint32(gid)) if err != nil { return err } if err := os.Lchown(path, 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 typ != tar.TypeSymlink { if err := os.Chmod(path, fi.Mode()); err != nil { return err } } return nil }, nil }
func CopyTree(src, dest string, uidRange *user.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 }
func runExport(cmd *cobra.Command, args []string) (exit int) { if len(args) != 2 { cmd.Usage() return 1 } outACI := args[1] ext := filepath.Ext(outACI) if ext != schema.ACIExtension { stderr.Printf("extension must be %s (given %s)", schema.ACIExtension, outACI) return 1 } p, err := getPodFromUUIDString(args[0]) if err != nil { stderr.PrintE("problem retrieving pod", err) return 1 } defer p.Close() if !p.isExited { stderr.Print("pod is not exited. Only exited pods can be exported") return 1 } app, err := getApp(p) if err != nil { stderr.PrintE("unable to find app", err) return 1 } root := common.AppPath(p.path(), app.Name) manifestPath := filepath.Join(common.AppInfoPath(p.path(), app.Name), aci.ManifestFile) if p.usesOverlay() { tmpDir := filepath.Join(getDataDir(), "tmp") if err := os.MkdirAll(tmpDir, common.DefaultRegularDirPerm); err != nil { stderr.PrintE("unable to create temp directory", err) return 1 } podDir, err := ioutil.TempDir(tmpDir, fmt.Sprintf("rkt-export-%s", p.uuid)) if err != nil { stderr.PrintE("unable to create export temp directory", err) return 1 } defer func() { if err := os.RemoveAll(podDir); err != nil { stderr.PrintE("problem removing temp directory", err) exit = 1 } }() mntDir := filepath.Join(podDir, "rootfs") if err := os.Mkdir(mntDir, common.DefaultRegularDirPerm); err != nil { stderr.PrintE("unable to create rootfs directory inside temp directory", err) return 1 } if err := mountOverlay(p, app, mntDir); err != nil { stderr.PrintE(fmt.Sprintf("couldn't mount directory at %s", mntDir), err) return 1 } defer func() { if err := syscall.Unmount(mntDir, 0); err != nil { stderr.PrintE(fmt.Sprintf("error unmounting directory %s", mntDir), err) exit = 1 } }() root = podDir } else { hasMPs, err := appHasMountpoints(p.path(), app.Name) if err != nil { stderr.PrintE("error parsing mountpoints", err) return 1 } if hasMPs { stderr.Printf("pod has remaining mountpoints. Only pods using overlayfs or with no mountpoints can be exported") return 1 } } // Check for user namespace (--private-user), if in use get uidRange var uidRange *user.UidRange privUserFile := filepath.Join(p.path(), common.PrivateUsersPreparedFilename) privUserContent, err := ioutil.ReadFile(privUserFile) if err == nil { uidRange = user.NewBlankUidRange() // The file was found, save uid & gid shift and count if err := uidRange.Deserialize(privUserContent); err != nil { stderr.PrintE(fmt.Sprintf("problem deserializing the content of %s", common.PrivateUsersPreparedFilename), err) return 1 } } if err = buildAci(root, manifestPath, outACI, uidRange); err != nil { stderr.PrintE("error building aci", err) return 1 } return 0 }
// buildAci builds a target aci from the root directory using any uid shift // information from uidRange. func buildAci(root, manifestPath, target string, uidRange *user.UidRange) (e error) { mode := os.O_CREATE | os.O_WRONLY if flagOverwriteACI { mode |= os.O_TRUNC } else { mode |= os.O_EXCL } aciFile, err := os.OpenFile(target, mode, 0644) if err != nil { if os.IsExist(err) { return errors.New("target file exists (try --overwrite)") } else { return errwrap.Wrap(fmt.Errorf("unable to open target %s", target), err) } } gw := gzip.NewWriter(aciFile) tr := tar.NewWriter(gw) defer func() { tr.Close() gw.Close() aciFile.Close() // e is implicitly assigned by the return statement. As defer runs // after return, but before actually returning, this works. if e != nil { os.Remove(target) } }() b, err := ioutil.ReadFile(manifestPath) if err != nil { return errwrap.Wrap(errors.New("unable to read Image Manifest"), err) } var im schema.ImageManifest if err := im.UnmarshalJSON(b); err != nil { return errwrap.Wrap(errors.New("unable to load Image Manifest"), err) } iw := aci.NewImageWriter(im, tr) // Unshift uid and gid when pod was started with --private-user (user namespace) var walkerCb aci.TarHeaderWalkFunc = func(hdr *tar.Header) bool { if uidRange != nil { uid, gid, err := uidRange.UnshiftRange(uint32(hdr.Uid), uint32(hdr.Gid)) if err != nil { stderr.PrintE("error unshifting gid and uid", err) return false } hdr.Uid, hdr.Gid = int(uid), int(gid) } return true } if err := filepath.Walk(root, aci.BuildWalker(root, iw, walkerCb)); err != nil { return errwrap.Wrap(errors.New("error walking rootfs"), err) } if err = iw.Close(); err != nil { return errwrap.Wrap(fmt.Errorf("unable to close image %s", target), err) } return }