2018-03-13 21:30:51 +00:00
package rclone
import (
2018-03-14 20:29:54 +00:00
"bufio"
2018-03-13 21:30:51 +00:00
"context"
"crypto/tls"
2018-03-14 20:29:54 +00:00
"fmt"
2018-05-22 18:48:17 +00:00
"io"
2018-03-18 19:30:02 +00:00
"math/rand"
2018-03-13 21:30:51 +00:00
"net"
"net/http"
"net/url"
"os"
"os/exec"
2018-03-15 20:22:14 +00:00
"sync"
2018-03-13 21:30:51 +00:00
"time"
2022-08-23 20:05:04 +00:00
"github.com/cenkalti/backoff/v4"
2018-03-13 21:30:51 +00:00
"github.com/restic/restic/internal/backend"
2022-06-12 12:38:19 +00:00
"github.com/restic/restic/internal/backend/limiter"
2018-03-13 21:30:51 +00:00
"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 20:22:14 +00:00
wg * sync . WaitGroup
conn * StdioConn
2018-03-13 21:30:51 +00:00
}
// run starts command with args and initializes the StdioConn.
2021-08-26 16:12:08 +00:00
func run ( command string , args ... string ) ( * StdioConn , * sync . WaitGroup , func ( ) error , error ) {
2018-03-13 21:30:51 +00:00
cmd := exec . Command ( command , args ... )
2018-03-15 20:22:14 +00:00
2018-03-14 20:29:54 +00:00
p , err := cmd . StderrPipe ( )
if err != nil {
2021-08-26 16:12:08 +00:00
return nil , nil , nil , err
2018-03-14 20:29:54 +00:00
}
2018-03-15 20:22:14 +00:00
var wg sync . WaitGroup
2018-03-14 20:29:54 +00:00
// start goroutine to add a prefix to all messages printed by to stderr by rclone
2018-03-15 20:22:14 +00:00
wg . Add ( 1 )
2018-03-14 20:29:54 +00:00
go func ( ) {
2018-03-15 20:22:14 +00:00
defer wg . Done ( )
2018-03-14 20:29:54 +00:00
sc := bufio . NewScanner ( p )
for sc . Scan ( ) {
fmt . Fprintf ( os . Stderr , "rclone: %v\n" , sc . Text ( ) )
}
} ( )
2018-03-13 21:30:51 +00:00
r , stdin , err := os . Pipe ( )
if err != nil {
2021-08-26 16:12:08 +00:00
return nil , nil , nil , err
2018-03-13 21:30:51 +00:00
}
stdout , w , err := os . Pipe ( )
if err != nil {
2021-01-30 15:46:34 +00:00
// close first pipe and ignore subsequent errors
_ = r . Close ( )
_ = stdin . Close ( )
2021-08-26 16:12:08 +00:00
return nil , nil , nil , err
2018-03-13 21:30:51 +00:00
}
cmd . Stdin = r
cmd . Stdout = w
bg , err := backend . StartForeground ( cmd )
2020-07-24 20:29:37 +00:00
// close rclone side of pipes
errR := r . Close ( )
errW := w . Close ( )
// return first error
if err == nil {
err = errR
}
if err == nil {
err = errW
}
2018-03-13 21:30:51 +00:00
if err != nil {
2022-09-20 19:26:01 +00:00
if backend . IsErrDot ( err ) {
return nil , nil , nil , errors . Errorf ( "cannot implicitly run relative executable %v found in current directory, use -o rclone.program=./<program> to override" , cmd . Path )
}
2021-08-26 16:12:08 +00:00
return nil , nil , nil , err
2018-03-13 21:30:51 +00:00
}
c := & StdioConn {
2020-07-25 21:51:02 +00:00
receive : stdout ,
send : stdin ,
cmd : cmd ,
2018-03-13 21:30:51 +00:00
}
2021-08-26 16:12:08 +00:00
return c , & wg , bg , nil
2018-03-13 21:30:51 +00:00
}
2018-05-22 18:48:17 +00:00
// wrappedConn adds bandwidth limiting capabilities to the StdioConn by
// wrapping the Read/Write methods.
type wrappedConn struct {
* StdioConn
io . Reader
io . Writer
}
2021-08-01 07:11:50 +00:00
func ( c * wrappedConn ) Read ( p [ ] byte ) ( int , error ) {
2018-05-22 18:48:17 +00:00
return c . Reader . Read ( p )
}
2021-08-01 07:11:50 +00:00
func ( c * wrappedConn ) Write ( p [ ] byte ) ( int , error ) {
2018-05-22 18:48:17 +00:00
return c . Writer . Write ( p )
}
2021-08-01 07:11:50 +00:00
func wrapConn ( c * StdioConn , lim limiter . Limiter ) * wrappedConn {
wc := & wrappedConn {
2018-05-22 18:48:17 +00:00
StdioConn : c ,
Reader : c ,
Writer : c ,
}
if lim != nil {
wc . Reader = lim . Downstream ( c )
wc . Writer = lim . UpstreamWriter ( c )
}
return wc
}
2018-03-13 21:30:51 +00:00
// New initializes a Backend and starts the process.
2020-06-18 10:55:29 +00:00
func newBackend ( cfg Config , lim limiter . Limiter ) ( * Backend , error ) {
2018-03-13 21:30:51 +00:00
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 ... )
}
// then add the arguments
if cfg . Args != "" {
a , err := backend . SplitShellStrings ( cfg . Args )
if err != nil {
return nil , err
}
args = append ( args , a ... )
}
// finally, add the remote
args = append ( args , cfg . Remote )
arg0 , args := args [ 0 ] , args [ 1 : ]
debug . Log ( "running command: %v %v" , arg0 , args )
2021-08-26 16:12:08 +00:00
stdioConn , wg , bg , err := run ( arg0 , args ... )
2018-03-13 21:30:51 +00:00
if err != nil {
return nil , err
}
2018-05-22 18:48:17 +00:00
var conn net . Conn = stdioConn
if lim != nil {
conn = wrapConn ( stdioConn , lim )
}
2018-03-15 20:22:14 +00:00
dialCount := 0
2018-03-13 21:30:51 +00: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 20:22:14 +00:00
if dialCount > 0 {
2020-08-01 10:06:30 +00:00
// the connection to the child process is already closed
2022-08-23 20:05:04 +00:00
return nil , backoff . Permanent ( errors . New ( "rclone stdio connection already closed" ) )
2018-03-15 20:22:14 +00:00
}
dialCount ++
2018-03-13 21:30:51 +00:00
return conn , nil
} ,
}
2021-08-26 16:12:08 +00:00
cmd := stdioConn . cmd
2018-03-13 21:30:51 +00:00
waitCh := make ( chan struct { } )
be := & Backend {
tr : tr ,
cmd : cmd ,
waitCh : waitCh ,
2018-05-22 18:48:17 +00:00
conn : stdioConn ,
2018-03-15 20:22:14 +00:00
wg : wg ,
2018-03-13 21:30:51 +00:00
}
2018-03-15 20:22:14 +00:00
wg . Add ( 1 )
2018-03-13 21:30:51 +00:00
go func ( ) {
2018-03-15 20:22:14 +00:00
defer wg . Done ( )
2018-03-13 21:30:51 +00:00
debug . Log ( "waiting for error result" )
err := cmd . Wait ( )
debug . Log ( "Wait returned %v" , err )
be . waitResult = err
2021-01-30 15:46:34 +00:00
// close our side of the pipes to rclone, ignore errors
_ = stdioConn . CloseAll ( )
2018-03-13 21:30:51 +00:00
close ( waitCh )
} ( )
ctx , cancel := context . WithCancel ( context . Background ( ) )
defer cancel ( )
2018-03-15 20:22:14 +00:00
wg . Add ( 1 )
2018-03-13 21:30:51 +00:00
go func ( ) {
2018-03-15 20:22:14 +00:00
defer wg . Done ( )
2018-03-13 21:30:51 +00: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 {
2018-10-21 17:58:40 +00:00
Transport : debug . RoundTripper ( tr ) ,
2021-11-07 16:47:18 +00:00
Timeout : cfg . Timeout ,
2018-03-13 21:30:51 +00:00
}
2018-03-18 19:30:02 +00: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 ( ) )
2021-01-30 19:43:53 +00:00
req , err := http . NewRequestWithContext ( ctx , http . MethodGet , url , nil )
2018-03-13 21:30:51 +00:00
if err != nil {
return nil , err
}
req . Header . Set ( "Accept" , rest . ContentTypeV2 )
res , err := ctxhttp . Do ( ctx , client , req )
if err != nil {
2021-01-30 15:46:34 +00:00
// ignore subsequent errors
_ = bg ( )
2018-03-13 21:30:51 +00:00
_ = 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 )
2021-01-30 15:46:34 +00:00
err = bg ( )
if err != nil {
return nil , fmt . Errorf ( "error moving process to background: %w" , err )
}
2018-03-13 21:30:51 +00:00
return be , nil
}
// Open starts an rclone process with the given config.
2018-05-22 18:48:17 +00:00
func Open ( cfg Config , lim limiter . Limiter ) ( * Backend , error ) {
2020-06-18 10:55:29 +00:00
be , err := newBackend ( cfg , lim )
2018-03-13 21:30:51 +00:00
if err != nil {
return nil , err
}
url , err := url . Parse ( "http://localhost/" )
if err != nil {
return nil , err
}
restConfig := rest . Config {
2018-03-24 17:37:36 +00:00
Connections : cfg . Connections ,
2018-03-13 21:30:51 +00:00
URL : url ,
}
2018-10-21 17:58:40 +00:00
restBackend , err := rest . Open ( restConfig , debug . RoundTripper ( be . tr ) )
2018-03-13 21:30:51 +00:00
if err != nil {
2020-04-10 10:13:12 +00:00
_ = be . Close ( )
2018-03-13 21:30:51 +00:00
return nil , err
}
be . Backend = restBackend
return be , nil
}
2020-04-10 10:08:52 +00:00
// Create initializes a new restic repo with rclone.
func Create ( ctx context . Context , cfg Config ) ( * Backend , error ) {
2020-06-18 10:55:29 +00:00
be , err := newBackend ( cfg , nil )
2018-03-13 21:30:51 +00:00
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 {
2020-09-19 17:11:28 +00:00
Connections : cfg . Connections ,
2018-03-13 21:30:51 +00:00
URL : url ,
}
2020-04-10 10:08:52 +00:00
restBackend , err := rest . Create ( ctx , restConfig , debug . RoundTripper ( be . tr ) )
2018-03-13 21:30:51 +00:00
if err != nil {
2018-03-14 21:28:12 +00:00
_ = be . Close ( )
2018-03-13 21:30:51 +00:00
return nil , err
}
be . Backend = restBackend
return be , nil
}
2018-03-15 20:22:14 +00:00
const waitForExit = 5 * time . Second
2018-03-13 21:30:51 +00:00
// Close terminates the backend.
func ( be * Backend ) Close ( ) error {
2018-03-15 18:00:25 +00:00
debug . Log ( "exiting rclone" )
2018-03-13 21:30:51 +00:00
be . tr . CloseIdleConnections ( )
2018-03-15 20:22:14 +00:00
select {
case <- be . waitCh :
debug . Log ( "rclone exited" )
case <- time . After ( waitForExit ) :
debug . Log ( "timeout, closing file descriptors" )
2020-07-24 21:27:47 +00:00
err := be . conn . CloseAll ( )
2018-03-15 20:22:14 +00:00
if err != nil {
return err
}
}
be . wg . Wait ( )
2018-03-13 21:30:51 +00:00
debug . Log ( "wait for rclone returned: %v" , be . waitResult )
return be . waitResult
}