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 }