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  }