Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2510c81dcf |
No files matched your search
@@ -178,7 +178,7 @@ vaultik version
|
|||||||
* `--verbose`, `-v`: Enable verbose output (on stderr — see below)
|
* `--verbose`, `-v`: Enable verbose output (on stderr — see below)
|
||||||
* `--debug`: Enable debug output (on stderr — see below)
|
* `--debug`: Enable debug output (on stderr — see below)
|
||||||
* `--quiet`, `-q`: Suppress non-error output (also suppresses startup banner)
|
* `--quiet`, `-q`: Suppress non-error output (also suppresses startup banner)
|
||||||
* `--skip-errors`: Skip files that cannot be read when creating a snapshot, or that cannot be restored when restoring, instead of aborting. Packing and storage errors while creating a snapshot (which would leave a chunk recorded but not stored) still abort the run. A run that skipped anything still finishes but exits non-zero: `snapshot create` keeps the snapshot and counts each file or directory it skipped as failed. A file removed during the backup is also counted as failed but does not change the exit status.
|
* `--skip-errors`: Skip files that cannot be read when creating a snapshot, or that cannot be restored when restoring, instead of aborting. Packing and storage errors while creating a snapshot (which would leave a chunk recorded but not stored) still abort the run. A run that skipped anything still finishes but exits non-zero: `snapshot create` keeps the snapshot and counts each file or directory it skipped as failed.
|
||||||
|
|
||||||
### locking
|
### locking
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,6 @@ func TestEntrySnapshotCreateSkipErrorsExitsNonZeroWhenItSkips(t *testing.T) {
|
|||||||
"Files: 1 examined, 0 backed up, 0 unchanged, 1 failed.")
|
"Files: 1 examined, 0 backed up, 0 unchanged, 1 failed.")
|
||||||
assert.NotContains(t, stdout, "Finished successfully.")
|
assert.NotContains(t, stdout, "Finished successfully.")
|
||||||
assert.Contains(t, stderr,
|
assert.Contains(t, stderr,
|
||||||
"Backup completed, but 1 file(s) could not be read")
|
"Backup completed, but 1 file(s) failed to back up")
|
||||||
assert.NotContains(t, stderr, "Snapshot creation failed")
|
assert.NotContains(t, stderr, "Snapshot creation failed")
|
||||||
}
|
}
|
||||||
@@ -115,8 +115,8 @@ on Linux), then /etc/vaultik/config.yml.`,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if errors.Is(err, vaultik.ErrFilesUnreadable) {
|
if errors.Is(err, vaultik.ErrFilesFailedBackup) {
|
||||||
log.Error("Backup completed with unreadable files", "error", err)
|
log.Error("Backup completed with failed files", "error", err)
|
||||||
ReportErrorf("Backup completed, but %v", err)
|
ReportErrorf("Backup completed, but %v", err)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -162,11 +162,6 @@ type ScanResult struct {
|
|||||||
UploadDuration time.Duration
|
UploadDuration time.Duration
|
||||||
StartTime time.Time
|
StartTime time.Time
|
||||||
EndTime time.Time
|
EndTime time.Time
|
||||||
|
|
||||||
// FilesUnreadable is the part of FilesFailed that --skip-errors
|
|
||||||
// skipped because it could not be read or listed; the rest are files
|
|
||||||
// removed after the walk listed them.
|
|
||||||
FilesUnreadable int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewScanner creates a new scanner instance
|
// NewScanner creates a new scanner instance
|
||||||
@@ -990,7 +985,7 @@ func (s *Scanner) recordSpecialEntry(
|
|||||||
|
|
||||||
// handleWalkError deals with a filesystem error surfaced by the walk:
|
// handleWalkError deals with a filesystem error surfaced by the walk:
|
||||||
// under --skip-errors the entry is skipped with a UI notice and counted
|
// under --skip-errors the entry is skipped with a UI notice and counted
|
||||||
// as examined, failed and unreadable, otherwise the error is fatal.
|
// as examined and failed, otherwise the error is fatal.
|
||||||
func (s *Scanner) handleWalkError(
|
func (s *Scanner) handleWalkError(
|
||||||
filePath string, err error, result *ScanResult,
|
filePath string, err error, result *ScanResult,
|
||||||
) error {
|
) error {
|
||||||
@@ -1002,7 +997,6 @@ func (s *Scanner) handleWalkError(
|
|||||||
|
|
||||||
result.FilesScanned++
|
result.FilesScanned++
|
||||||
result.FilesFailed++
|
result.FilesFailed++
|
||||||
result.FilesUnreadable++
|
|
||||||
|
|
||||||
return nil // Continue scanning
|
return nil // Continue scanning
|
||||||
}
|
}
|
||||||
@@ -1435,7 +1429,6 @@ func (s *Scanner) processFileWithErrorHandling(
|
|||||||
s.ui.Path(fileToProcess.Path), err)
|
s.ui.Path(fileToProcess.Path), err)
|
||||||
|
|
||||||
countFailedFile(fileToProcess, result)
|
countFailedFile(fileToProcess, result)
|
||||||
result.FilesUnreadable++
|
|
||||||
|
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,10 +39,10 @@ var (
|
|||||||
// caller can report it apart from a failed backup.
|
// caller can report it apart from a failed backup.
|
||||||
var ErrPostBackupPruneFailed = errors.New("post-backup prune failed")
|
var ErrPostBackupPruneFailed = errors.New("post-backup prune failed")
|
||||||
|
|
||||||
// ErrFilesUnreadable is returned when --skip-errors skipped files or
|
// ErrFilesFailedBackup is returned when --skip-errors skipped files or
|
||||||
// directories it could not read or list. The snapshot is complete and
|
// directories. The snapshot is complete and kept, so the caller can
|
||||||
// kept, so the caller can report it apart from a failed backup.
|
// report it apart from a failed backup.
|
||||||
var ErrFilesUnreadable = errors.New("file(s) could not be read")
|
var ErrFilesFailedBackup = errors.New("file(s) failed to back up")
|
||||||
|
|
||||||
// listRecentLimit caps how many snapshot rows are fetched from the
|
// listRecentLimit caps how many snapshot rows are fetched from the
|
||||||
// local index for listing and purge operations.
|
// local index for listing and purge operations.
|
||||||
@@ -140,16 +140,16 @@ func (v *Vaultik) CreateSnapshot(opts *SnapshotCreateOptions) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Process each named snapshot
|
// Process each named snapshot
|
||||||
filesUnreadable := 0
|
filesFailed := 0
|
||||||
|
|
||||||
for snapIdx, snapName := range snapshotNames {
|
for snapIdx, snapName := range snapshotNames {
|
||||||
unreadable, err := v.createNamedSnapshot(
|
failed, err := v.createNamedSnapshot(
|
||||||
opts, hostname, snapName, snapIdx+1, len(snapshotNames))
|
opts, hostname, snapName, snapIdx+1, len(snapshotNames))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
filesUnreadable += unreadable
|
filesFailed += failed
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print overall summary if multiple snapshots
|
// Print overall summary if multiple snapshots
|
||||||
@@ -165,17 +165,17 @@ func (v *Vaultik) CreateSnapshot(opts *SnapshotCreateOptions) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return v.finishCreateSnapshot(filesUnreadable)
|
return v.finishCreateSnapshot(opts.SkipErrors, filesFailed)
|
||||||
}
|
}
|
||||||
|
|
||||||
// finishCreateSnapshot ends a run whose snapshots are all complete and
|
// finishCreateSnapshot ends a run whose snapshots are all complete and
|
||||||
// kept. Like finishRestore, it returns an error when --skip-errors
|
// kept. Like finishRestore, it returns an error when --skip-errors
|
||||||
// skipped anything it could not read or list; otherwise it prints the
|
// skipped anything; otherwise it prints the closing line.
|
||||||
// closing line. A file removed during the run is counted as failed in
|
func (v *Vaultik) finishCreateSnapshot(skipErrors bool, filesFailed int) error {
|
||||||
// the summary but is not an error, with or without --skip-errors.
|
// Without --skip-errors a file is counted as failed only when it was
|
||||||
func (v *Vaultik) finishCreateSnapshot(filesUnreadable int) error {
|
// removed during the run, which is not an error.
|
||||||
if filesUnreadable > 0 {
|
if skipErrors && filesFailed > 0 {
|
||||||
return fmt.Errorf("%d %w", filesUnreadable, ErrFilesUnreadable)
|
return fmt.Errorf("%d %w", filesFailed, ErrFilesFailedBackup)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Terminus must obey the --cron invariant: silent on total
|
// Terminus must obey the --cron invariant: silent on total
|
||||||
@@ -245,10 +245,6 @@ type snapshotStats struct {
|
|||||||
totalBlobsUploaded int
|
totalBlobsUploaded int
|
||||||
uploadDuration time.Duration
|
uploadDuration time.Duration
|
||||||
|
|
||||||
// The part of totalFilesFailed that --skip-errors skipped because it
|
|
||||||
// could not be read or listed.
|
|
||||||
totalFilesUnreadable int
|
|
||||||
|
|
||||||
// The sizes of all blobs the snapshot references, set by
|
// The sizes of all blobs the snapshot references, set by
|
||||||
// finalizeSnapshotMetadata once snapshot_blobs is populated.
|
// finalizeSnapshotMetadata once snapshot_blobs is populated.
|
||||||
blobSize int64
|
blobSize int64
|
||||||
@@ -256,8 +252,7 @@ type snapshotStats struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// createNamedSnapshot creates a single named snapshot and returns the
|
// createNamedSnapshot creates a single named snapshot and returns the
|
||||||
// number of entries --skip-errors skipped because it could not read or
|
// number of files counted as failed.
|
||||||
// list them.
|
|
||||||
func (v *Vaultik) createNamedSnapshot(
|
func (v *Vaultik) createNamedSnapshot(
|
||||||
opts *SnapshotCreateOptions, hostname, snapName string, idx, total int,
|
opts *SnapshotCreateOptions, hostname, snapName string, idx, total int,
|
||||||
) (int, error) {
|
) (int, error) {
|
||||||
@@ -315,7 +310,7 @@ func (v *Vaultik) createNamedSnapshot(
|
|||||||
|
|
||||||
v.printSnapshotSummary(snapshotID, snapshotStartTime, stats)
|
v.printSnapshotSummary(snapshotID, snapshotStartTime, stats)
|
||||||
|
|
||||||
return stats.totalFilesUnreadable, nil
|
return stats.totalFilesFailed, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// previousSnapshotID returns the ID of the most recent completed snapshot
|
// previousSnapshotID returns the ID of the most recent completed snapshot
|
||||||
@@ -401,7 +396,6 @@ func (v *Vaultik) scanAllDirectories(
|
|||||||
stats.totalBlobs += result.BlobsCreated
|
stats.totalBlobs += result.BlobsCreated
|
||||||
stats.totalFilesSkipped += result.FilesSkipped
|
stats.totalFilesSkipped += result.FilesSkipped
|
||||||
stats.totalFilesFailed += result.FilesFailed
|
stats.totalFilesFailed += result.FilesFailed
|
||||||
stats.totalFilesUnreadable += result.FilesUnreadable
|
|
||||||
stats.totalBytesSkipped += result.BytesSkipped
|
stats.totalBytesSkipped += result.BytesSkipped
|
||||||
stats.totalFilesDeleted += result.FilesDeleted
|
stats.totalFilesDeleted += result.FilesDeleted
|
||||||
stats.totalBytesDeleted += result.BytesDeleted
|
stats.totalBytesDeleted += result.BytesDeleted
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ func TestBackupDoesNotCountFileInUnopenableDirectoryAsDeleted(t *testing.T) {
|
|||||||
SkipErrors: true,
|
SkipErrors: true,
|
||||||
Snapshots: []string{"work"},
|
Snapshots: []string{"work"},
|
||||||
})
|
})
|
||||||
require.ErrorIs(t, err, vaultik.ErrFilesUnreadable)
|
require.ErrorIs(t, err, vaultik.ErrFilesFailedBackup)
|
||||||
assert.Contains(t, env.out.String(),
|
assert.Contains(t, env.out.String(),
|
||||||
"Files: 2 examined, 0 backed up, 1 unchanged, 1 failed.")
|
"Files: 2 examined, 0 backed up, 1 unchanged, 1 failed.")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,14 +54,10 @@ func TestSnapshotSummaryCountsFileNotStoredAsFailed(t *testing.T) {
|
|||||||
// What a normal user gets opening a file with mode 000.
|
// What a normal user gets opening a file with mode 000.
|
||||||
{name: "unopenable under skip-errors",
|
{name: "unopenable under skip-errors",
|
||||||
openErr: os.ErrPermission, skipErrors: true,
|
openErr: os.ErrPermission, skipErrors: true,
|
||||||
wantErr: vaultik.ErrFilesUnreadable},
|
wantErr: vaultik.ErrFilesFailedBackup},
|
||||||
// What opening a file removed after phase 1 gives.
|
// What opening a file removed after phase 1 gives.
|
||||||
{name: "removed between the phases",
|
{name: "removed between the phases",
|
||||||
openErr: os.ErrNotExist, skipErrors: false},
|
openErr: os.ErrNotExist, skipErrors: false},
|
||||||
// A removal is not an error, so --skip-errors does not make the
|
|
||||||
// run fail. See https://git.eeqj.de/sneak/vaultik/issues/310.
|
|
||||||
{name: "removed between the phases under skip-errors",
|
|
||||||
openErr: os.ErrNotExist, skipErrors: true},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ func TestSkipErrorsBackupKeepsSnapshotAndFailsWhenItSkips(t *testing.T) {
|
|||||||
SkipErrors: true,
|
SkipErrors: true,
|
||||||
Snapshots: []string{"work"},
|
Snapshots: []string{"work"},
|
||||||
})
|
})
|
||||||
require.ErrorIs(t, err, vaultik.ErrFilesUnreadable)
|
require.ErrorIs(t, err, vaultik.ErrFilesFailedBackup)
|
||||||
|
|
||||||
assert.Contains(t, env.out.String(),
|
assert.Contains(t, env.out.String(),
|
||||||
"Files: 2 examined, 1 backed up, 0 unchanged, 1 failed.")
|
"Files: 2 examined, 1 backed up, 0 unchanged, 1 failed.")
|
||||||
|
|||||||
Reference in new issue
Block a user