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 }