Skip to content
This repository was archived by the owner on Aug 30, 2024. It is now read-only.

Commit f745670

Browse files
committed
Verify rsync protocol version match prior to proceeding.
`sync.Version()` proceeds with a warning that there may be a version mismatch if it timesout. `syncCmd.version` assumes rsync is present in the path. `wsep.RemoteExecer` isn't available publicly on GitHub, so I took a best guess with it and `wsep.ExitError`.
1 parent 9b58d37 commit f745670

File tree

2 files changed

+78
-0
lines changed

2 files changed

+78
-0
lines changed

cmd/coder/sync.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
package main
22

33
import (
4+
"bufio"
5+
"errors"
6+
"fmt"
7+
"log"
48
"os"
9+
"os/exec"
510
"path/filepath"
611
"strings"
712

@@ -29,6 +34,32 @@ func (cmd *syncCmd) RegisterFlags(fl *pflag.FlagSet) {
2934
fl.BoolVarP(&cmd.init, "init", "i", false, "do initial transfer and exit")
3035
}
3136

37+
// See https://lxadm.com/Rsync_exit_codes#List_of_standard_rsync_exit_codes.
38+
var IncompatRsync = errors.New("rsync: exit status 2")
39+
var StreamErrRsync = errors.New("rsync: exit status 12")
40+
41+
// Returns local rsync protocol version as a string.
42+
func (s *syncCmd) version() string {
43+
cmd := exec.Command("rsync", "--version")
44+
stdout, err := cmd.StdoutPipe()
45+
if err != nil {
46+
log.Fatal(err)
47+
}
48+
49+
if err := cmd.Start(); err != nil {
50+
log.Fatal(err)
51+
}
52+
53+
r := bufio.NewReader(stdout)
54+
if err := cmd.Wait(); err != nil {
55+
log.Fatal(err)
56+
}
57+
58+
versionString := strings.Split(r.ReadLine(), "protocol version ")
59+
60+
return versionString[1]
61+
}
62+
3263
func (cmd *syncCmd) Run(fl *pflag.FlagSet) {
3364
var (
3465
local = fl.Arg(0)
@@ -71,6 +102,16 @@ func (cmd *syncCmd) Run(fl *pflag.FlagSet) {
71102
LocalDir: absLocal,
72103
Client: entClient,
73104
}
105+
106+
localVersion := s.version()
107+
remoteVersion, rsyncErr := sync.Version()
108+
109+
if rsyncErr != nil {
110+
flog.Info("Unable to determine remote rsync version. Proceeding cautiously.")
111+
} else if localVersion != remoteVersion {
112+
flog.Fatal(fmt.Sprintf("rsync protocol mismatch. local is %s; remote is %s.", localVersion, remoteVersion))
113+
}
114+
74115
for err == nil || err == sync.ErrRestartSync {
75116
err = s.Run()
76117
}

internal/sync/sync.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package sync
22

33
import (
4+
"bufio"
45
"context"
56
"errors"
67
"fmt"
@@ -10,6 +11,7 @@ import (
1011
"os/exec"
1112
"path"
1213
"path/filepath"
14+
"strings"
1315
"sync"
1416
"sync/atomic"
1517
"time"
@@ -261,6 +263,41 @@ const (
261263
maxAcceptableDispatch = time.Millisecond * 50
262264
)
263265

266+
// Returns remote protocol version as a string.
267+
// Or, an error if one exists.
268+
func (s Sync) Version() (string, error) {
269+
conn, err := s.Client.DialWsep(ctx, s.Env)
270+
if err != nil {
271+
return "", err
272+
}
273+
defer conn.Close(websocket.CloseNormalClosure, "")
274+
275+
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
276+
defer cancel()
277+
278+
execer := wsep.RemoteExecer(conn)
279+
process, err := execer.Start(ctx, wsep.Command{
280+
Command: "rsync",
281+
Args: []string{"--version"},
282+
})
283+
if err != nil {
284+
return "", err
285+
}
286+
r := bufio.NewReader(process.Stdout())
287+
288+
err = process.Wait()
289+
if code, ok := err.(wsep.ExitError); ok {
290+
return "", err
291+
}
292+
if err != nil {
293+
return "", err
294+
}
295+
296+
versionString := strings.Split(r.ReadLine(), "protocol version ")
297+
298+
return versionString[1], nil
299+
}
300+
264301
// Run starts the sync synchronously.
265302
// Use this command to debug what wasn't sync'd correctly:
266303
// rsync -e "coder sh" -nicr ~/Projects/cdr/coder-cli/. ammar:/home/coder/coder-cli/

0 commit comments

Comments
 (0)