mirror of
https://github.com/restic/restic.git
synced 2025-12-10 14:53:32 +00:00
backup: return exit code 3 if not all targets are available (#5347)
to make the exit code behaviour consistent with files inaccessible during the backup phase, making this change to exit with code 3 if not all target files/folders are accessible for backup --------- Co-authored-by: Michael Eischer <michael.eischer@fau.de>
This commit is contained in:
11
changelog/unreleased/issue-4467
Normal file
11
changelog/unreleased/issue-4467
Normal file
@@ -0,0 +1,11 @@
|
||||
Bugfix: Exit with code 3 when some `backup` source files do not exist
|
||||
|
||||
Restic used to exit with code 0 even when some backup sources did not exist. Restic
|
||||
would exit with code 3 only when child directories or files did not exist. This
|
||||
could cause confusion and unexpected behavior in scripts that relied on the exit
|
||||
code to determine if the backup was successful.
|
||||
|
||||
Restic now exits with code 3 when some backup sources do not exist.
|
||||
|
||||
https://github.com/restic/restic/issues/4467
|
||||
https://github.com/restic/restic/pull/5347
|
||||
@@ -157,6 +157,9 @@ var backupFSTestHook func(fs fs.FS) fs.FS
|
||||
// ErrInvalidSourceData is used to report an incomplete backup
|
||||
var ErrInvalidSourceData = errors.New("at least one source file could not be read")
|
||||
|
||||
// ErrNoSourceData is used to report that no source data was found
|
||||
var ErrNoSourceData = errors.Fatal("all source directories/files do not exist")
|
||||
|
||||
// filterExisting returns a slice of all existing items, or an error if no
|
||||
// items exist at all.
|
||||
func filterExisting(items []string, warnf func(msg string, args ...interface{})) (result []string, err error) {
|
||||
@@ -171,10 +174,12 @@ func filterExisting(items []string, warnf func(msg string, args ...interface{}))
|
||||
}
|
||||
|
||||
if len(result) == 0 {
|
||||
return nil, errors.Fatal("all source directories/files do not exist")
|
||||
return nil, ErrNoSourceData
|
||||
} else if len(result) < len(items) {
|
||||
return result, ErrInvalidSourceData
|
||||
}
|
||||
|
||||
return
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// readLines reads all lines from the named file and returns them as a
|
||||
@@ -437,12 +442,7 @@ func collectTargets(opts BackupOptions, args []string, warnf func(msg string, ar
|
||||
return nil, errors.Fatal("nothing to backup, please specify source files/dirs")
|
||||
}
|
||||
|
||||
targets, err = filterExisting(targets, warnf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return targets, nil
|
||||
return filterExisting(targets, warnf)
|
||||
}
|
||||
|
||||
// parent returns the ID of the parent snapshot. If there is none, nil is
|
||||
@@ -496,9 +496,14 @@ func runBackup(ctx context.Context, opts BackupOptions, gopts GlobalOptions, ter
|
||||
return err
|
||||
}
|
||||
|
||||
success := true
|
||||
targets, err := collectTargets(opts, args, printer.E, term.InputRaw())
|
||||
if err != nil {
|
||||
return err
|
||||
if errors.Is(err, ErrInvalidSourceData) {
|
||||
success = false
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
timeStamp := time.Now()
|
||||
@@ -632,7 +637,7 @@ func runBackup(ctx context.Context, opts BackupOptions, gopts GlobalOptions, ter
|
||||
arch.SelectByName = selectByNameFilter
|
||||
arch.Select = selectFilter
|
||||
arch.WithAtime = opts.WithAtime
|
||||
success := true
|
||||
|
||||
arch.Error = func(item string, err error) error {
|
||||
success = false
|
||||
reterr := progressReporter.Error(item, err)
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/restic/restic/internal/data"
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/fs"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
@@ -271,7 +272,17 @@ func TestBackupNonExistingFile(t *testing.T) {
|
||||
|
||||
opts := BackupOptions{}
|
||||
|
||||
testRunBackup(t, "", dirs, opts, env.gopts)
|
||||
// mix of existing and non-existing files
|
||||
err := testRunBackupAssumeFailure(t, "", dirs, opts, env.gopts)
|
||||
rtest.Assert(t, err != nil, "expected error for non-existing file")
|
||||
rtest.Assert(t, errors.Is(err, ErrInvalidSourceData), "expected ErrInvalidSourceData; got %v", err)
|
||||
// only non-existing file
|
||||
dirs = []string{
|
||||
filepath.Join(p, "nonexisting"),
|
||||
}
|
||||
err = testRunBackupAssumeFailure(t, "", dirs, opts, env.gopts)
|
||||
rtest.Assert(t, err != nil, "expected error for non-existing file")
|
||||
rtest.Assert(t, errors.Is(err, ErrNoSourceData), "expected ErrNoSourceData; got %v", err)
|
||||
}
|
||||
|
||||
func TestBackupSelfHealing(t *testing.T) {
|
||||
|
||||
@@ -71,6 +71,9 @@ func TestCollectTargets(t *testing.T) {
|
||||
rtest.OK(t, err)
|
||||
sort.Strings(targets)
|
||||
rtest.Equals(t, expect, targets)
|
||||
|
||||
_, err = collectTargets(opts, []string{filepath.Join(dir, "cmdline arg"), filepath.Join(dir, "non-existing-file")}, t.Logf, nil)
|
||||
rtest.Assert(t, err == ErrInvalidSourceData, "expected error when not all targets exist")
|
||||
}
|
||||
|
||||
func TestReadFilenamesRaw(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user