mirror of
https://github.com/rclone/rclone.git
synced 2024-12-28 09:59:27 +01:00
280 lines
6.6 KiB
Go
280 lines
6.6 KiB
Go
package vfstest
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"runtime"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/rclone/rclone/cmd/mountlib"
|
|
"github.com/rclone/rclone/fs"
|
|
"github.com/rclone/rclone/fs/cache"
|
|
"github.com/rclone/rclone/fstest"
|
|
"github.com/rclone/rclone/lib/file"
|
|
"github.com/rclone/rclone/vfs"
|
|
"github.com/rclone/rclone/vfs/vfscommon"
|
|
)
|
|
|
|
// Functions to run and control the mount subprocess
|
|
|
|
var (
|
|
runMount = flag.String("run-mount", "", "If set, run the mount subprocess with the options (internal use only)")
|
|
)
|
|
|
|
// Options for the mount sub processes passed with the -run-mount flag
|
|
type runMountOpt struct {
|
|
MountPoint string
|
|
MountOpt mountlib.Options
|
|
VFSOpt vfscommon.Options
|
|
Remote string
|
|
}
|
|
|
|
// Start the mount subprocess and wait for it to start
|
|
func (r *Run) startMountSubProcess() {
|
|
// If testing the VFS we don't start a subprocess, we just use
|
|
// the VFS directly
|
|
if r.useVFS {
|
|
vfs := vfs.New(r.fremote, r.vfsOpt)
|
|
r.os = vfsOs{vfs}
|
|
return
|
|
}
|
|
r.os = realOs{}
|
|
r.mountPath = findMountPath()
|
|
log.Printf("startMountSubProcess %q (%q) %q", r.fremote, r.fremoteName, r.mountPath)
|
|
|
|
opt := runMountOpt{
|
|
MountPoint: r.mountPath,
|
|
MountOpt: mountlib.Opt,
|
|
VFSOpt: *r.vfsOpt,
|
|
Remote: r.fremoteName,
|
|
}
|
|
|
|
opts, err := json.Marshal(&opt)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
// Re-run this executable with a new option -run-mount
|
|
args := append(os.Args, "-run-mount", string(opts))
|
|
r.cmd = exec.Command(args[0], args[1:]...)
|
|
r.cmd.Stderr = os.Stderr
|
|
r.out, err = r.cmd.StdinPipe()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
r.in, err = r.cmd.StdoutPipe()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
err = r.cmd.Start()
|
|
if err != nil {
|
|
log.Fatal("startMountSubProcess failed", err)
|
|
}
|
|
r.scanner = bufio.NewScanner(r.in)
|
|
|
|
// Wait it for startup
|
|
log.Print("Waiting for mount to start")
|
|
for r.scanner.Scan() {
|
|
rx := strings.TrimSpace(r.scanner.Text())
|
|
if rx == "STARTED" {
|
|
break
|
|
}
|
|
log.Printf("..Mount said: %s", rx)
|
|
}
|
|
if r.scanner.Err() != nil {
|
|
log.Printf("scanner err %v", r.scanner.Err())
|
|
}
|
|
|
|
log.Printf("startMountSubProcess: end")
|
|
}
|
|
|
|
// Find a free path to run the mount on
|
|
func findMountPath() string {
|
|
if runtime.GOOS != "windows" {
|
|
mountPath, err := ioutil.TempDir("", "rclonefs-mount")
|
|
if err != nil {
|
|
log.Fatalf("Failed to create mount dir: %v", err)
|
|
}
|
|
return mountPath
|
|
}
|
|
|
|
// Find a free drive letter
|
|
letter := file.FindUnusedDriveLetter()
|
|
drive := ""
|
|
if letter == 0 {
|
|
log.Fatalf("Couldn't find free drive letter for test")
|
|
} else {
|
|
drive = string(letter) + ":"
|
|
}
|
|
return drive
|
|
}
|
|
|
|
// Return true if we are running as a subprocess to run the mount
|
|
func isSubProcess() bool {
|
|
return *runMount != ""
|
|
}
|
|
|
|
// Run the mount - this is running in a subprocesses and the config
|
|
// is passed JSON encoded as the -run-mount parameter
|
|
//
|
|
// It reads commands from standard input and writes results to
|
|
// standard output.
|
|
func startMount(mountFn mountlib.MountFn, useVFS bool, opts string) {
|
|
log.Print("startMount")
|
|
ctx := context.Background()
|
|
|
|
var opt runMountOpt
|
|
err := json.Unmarshal([]byte(opts), &opt)
|
|
if err != nil {
|
|
log.Fatalf("Unmarshal failed: %v", err)
|
|
}
|
|
|
|
fstest.Initialise()
|
|
|
|
f, err := cache.Get(ctx, opt.Remote)
|
|
if err != nil {
|
|
log.Fatalf("Failed to open remote %q: %v", opt.Remote, err)
|
|
}
|
|
|
|
err = f.Mkdir(ctx, "")
|
|
if err != nil {
|
|
log.Fatalf("Failed to mkdir %q: %v", opt.Remote, err)
|
|
}
|
|
|
|
log.Printf("startMount: Mounting %q on %q with %q", opt.Remote, opt.MountPoint, opt.VFSOpt.CacheMode)
|
|
mnt := mountlib.NewMountPoint(mountFn, opt.MountPoint, f, &opt.MountOpt, &opt.VFSOpt)
|
|
|
|
_, err = mnt.Mount()
|
|
if err != nil {
|
|
log.Fatalf("mount FAILED %q: %v", opt.Remote, err)
|
|
}
|
|
defer umount(mnt)
|
|
log.Printf("startMount: mount OK")
|
|
fmt.Println("STARTED") // signal to parent all is good
|
|
|
|
// Read commands from stdin
|
|
scanner := bufio.NewScanner(os.Stdin)
|
|
exit := false
|
|
for !exit && scanner.Scan() {
|
|
rx := strings.Trim(scanner.Text(), "\r\n")
|
|
var tx string
|
|
tx, exit = doMountCommand(mnt.VFS, rx)
|
|
fmt.Println(tx)
|
|
}
|
|
|
|
err = scanner.Err()
|
|
if err != nil {
|
|
log.Fatalf("scanner failed %q: %v", opt.Remote, err)
|
|
}
|
|
}
|
|
|
|
// Do a mount command which is a line read from stdin and return a
|
|
// line to send to stdout with an exit flag.
|
|
//
|
|
// The format of the lines is
|
|
//
|
|
// command \t parameter (optional)
|
|
//
|
|
// The response should be
|
|
//
|
|
// OK|ERR \t result (optional)
|
|
func doMountCommand(vfs *vfs.VFS, rx string) (tx string, exit bool) {
|
|
command := strings.Split(rx, "\t")
|
|
// log.Printf("doMountCommand: %q received", command)
|
|
var out = []string{"OK", ""}
|
|
switch command[0] {
|
|
case "waitForWriters":
|
|
vfs.WaitForWriters(waitForWritersDelay)
|
|
case "forget":
|
|
root, err := vfs.Root()
|
|
if err != nil {
|
|
out = []string{"ERR", err.Error()}
|
|
} else {
|
|
root.ForgetPath(command[1], fs.EntryDirectory)
|
|
}
|
|
case "exit":
|
|
exit = true
|
|
default:
|
|
out = []string{"ERR", "command not found"}
|
|
}
|
|
return strings.Join(out, "\t"), exit
|
|
}
|
|
|
|
// Send a command to the mount subprocess and await a response
|
|
func (r *Run) sendMountCommand(args ...string) {
|
|
r.cmdMu.Lock()
|
|
defer r.cmdMu.Unlock()
|
|
tx := strings.Join(args, "\t")
|
|
// log.Printf("Send mount command: %q", tx)
|
|
var rx string
|
|
if r.useVFS {
|
|
// if using VFS do the VFS command directly
|
|
rx, _ = doMountCommand(r.os.(vfsOs).VFS, tx)
|
|
} else {
|
|
_, err := io.WriteString(r.out, tx+"\n")
|
|
if err != nil {
|
|
log.Fatalf("WriteString err %v", err)
|
|
}
|
|
if !r.scanner.Scan() {
|
|
log.Fatalf("Mount has gone away")
|
|
}
|
|
rx = strings.Trim(r.scanner.Text(), "\r\n")
|
|
}
|
|
in := strings.Split(rx, "\t")
|
|
// log.Printf("Answer is %q", in)
|
|
if in[0] != "OK" {
|
|
log.Fatalf("Error from mount: %q", in[1:])
|
|
}
|
|
}
|
|
|
|
// wait for any files being written to be released by fuse
|
|
func (r *Run) waitForWriters() {
|
|
r.sendMountCommand("waitForWriters")
|
|
}
|
|
|
|
// forget the directory passed in
|
|
func (r *Run) forget(dir string) {
|
|
r.sendMountCommand("forget", dir)
|
|
}
|
|
|
|
// Unmount the mount
|
|
func umount(mnt *mountlib.MountPoint) {
|
|
/*
|
|
log.Printf("Calling fusermount -u %q", mountPath)
|
|
err := exec.Command("fusermount", "-u", mountPath).Run()
|
|
if err != nil {
|
|
log.Printf("fusermount failed: %v", err)
|
|
}
|
|
*/
|
|
log.Printf("Unmounting %q", mnt.MountPoint)
|
|
err := mnt.Unmount()
|
|
if err != nil {
|
|
log.Printf("signal to umount failed - retrying: %v", err)
|
|
time.Sleep(3 * time.Second)
|
|
err = mnt.Unmount()
|
|
}
|
|
if err != nil {
|
|
log.Fatalf("signal to umount failed: %v", err)
|
|
}
|
|
log.Printf("Waiting for umount")
|
|
err = <-mnt.ErrChan
|
|
if err != nil {
|
|
log.Fatalf("umount failed: %v", err)
|
|
}
|
|
|
|
// Cleanup the VFS cache - umount has called Shutdown
|
|
err = mnt.VFS.CleanUp()
|
|
if err != nil {
|
|
log.Printf("Failed to cleanup the VFS cache: %v", err)
|
|
}
|
|
}
|