github.com/mckael/restic@v0.8.3/cmd/restic/cmd_mount.go (about)

     1  // +build !openbsd
     2  // +build !windows
     3  
     4  package main
     5  
     6  import (
     7  	"os"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/spf13/cobra"
    12  
    13  	"github.com/restic/restic/internal/debug"
    14  	"github.com/restic/restic/internal/errors"
    15  	"github.com/restic/restic/internal/restic"
    16  
    17  	resticfs "github.com/restic/restic/internal/fs"
    18  	"github.com/restic/restic/internal/fuse"
    19  
    20  	systemFuse "bazil.org/fuse"
    21  	"bazil.org/fuse/fs"
    22  )
    23  
    24  var cmdMount = &cobra.Command{
    25  	Use:   "mount [flags] mountpoint",
    26  	Short: "Mount the repository",
    27  	Long: `
    28  The "mount" command mounts the repository via fuse to a directory. This is a
    29  read-only mount.
    30  
    31  Snapshot Directories
    32  ====================
    33  
    34  If you need a different template for all directories that contain snapshots,
    35  you can pass a template via --snapshot-template. Example without colons:
    36  
    37      --snapshot-template "2006-01-02_15-04-05"
    38  
    39  You need to specify a sample format for exactly the following timestamp:
    40  
    41      Mon Jan 2 15:04:05 -0700 MST 2006
    42  
    43  For details please see the documentation for time.Format() at:
    44    https://godoc.org/time#Time.Format
    45  `,
    46  	DisableAutoGenTag: true,
    47  	RunE: func(cmd *cobra.Command, args []string) error {
    48  		return runMount(mountOptions, globalOptions, args)
    49  	},
    50  }
    51  
    52  // MountOptions collects all options for the mount command.
    53  type MountOptions struct {
    54  	OwnerRoot        bool
    55  	AllowRoot        bool
    56  	AllowOther       bool
    57  	Host             string
    58  	Tags             restic.TagLists
    59  	Paths            []string
    60  	SnapshotTemplate string
    61  }
    62  
    63  var mountOptions MountOptions
    64  
    65  func init() {
    66  	cmdRoot.AddCommand(cmdMount)
    67  
    68  	mountFlags := cmdMount.Flags()
    69  	mountFlags.BoolVar(&mountOptions.OwnerRoot, "owner-root", false, "use 'root' as the owner of files and dirs")
    70  	mountFlags.BoolVar(&mountOptions.AllowRoot, "allow-root", false, "allow root user to access the data in the mounted directory")
    71  	mountFlags.BoolVar(&mountOptions.AllowOther, "allow-other", false, "allow other users to access the data in the mounted directory")
    72  
    73  	mountFlags.StringVarP(&mountOptions.Host, "host", "H", "", `only consider snapshots for this host`)
    74  	mountFlags.Var(&mountOptions.Tags, "tag", "only consider snapshots which include this `taglist`")
    75  	mountFlags.StringArrayVar(&mountOptions.Paths, "path", nil, "only consider snapshots which include this (absolute) `path`")
    76  
    77  	mountFlags.StringVar(&mountOptions.SnapshotTemplate, "snapshot-template", time.RFC3339, "set `template` to use for snapshot dirs")
    78  }
    79  
    80  func mount(opts MountOptions, gopts GlobalOptions, mountpoint string) error {
    81  	debug.Log("start mount")
    82  	defer debug.Log("finish mount")
    83  
    84  	repo, err := OpenRepository(gopts)
    85  	if err != nil {
    86  		return err
    87  	}
    88  
    89  	lock, err := lockRepo(repo)
    90  	defer unlockRepo(lock)
    91  	if err != nil {
    92  		return err
    93  	}
    94  
    95  	err = repo.LoadIndex(gopts.ctx)
    96  	if err != nil {
    97  		return err
    98  	}
    99  
   100  	if _, err := resticfs.Stat(mountpoint); os.IsNotExist(errors.Cause(err)) {
   101  		Verbosef("Mountpoint %s doesn't exist, creating it\n", mountpoint)
   102  		err = resticfs.Mkdir(mountpoint, os.ModeDir|0700)
   103  		if err != nil {
   104  			return err
   105  		}
   106  	}
   107  
   108  	mountOptions := []systemFuse.MountOption{
   109  		systemFuse.ReadOnly(),
   110  		systemFuse.FSName("restic"),
   111  	}
   112  
   113  	if opts.AllowRoot {
   114  		mountOptions = append(mountOptions, systemFuse.AllowRoot())
   115  	}
   116  
   117  	if opts.AllowOther {
   118  		mountOptions = append(mountOptions, systemFuse.AllowOther())
   119  	}
   120  
   121  	c, err := systemFuse.Mount(mountpoint, mountOptions...)
   122  	if err != nil {
   123  		return err
   124  	}
   125  
   126  	systemFuse.Debug = func(msg interface{}) {
   127  		debug.Log("fuse: %v", msg)
   128  	}
   129  
   130  	cfg := fuse.Config{
   131  		OwnerIsRoot:      opts.OwnerRoot,
   132  		Host:             opts.Host,
   133  		Tags:             opts.Tags,
   134  		Paths:            opts.Paths,
   135  		SnapshotTemplate: opts.SnapshotTemplate,
   136  	}
   137  	root, err := fuse.NewRoot(gopts.ctx, repo, cfg)
   138  	if err != nil {
   139  		return err
   140  	}
   141  
   142  	Printf("Now serving the repository at %s\n", mountpoint)
   143  	Printf("Don't forget to umount after quitting!\n")
   144  
   145  	debug.Log("serving mount at %v", mountpoint)
   146  	err = fs.Serve(c, root)
   147  	if err != nil {
   148  		return err
   149  	}
   150  
   151  	<-c.Ready
   152  	return c.MountError
   153  }
   154  
   155  func umount(mountpoint string) error {
   156  	return systemFuse.Unmount(mountpoint)
   157  }
   158  
   159  func runMount(opts MountOptions, gopts GlobalOptions, args []string) error {
   160  	if opts.SnapshotTemplate == "" {
   161  		return errors.Fatal("snapshot template string cannot be empty")
   162  	}
   163  
   164  	if strings.ContainsAny(opts.SnapshotTemplate, `\/`) {
   165  		return errors.Fatal("snapshot template string contains a slash (/) or backslash (\\) character")
   166  	}
   167  
   168  	if len(args) == 0 {
   169  		return errors.Fatal("wrong number of parameters")
   170  	}
   171  
   172  	mountpoint := args[0]
   173  
   174  	AddCleanupHandler(func() error {
   175  		debug.Log("running umount cleanup handler for mount at %v", mountpoint)
   176  		err := umount(mountpoint)
   177  		if err != nil {
   178  			Warnf("unable to umount (maybe already umounted?): %v\n", err)
   179  		}
   180  		return nil
   181  	})
   182  
   183  	return mount(opts, gopts, mountpoint)
   184  }