mirror of
https://github.com/rclone/rclone.git
synced 2024-11-22 08:23:47 +01:00
4a382c09ec
Before this change we ran the tests and the mount in the same process. This could cause deadlocks and often did, and made the mount tests very unreliable. This fixes the problem by running the mount in a seperate process and commanding it via a pipe over stdin/stdout.
277 lines
6.6 KiB
Go
277 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)
|
|
}
|
|
}
|