diff --git a/cmd/mountlib/check_linux.go b/cmd/mountlib/check_linux.go new file mode 100644 index 000000000..bad2dee8d --- /dev/null +++ b/cmd/mountlib/check_linux.go @@ -0,0 +1,77 @@ +//go:build linux +// +build linux + +package mountlib + +import ( + "path/filepath" + "strings" + "time" + + "github.com/artyom/mtab" + "github.com/pkg/errors" +) + +const ( + mtabPath = "/proc/mounts" + pollInterval = 100 * time.Millisecond +) + +// CheckMountEmpty checks if folder is not already a mountpoint. +// On Linux we use the OS-specific /proc/mount API so the check won't access the path. +// Directories marked as "mounted" by autofs are considered not mounted. +func CheckMountEmpty(mountpoint string) error { + const msg = "Directory already mounted, use --allow-non-empty to mount anyway: %s" + + mountpointAbs, err := filepath.Abs(mountpoint) + if err != nil { + return errors.Wrapf(err, "cannot get absolute path: %s", mountpoint) + } + + entries, err := mtab.Entries(mtabPath) + if err != nil { + return errors.Wrapf(err, "cannot read %s", mtabPath) + } + for _, entry := range entries { + if entry.Dir == mountpointAbs && entry.Type != "autofs" { + return errors.Errorf(msg, mountpointAbs) + } + } + return nil +} + +// CheckMountReady checks whether mountpoint is mounted by rclone. +// Only mounts with type "rclone" or "fuse.rclone" count. +func CheckMountReady(mountpoint string) error { + mountpointAbs, err := filepath.Abs(mountpoint) + if err != nil { + return errors.Wrapf(err, "cannot get absolute path: %s", mountpoint) + } + entries, err := mtab.Entries(mtabPath) + if err != nil { + return errors.Wrapf(err, "cannot read %s", mtabPath) + } + for _, entry := range entries { + if entry.Dir == mountpointAbs && strings.Contains(entry.Type, "rclone") { + return nil + } + } + return errors.New("mount not ready") +} + +// WaitMountReady waits until mountpoint is mounted by rclone. +func WaitMountReady(mountpoint string, timeout time.Duration) (err error) { + endTime := time.Now().Add(timeout) + for { + err = CheckMountReady(mountpoint) + delay := time.Until(endTime) + if err == nil || delay <= 0 { + break + } + if delay > pollInterval { + delay = pollInterval + } + time.Sleep(delay) + } + return +} diff --git a/cmd/mountlib/check_other.go b/cmd/mountlib/check_other.go new file mode 100644 index 000000000..cfcbd133c --- /dev/null +++ b/cmd/mountlib/check_other.go @@ -0,0 +1,47 @@ +//go:build !linux +// +build !linux + +package mountlib + +import ( + "io" + "os" + "time" + + "github.com/pkg/errors" + "github.com/rclone/rclone/fs" +) + +// CheckMountEmpty checks if mountpoint folder is empty. +// On non-Linux unixes we list directory to ensure that. +func CheckMountEmpty(mountpoint string) error { + fp, err := os.Open(mountpoint) + if err != nil { + return errors.Wrapf(err, "Can not open: %s", mountpoint) + } + defer fs.CheckClose(fp, &err) + + _, err = fp.Readdirnames(1) + if err == io.EOF { + return nil + } + + const msg = "Directory is not empty, use --allow-non-empty to mount anyway: %s" + if err == nil { + return errors.Errorf(msg, mountpoint) + } + return errors.Wrapf(err, msg, mountpoint) +} + +// CheckMountReady should check if mountpoint is mounted by rclone. +// The check is implemented only for Linux so this does nothing. +func CheckMountReady(mountpoint string) error { + return nil +} + +// WaitMountReady should wait until mountpoint is mounted by rclone. +// The check is implemented only for Linux so we just sleep a little. +func WaitMountReady(mountpoint string, timeout time.Duration) error { + time.Sleep(timeout) + return nil +} diff --git a/cmd/mountlib/mount.go b/cmd/mountlib/mount.go index 2a9f6fba6..f85a4ea20 100644 --- a/cmd/mountlib/mount.go +++ b/cmd/mountlib/mount.go @@ -4,7 +4,6 @@ import ( "context" "log" "os" - "path/filepath" "runtime" "strings" "sync" @@ -199,58 +198,6 @@ func (m *MountPoint) Mount() (daemonized bool, err error) { return false, nil } -// CheckOverlap checks that root doesn't overlap with mountpoint -func (m *MountPoint) CheckOverlap() error { - name := m.Fs.Name() - if name != "" && name != "local" { - return nil - } - rootAbs := absPath(m.Fs.Root()) - mountpointAbs := absPath(m.MountPoint) - if strings.HasPrefix(rootAbs, mountpointAbs) || strings.HasPrefix(mountpointAbs, rootAbs) { - const msg = "mount point %q and directory to be mounted %q mustn't overlap" - return errors.Errorf(msg, m.MountPoint, m.Fs.Root()) - } - return nil -} - -// absPath is a helper function for MountPoint.CheckOverlap -func absPath(path string) string { - if abs, err := filepath.EvalSymlinks(path); err == nil { - path = abs - } - if abs, err := filepath.Abs(path); err == nil { - path = abs - } - path = filepath.ToSlash(path) - if !strings.HasSuffix(path, "/") { - path += "/" - } - return path -} - -// CheckAllowings informs about ignored flags on Windows. If not on Windows -// and not --allow-non-empty flag is used, verify that mountpoint is empty. -func (m *MountPoint) CheckAllowings() error { - opt := &m.MountOpt - if runtime.GOOS == "windows" { - if opt.AllowNonEmpty { - fs.Logf(nil, "--allow-non-empty flag does nothing on Windows") - } - if opt.AllowRoot { - fs.Logf(nil, "--allow-root flag does nothing on Windows") - } - if opt.AllowOther { - fs.Logf(nil, "--allow-other flag does nothing on Windows") - } - return nil - } - if !opt.AllowNonEmpty { - return CheckMountEmpty(m.MountPoint) - } - return nil -} - // Wait for mount end func (m *MountPoint) Wait() error { // Unmount on exit @@ -303,22 +250,3 @@ func (m *MountPoint) Wait() error { func (m *MountPoint) Unmount() (err error) { return m.UnmountFn() } - -// SetVolumeName with sensible default -func (m *MountPoint) SetVolumeName(vol string) { - if vol == "" { - vol = m.Fs.Name() + ":" + m.Fs.Root() - } - m.MountOpt.SetVolumeName(vol) -} - -// SetVolumeName removes special characters from volume name if necessary -func (opt *Options) SetVolumeName(vol string) { - vol = strings.ReplaceAll(vol, ":", " ") - vol = strings.ReplaceAll(vol, "/", " ") - vol = strings.TrimSpace(vol) - if runtime.GOOS == "windows" && len(vol) > 32 { - vol = vol[:32] - } - opt.VolumeName = vol -} diff --git a/cmd/mountlib/utils.go b/cmd/mountlib/utils.go index 5c106ad25..510bd87aa 100644 --- a/cmd/mountlib/utils.go +++ b/cmd/mountlib/utils.go @@ -1,36 +1,14 @@ package mountlib import ( - "io" - "os" + "path/filepath" "runtime" + "strings" "github.com/pkg/errors" "github.com/rclone/rclone/fs" ) -// CheckMountEmpty checks if folder is empty -func CheckMountEmpty(mountpoint string) error { - fp, fpErr := os.Open(mountpoint) - - if fpErr != nil { - return errors.Wrap(fpErr, "Can not open: "+mountpoint) - } - defer fs.CheckClose(fp, &fpErr) - - _, fpErr = fp.Readdirnames(1) - - if fpErr == io.EOF { - return nil - } - - msg := "Directory is not empty: " + mountpoint + " If you want to mount it anyway use: --allow-non-empty option" - if fpErr == nil { - return errors.New(msg) - } - return errors.Wrap(fpErr, msg) -} - // ClipBlocks clips the blocks pointed to the OS max func ClipBlocks(b *uint64) { var max uint64 @@ -53,3 +31,74 @@ func ClipBlocks(b *uint64) { *b = max } } + +// CheckOverlap checks that root doesn't overlap with mountpoint +func (m *MountPoint) CheckOverlap() error { + name := m.Fs.Name() + if name != "" && name != "local" { + return nil + } + rootAbs := absPath(m.Fs.Root()) + mountpointAbs := absPath(m.MountPoint) + if strings.HasPrefix(rootAbs, mountpointAbs) || strings.HasPrefix(mountpointAbs, rootAbs) { + const msg = "mount point %q and directory to be mounted %q mustn't overlap" + return errors.Errorf(msg, m.MountPoint, m.Fs.Root()) + } + return nil +} + +// absPath is a helper function for MountPoint.CheckOverlap +func absPath(path string) string { + if abs, err := filepath.EvalSymlinks(path); err == nil { + path = abs + } + if abs, err := filepath.Abs(path); err == nil { + path = abs + } + path = filepath.ToSlash(path) + if !strings.HasSuffix(path, "/") { + path += "/" + } + return path +} + +// CheckAllowings informs about ignored flags on Windows. If not on Windows +// and not --allow-non-empty flag is used, verify that mountpoint is empty. +func (m *MountPoint) CheckAllowings() error { + opt := &m.MountOpt + if runtime.GOOS == "windows" { + if opt.AllowNonEmpty { + fs.Logf(nil, "--allow-non-empty flag does nothing on Windows") + } + if opt.AllowRoot { + fs.Logf(nil, "--allow-root flag does nothing on Windows") + } + if opt.AllowOther { + fs.Logf(nil, "--allow-other flag does nothing on Windows") + } + return nil + } + if !opt.AllowNonEmpty { + return CheckMountEmpty(m.MountPoint) + } + return nil +} + +// SetVolumeName with sensible default +func (m *MountPoint) SetVolumeName(vol string) { + if vol == "" { + vol = m.Fs.Name() + ":" + m.Fs.Root() + } + m.MountOpt.SetVolumeName(vol) +} + +// SetVolumeName removes special characters from volume name if necessary +func (o *Options) SetVolumeName(vol string) { + vol = strings.ReplaceAll(vol, ":", " ") + vol = strings.ReplaceAll(vol, "/", " ") + vol = strings.TrimSpace(vol) + if runtime.GOOS == "windows" && len(vol) > 32 { + vol = vol[:32] + } + o.VolumeName = vol +} diff --git a/go.mod b/go.mod index 33c55e97d..842c2d3f7 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/aalpar/deheap v0.0.0-20200318053559-9a0c2883bd56 github.com/abbot/go-http-auth v0.4.0 github.com/anacrolix/dms v1.2.2 + github.com/artyom/mtab v0.0.0-20141107123140-74b6fd01d416 github.com/atotto/clipboard v0.1.4 github.com/aws/aws-sdk-go v1.40.27 github.com/billziss-gh/cgofuse v1.5.0 diff --git a/go.sum b/go.sum index 6a112774c..4c47cc09b 100644 --- a/go.sum +++ b/go.sum @@ -106,6 +106,8 @@ github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kd github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/artyom/mtab v0.0.0-20141107123140-74b6fd01d416 h1:8VH5S3f48ca549Ij9/mIIzwp5kkBio0enC+Zte5xBr4= +github.com/artyom/mtab v0.0.0-20141107123140-74b6fd01d416/go.mod h1:4/w3KGZo0/xcC5ghHHq/Ij/CRCfK8s00v8oTmB7UTO0= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/aws/aws-sdk-go v1.40.27 h1:8fWW0CpmBZ8WWduNwl4vE9t07nMYFrhAsUHjPj81qUM=