| 
									
										
										
										
											2018-03-13 22:30:51 +01:00
										 |  |  | package rclone | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2018-03-14 21:29:54 +01:00
										 |  |  | 	"bufio" | 
					
						
							| 
									
										
										
										
											2018-03-13 22:30:51 +01:00
										 |  |  | 	"context" | 
					
						
							|  |  |  | 	"crypto/tls" | 
					
						
							| 
									
										
										
										
											2018-03-14 21:29:54 +01:00
										 |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2018-03-18 20:30:02 +01:00
										 |  |  | 	"math/rand" | 
					
						
							| 
									
										
										
										
											2018-03-13 22:30:51 +01:00
										 |  |  | 	"net" | 
					
						
							|  |  |  | 	"net/http" | 
					
						
							|  |  |  | 	"net/url" | 
					
						
							|  |  |  | 	"os" | 
					
						
							|  |  |  | 	"os/exec" | 
					
						
							| 
									
										
										
										
											2018-03-15 21:22:14 +01:00
										 |  |  | 	"sync" | 
					
						
							| 
									
										
										
										
											2018-03-13 22:30:51 +01:00
										 |  |  | 	"time" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/restic/restic/internal/backend" | 
					
						
							|  |  |  | 	"github.com/restic/restic/internal/backend/rest" | 
					
						
							|  |  |  | 	"github.com/restic/restic/internal/debug" | 
					
						
							|  |  |  | 	"github.com/restic/restic/internal/errors" | 
					
						
							|  |  |  | 	"golang.org/x/net/context/ctxhttp" | 
					
						
							|  |  |  | 	"golang.org/x/net/http2" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Backend is used to access data stored somewhere via rclone. | 
					
						
							|  |  |  | type Backend struct { | 
					
						
							|  |  |  | 	*rest.Backend | 
					
						
							|  |  |  | 	tr         *http2.Transport | 
					
						
							|  |  |  | 	cmd        *exec.Cmd | 
					
						
							|  |  |  | 	waitCh     <-chan struct{} | 
					
						
							|  |  |  | 	waitResult error | 
					
						
							| 
									
										
										
										
											2018-03-15 21:22:14 +01:00
										 |  |  | 	wg         *sync.WaitGroup | 
					
						
							|  |  |  | 	conn       *StdioConn | 
					
						
							| 
									
										
										
										
											2018-03-13 22:30:51 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // run starts command with args and initializes the StdioConn. | 
					
						
							| 
									
										
										
										
											2018-03-15 21:22:14 +01:00
										 |  |  | func run(command string, args ...string) (*StdioConn, *exec.Cmd, *sync.WaitGroup, func() error, error) { | 
					
						
							| 
									
										
										
										
											2018-03-13 22:30:51 +01:00
										 |  |  | 	cmd := exec.Command(command, args...) | 
					
						
							| 
									
										
										
										
											2018-03-15 21:22:14 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-14 21:29:54 +01:00
										 |  |  | 	p, err := cmd.StderrPipe() | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2018-03-15 21:22:14 +01:00
										 |  |  | 		return nil, nil, nil, nil, err | 
					
						
							| 
									
										
										
										
											2018-03-14 21:29:54 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-15 21:22:14 +01:00
										 |  |  | 	var wg sync.WaitGroup | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-14 21:29:54 +01:00
										 |  |  | 	// start goroutine to add a prefix to all messages printed by to stderr by rclone | 
					
						
							| 
									
										
										
										
											2018-03-15 21:22:14 +01:00
										 |  |  | 	wg.Add(1) | 
					
						
							| 
									
										
										
										
											2018-03-14 21:29:54 +01:00
										 |  |  | 	go func() { | 
					
						
							| 
									
										
										
										
											2018-03-15 21:22:14 +01:00
										 |  |  | 		defer wg.Done() | 
					
						
							| 
									
										
										
										
											2018-03-14 21:29:54 +01:00
										 |  |  | 		sc := bufio.NewScanner(p) | 
					
						
							|  |  |  | 		for sc.Scan() { | 
					
						
							|  |  |  | 			fmt.Fprintf(os.Stderr, "rclone: %v\n", sc.Text()) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	}() | 
					
						
							| 
									
										
										
										
											2018-03-13 22:30:51 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	r, stdin, err := os.Pipe() | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2018-03-15 21:22:14 +01:00
										 |  |  | 		return nil, nil, nil, nil, err | 
					
						
							| 
									
										
										
										
											2018-03-13 22:30:51 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	stdout, w, err := os.Pipe() | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2018-03-15 21:22:14 +01:00
										 |  |  | 		return nil, nil, nil, nil, err | 
					
						
							| 
									
										
										
										
											2018-03-13 22:30:51 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	cmd.Stdin = r | 
					
						
							|  |  |  | 	cmd.Stdout = w | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	bg, err := backend.StartForeground(cmd) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2018-03-15 21:22:14 +01:00
										 |  |  | 		return nil, nil, nil, nil, err | 
					
						
							| 
									
										
										
										
											2018-03-13 22:30:51 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	c := &StdioConn{ | 
					
						
							|  |  |  | 		stdin:  stdout, | 
					
						
							|  |  |  | 		stdout: stdin, | 
					
						
							|  |  |  | 		cmd:    cmd, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-15 21:22:14 +01:00
										 |  |  | 	return c, cmd, &wg, bg, nil | 
					
						
							| 
									
										
										
										
											2018-03-13 22:30:51 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // New initializes a Backend and starts the process. | 
					
						
							|  |  |  | func New(cfg Config) (*Backend, error) { | 
					
						
							|  |  |  | 	var ( | 
					
						
							|  |  |  | 		args []string | 
					
						
							|  |  |  | 		err  error | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// build program args, start with the program | 
					
						
							|  |  |  | 	if cfg.Program != "" { | 
					
						
							|  |  |  | 		a, err := backend.SplitShellStrings(cfg.Program) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return nil, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		args = append(args, a...) | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		args = append(args, "rclone") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// then add the arguments | 
					
						
							|  |  |  | 	if cfg.Args != "" { | 
					
						
							|  |  |  | 		a, err := backend.SplitShellStrings(cfg.Args) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return nil, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		args = append(args, a...) | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		args = append(args, "serve", "restic", "--stdio") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// finally, add the remote | 
					
						
							|  |  |  | 	args = append(args, cfg.Remote) | 
					
						
							|  |  |  | 	arg0, args := args[0], args[1:] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	debug.Log("running command: %v %v", arg0, args) | 
					
						
							| 
									
										
										
										
											2018-03-15 21:22:14 +01:00
										 |  |  | 	conn, cmd, wg, bg, err := run(arg0, args...) | 
					
						
							| 
									
										
										
										
											2018-03-13 22:30:51 +01:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-15 21:22:14 +01:00
										 |  |  | 	dialCount := 0 | 
					
						
							| 
									
										
										
										
											2018-03-13 22:30:51 +01:00
										 |  |  | 	tr := &http2.Transport{ | 
					
						
							|  |  |  | 		AllowHTTP: true, // this is not really HTTP, just stdin/stdout | 
					
						
							|  |  |  | 		DialTLS: func(network, address string, cfg *tls.Config) (net.Conn, error) { | 
					
						
							|  |  |  | 			debug.Log("new connection requested, %v %v", network, address) | 
					
						
							| 
									
										
										
										
											2018-03-15 21:22:14 +01:00
										 |  |  | 			if dialCount > 0 { | 
					
						
							|  |  |  | 				panic("dial count > 0") | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			dialCount++ | 
					
						
							| 
									
										
										
										
											2018-03-13 22:30:51 +01:00
										 |  |  | 			return conn, nil | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	waitCh := make(chan struct{}) | 
					
						
							|  |  |  | 	be := &Backend{ | 
					
						
							|  |  |  | 		tr:     tr, | 
					
						
							|  |  |  | 		cmd:    cmd, | 
					
						
							|  |  |  | 		waitCh: waitCh, | 
					
						
							| 
									
										
										
										
											2018-03-15 21:22:14 +01:00
										 |  |  | 		conn:   conn, | 
					
						
							|  |  |  | 		wg:     wg, | 
					
						
							| 
									
										
										
										
											2018-03-13 22:30:51 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-15 21:22:14 +01:00
										 |  |  | 	wg.Add(1) | 
					
						
							| 
									
										
										
										
											2018-03-13 22:30:51 +01:00
										 |  |  | 	go func() { | 
					
						
							| 
									
										
										
										
											2018-03-15 21:22:14 +01:00
										 |  |  | 		defer wg.Done() | 
					
						
							| 
									
										
										
										
											2018-03-13 22:30:51 +01:00
										 |  |  | 		debug.Log("waiting for error result") | 
					
						
							|  |  |  | 		err := cmd.Wait() | 
					
						
							|  |  |  | 		debug.Log("Wait returned %v", err) | 
					
						
							|  |  |  | 		be.waitResult = err | 
					
						
							|  |  |  | 		close(waitCh) | 
					
						
							|  |  |  | 	}() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ctx, cancel := context.WithCancel(context.Background()) | 
					
						
							|  |  |  | 	defer cancel() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-15 21:22:14 +01:00
										 |  |  | 	wg.Add(1) | 
					
						
							| 
									
										
										
										
											2018-03-13 22:30:51 +01:00
										 |  |  | 	go func() { | 
					
						
							| 
									
										
										
										
											2018-03-15 21:22:14 +01:00
										 |  |  | 		defer wg.Done() | 
					
						
							| 
									
										
										
										
											2018-03-13 22:30:51 +01:00
										 |  |  | 		debug.Log("monitoring command to cancel first HTTP request context") | 
					
						
							|  |  |  | 		select { | 
					
						
							|  |  |  | 		case <-ctx.Done(): | 
					
						
							|  |  |  | 			debug.Log("context has been cancelled, returning") | 
					
						
							|  |  |  | 		case <-be.waitCh: | 
					
						
							|  |  |  | 			debug.Log("command has exited, cancelling context") | 
					
						
							|  |  |  | 			cancel() | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	}() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// send an HTTP request to the base URL, see if the server is there | 
					
						
							|  |  |  | 	client := &http.Client{ | 
					
						
							|  |  |  | 		Transport: tr, | 
					
						
							| 
									
										
										
										
											2018-03-18 12:54:59 +01:00
										 |  |  | 		Timeout:   60 * time.Second, | 
					
						
							| 
									
										
										
										
											2018-03-13 22:30:51 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-18 20:30:02 +01:00
										 |  |  | 	// request a random file which does not exist. we just want to test when | 
					
						
							|  |  |  | 	// rclone is able to accept HTTP requests. | 
					
						
							|  |  |  | 	url := fmt.Sprintf("http://localhost/file-%d", rand.Uint64()) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	req, err := http.NewRequest(http.MethodGet, url, nil) | 
					
						
							| 
									
										
										
										
											2018-03-13 22:30:51 +01:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	req.Header.Set("Accept", rest.ContentTypeV2) | 
					
						
							| 
									
										
										
										
											2018-03-15 21:22:14 +01:00
										 |  |  | 	req.Cancel = ctx.Done() | 
					
						
							| 
									
										
										
										
											2018-03-13 22:30:51 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	res, err := ctxhttp.Do(ctx, client, req) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		bg() | 
					
						
							|  |  |  | 		_ = cmd.Process.Kill() | 
					
						
							|  |  |  | 		return nil, errors.Errorf("error talking HTTP to rclone: %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	debug.Log("HTTP status %q returned, moving instance to background", res.Status) | 
					
						
							|  |  |  | 	bg() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return be, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Open starts an rclone process with the given config. | 
					
						
							|  |  |  | func Open(cfg Config) (*Backend, error) { | 
					
						
							|  |  |  | 	be, err := New(cfg) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	url, err := url.Parse("http://localhost/") | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	restConfig := rest.Config{ | 
					
						
							| 
									
										
										
										
											2018-03-24 18:37:36 +01:00
										 |  |  | 		Connections: cfg.Connections, | 
					
						
							| 
									
										
										
										
											2018-03-13 22:30:51 +01:00
										 |  |  | 		URL:         url, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	restBackend, err := rest.Open(restConfig, be.tr) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	be.Backend = restBackend | 
					
						
							|  |  |  | 	return be, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Create initializes a new restic repo with clone. | 
					
						
							|  |  |  | func Create(cfg Config) (*Backend, error) { | 
					
						
							|  |  |  | 	be, err := New(cfg) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	debug.Log("new backend created") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	url, err := url.Parse("http://localhost/") | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	restConfig := rest.Config{ | 
					
						
							|  |  |  | 		Connections: 20, | 
					
						
							|  |  |  | 		URL:         url, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	restBackend, err := rest.Create(restConfig, be.tr) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2018-03-14 22:28:12 +01:00
										 |  |  | 		_ = be.Close() | 
					
						
							| 
									
										
										
										
											2018-03-13 22:30:51 +01:00
										 |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	be.Backend = restBackend | 
					
						
							|  |  |  | 	return be, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-15 21:22:14 +01:00
										 |  |  | const waitForExit = 5 * time.Second | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-13 22:30:51 +01:00
										 |  |  | // Close terminates the backend. | 
					
						
							|  |  |  | func (be *Backend) Close() error { | 
					
						
							| 
									
										
										
										
											2018-03-15 19:00:25 +01:00
										 |  |  | 	debug.Log("exiting rclone") | 
					
						
							| 
									
										
										
										
											2018-03-13 22:30:51 +01:00
										 |  |  | 	be.tr.CloseIdleConnections() | 
					
						
							| 
									
										
										
										
											2018-03-15 21:22:14 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	select { | 
					
						
							|  |  |  | 	case <-be.waitCh: | 
					
						
							|  |  |  | 		debug.Log("rclone exited") | 
					
						
							|  |  |  | 	case <-time.After(waitForExit): | 
					
						
							|  |  |  | 		debug.Log("timeout, closing file descriptors") | 
					
						
							|  |  |  | 		err := be.conn.Close() | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	be.wg.Wait() | 
					
						
							| 
									
										
										
										
											2018-03-13 22:30:51 +01:00
										 |  |  | 	debug.Log("wait for rclone returned: %v", be.waitResult) | 
					
						
							|  |  |  | 	return be.waitResult | 
					
						
							|  |  |  | } |