mirror of
https://github.com/restic/restic.git
synced 2024-06-27 07:55:08 +02:00
Previously the global context was either accessed via gopts.ctx, stored in a local variable and then used within that function or sometimes both. This makes it very hard to follow which ctx or a wrapped version of it reaches which method. Thus just drop the context from the globalOptions struct and pass it explicitly to every command line handler method.
206 lines
5.4 KiB
Go
206 lines
5.4 KiB
Go
//go:build darwin || freebsd || linux
|
|
// +build darwin freebsd linux
|
|
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
"github.com/restic/restic/internal/debug"
|
|
"github.com/restic/restic/internal/errors"
|
|
|
|
resticfs "github.com/restic/restic/internal/fs"
|
|
"github.com/restic/restic/internal/fuse"
|
|
|
|
systemFuse "bazil.org/fuse"
|
|
"bazil.org/fuse/fs"
|
|
)
|
|
|
|
var cmdMount = &cobra.Command{
|
|
Use: "mount [flags] mountpoint",
|
|
Short: "Mount the repository",
|
|
Long: `
|
|
The "mount" command mounts the repository via fuse to a directory. This is a
|
|
read-only mount.
|
|
|
|
Snapshot Directories
|
|
====================
|
|
|
|
If you need a different template for directories that contain snapshots,
|
|
you can pass a time template via --time-template and path templates via
|
|
--path-template.
|
|
|
|
Example time template without colons:
|
|
|
|
--time-template "2006-01-02_15-04-05"
|
|
|
|
You need to specify a sample format for exactly the following timestamp:
|
|
|
|
Mon Jan 2 15:04:05 -0700 MST 2006
|
|
|
|
For details please see the documentation for time.Format() at:
|
|
https://godoc.org/time#Time.Format
|
|
|
|
For path templates, you can use the following patterns which will be replaced:
|
|
%i by short snapshot ID
|
|
%I by long snapshot ID
|
|
%u by username
|
|
%h by hostname
|
|
%t by tags
|
|
%T by timestamp as specified by --time-template
|
|
|
|
The default path templates are:
|
|
"ids/%i"
|
|
"snapshots/%T"
|
|
"hosts/%h/%T"
|
|
"tags/%t/%T"
|
|
|
|
EXIT STATUS
|
|
===========
|
|
|
|
Exit status is 0 if the command was successful, and non-zero if there was any error.
|
|
`,
|
|
DisableAutoGenTag: true,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
return runMount(globalCtx(), mountOptions, globalOptions, args)
|
|
},
|
|
}
|
|
|
|
// MountOptions collects all options for the mount command.
|
|
type MountOptions struct {
|
|
OwnerRoot bool
|
|
AllowOther bool
|
|
NoDefaultPermissions bool
|
|
snapshotFilterOptions
|
|
TimeTemplate string
|
|
PathTemplates []string
|
|
}
|
|
|
|
var mountOptions MountOptions
|
|
|
|
func init() {
|
|
cmdRoot.AddCommand(cmdMount)
|
|
|
|
mountFlags := cmdMount.Flags()
|
|
mountFlags.BoolVar(&mountOptions.OwnerRoot, "owner-root", false, "use 'root' as the owner of files and dirs")
|
|
mountFlags.BoolVar(&mountOptions.AllowOther, "allow-other", false, "allow other users to access the data in the mounted directory")
|
|
mountFlags.BoolVar(&mountOptions.NoDefaultPermissions, "no-default-permissions", false, "for 'allow-other', ignore Unix permissions and allow users to read all snapshot files")
|
|
|
|
initMultiSnapshotFilterOptions(mountFlags, &mountOptions.snapshotFilterOptions, true)
|
|
|
|
mountFlags.StringArrayVar(&mountOptions.PathTemplates, "path-template", nil, "set `template` for path names (can be specified multiple times)")
|
|
mountFlags.StringVar(&mountOptions.TimeTemplate, "snapshot-template", time.RFC3339, "set `template` to use for snapshot dirs")
|
|
mountFlags.StringVar(&mountOptions.TimeTemplate, "time-template", time.RFC3339, "set `template` to use for times")
|
|
_ = mountFlags.MarkDeprecated("snapshot-template", "use --time-template")
|
|
}
|
|
|
|
func runMount(ctx context.Context, opts MountOptions, gopts GlobalOptions, args []string) error {
|
|
if opts.TimeTemplate == "" {
|
|
return errors.Fatal("time template string cannot be empty")
|
|
}
|
|
|
|
if strings.HasPrefix(opts.TimeTemplate, "/") || strings.HasSuffix(opts.TimeTemplate, "/") {
|
|
return errors.Fatal("time template string cannot start or end with '/'")
|
|
}
|
|
|
|
if len(args) == 0 {
|
|
return errors.Fatal("wrong number of parameters")
|
|
}
|
|
|
|
debug.Log("start mount")
|
|
defer debug.Log("finish mount")
|
|
|
|
repo, err := OpenRepository(ctx, gopts)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !gopts.NoLock {
|
|
lock, err := lockRepo(ctx, repo)
|
|
defer unlockRepo(lock)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
err = repo.LoadIndex(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
mountpoint := args[0]
|
|
|
|
if _, err := resticfs.Stat(mountpoint); errors.Is(err, os.ErrNotExist) {
|
|
Verbosef("Mountpoint %s doesn't exist\n", mountpoint)
|
|
return err
|
|
}
|
|
mountOptions := []systemFuse.MountOption{
|
|
systemFuse.ReadOnly(),
|
|
systemFuse.FSName("restic"),
|
|
systemFuse.MaxReadahead(128 * 1024),
|
|
}
|
|
|
|
if opts.AllowOther {
|
|
mountOptions = append(mountOptions, systemFuse.AllowOther())
|
|
|
|
// let the kernel check permissions unless it is explicitly disabled
|
|
if !opts.NoDefaultPermissions {
|
|
mountOptions = append(mountOptions, systemFuse.DefaultPermissions())
|
|
}
|
|
}
|
|
|
|
AddCleanupHandler(func(code int) (int, error) {
|
|
debug.Log("running umount cleanup handler for mount at %v", mountpoint)
|
|
err := umount(mountpoint)
|
|
if err != nil {
|
|
Warnf("unable to umount (maybe already umounted or still in use?): %v\n", err)
|
|
}
|
|
// replace error code of sigint
|
|
if code == 130 {
|
|
code = 0
|
|
}
|
|
return code, nil
|
|
})
|
|
|
|
c, err := systemFuse.Mount(mountpoint, mountOptions...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
systemFuse.Debug = func(msg interface{}) {
|
|
debug.Log("fuse: %v", msg)
|
|
}
|
|
|
|
cfg := fuse.Config{
|
|
OwnerIsRoot: opts.OwnerRoot,
|
|
Hosts: opts.Hosts,
|
|
Tags: opts.Tags,
|
|
Paths: opts.Paths,
|
|
TimeTemplate: opts.TimeTemplate,
|
|
PathTemplates: opts.PathTemplates,
|
|
}
|
|
root := fuse.NewRoot(repo, cfg)
|
|
|
|
Printf("Now serving the repository at %s\n", mountpoint)
|
|
Printf("Use another terminal or tool to browse the contents of this folder.\n")
|
|
Printf("When finished, quit with Ctrl-c here or umount the mountpoint.\n")
|
|
|
|
debug.Log("serving mount at %v", mountpoint)
|
|
err = fs.Serve(c, root)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
<-c.Ready
|
|
return c.MountError
|
|
}
|
|
|
|
func umount(mountpoint string) error {
|
|
return systemFuse.Unmount(mountpoint)
|
|
}
|