mirror of
https://github.com/restic/restic.git
synced 2026-02-22 16:56:24 +00:00
Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2fb07dcdb1 | ||
|
|
5dcee7f0a3 | ||
|
|
44968c7d43 | ||
|
|
dbb5fb9fbd | ||
|
|
3a4a5a8215 | ||
|
|
d8d955e0aa | ||
|
|
2ce485063f | ||
|
|
f72febb34f | ||
|
|
ee9a5cdf70 | ||
|
|
46dce1f4fa | ||
|
|
841f8bfef0 | ||
|
|
1f5791222a | ||
|
|
a7b13bd603 | ||
|
|
0c711f5605 | ||
|
|
4df2e33568 | ||
|
|
11c1fbce20 | ||
|
|
9553d873ff | ||
|
|
048c3bb240 | ||
|
|
d6e76a22a8 | ||
|
|
e3a022f9b5 | ||
|
|
fe269c752a | ||
|
|
fc1fc00aa4 | ||
|
|
3c82fe6ef5 | ||
|
|
986d981bf6 | ||
|
|
0df2fa8135 | ||
|
|
49ccb7734c | ||
|
|
491cc65e3a | ||
|
|
8c1d6a50c1 | ||
|
|
9386acc4a6 | ||
|
|
5b60d49654 | ||
|
|
8056181301 | ||
|
|
76a647febf |
84
CHANGELOG.md
84
CHANGELOG.md
@@ -1,5 +1,6 @@
|
||||
# Table of Contents
|
||||
|
||||
* [Changelog for 0.17.2](#changelog-for-restic-0172-2024-10-27)
|
||||
* [Changelog for 0.17.1](#changelog-for-restic-0171-2024-09-05)
|
||||
* [Changelog for 0.17.0](#changelog-for-restic-0170-2024-07-26)
|
||||
* [Changelog for 0.16.5](#changelog-for-restic-0165-2024-07-01)
|
||||
@@ -36,6 +37,89 @@
|
||||
* [Changelog for 0.6.0](#changelog-for-restic-060-2017-05-29)
|
||||
|
||||
|
||||
# Changelog for restic 0.17.2 (2024-10-27)
|
||||
The following sections list the changes in restic 0.17.2 relevant to
|
||||
restic users. The changes are ordered by importance.
|
||||
|
||||
## Summary
|
||||
|
||||
* Fix #4004: Support container-level SAS/SAT tokens for Azure backend
|
||||
* Fix #5047: Resolve potential error during concurrent cache cleanup
|
||||
* Fix #5050: Return error if `tag` fails to lock repository
|
||||
* Fix #5057: Exclude irregular files from backups
|
||||
* Fix #5063: Correctly `backup` extended metadata when using VSS on Windows
|
||||
|
||||
## Details
|
||||
|
||||
* Bugfix #4004: Support container-level SAS/SAT tokens for Azure backend
|
||||
|
||||
Restic previously expected SAS/SAT tokens to be generated at the account level,
|
||||
which prevented tokens created at the container level from being used to
|
||||
initialize a repository. This caused an error when attempting to initialize a
|
||||
repository with container-level tokens.
|
||||
|
||||
Restic now supports both account-level and container-level SAS/SAT tokens for
|
||||
initializing a repository.
|
||||
|
||||
https://github.com/restic/restic/issues/4004
|
||||
https://github.com/restic/restic/pull/5093
|
||||
|
||||
* Bugfix #5047: Resolve potential error during concurrent cache cleanup
|
||||
|
||||
When multiple restic processes ran concurrently, they could compete to remove
|
||||
obsolete snapshots from the local backend cache, sometimes leading to a "no such
|
||||
file or directory" error. Restic now suppresses this error to prevent issues
|
||||
during cache cleanup.
|
||||
|
||||
https://github.com/restic/restic/pull/5047
|
||||
|
||||
* Bugfix #5050: Return error if `tag` fails to lock repository
|
||||
|
||||
Since restic 0.17.0, the `tag` command did not return an error when it failed to
|
||||
open or lock the repository. This issue has now been fixed.
|
||||
|
||||
https://github.com/restic/restic/issues/5050
|
||||
https://github.com/restic/restic/pull/5056
|
||||
|
||||
* Bugfix #5057: Exclude irregular files from backups
|
||||
|
||||
Since restic 0.17.1, files with the type `irregular` could mistakenly be
|
||||
included in snapshots, especially when backing up special file types on Windows
|
||||
that restic cannot process. This issue has now been fixed.
|
||||
|
||||
Previously, this bug caused the `check` command to report errors like the
|
||||
following one:
|
||||
|
||||
```
|
||||
tree 12345678[...]: node "example.zip" with invalid type "irregular"
|
||||
```
|
||||
|
||||
To repair affected snapshots, upgrade to restic 0.17.2 and run:
|
||||
|
||||
```
|
||||
restic repair snapshots --forget
|
||||
```
|
||||
|
||||
This will remove the `irregular` files from the snapshots (creating a new
|
||||
snapshot ID for each of the affected snapshots).
|
||||
|
||||
https://github.com/restic/restic/pull/5057
|
||||
https://forum.restic.net/t/errors-found-by-check-1-invalid-type-irregular-2-ciphertext-verification-failed/8447/2
|
||||
|
||||
* Bugfix #5063: Correctly `backup` extended metadata when using VSS on Windows
|
||||
|
||||
On Windows, when creating a backup with the `--use-fs-snapshot` option, restic
|
||||
read extended metadata from the original filesystem path instead of from the
|
||||
snapshot. This could result in errors if files were removed during the backup
|
||||
process.
|
||||
|
||||
This issue has now been resolved.
|
||||
|
||||
https://github.com/restic/restic/issues/5063
|
||||
https://github.com/restic/restic/pull/5097
|
||||
https://github.com/restic/restic/pull/5099
|
||||
|
||||
|
||||
# Changelog for restic 0.17.1 (2024-09-05)
|
||||
The following sections list the changes in restic 0.17.1 relevant to
|
||||
restic users. The changes are ordered by importance.
|
||||
|
||||
12
changelog/0.17.2_2024-10-27/issue-4004
Normal file
12
changelog/0.17.2_2024-10-27/issue-4004
Normal file
@@ -0,0 +1,12 @@
|
||||
Bugfix: Support container-level SAS/SAT tokens for Azure backend
|
||||
|
||||
Restic previously expected SAS/SAT tokens to be generated at the account level,
|
||||
which prevented tokens created at the container level from being used to
|
||||
initialize a repository. This caused an error when attempting to initialize a
|
||||
repository with container-level tokens.
|
||||
|
||||
Restic now supports both account-level and container-level SAS/SAT tokens for
|
||||
initializing a repository.
|
||||
|
||||
https://github.com/restic/restic/issues/4004
|
||||
https://github.com/restic/restic/pull/5093
|
||||
7
changelog/0.17.2_2024-10-27/issue-5050
Normal file
7
changelog/0.17.2_2024-10-27/issue-5050
Normal file
@@ -0,0 +1,7 @@
|
||||
Bugfix: Return error if `tag` fails to lock repository
|
||||
|
||||
Since restic 0.17.0, the `tag` command did not return an error when it failed
|
||||
to open or lock the repository. This issue has now been fixed.
|
||||
|
||||
https://github.com/restic/restic/issues/5050
|
||||
https://github.com/restic/restic/pull/5056
|
||||
12
changelog/0.17.2_2024-10-27/issue-5063
Normal file
12
changelog/0.17.2_2024-10-27/issue-5063
Normal file
@@ -0,0 +1,12 @@
|
||||
Bugfix: Correctly `backup` extended metadata when using VSS on Windows
|
||||
|
||||
On Windows, when creating a backup with the `--use-fs-snapshot` option, restic
|
||||
read extended metadata from the original filesystem path instead of from the
|
||||
snapshot. This could result in errors if files were removed during the backup
|
||||
process.
|
||||
|
||||
This issue has now been resolved.
|
||||
|
||||
https://github.com/restic/restic/issues/5063
|
||||
https://github.com/restic/restic/pull/5097
|
||||
https://github.com/restic/restic/pull/5099
|
||||
8
changelog/0.17.2_2024-10-27/pull-5047
Normal file
8
changelog/0.17.2_2024-10-27/pull-5047
Normal file
@@ -0,0 +1,8 @@
|
||||
Bugfix: Resolve potential error during concurrent cache cleanup
|
||||
|
||||
When multiple restic processes ran concurrently, they could compete to remove
|
||||
obsolete snapshots from the local backend cache, sometimes leading to a "no
|
||||
such file or directory" error. Restic now suppresses this error to prevent
|
||||
issues during cache cleanup.
|
||||
|
||||
https://github.com/restic/restic/pull/5047
|
||||
24
changelog/0.17.2_2024-10-27/pull-5057
Normal file
24
changelog/0.17.2_2024-10-27/pull-5057
Normal file
@@ -0,0 +1,24 @@
|
||||
Bugfix: Exclude irregular files from backups
|
||||
|
||||
Since restic 0.17.1, files with the type `irregular` could mistakenly be included
|
||||
in snapshots, especially when backing up special file types on Windows that
|
||||
restic cannot process. This issue has now been fixed.
|
||||
|
||||
Previously, this bug caused the `check` command to report errors like the
|
||||
following one:
|
||||
|
||||
```
|
||||
tree 12345678[...]: node "example.zip" with invalid type "irregular"
|
||||
```
|
||||
|
||||
To repair affected snapshots, upgrade to restic 0.17.2 and run:
|
||||
|
||||
```
|
||||
restic repair snapshots --forget
|
||||
```
|
||||
|
||||
This will remove the `irregular` files from the snapshots (creating
|
||||
a new snapshot ID for each of the affected snapshots).
|
||||
|
||||
https://github.com/restic/restic/pull/5057
|
||||
https://forum.restic.net/t/errors-found-by-check-1-invalid-type-irregular-2-ciphertext-verification-failed/8447/2
|
||||
@@ -15,7 +15,7 @@ Details
|
||||
{{ range $entry := .Entries }}{{ with $entry }}
|
||||
* {{ .Type }} #{{ .PrimaryID }}: {{ .Title }}
|
||||
{{ range $par := .Paragraphs }}
|
||||
{{ $par }}
|
||||
{{ indent 3 $par }}
|
||||
{{ end }}
|
||||
{{ range $id := .Issues -}}
|
||||
{{ ` ` }}[#{{ $id }}](https://github.com/restic/restic/issues/{{ $id -}})
|
||||
|
||||
@@ -95,6 +95,7 @@ type BackupOptions struct {
|
||||
}
|
||||
|
||||
var backupOptions BackupOptions
|
||||
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")
|
||||
@@ -598,6 +599,10 @@ func runBackup(ctx context.Context, opts BackupOptions, gopts GlobalOptions, ter
|
||||
targets = []string{filename}
|
||||
}
|
||||
|
||||
if backupFSTestHook != nil {
|
||||
targetFS = backupFSTestHook(targetFS)
|
||||
}
|
||||
|
||||
wg, wgCtx := errgroup.WithContext(ctx)
|
||||
cancelCtx, cancel := context.WithCancel(wgCtx)
|
||||
defer cancel()
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/restic/restic/internal/fs"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
@@ -111,6 +112,63 @@ func TestBackupWithRelativePath(t *testing.T) {
|
||||
rtest.Assert(t, latestSn.Parent != nil && latestSn.Parent.Equal(firstSnapshotID), "second snapshot selected unexpected parent %v instead of %v", latestSn.Parent, firstSnapshotID)
|
||||
}
|
||||
|
||||
type vssDeleteOriginalFS struct {
|
||||
fs.FS
|
||||
testdata string
|
||||
hasRemoved bool
|
||||
}
|
||||
|
||||
func (f *vssDeleteOriginalFS) Lstat(name string) (os.FileInfo, error) {
|
||||
if !f.hasRemoved {
|
||||
// call Lstat to trigger snapshot creation
|
||||
_, _ = f.FS.Lstat(name)
|
||||
// nuke testdata
|
||||
var err error
|
||||
for i := 0; i < 3; i++ {
|
||||
// The CI sometimes runs into "The process cannot access the file because it is being used by another process" errors
|
||||
// thus try a few times to remove the data
|
||||
err = os.RemoveAll(f.testdata)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f.hasRemoved = true
|
||||
}
|
||||
return f.FS.Lstat(name)
|
||||
}
|
||||
|
||||
func TestBackupVSS(t *testing.T) {
|
||||
if runtime.GOOS != "windows" || fs.HasSufficientPrivilegesForVSS() != nil {
|
||||
t.Skip("vss fs test can only be run on windows with admin privileges")
|
||||
}
|
||||
|
||||
env, cleanup := withTestEnvironment(t)
|
||||
defer cleanup()
|
||||
|
||||
testSetupBackupData(t, env)
|
||||
opts := BackupOptions{UseFsSnapshot: true}
|
||||
|
||||
var testFS *vssDeleteOriginalFS
|
||||
backupFSTestHook = func(fs fs.FS) fs.FS {
|
||||
testFS = &vssDeleteOriginalFS{
|
||||
FS: fs,
|
||||
testdata: env.testdata,
|
||||
}
|
||||
return testFS
|
||||
}
|
||||
defer func() {
|
||||
backupFSTestHook = nil
|
||||
}()
|
||||
|
||||
testRunBackup(t, filepath.Dir(env.testdata), []string{"testdata"}, opts, env.gopts)
|
||||
testListSnapshots(t, env.gopts, 1)
|
||||
rtest.Equals(t, true, testFS.hasRemoved, "testdata was not removed")
|
||||
}
|
||||
|
||||
func TestBackupParentSelection(t *testing.T) {
|
||||
env, cleanup := withTestEnvironment(t)
|
||||
defer cleanup()
|
||||
|
||||
@@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/repository/index"
|
||||
@@ -10,8 +11,11 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var listAllowedArgs = []string{"blobs", "packs", "index", "snapshots", "keys", "locks"}
|
||||
var listAllowedArgsUseString = strings.Join(listAllowedArgs, "|")
|
||||
|
||||
var cmdList = &cobra.Command{
|
||||
Use: "list [flags] [blobs|packs|index|snapshots|keys|locks]",
|
||||
Use: "list [flags] [" + listAllowedArgsUseString + "]",
|
||||
Short: "List objects in the repository",
|
||||
Long: `
|
||||
The "list" command allows listing objects in the repository based on type.
|
||||
@@ -30,6 +34,8 @@ Exit status is 12 if the password is incorrect.
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runList(cmd.Context(), globalOptions, args)
|
||||
},
|
||||
ValidArgs: listAllowedArgs,
|
||||
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -92,6 +92,10 @@ func runRepairSnapshots(ctx context.Context, gopts GlobalOptions, opts RepairOpt
|
||||
// - files whose contents are not fully available (-> file will be modified)
|
||||
rewriter := walker.NewTreeRewriter(walker.RewriteOpts{
|
||||
RewriteNode: func(node *restic.Node, path string) *restic.Node {
|
||||
if node.Type == "irregular" || node.Type == "" {
|
||||
Verbosef(" file %q: removed node with invalid type %q\n", path, node.Type)
|
||||
return nil
|
||||
}
|
||||
if node.Type != "file" {
|
||||
return node
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ func runTag(ctx context.Context, opts TagOptions, gopts GlobalOptions, args []st
|
||||
Verbosef("create exclusive lock for repository\n")
|
||||
ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, false)
|
||||
if err != nil {
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
defer unlock()
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ import (
|
||||
// to a missing backend storage location or config file
|
||||
var ErrNoRepository = errors.New("repository does not exist")
|
||||
|
||||
var version = "0.17.1"
|
||||
var version = "0.17.2"
|
||||
|
||||
// TimeFormat is the format used for all timestamps printed by restic.
|
||||
const TimeFormat = "2006-01-02 15:04:05"
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
@@ -168,6 +169,16 @@ type testEnvironment struct {
|
||||
gopts GlobalOptions
|
||||
}
|
||||
|
||||
type logOutputter struct {
|
||||
t testing.TB
|
||||
}
|
||||
|
||||
func (l *logOutputter) Write(p []byte) (n int, err error) {
|
||||
l.t.Helper()
|
||||
l.t.Log(strings.TrimSuffix(string(p), "\n"))
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// withTestEnvironment creates a test environment and returns a cleanup
|
||||
// function which removes it.
|
||||
func withTestEnvironment(t testing.TB) (env *testEnvironment, cleanup func()) {
|
||||
@@ -200,8 +211,11 @@ func withTestEnvironment(t testing.TB) (env *testEnvironment, cleanup func()) {
|
||||
Quiet: true,
|
||||
CacheDir: env.cache,
|
||||
password: rtest.TestPassword,
|
||||
stdout: os.Stdout,
|
||||
stderr: os.Stderr,
|
||||
// stdout and stderr are written to by Warnf etc. That is the written data
|
||||
// usually consists of one or multiple lines and therefore can be handled well
|
||||
// by t.Log.
|
||||
stdout: &logOutputter{t},
|
||||
stderr: &logOutputter{t},
|
||||
extended: make(options.Options),
|
||||
|
||||
// replace this hook with "nil" if listing a filetype more than once is necessary
|
||||
|
||||
@@ -455,9 +455,11 @@ Backblaze B2
|
||||
than using the Backblaze B2 backend directly.
|
||||
|
||||
Different from the B2 backend, restic's S3 backend will only hide no longer
|
||||
necessary files. Thus, make sure to setup lifecycle rules to eventually
|
||||
delete hidden files. The lifecycle setting "Keep only the last version of the file"
|
||||
will keep only the most current version of a file. Read the [Backblaze documentation](https://www.backblaze.com/docs/cloud-storage-lifecycle-rules).
|
||||
necessary files. By default, Backblaze B2 retains all of the different versions of the
|
||||
files and "hides" the older versions. Thus, to free space occupied by hidden files,
|
||||
it is **recommended** to use the B2 lifecycle "Keep only the last version of the file".
|
||||
The previous version of the file is "hidden" for one day and then deleted automatically
|
||||
by B2. More details at the [Backblaze documentation](https://www.backblaze.com/docs/cloud-storage-lifecycle-rules).
|
||||
|
||||
Restic can backup data to any Backblaze B2 bucket. You need to first setup the
|
||||
following environment variables with the credentials you can find in the
|
||||
|
||||
@@ -2177,6 +2177,12 @@ _restic_list()
|
||||
|
||||
must_have_one_flag=()
|
||||
must_have_one_noun=()
|
||||
must_have_one_noun+=("blobs")
|
||||
must_have_one_noun+=("index")
|
||||
must_have_one_noun+=("keys")
|
||||
must_have_one_noun+=("locks")
|
||||
must_have_one_noun+=("packs")
|
||||
must_have_one_noun+=("snapshots")
|
||||
noun_aliases=()
|
||||
}
|
||||
|
||||
|
||||
@@ -248,7 +248,8 @@ func (arch *Archiver) trackItem(item string, previous, current *restic.Node, s I
|
||||
|
||||
// nodeFromFileInfo returns the restic node from an os.FileInfo.
|
||||
func (arch *Archiver) nodeFromFileInfo(snPath, filename string, fi os.FileInfo, ignoreXattrListError bool) (*restic.Node, error) {
|
||||
node, err := restic.NodeFromFileInfo(filename, fi, ignoreXattrListError)
|
||||
mappedFilename := arch.FS.MapFilename(filename)
|
||||
node, err := restic.NodeFromFileInfo(mappedFilename, fi, ignoreXattrListError)
|
||||
if !arch.WithAtime {
|
||||
node.AccessTime = node.ModTime
|
||||
}
|
||||
@@ -262,7 +263,8 @@ func (arch *Archiver) nodeFromFileInfo(snPath, filename string, fi os.FileInfo,
|
||||
}
|
||||
// overwrite name to match that within the snapshot
|
||||
node.Name = path.Base(snPath)
|
||||
if err != nil {
|
||||
// do not filter error for nodes of irregular or invalid type
|
||||
if node.Type != "irregular" && node.Type != "" && err != nil {
|
||||
err = fmt.Errorf("incomplete metadata for %v: %w", filename, err)
|
||||
return node, arch.error(filename, err)
|
||||
}
|
||||
|
||||
@@ -2423,4 +2423,47 @@ func TestMetadataBackupErrorFiltering(t *testing.T) {
|
||||
rtest.Assert(t, node != nil, "node is missing")
|
||||
rtest.Assert(t, err == replacementErr, "expected %v got %v", replacementErr, err)
|
||||
rtest.Assert(t, filteredErr != nil, "missing inner error")
|
||||
|
||||
// check that errors from reading irregular file are not filtered
|
||||
filteredErr = nil
|
||||
node, err = arch.nodeFromFileInfo("file", filename, wrapIrregularFileInfo(fi), false)
|
||||
rtest.Assert(t, node != nil, "node is missing")
|
||||
rtest.Assert(t, filteredErr == nil, "error for irregular node should not have been filtered")
|
||||
rtest.Assert(t, strings.Contains(err.Error(), "irregular"), "unexpected error %q does not warn about irregular file mode", err)
|
||||
}
|
||||
|
||||
func TestIrregularFile(t *testing.T) {
|
||||
files := TestDir{
|
||||
"testfile": TestFile{
|
||||
Content: "foo bar test file",
|
||||
},
|
||||
}
|
||||
tempdir, repo := prepareTempdirRepoSrc(t, files)
|
||||
|
||||
back := rtest.Chdir(t, tempdir)
|
||||
defer back()
|
||||
|
||||
tempfile := filepath.Join(tempdir, "testfile")
|
||||
fi := lstat(t, "testfile")
|
||||
|
||||
statfs := &StatFS{
|
||||
FS: fs.Local{},
|
||||
OverrideLstat: map[string]os.FileInfo{
|
||||
tempfile: wrapIrregularFileInfo(fi),
|
||||
},
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
arch := New(repo, fs.Track{FS: statfs}, Options{})
|
||||
_, excluded, err := arch.save(ctx, "/", tempfile, nil)
|
||||
if err == nil {
|
||||
t.Fatalf("Save() should have failed")
|
||||
}
|
||||
rtest.Assert(t, strings.Contains(err.Error(), "irregular"), "unexpected error %q does not warn about irregular file mode", err)
|
||||
|
||||
if excluded {
|
||||
t.Errorf("Save() excluded the node, that's unexpected")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,16 @@ func wrapFileInfo(fi os.FileInfo) os.FileInfo {
|
||||
return res
|
||||
}
|
||||
|
||||
// wrapIrregularFileInfo returns a new os.FileInfo with the mode changed to irregular file
|
||||
func wrapIrregularFileInfo(fi os.FileInfo) os.FileInfo {
|
||||
// wrap the os.FileInfo so we can return a modified stat_t
|
||||
return wrappedFileInfo{
|
||||
FileInfo: fi,
|
||||
sys: fi.Sys().(*syscall.Stat_t),
|
||||
mode: (fi.Mode() &^ os.ModeType) | os.ModeIrregular,
|
||||
}
|
||||
}
|
||||
|
||||
func statAndSnapshot(t *testing.T, repo archiverRepo, name string) (*restic.Node, *restic.Node) {
|
||||
fi := lstat(t, name)
|
||||
want, err := restic.NodeFromFileInfo(name, fi, false)
|
||||
|
||||
@@ -26,3 +26,11 @@ func wrapFileInfo(fi os.FileInfo) os.FileInfo {
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// wrapIrregularFileInfo returns a new os.FileInfo with the mode changed to irregular file
|
||||
func wrapIrregularFileInfo(fi os.FileInfo) os.FileInfo {
|
||||
return wrappedFileInfo{
|
||||
FileInfo: fi,
|
||||
mode: (fi.Mode() &^ os.ModeType) | os.ModeIrregular,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,7 +156,7 @@ func (s *FileSaver) saveFile(ctx context.Context, chnker *chunker.Chunker, snPat
|
||||
|
||||
debug.Log("%v", snPath)
|
||||
|
||||
node, err := s.NodeFromFileInfo(snPath, f.Name(), fi, false)
|
||||
node, err := s.NodeFromFileInfo(snPath, target, fi, false)
|
||||
if err != nil {
|
||||
_ = f.Close()
|
||||
completeError(err)
|
||||
|
||||
@@ -160,6 +160,12 @@ func Create(ctx context.Context, cfg Config, rt http.RoundTripper) (*Backend, er
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "container.Create")
|
||||
}
|
||||
} else if err != nil && bloberror.HasCode(err, bloberror.AuthorizationFailure) {
|
||||
// We ignore this Auth. Failure, as the failure is related to the type
|
||||
// of SAS/SAT, not an actual real failure. If the token is invalid, we
|
||||
// fail later on anyway.
|
||||
// For details see Issue #4004.
|
||||
debug.Log("Ignoring AuthorizationFailure when calling GetProperties")
|
||||
} else if err != nil {
|
||||
return be, errors.Wrap(err, "container.GetProperties")
|
||||
}
|
||||
|
||||
@@ -80,6 +80,91 @@ func BenchmarkBackendAzure(t *testing.B) {
|
||||
newAzureTestSuite().RunBenchmarks(t)
|
||||
}
|
||||
|
||||
// TestBackendAzureAccountToken tests that a Storage Account SAS/SAT token can authorize.
|
||||
// This test ensures that restic can use a token that was generated using the storage
|
||||
// account keys can be used to authorize the azure connection.
|
||||
// Requires the RESTIC_TEST_AZURE_ACCOUNT_NAME, RESTIC_TEST_AZURE_REPOSITORY, and the
|
||||
// RESTIC_TEST_AZURE_ACCOUNT_SAS environment variables to be set, otherwise this test
|
||||
// will be skipped.
|
||||
func TestBackendAzureAccountToken(t *testing.T) {
|
||||
vars := []string{
|
||||
"RESTIC_TEST_AZURE_ACCOUNT_NAME",
|
||||
"RESTIC_TEST_AZURE_REPOSITORY",
|
||||
"RESTIC_TEST_AZURE_ACCOUNT_SAS",
|
||||
}
|
||||
|
||||
for _, v := range vars {
|
||||
if os.Getenv(v) == "" {
|
||||
t.Skipf("set %v to test SAS/SAT Token Authentication", v)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.TODO())
|
||||
defer cancel()
|
||||
|
||||
cfg, err := azure.ParseConfig(os.Getenv("RESTIC_TEST_AZURE_REPOSITORY"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cfg.AccountName = os.Getenv("RESTIC_TEST_AZURE_ACCOUNT_NAME")
|
||||
cfg.AccountSAS = options.NewSecretString(os.Getenv("RESTIC_TEST_AZURE_ACCOUNT_SAS"))
|
||||
|
||||
tr, err := backend.Transport(backend.TransportOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = azure.Create(ctx, *cfg, tr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestBackendAzureContainerToken tests that a container SAS/SAT token can authorize.
|
||||
// This test ensures that restic can use a token that was generated using a user
|
||||
// delegation key against the container we are storing data in can be used to
|
||||
// authorize the azure connection.
|
||||
// Requires the RESTIC_TEST_AZURE_ACCOUNT_NAME, RESTIC_TEST_AZURE_REPOSITORY, and the
|
||||
// RESTIC_TEST_AZURE_CONTAINER_SAS environment variables to be set, otherwise this test
|
||||
// will be skipped.
|
||||
func TestBackendAzureContainerToken(t *testing.T) {
|
||||
vars := []string{
|
||||
"RESTIC_TEST_AZURE_ACCOUNT_NAME",
|
||||
"RESTIC_TEST_AZURE_REPOSITORY",
|
||||
"RESTIC_TEST_AZURE_CONTAINER_SAS",
|
||||
}
|
||||
|
||||
for _, v := range vars {
|
||||
if os.Getenv(v) == "" {
|
||||
t.Skipf("set %v to test SAS/SAT Token Authentication", v)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.TODO())
|
||||
defer cancel()
|
||||
|
||||
cfg, err := azure.ParseConfig(os.Getenv("RESTIC_TEST_AZURE_REPOSITORY"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cfg.AccountName = os.Getenv("RESTIC_TEST_AZURE_ACCOUNT_NAME")
|
||||
cfg.AccountSAS = options.NewSecretString(os.Getenv("RESTIC_TEST_AZURE_CONTAINER_SAS"))
|
||||
|
||||
tr, err := backend.Transport(backend.TransportOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = azure.Create(ctx, *cfg, tr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUploadLargeFile(t *testing.T) {
|
||||
if os.Getenv("RESTIC_AZURE_TEST_LARGE_UPLOAD") == "" {
|
||||
t.Skip("set RESTIC_AZURE_TEST_LARGE_UPLOAD=1 to test large uploads")
|
||||
|
||||
4
internal/backend/cache/file.go
vendored
4
internal/backend/cache/file.go
vendored
@@ -211,6 +211,10 @@ func (c *Cache) list(t restic.FileType) (restic.IDSet, error) {
|
||||
dir := filepath.Join(c.path, cacheLayoutPaths[t])
|
||||
err := filepath.Walk(dir, func(name string, fi os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
// ignore ErrNotExist to gracefully handle multiple processes clearing the cache
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return nil
|
||||
}
|
||||
return errors.Wrap(err, "Walk")
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,12 @@ func (fs Local) VolumeName(path string) string {
|
||||
return filepath.VolumeName(path)
|
||||
}
|
||||
|
||||
// MapFilename is a temporary hack to prepare a filename for usage with
|
||||
// NodeFromFileInfo. This is only relevant for LocalVss.
|
||||
func (fs Local) MapFilename(filename string) string {
|
||||
return filename
|
||||
}
|
||||
|
||||
// Open opens a file for reading.
|
||||
func (fs Local) Open(name string) (File, error) {
|
||||
f, err := os.Open(fixpath(name))
|
||||
|
||||
@@ -145,6 +145,12 @@ func (fs *LocalVss) Lstat(name string) (os.FileInfo, error) {
|
||||
return os.Lstat(fs.snapshotPath(name))
|
||||
}
|
||||
|
||||
// MapFilename is a temporary hack to prepare a filename for usage with
|
||||
// NodeFromFileInfo. This is only relevant for LocalVss.
|
||||
func (fs *LocalVss) MapFilename(filename string) string {
|
||||
return fs.snapshotPath(filename)
|
||||
}
|
||||
|
||||
// isMountPointIncluded is true if given mountpoint included by user.
|
||||
func (fs *LocalVss) isMountPointIncluded(mountPoint string) bool {
|
||||
if fs.excludeVolumes == nil {
|
||||
|
||||
@@ -39,6 +39,12 @@ func (fs *Reader) VolumeName(_ string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// MapFilename is a temporary hack to prepare a filename for usage with
|
||||
// NodeFromFileInfo. This is only relevant for LocalVss.
|
||||
func (fs *Reader) MapFilename(filename string) string {
|
||||
return filename
|
||||
}
|
||||
|
||||
// Open opens a file for reading.
|
||||
func (fs *Reader) Open(name string) (f File, err error) {
|
||||
switch name {
|
||||
@@ -223,7 +229,7 @@ func (r *readerFile) Close() error {
|
||||
var _ File = &readerFile{}
|
||||
|
||||
// fakeFile implements all File methods, but only returns errors for anything
|
||||
// except Stat() and Name().
|
||||
// except Stat()
|
||||
type fakeFile struct {
|
||||
name string
|
||||
os.FileInfo
|
||||
@@ -260,10 +266,6 @@ func (f fakeFile) Stat() (os.FileInfo, error) {
|
||||
return f.FileInfo, nil
|
||||
}
|
||||
|
||||
func (f fakeFile) Name() string {
|
||||
return f.name
|
||||
}
|
||||
|
||||
// fakeDir implements Readdirnames and Readdir, everything else is delegated to fakeFile.
|
||||
type fakeDir struct {
|
||||
entries []os.FileInfo
|
||||
|
||||
@@ -11,6 +11,7 @@ type FS interface {
|
||||
OpenFile(name string, flag int, perm os.FileMode) (File, error)
|
||||
Stat(name string) (os.FileInfo, error)
|
||||
Lstat(name string) (os.FileInfo, error)
|
||||
MapFilename(filename string) string
|
||||
|
||||
Join(elem ...string) string
|
||||
Separator() string
|
||||
@@ -33,5 +34,4 @@ type File interface {
|
||||
Readdir(int) ([]os.FileInfo, error)
|
||||
Seek(int64, int) (int64, error)
|
||||
Stat() (os.FileInfo, error)
|
||||
Name() string
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package fs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
@@ -12,5 +13,17 @@ func PreallocateFile(wr *os.File, size int64) error {
|
||||
}
|
||||
// int fallocate(int fd, int mode, off_t offset, off_t len)
|
||||
// use mode = 0 to also change the file size
|
||||
return unix.Fallocate(int(wr.Fd()), 0, 0, size)
|
||||
return ignoringEINTR(func() error { return unix.Fallocate(int(wr.Fd()), 0, 0, size) })
|
||||
}
|
||||
|
||||
// ignoringEINTR makes a function call and repeats it if it returns
|
||||
// an EINTR error.
|
||||
// copied from /usr/lib/go/src/internal/poll/fd_posix.go of go 1.23.1
|
||||
func ignoringEINTR(fn func() error) error {
|
||||
for {
|
||||
err := fn()
|
||||
if err != syscall.EINTR {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,16 @@
|
||||
|
||||
package restic
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
import (
|
||||
"os"
|
||||
|
||||
func mknod(path string, mode uint32, dev uint64) (err error) {
|
||||
return unix.Mknod(path, mode, int(dev))
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func mknod(path string, mode uint32, dev uint64) error {
|
||||
err := unix.Mknod(path, mode, int(dev))
|
||||
if err != nil {
|
||||
err = &os.PathError{Op: "mknod", Path: path, Err: err}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -3,14 +3,21 @@
|
||||
|
||||
package restic
|
||||
|
||||
import "syscall"
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func (node Node) restoreSymlinkTimestamps(path string, utimes [2]syscall.Timespec) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func mknod(path string, mode uint32, dev uint64) (err error) {
|
||||
return syscall.Mknod(path, mode, dev)
|
||||
func mknod(path string, mode uint32, dev uint64) error {
|
||||
err := syscall.Mknod(path, mode, dev)
|
||||
if err != nil {
|
||||
err = &os.PathError{Op: "mknod", Path: path, Err: err}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s statT) atim() syscall.Timespec { return s.Atimespec }
|
||||
|
||||
@@ -7,10 +7,12 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/restic/restic/internal/errors"
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
)
|
||||
|
||||
@@ -145,3 +147,12 @@ func TestNodeFromFileInfo(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMknodError(t *testing.T) {
|
||||
d := t.TempDir()
|
||||
// Call mkfifo, which calls mknod, as mknod may give
|
||||
// "operation not permitted" on Mac.
|
||||
err := mkfifo(d, 0)
|
||||
rtest.Assert(t, errors.Is(err, os.ErrExist), "want ErrExist, got %q", err)
|
||||
rtest.Assert(t, strings.Contains(err.Error(), d), "filename not in %q", err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user