diff options
Diffstat (limited to 'pkg/user/mount.go')
-rw-r--r-- | pkg/user/mount.go | 122 |
1 files changed, 122 insertions, 0 deletions
diff --git a/pkg/user/mount.go b/pkg/user/mount.go new file mode 100644 index 0000000..c96e414 --- /dev/null +++ b/pkg/user/mount.go @@ -0,0 +1,122 @@ +// Copyright (c) 2022, Sylabs Inc. All rights reserved. +// This software is licensed under a 3-clause BSD license. Please consult the +// LICENSE file distributed with the sources of this project regarding your +// rights to use or distribute this software. + +package user + +import ( + "context" + "errors" + "fmt" + "io" + "os" + "os/exec" + "path/filepath" + + "github.com/sylabs/sif/v2/pkg/sif" +) + +// mountSquashFS mounts the SquashFS filesystem from path at offset into mountPath. +func mountSquashFS(ctx context.Context, offset int64, path, mountPath string, mo mountOpts) error { + args := []string{ + "-o", fmt.Sprintf("ro,offset=%d", offset), + filepath.Clean(path), + filepath.Clean(mountPath), + } + //nolint:gosec // note (gosec exclusion) - we require callers to be able to specify squashfuse not on PATH + cmd := exec.CommandContext(ctx, mo.squashfusePath, args...) + cmd.Stdout = mo.stdout + cmd.Stderr = mo.stderr + + if err := cmd.Run(); err != nil { + return fmt.Errorf("failed to mount: %w", err) + } + + return nil +} + +// mountOpts accumulates mount options. +type mountOpts struct { + stdout io.Writer + stderr io.Writer + squashfusePath string +} + +// MountOpt are used to specify mount options. +type MountOpt func(*mountOpts) error + +// OptMountStdout writes standard output to w. +func OptMountStdout(w io.Writer) MountOpt { + return func(mo *mountOpts) error { + mo.stdout = w + return nil + } +} + +// OptMountStderr writes standard error to w. +func OptMountStderr(w io.Writer) MountOpt { + return func(mo *mountOpts) error { + mo.stderr = w + return nil + } +} + +var errSquashfusePathInvalid = errors.New("squashfuse path must be relative or absolute") + +// OptMountSquashfusePath sets an explicit path to the squashfuse binary. The path must be an +// absolute or relative path. +func OptMountSquashfusePath(path string) MountOpt { + return func(mo *mountOpts) error { + if filepath.Base(path) == path { + return errSquashfusePathInvalid + } + mo.squashfusePath = path + return nil + } +} + +var errUnsupportedFSType = errors.New("unrecognized filesystem type") + +// Mount mounts the primary system partition of the SIF file at path into mountPath. +// +// Mount may start one or more underlying processes. By default, stdout and stderr of these +// processes is discarded. To modify this behavior, consider using OptMountStdout and/or +// OptMountStderr. +// +// By default, Mount searches for a squashfuse binary in the directories named by the PATH +// environment variable. To override this behavior, consider using OptMountSquashfusePath(). +func Mount(ctx context.Context, path, mountPath string, opts ...MountOpt) error { + mo := mountOpts{ + squashfusePath: "squashfuse", + } + + for _, opt := range opts { + if err := opt(&mo); err != nil { + return fmt.Errorf("%w", err) + } + } + + f, err := sif.LoadContainerFromPath(path, sif.OptLoadWithFlag(os.O_RDONLY)) + if err != nil { + return fmt.Errorf("failed to load image: %w", err) + } + defer func() { _ = f.UnloadContainer() }() + + d, err := f.GetDescriptor(sif.WithPartitionType(sif.PartPrimSys)) + if err != nil { + return fmt.Errorf("failed to get partition descriptor: %w", err) + } + + fs, _, _, err := d.PartitionMetadata() + if err != nil { + return fmt.Errorf("failed to get partition metadata: %w", err) + } + + switch fs { + case sif.FsSquash: + return mountSquashFS(ctx, d.Offset(), path, mountPath, mo) + default: + return errUnsupportedFSType + } +} |