github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/vfs/vfstest/submount.go (about)

     1  package vfstest
     2  
     3  import (
     4  	"bufio"
     5  	"context"
     6  	"encoding/json"
     7  	"flag"
     8  	"fmt"
     9  	"io"
    10  	"log"
    11  	"os"
    12  	"os/exec"
    13  	"runtime"
    14  	"strings"
    15  	"time"
    16  
    17  	"github.com/rclone/rclone/cmd/mountlib"
    18  	"github.com/rclone/rclone/fs"
    19  	"github.com/rclone/rclone/fs/cache"
    20  	"github.com/rclone/rclone/fstest"
    21  	"github.com/rclone/rclone/lib/file"
    22  	"github.com/rclone/rclone/vfs"
    23  	"github.com/rclone/rclone/vfs/vfscommon"
    24  )
    25  
    26  // Functions to run and control the mount subprocess
    27  
    28  var (
    29  	runMount = flag.String("run-mount", "", "If set, run the mount subprocess with the options (internal use only)")
    30  )
    31  
    32  // Options for the mount sub processes passed with the -run-mount flag
    33  type runMountOpt struct {
    34  	MountPoint string
    35  	MountOpt   mountlib.Options
    36  	VFSOpt     vfscommon.Options
    37  	Remote     string
    38  }
    39  
    40  // Start the mount subprocess and wait for it to start
    41  func (r *Run) startMountSubProcess() {
    42  	// If testing the VFS we don't start a subprocess, we just use
    43  	// the VFS directly
    44  	if r.useVFS {
    45  		vfs := vfs.New(r.fremote, r.vfsOpt)
    46  		r.os = vfsOs{vfs}
    47  		return
    48  	}
    49  	r.os = realOs{}
    50  	r.mountPath = findMountPath()
    51  	log.Printf("startMountSubProcess %q (%q) %q", r.fremote, r.fremoteName, r.mountPath)
    52  
    53  	opt := runMountOpt{
    54  		MountPoint: r.mountPath,
    55  		MountOpt:   mountlib.Opt,
    56  		VFSOpt:     *r.vfsOpt,
    57  		Remote:     r.fremoteName,
    58  	}
    59  
    60  	opts, err := json.Marshal(&opt)
    61  	if err != nil {
    62  		log.Fatal(err)
    63  	}
    64  
    65  	// Re-run this executable with a new option -run-mount
    66  	args := append(os.Args, "-run-mount", string(opts))
    67  	r.cmd = exec.Command(args[0], args[1:]...)
    68  	r.cmd.Stderr = os.Stderr
    69  	r.out, err = r.cmd.StdinPipe()
    70  	if err != nil {
    71  		log.Fatal(err)
    72  	}
    73  	r.in, err = r.cmd.StdoutPipe()
    74  	if err != nil {
    75  		log.Fatal(err)
    76  	}
    77  	err = r.cmd.Start()
    78  	if err != nil {
    79  		log.Fatal("startMountSubProcess failed", err)
    80  	}
    81  	r.scanner = bufio.NewScanner(r.in)
    82  
    83  	// Wait it for startup
    84  	log.Print("Waiting for mount to start")
    85  	for r.scanner.Scan() {
    86  		rx := strings.TrimSpace(r.scanner.Text())
    87  		if rx == "STARTED" {
    88  			break
    89  		}
    90  		log.Printf("..Mount said: %s", rx)
    91  	}
    92  	if r.scanner.Err() != nil {
    93  		log.Printf("scanner err %v", r.scanner.Err())
    94  	}
    95  
    96  	log.Printf("startMountSubProcess: end")
    97  }
    98  
    99  // Find a free path to run the mount on
   100  func findMountPath() string {
   101  	if runtime.GOOS != "windows" {
   102  		mountPath, err := os.MkdirTemp("", "rclonefs-mount")
   103  		if err != nil {
   104  			log.Fatalf("Failed to create mount dir: %v", err)
   105  		}
   106  		return mountPath
   107  	}
   108  
   109  	// Find a free drive letter
   110  	letter := file.FindUnusedDriveLetter()
   111  	drive := ""
   112  	if letter == 0 {
   113  		log.Fatalf("Couldn't find free drive letter for test")
   114  	} else {
   115  		drive = string(letter) + ":"
   116  	}
   117  	return drive
   118  }
   119  
   120  // Return true if we are running as a subprocess to run the mount
   121  func isSubProcess() bool {
   122  	return *runMount != ""
   123  }
   124  
   125  // Run the mount - this is running in a subprocesses and the config
   126  // is passed JSON encoded as the -run-mount parameter
   127  //
   128  // It reads commands from standard input and writes results to
   129  // standard output.
   130  func startMount(mountFn mountlib.MountFn, useVFS bool, opts string) {
   131  	log.Print("startMount")
   132  	ctx := context.Background()
   133  
   134  	var opt runMountOpt
   135  	err := json.Unmarshal([]byte(opts), &opt)
   136  	if err != nil {
   137  		log.Fatalf("Unmarshal failed: %v", err)
   138  	}
   139  
   140  	fstest.Initialise()
   141  
   142  	f, err := cache.Get(ctx, opt.Remote)
   143  	if err != nil {
   144  		log.Fatalf("Failed to open remote %q: %v", opt.Remote, err)
   145  	}
   146  
   147  	err = f.Mkdir(ctx, "")
   148  	if err != nil {
   149  		log.Fatalf("Failed to mkdir %q: %v", opt.Remote, err)
   150  	}
   151  
   152  	log.Printf("startMount: Mounting %q on %q with %q", opt.Remote, opt.MountPoint, opt.VFSOpt.CacheMode)
   153  	mnt := mountlib.NewMountPoint(mountFn, opt.MountPoint, f, &opt.MountOpt, &opt.VFSOpt)
   154  
   155  	_, err = mnt.Mount()
   156  	if err != nil {
   157  		log.Fatalf("mount FAILED %q: %v", opt.Remote, err)
   158  	}
   159  	defer umount(mnt)
   160  	log.Printf("startMount: mount OK")
   161  	fmt.Println("STARTED") // signal to parent all is good
   162  
   163  	// Read commands from stdin
   164  	scanner := bufio.NewScanner(os.Stdin)
   165  	exit := false
   166  	for !exit && scanner.Scan() {
   167  		rx := strings.Trim(scanner.Text(), "\r\n")
   168  		var tx string
   169  		tx, exit = doMountCommand(mnt.VFS, rx)
   170  		fmt.Println(tx)
   171  	}
   172  
   173  	err = scanner.Err()
   174  	if err != nil {
   175  		log.Fatalf("scanner failed %q: %v", opt.Remote, err)
   176  	}
   177  }
   178  
   179  // Do a mount command which is a line read from stdin and return a
   180  // line to send to stdout with an exit flag.
   181  //
   182  // The format of the lines is
   183  //
   184  //	command \t parameter (optional)
   185  //
   186  // The response should be
   187  //
   188  //	OK|ERR \t result (optional)
   189  func doMountCommand(vfs *vfs.VFS, rx string) (tx string, exit bool) {
   190  	command := strings.Split(rx, "\t")
   191  	// log.Printf("doMountCommand: %q received", command)
   192  	var out = []string{"OK", ""}
   193  	switch command[0] {
   194  	case "waitForWriters":
   195  		vfs.WaitForWriters(waitForWritersDelay)
   196  	case "forget":
   197  		root, err := vfs.Root()
   198  		if err != nil {
   199  			out = []string{"ERR", err.Error()}
   200  		} else {
   201  			root.ForgetPath(command[1], fs.EntryDirectory)
   202  		}
   203  	case "exit":
   204  		exit = true
   205  	default:
   206  		out = []string{"ERR", "command not found"}
   207  	}
   208  	return strings.Join(out, "\t"), exit
   209  }
   210  
   211  // Send a command to the mount subprocess and await a response
   212  func (r *Run) sendMountCommand(args ...string) {
   213  	r.cmdMu.Lock()
   214  	defer r.cmdMu.Unlock()
   215  	tx := strings.Join(args, "\t")
   216  	// log.Printf("Send mount command: %q", tx)
   217  	var rx string
   218  	if r.useVFS {
   219  		// if using VFS do the VFS command directly
   220  		rx, _ = doMountCommand(r.os.(vfsOs).VFS, tx)
   221  	} else {
   222  		_, err := io.WriteString(r.out, tx+"\n")
   223  		if err != nil {
   224  			log.Fatalf("WriteString err %v", err)
   225  		}
   226  		if !r.scanner.Scan() {
   227  			log.Fatalf("Mount has gone away")
   228  		}
   229  		rx = strings.Trim(r.scanner.Text(), "\r\n")
   230  	}
   231  	in := strings.Split(rx, "\t")
   232  	// log.Printf("Answer is %q", in)
   233  	if in[0] != "OK" {
   234  		log.Fatalf("Error from mount: %q", in[1:])
   235  	}
   236  }
   237  
   238  // wait for any files being written to be released by fuse
   239  func (r *Run) waitForWriters() {
   240  	r.sendMountCommand("waitForWriters")
   241  }
   242  
   243  // forget the directory passed in
   244  func (r *Run) forget(dir string) {
   245  	r.sendMountCommand("forget", dir)
   246  }
   247  
   248  // Unmount the mount
   249  func umount(mnt *mountlib.MountPoint) {
   250  	/*
   251  		log.Printf("Calling fusermount -u %q", mountPath)
   252  		err := exec.Command("fusermount", "-u", mountPath).Run()
   253  		if err != nil {
   254  			log.Printf("fusermount failed: %v", err)
   255  		}
   256  	*/
   257  	log.Printf("Unmounting %q", mnt.MountPoint)
   258  	err := mnt.Unmount()
   259  	if err != nil {
   260  		log.Printf("signal to umount failed - retrying: %v", err)
   261  		time.Sleep(3 * time.Second)
   262  		err = mnt.Unmount()
   263  	}
   264  	if err != nil {
   265  		log.Fatalf("signal to umount failed: %v", err)
   266  	}
   267  	log.Printf("Waiting for umount")
   268  	err = <-mnt.ErrChan
   269  	if err != nil {
   270  		log.Fatalf("umount failed: %v", err)
   271  	}
   272  
   273  	// Cleanup the VFS cache - umount has called Shutdown
   274  	err = mnt.VFS.CleanUp()
   275  	if err != nil {
   276  		log.Printf("Failed to cleanup the VFS cache: %v", err)
   277  	}
   278  }