diff --git a/cmd/coder/sync.go b/cmd/coder/sync.go index 0e26af96..e0d62708 100644 --- a/cmd/coder/sync.go +++ b/cmd/coder/sync.go @@ -1,7 +1,11 @@ package main import ( + "bytes" + "fmt" + "log" "os" + "os/exec" "path/filepath" "strings" @@ -29,6 +33,23 @@ func (cmd *syncCmd) RegisterFlags(fl *pflag.FlagSet) { fl.BoolVarP(&cmd.init, "init", "i", false, "do initial transfer and exit") } +// version returns local rsync protocol version as a string. +func (_ *syncCmd) version() string { + cmd := exec.Command("rsync", "--version") + out, err := cmd.CombinedOutput() + if err != nil { + log.Fatal(err) + } + + firstLine, err := bytes.NewBuffer(out).ReadString('\n') + if err != nil { + log.Fatal(err) + } + versionString := strings.Split(firstLine, "protocol version ") + + return versionString[1] +} + func (cmd *syncCmd) Run(fl *pflag.FlagSet) { var ( local = fl.Arg(0) @@ -71,6 +92,16 @@ func (cmd *syncCmd) Run(fl *pflag.FlagSet) { LocalDir: absLocal, Client: entClient, } + + localVersion := cmd.version() + remoteVersion, rsyncErr := s.Version() + + if rsyncErr != nil { + flog.Info("Unable to determine remote rsync version. Proceeding cautiously.") + } else if localVersion != remoteVersion { + flog.Fatal(fmt.Sprintf("rsync protocol mismatch. %s.", localVersion, rsyncErr)) + } + for err == nil || err == sync.ErrRestartSync { err = s.Run() } diff --git a/internal/sync/sync.go b/internal/sync/sync.go index 32086896..b00cbeb1 100644 --- a/internal/sync/sync.go +++ b/internal/sync/sync.go @@ -1,6 +1,7 @@ package sync import ( + "bytes" "context" "errors" "fmt" @@ -10,6 +11,7 @@ import ( "os/exec" "path" "path/filepath" + "strings" "sync" "sync/atomic" "time" @@ -261,6 +263,44 @@ const ( maxAcceptableDispatch = time.Millisecond * 50 ) +// Version returns remote protocol version as a string. +// Or, an error if one exists. +func (s Sync) Version() (string, error) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) + defer cancel() + + conn, err := s.Client.DialWsep(ctx, s.Env) + if err != nil { + return "", err + } + defer conn.Close(websocket.CloseNormalClosure, "") + + execer := wsep.RemoteExecer(conn) + process, err := execer.Start(ctx, wsep.Command{ + Command: "rsync", + Args: []string{"--version"}, + }) + if err != nil { + return "", err + } + buf := &bytes.Buffer{} + io.Copy(buf, process.Stdout()) + + err = process.Wait() + if err != nil { + return "", err + } + + firstLine, err := buf.ReadString('\n') + if err != nil { + return "", err + } + + versionString := strings.Split(firstLine, "protocol version ") + + return versionString[1], nil +} + // Run starts the sync synchronously. // Use this command to debug what wasn't sync'd correctly: // rsync -e "coder sh" -nicr ~/Projects/cdr/coder-cli/. ammar:/home/coder/coder-cli/