github.com/artpar/rclone@v1.67.3/cmd/serve/docker/volume.go (about) 1 package docker 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "os" 8 "path/filepath" 9 "runtime" 10 "sort" 11 "time" 12 13 "github.com/artpar/rclone/cmd/mountlib" 14 "github.com/artpar/rclone/fs" 15 "github.com/artpar/rclone/fs/config" 16 "github.com/artpar/rclone/fs/rc" 17 "github.com/artpar/rclone/lib/file" 18 ) 19 20 // Errors 21 var ( 22 ErrVolumeNotFound = errors.New("volume not found") 23 ErrVolumeExists = errors.New("volume already exists") 24 ErrMountpointExists = errors.New("non-empty mountpoint already exists") 25 ) 26 27 // Volume keeps volume runtime state 28 // Public members get persisted in saved state 29 type Volume struct { 30 Name string `json:"name"` 31 MountPoint string `json:"mountpoint"` 32 CreatedAt time.Time `json:"created"` 33 Fs string `json:"fs"` // remote[,connectString]:path 34 Type string `json:"type,omitempty"` // same as ":backend:" 35 Path string `json:"path,omitempty"` // for "remote:path" or ":backend:path" 36 Options VolOpts `json:"options"` // all options together 37 Mounts []string `json:"mounts"` // mountReqs as a string list 38 mountReqs map[string]interface{} 39 fsString string // result of merging Fs, Type and Options 40 persist bool 41 mountType string 42 drv *Driver 43 mnt *mountlib.MountPoint 44 } 45 46 // VolOpts keeps volume options 47 type VolOpts map[string]string 48 49 // VolInfo represents a volume for Get and List requests 50 type VolInfo struct { 51 Name string 52 Mountpoint string `json:",omitempty"` 53 CreatedAt string `json:",omitempty"` 54 Status map[string]interface{} `json:",omitempty"` 55 } 56 57 func newVolume(ctx context.Context, name string, volOpt VolOpts, drv *Driver) (*Volume, error) { 58 path := filepath.Join(drv.root, name) 59 mnt := &mountlib.MountPoint{ 60 MountPoint: path, 61 } 62 vol := &Volume{ 63 Name: name, 64 MountPoint: path, 65 CreatedAt: time.Now(), 66 drv: drv, 67 mnt: mnt, 68 mountReqs: make(map[string]interface{}), 69 } 70 err := vol.applyOptions(volOpt) 71 if err == nil { 72 err = vol.setup(ctx) 73 } 74 if err != nil { 75 return nil, err 76 } 77 return vol, nil 78 } 79 80 // getInfo returns short digest about volume 81 func (vol *Volume) getInfo() *VolInfo { 82 vol.prepareState() 83 return &VolInfo{ 84 Name: vol.Name, 85 CreatedAt: vol.CreatedAt.Format(time.RFC3339), 86 Mountpoint: vol.MountPoint, 87 Status: rc.Params{"Mounts": vol.Mounts}, 88 } 89 } 90 91 // prepareState prepares volume for saving state 92 func (vol *Volume) prepareState() { 93 vol.Mounts = []string{} 94 for id := range vol.mountReqs { 95 vol.Mounts = append(vol.Mounts, id) 96 } 97 sort.Strings(vol.Mounts) 98 } 99 100 // restoreState updates volume from saved state 101 func (vol *Volume) restoreState(ctx context.Context, drv *Driver) error { 102 vol.drv = drv 103 vol.mnt = &mountlib.MountPoint{ 104 MountPoint: vol.MountPoint, 105 } 106 volOpt := vol.Options 107 volOpt["fs"] = vol.Fs 108 volOpt["type"] = vol.Type 109 if err := vol.applyOptions(volOpt); err != nil { 110 return err 111 } 112 if err := vol.validate(); err != nil { 113 return err 114 } 115 if err := vol.setup(ctx); err != nil { 116 return err 117 } 118 for _, id := range vol.Mounts { 119 if err := vol.mount(id); err != nil { 120 return err 121 } 122 } 123 return nil 124 } 125 126 // validate volume 127 func (vol *Volume) validate() error { 128 if vol.Name == "" { 129 return errors.New("volume name is required") 130 } 131 if (vol.Type != "" && vol.Fs != "") || (vol.Type == "" && vol.Fs == "") { 132 return errors.New("volume must have either remote or backend type") 133 } 134 if vol.persist && vol.Type == "" { 135 return errors.New("backend type is required to persist remotes") 136 } 137 if vol.persist && !canPersist { 138 return errors.New("using backend type to persist remotes is prohibited") 139 } 140 if vol.MountPoint == "" { 141 return errors.New("mount point is required") 142 } 143 if vol.mountReqs == nil { 144 vol.mountReqs = make(map[string]interface{}) 145 } 146 return nil 147 } 148 149 // checkMountpoint verifies that mount point is an existing empty directory 150 func (vol *Volume) checkMountpoint() error { 151 path := vol.mnt.MountPoint 152 if runtime.GOOS == "windows" { 153 path = filepath.Dir(path) 154 } 155 _, err := os.Lstat(path) 156 if os.IsNotExist(err) { 157 if err = file.MkdirAll(path, 0700); err != nil { 158 return fmt.Errorf("failed to create mountpoint: %s: %w", path, err) 159 } 160 } else if err != nil { 161 return err 162 } 163 if runtime.GOOS != "windows" { 164 if err := mountlib.CheckMountEmpty(path); err != nil { 165 return ErrMountpointExists 166 } 167 } 168 return nil 169 } 170 171 // setup volume filesystem 172 func (vol *Volume) setup(ctx context.Context) error { 173 fs.Debugf(nil, "Setup volume %q as %q at path %s", vol.Name, vol.fsString, vol.MountPoint) 174 175 if err := vol.checkMountpoint(); err != nil { 176 return err 177 } 178 if vol.drv.dummy { 179 return nil 180 } 181 182 _, mountFn := mountlib.ResolveMountMethod(vol.mountType) 183 if mountFn == nil { 184 if vol.mountType != "" { 185 return fmt.Errorf("unsupported mount type %q", vol.mountType) 186 } 187 return errors.New("mount command unsupported by this build") 188 } 189 vol.mnt.MountFn = mountFn 190 191 if vol.persist { 192 // Add remote to config file 193 params := rc.Params{} 194 for key, val := range vol.Options { 195 params[key] = val 196 } 197 updateMode := config.UpdateRemoteOpt{} 198 _, err := config.CreateRemote(ctx, vol.Name, vol.Type, params, updateMode) 199 if err != nil { 200 return err 201 } 202 } 203 204 // Use existing remote 205 f, err := fs.NewFs(ctx, vol.fsString) 206 if err == nil { 207 vol.mnt.Fs = f 208 } 209 return err 210 } 211 212 // remove volume filesystem and mounts 213 func (vol *Volume) remove(ctx context.Context) error { 214 count := len(vol.mountReqs) 215 fs.Debugf(nil, "Remove volume %q (count %d)", vol.Name, count) 216 217 if count > 0 { 218 return errors.New("volume is in use") 219 } 220 221 if !vol.drv.dummy { 222 shutdownFn := vol.mnt.Fs.Features().Shutdown 223 if shutdownFn != nil { 224 if err := shutdownFn(ctx); err != nil { 225 return err 226 } 227 } 228 } 229 230 if vol.persist { 231 // Remote remote from config file 232 config.DeleteRemote(vol.Name) 233 } 234 return nil 235 } 236 237 // clearCache will clear VFS cache for the volume 238 func (vol *Volume) clearCache() error { 239 VFS := vol.mnt.VFS 240 if VFS == nil { 241 return nil 242 } 243 root, err := VFS.Root() 244 if err != nil { 245 return fmt.Errorf("error reading root: %v: %w", VFS.Fs(), err) 246 } 247 root.ForgetAll() 248 return nil 249 } 250 251 // mount volume filesystem 252 func (vol *Volume) mount(id string) error { 253 drv := vol.drv 254 count := len(vol.mountReqs) 255 fs.Debugf(nil, "Mount volume %q for id %q at path %s (count %d)", 256 vol.Name, id, vol.MountPoint, count) 257 258 if _, found := vol.mountReqs[id]; found { 259 return errors.New("volume is already mounted by this id") 260 } 261 262 if count > 0 { // already mounted 263 vol.mountReqs[id] = nil 264 return nil 265 } 266 if drv.dummy { 267 vol.mountReqs[id] = nil 268 return nil 269 } 270 if vol.mnt.Fs == nil { 271 return errors.New("volume filesystem is not ready") 272 } 273 274 if _, err := vol.mnt.Mount(); err != nil { 275 return err 276 } 277 vol.mountReqs[id] = nil 278 vol.drv.monChan <- false // ask monitor to refresh channels 279 return nil 280 } 281 282 // unmount volume 283 func (vol *Volume) unmount(id string) error { 284 count := len(vol.mountReqs) 285 fs.Debugf(nil, "Unmount volume %q from id %q at path %s (count %d)", 286 vol.Name, id, vol.MountPoint, count) 287 288 if count == 0 { 289 return errors.New("volume is not mounted") 290 } 291 if _, found := vol.mountReqs[id]; !found { 292 return errors.New("volume is not mounted by this id") 293 } 294 295 delete(vol.mountReqs, id) 296 if len(vol.mountReqs) > 0 { 297 return nil // more mounts left 298 } 299 300 if vol.drv.dummy { 301 return nil 302 } 303 304 mnt := vol.mnt 305 if mnt.UnmountFn != nil { 306 if err := mnt.UnmountFn(); err != nil { 307 return err 308 } 309 } 310 mnt.ErrChan = nil 311 mnt.UnmountFn = nil 312 mnt.VFS = nil 313 vol.drv.monChan <- false // ask monitor to refresh channels 314 return nil 315 } 316 317 func (vol *Volume) unmountAll() error { 318 var firstErr error 319 for id := range vol.mountReqs { 320 err := vol.unmount(id) 321 if firstErr == nil { 322 firstErr = err 323 } 324 } 325 return firstErr 326 }