//go:build linux // +build linux package overlay import ( "bytes" "flag" "fmt" "os" "path/filepath" "runtime" "strings" "github.com/containers/storage/pkg/reexec" "golang.org/x/sys/unix" ) func init() { reexec.Register("storage-mountfrom", mountOverlayFromMain) } func fatal(err error) { fmt.Fprint(os.Stderr, err) os.Exit(1) } type mountOptions struct { Device string Target string Type string Label string Flag uint32 } func mountOverlayFrom(dir, device, target, mType string, flags uintptr, label string) error { options := &mountOptions{ Device: device, Target: target, Type: mType, Flag: uint32(flags), Label: label, } cmd := reexec.Command("storage-mountfrom", dir) w, err := cmd.StdinPipe() if err != nil { return fmt.Errorf("mountfrom error on pipe creation: %w", err) } output := bytes.NewBuffer(nil) cmd.Stdout = output cmd.Stderr = output if err := cmd.Start(); err != nil { w.Close() return fmt.Errorf("mountfrom error on re-exec cmd: %w", err) } // write the options to the pipe for the untar exec to read if err := json.NewEncoder(w).Encode(options); err != nil { w.Close() return fmt.Errorf("mountfrom json encode to pipe failed: %w", err) } w.Close() if err := cmd.Wait(); err != nil { return fmt.Errorf("mountfrom re-exec output: %s: error: %w", output, err) } return nil } // mountfromMain is the entry-point for storage-mountfrom on re-exec. func mountOverlayFromMain() { runtime.LockOSThread() flag.Parse() var options *mountOptions if err := json.NewDecoder(os.Stdin).Decode(&options); err != nil { fatal(err) } // Mount the arguments passed from the specified directory. Some of the // paths mentioned in the values we pass to the kernel are relative to // the specified directory. homedir := flag.Arg(0) if err := os.Chdir(homedir); err != nil { fatal(err) } pageSize := unix.Getpagesize() if len(options.Label) < pageSize { if err := unix.Mount(options.Device, options.Target, options.Type, uintptr(options.Flag), options.Label); err != nil { fatal(err) } os.Exit(0) } // Those arguments still took up too much space. Open the diff // directories and use their descriptor numbers as lowers, using // /proc/self/fd as the current directory. // Split out the various options, since we need to manipulate the // paths, but we don't want to mess with other options. var upperk, upperv, workk, workv, lowerk, lowerv, labelk, labelv, others string for _, arg := range strings.Split(options.Label, ",") { kv := strings.SplitN(arg, "=", 2) switch kv[0] { case "upperdir": upperk = "upperdir=" upperv = kv[1] case "workdir": workk = "workdir=" workv = kv[1] case "lowerdir": lowerk = "lowerdir=" lowerv = kv[1] case "label": labelk = "label=" labelv = kv[1] default: if others == "" { others = arg } else { others = others + "," + arg } } } // Make sure upperdir, workdir, and the target are absolute paths. if upperv != "" && !filepath.IsAbs(upperv) { upperv = filepath.Join(homedir, upperv) } if workv != "" && !filepath.IsAbs(workv) { workv = filepath.Join(homedir, workv) } if !filepath.IsAbs(options.Target) { options.Target = filepath.Join(homedir, options.Target) } // Get a descriptor for each lower, and use that descriptor's name as // the new value for the list of lowers, because it's shorter. if lowerv != "" { lowers := strings.Split(lowerv, ":") var newLowers []string dataOnly := false for _, lowerPath := range lowers { if lowerPath == "" { dataOnly = true continue } lowerFd, err := unix.Open(lowerPath, unix.O_RDONLY, 0) if err != nil { fatal(err) } var lower string if dataOnly { lower = fmt.Sprintf(":%d", lowerFd) dataOnly = false } else { lower = fmt.Sprintf("%d", lowerFd) } newLowers = append(newLowers, lower) } lowerv = strings.Join(newLowers, ":") } // Reconstruct the Label field. options.Label = upperk + upperv + "," + workk + workv + "," + lowerk + lowerv + "," + labelk + labelv + "," + others options.Label = strings.ReplaceAll(options.Label, ",,", ",") // Okay, try this, if we managed to make the arguments fit. var err error if len(options.Label) < pageSize { if err := os.Chdir("/proc/self/fd"); err != nil { fatal(err) } err = unix.Mount(options.Device, options.Target, options.Type, uintptr(options.Flag), options.Label) } else { err = fmt.Errorf("cannot mount layer, mount data %q too large %d >= page size %d", options.Label, len(options.Label), pageSize) } // Clean up. if err != nil { fmt.Fprintf(os.Stderr, "creating overlay mount to %s, mount_data=%q\n", options.Target, options.Label) fatal(err) } os.Exit(0) }