mirror of
https://github.com/restic/restic.git
synced 2025-12-11 10:11:53 +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
|
// ErrInvalidSourceData is used to report an incomplete backup
|
||||||
var ErrInvalidSourceData = errors.New("at least one source file could not be read")
|
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
|
// filterExisting returns a slice of all existing items, or an error if no
|
||||||
// items exist at all.
|
// items exist at all.
|
||||||
func filterExisting(items []string, warnf func(msg string, args ...interface{})) (result []string, err error) {
|
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 {
|
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
|
// 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")
|
return nil, errors.Fatal("nothing to backup, please specify source files/dirs")
|
||||||
}
|
}
|
||||||
|
|
||||||
targets, err = filterExisting(targets, warnf)
|
return filterExisting(targets, warnf)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return targets, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// parent returns the ID of the parent snapshot. If there is none, nil is
|
// 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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
success := true
|
||||||
targets, err := collectTargets(opts, args, printer.E, term.InputRaw())
|
targets, err := collectTargets(opts, args, printer.E, term.InputRaw())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
if errors.Is(err, ErrInvalidSourceData) {
|
||||||
|
success = false
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
timeStamp := time.Now()
|
timeStamp := time.Now()
|
||||||
@@ -632,7 +637,7 @@ func runBackup(ctx context.Context, opts BackupOptions, gopts GlobalOptions, ter
|
|||||||
arch.SelectByName = selectByNameFilter
|
arch.SelectByName = selectByNameFilter
|
||||||
arch.Select = selectFilter
|
arch.Select = selectFilter
|
||||||
arch.WithAtime = opts.WithAtime
|
arch.WithAtime = opts.WithAtime
|
||||||
success := true
|
|
||||||
arch.Error = func(item string, err error) error {
|
arch.Error = func(item string, err error) error {
|
||||||
success = false
|
success = false
|
||||||
reterr := progressReporter.Error(item, err)
|
reterr := progressReporter.Error(item, err)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/restic/restic/internal/data"
|
"github.com/restic/restic/internal/data"
|
||||||
|
"github.com/restic/restic/internal/errors"
|
||||||
"github.com/restic/restic/internal/fs"
|
"github.com/restic/restic/internal/fs"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
rtest "github.com/restic/restic/internal/test"
|
rtest "github.com/restic/restic/internal/test"
|
||||||
@@ -271,7 +272,17 @@ func TestBackupNonExistingFile(t *testing.T) {
|
|||||||
|
|
||||||
opts := BackupOptions{}
|
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) {
|
func TestBackupSelfHealing(t *testing.T) {
|
||||||
|
|||||||
@@ -71,6 +71,9 @@ func TestCollectTargets(t *testing.T) {
|
|||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
sort.Strings(targets)
|
sort.Strings(targets)
|
||||||
rtest.Equals(t, expect, 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) {
|
func TestReadFilenamesRaw(t *testing.T) {
|
||||||
|
|||||||
Reference in New Issue
Block a user