diff --git a/internal/util.go b/internal/util.go index bfc5c6f..7d47498 100644 --- a/internal/util.go +++ b/internal/util.go @@ -6,12 +6,37 @@ import ( "crypto/x509" "encoding/json" "errors" + "fmt" "io/ioutil" "net/http" + "os/exec" "regexp" "time" ) +var rsyncExitValues = map[int]string{ + 0: "Success", + 1: "Syntax or usage error", + 2: "Protocol incompatibility", + 3: "Errors selecting input/output files, dirs", + 4: "Requested action not supported: an attempt was made to manipulate 64-bit files on a platform that cannot support them; or an option was specified that is supported by the client and not by the server.", + 5: "Error starting client-server protocol", + 6: "Daemon unable to append to log-file", + 10: "Error in socket I/O", + 11: "Error in file I/O", + 12: "Error in rsync protocol data stream", + 13: "Errors with program diagnostics", + 14: "Error in IPC code", + 20: "Received SIGUSR1 or SIGINT", + 21: "Some error returned by waitpid()", + 22: "Error allocating core memory buffers", + 23: "Partial transfer due to error", + 24: "Partial transfer due to vanished source files", + 25: "The --max-delete limit stopped deletions", + 30: "Timeout in data send/receive", + 35: "Timeout waiting for daemon connection", +} + // GetTLSConfig generate tls.Config from CAFile func GetTLSConfig(CAFile string) (*tls.Config, error) { caCert, err := ioutil.ReadFile(CAFile) @@ -115,3 +140,16 @@ func ExtractSizeFromRsyncLog(logFile string) string { re := regexp.MustCompile(`(?m)^Total file size: ([0-9\.]+[KMGTP]?) bytes`) return ExtractSizeFromLog(logFile, re) } + +// TranslateRsyncErrorCode translates the exit code of rsync to a message +func TranslateRsyncErrorCode(cmdErr error) (exitCode int, msg string) { + + if exiterr, ok := cmdErr.(*exec.ExitError); ok { + exitCode = exiterr.ExitCode() + strerr, valid := rsyncExitValues[exitCode] + if valid { + msg = fmt.Sprintf("rsync error: %s", strerr) + } + } + return +} diff --git a/worker/provider_test.go b/worker/provider_test.go index ea6a84f..62f9a24 100644 --- a/worker/provider_test.go +++ b/worker/provider_test.go @@ -106,6 +106,34 @@ exit 0 }) }) + Convey("If the rsync program fails", t, func() { + tmpDir, err := ioutil.TempDir("", "tunasync") + defer os.RemoveAll(tmpDir) + So(err, ShouldBeNil) + tmpFile := filepath.Join(tmpDir, "log_file") + + Convey("in the rsyncProvider", func() { + + c := rsyncConfig{ + name: "tuna", + upstreamURL: "rsync://rsync.tuna.moe/tuna/", + workingDir: tmpDir, + logDir: tmpDir, + logFile: tmpFile, + extraOptions: []string{"--somethine-invalid"}, + interval: 600 * time.Second, + } + + provider, err := newRsyncProvider(c) + So(err, ShouldBeNil) + + err = provider.Run() + So(err, ShouldNotBeNil) + loggedContent, err := ioutil.ReadFile(provider.LogFile()) + So(err, ShouldBeNil) + So(string(loggedContent), ShouldContainSubstring, "Syntax or usage error") + }) + }) } func TestRsyncProviderWithAuthentication(t *testing.T) { @@ -556,4 +584,34 @@ exit 0 // fmt.Println(string(loggedContent)) }) }) + + Convey("If the rsync program fails", t, func(ctx C) { + tmpDir, err := ioutil.TempDir("", "tunasync") + defer os.RemoveAll(tmpDir) + So(err, ShouldBeNil) + tmpFile := filepath.Join(tmpDir, "log_file") + + Convey("in the twoStageRsyncProvider", func() { + + c := twoStageRsyncConfig{ + name: "tuna-two-stage-rsync", + upstreamURL: "rsync://0.0.0.1/", + stage1Profile: "debian", + workingDir: tmpDir, + logDir: tmpDir, + logFile: tmpFile, + excludeFile: tmpFile, + } + + provider, err := newTwoStageRsyncProvider(c) + So(err, ShouldBeNil) + + err = provider.Run() + So(err, ShouldNotBeNil) + loggedContent, err := ioutil.ReadFile(provider.LogFile()) + So(err, ShouldBeNil) + So(string(loggedContent), ShouldContainSubstring, "Error in socket I/O") + + }) + }) } diff --git a/worker/rsync_provider.go b/worker/rsync_provider.go index a1a5a4e..5ce1abb 100644 --- a/worker/rsync_provider.go +++ b/worker/rsync_provider.go @@ -110,6 +110,13 @@ func (p *rsyncProvider) Run() error { return err } if err := p.Wait(); err != nil { + code, msg := internal.TranslateRsyncErrorCode(err) + if code != 0 { + logger.Debug("Rsync exitcode %d (%s)", code, msg) + if p.logFileFd != nil { + p.logFileFd.WriteString(msg + "\n") + } + } return err } p.dataSize = internal.ExtractSizeFromRsyncLog(p.LogFile()) diff --git a/worker/two_stage_rsync_provider.go b/worker/two_stage_rsync_provider.go index e95da23..572d275 100644 --- a/worker/two_stage_rsync_provider.go +++ b/worker/two_stage_rsync_provider.go @@ -168,6 +168,13 @@ func (p *twoStageRsyncProvider) Run() error { err = p.Wait() p.Lock() if err != nil { + code, msg := internal.TranslateRsyncErrorCode(err) + if code != 0 { + logger.Debug("Rsync exitcode %d (%s)", code, msg) + if p.logFileFd != nil { + p.logFileFd.WriteString(msg + "\n") + } + } return err } }