Skip to content

Commit

Permalink
feat: add experimental 'mount' command
Browse files Browse the repository at this point in the history
Add experimental support for rootless mount of SIF images.
  • Loading branch information
Adam Hughes committed Apr 4, 2022
1 parent c5ac0f2 commit a31c3cd
Show file tree
Hide file tree
Showing 7 changed files with 170 additions and 0 deletions.
20 changes: 20 additions & 0 deletions internal/app/siftool/mount.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// 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 siftool

import (
"context"

"github.com/sylabs/sif/v2/internal/pkg/exp"
)

// Mount mounts the primary system partition of the SIF file at path into mountPath.
func (a *App) Mount(ctx context.Context, path, mountPath string) error {
return exp.Mount(ctx, path, mountPath,
exp.OptMountStdout(a.opts.out),
exp.OptMountStderr(a.opts.err),
)
}
104 changes: 104 additions & 0 deletions internal/pkg/exp/mount.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// 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 exp contains experimental functionality that is not sufficiently mature to be exported
// as part of the module API.
package exp

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),
}

cmd := exec.CommandContext(ctx, "squashfuse", 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
}

// 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 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.
func Mount(ctx context.Context, path, mountPath string, opts ...MountOpt) error {
mo := mountOpts{}

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
}
}
27 changes: 27 additions & 0 deletions pkg/siftool/mount.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// 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 siftool

import (
"github.com/spf13/cobra"
)

// getMount returns a command that mounts the primary system partition of a SIF image.
func (c *command) getMount() *cobra.Command {
return &cobra.Command{
Use: "mount <sif_path> <mount_path>",
Short: "Mount primary system partition",
Long: "Mount the primary system partition of a SIF image",
Example: c.opts.rootPath + " mount image.sif path/",
Args: cobra.ExactArgs(2),
PreRunE: c.initApp,
RunE: func(cmd *cobra.Command, args []string) error {
return c.app.Mount(cmd.Context(), args[0], args[1])
},
DisableFlagsInUseLine: true,
Hidden: true, // hide while command is experimental
}
}
4 changes: 4 additions & 0 deletions pkg/siftool/siftool.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,5 +76,9 @@ func AddCommands(cmd *cobra.Command, opts ...CommandOpt) error {
c.getSetPrim(),
)

if c.opts.experimental {
cmd.AddCommand(c.getMount())
}

return nil
}
5 changes: 5 additions & 0 deletions pkg/siftool/siftool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,11 @@ func TestAddCommands(t *testing.T) {
name: "SetPrim",
args: []string{"help", "setprim"},
},
{
name: "Mount",
opts: []CommandOpt{OptWithExperimental(true)},
args: []string{"help", "mount"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
Empty file.
10 changes: 10 additions & 0 deletions pkg/siftool/testdata/TestAddCommands/Mount/out.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Mount the primary system partition of a SIF image

Usage:
siftool mount <sif_path> <mount_path>

Examples:
siftool mount image.sif path/

Flags:
-h, --help help for mount

0 comments on commit a31c3cd

Please sign in to comment.