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:
Srigovind Nayak
2025-10-05 19:08:52 +05:30
committed by GitHub
parent 22f254c9ca
commit 481fcb9ca7
4 changed files with 41 additions and 11 deletions
+15 -10
View File
@@ -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)
+12 -1
View File
@@ -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) {
+3
View File
@@ -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) {