Compare commits

...

32 Commits

Author SHA1 Message Date
Alexander Neumann
2fb07dcdb1 Add version for 0.17.2 2024-10-27 16:37:19 +01:00
Alexander Neumann
5dcee7f0a3 Update manpages and auto-completion 2024-10-27 16:37:19 +01:00
Alexander Neumann
44968c7d43 Generate CHANGELOG.md for 0.17.2 2024-10-27 16:37:08 +01:00
Alexander Neumann
dbb5fb9fbd Prepare changelog for 0.17.2 2024-10-27 16:37:08 +01:00
Michael Eischer
3a4a5a8215 Merge pull request #5102 from MichaelEischer/polish-changelogs
Polish patch release changelogs
2024-10-23 18:52:40 +02:00
Michael Eischer
d8d955e0aa Tweak wording
Co-authored-by: rawtaz <rawtaz@users.noreply.github.com>
2024-10-22 20:00:39 +02:00
Michael Eischer
2ce485063f polish changelogs 2024-10-22 19:48:59 +02:00
Michael Eischer
f72febb34f Merge pull request #5099 from MichaelEischer/hackport-fix-vss-metadata
Hackport "backup: read extended metadata from snapshot"
2024-10-22 19:24:08 +02:00
Michael Eischer
ee9a5cdf70 add vss metadata changelog 2024-10-18 22:51:55 +02:00
Michael Eischer
46dce1f4fa backup: work around file deletion error in test 2024-10-18 22:51:55 +02:00
Michael Eischer
841f8bfef0 redirect test log output to t.Log() 2024-10-18 22:51:55 +02:00
Michael Eischer
1f5791222a backup: test that vss backups work if underlying data was removed 2024-10-18 22:51:55 +02:00
Michael Eischer
a7b13bd603 fs: remove file.Name() from interface
The only user was archiver.fileSaver.
2024-10-18 22:29:03 +02:00
Michael Eischer
0c711f5605 archiver: use correct filepath in fileSaver for vss
When using the VSS FS, then `f.Name()` contained the filename in the
snapshot. This caused a double mapping when calling NodeFromFileInfo.
2024-10-18 22:29:03 +02:00
Michael Eischer
4df2e33568 archiver: properly create node for vss backups
Previously, NodeFromFileInfo used the original file path to create the
node, which also meant that extended metadata was read from there
instead of within the vss snapshot.

This change is a temporary solution for restic 0.17.2 and will be
replaced with a clean fix in restic 0.18.0.
2024-10-18 22:26:18 +02:00
Michael Eischer
11c1fbce20 Merge pull request #5098 from MichaelEischer/prepare-patch-release
Prepare patch release
2024-10-18 22:20:27 +02:00
Connor Findlay
9553d873ff backend/azure: Add tests for both token types
Add two new test cases, TestBackendAzureAccountToken and
TestBackendAzureContainerToken, that ensure that the authorization using
both types of token works.

This introduces two new environment variables,
RESTIC_TEST_AZURE_ACCOUNT_SAS and RESTIC_TEST_AZURE_CONTAINER_SAS, that
contain the tokens to use when testing restic. If an environment
variable is missing, the related test is skipped.
2024-10-18 21:59:03 +02:00
Connor Findlay
048c3bb240 changelog: Add changes in issue-4004
Add changelog entry in the 'unreleased' sub-folder for changes
introduced when fixing issue #4004.
2024-10-18 21:59:03 +02:00
Connor Findlay
d6e76a22a8 backend/azure: Handle Container SAS/SAT
Ignore AuthorizationFailure caused by using a container level SAS/SAT
token when calling GetProperties during the Create() call. This is because the
GetProperties call expects an Account Level token, and the container
level token simply lacks the appropriate permissions. Supressing the
Authorization Failure is OK, because if the token is actually invalid,
this is caught elsewhere when we try to actually use the token to do
work.
2024-10-18 21:59:03 +02:00
Michael Eischer
e3a022f9b5 add irregular files bug changelog 2024-10-18 21:58:04 +02:00
Michael Eischer
fe269c752a repair snapshots: remove irregular files 2024-10-18 21:57:52 +02:00
Michael Eischer
fc1fc00aa4 backup: exclude irregular files from backup
restic cannot backup irregular files as those don't behave like normal
files. Thus skip them with an error.
2024-10-18 21:56:41 +02:00
greatroar
3c82fe6ef5 fs: Include filename in mknod errors 2024-10-18 21:53:15 +02:00
Michael Eischer
986d981bf6 tag: fix swallowed error if repository cannot be opened 2024-10-18 21:50:29 +02:00
Michael Eischer
0df2fa8135 fs: retry preallocate on Linux if interrupted by signal 2024-10-18 21:47:59 +02:00
Roman Inflianskas
49ccb7734c list: validate subcommand 2024-10-18 21:47:59 +02:00
Roman Inflianskas
491cc65e3a list: add subcommand completion 2024-10-18 21:47:59 +02:00
Damien Clark
8c1d6a50c1 cache: fix race condition in cache cleanup
Fix multiple restic processes executing concurrently and racing to remove obsolete snapshots.

Co-authored-by: Michael Eischer <michael.eischer@fau.de>
2024-10-18 21:47:59 +02:00
Michael Eischer
9386acc4a6 Fix indentation of blockquotes in github release notes 2024-10-18 21:47:59 +02:00
Git'Fellow
5b60d49654 fix: shorten sentence 2024-10-18 21:47:59 +02:00
Git'Fellow
8056181301 docs: Recommend to setup B2 versions lifecycle rules 2024-10-18 21:46:58 +02:00
Alexander Neumann
76a647febf Set development version for 0.17.1 2024-09-05 21:25:24 +02:00
33 changed files with 479 additions and 26 deletions

View File

@@ -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.

View File

@@ -1 +1 @@
0.17.1
0.17.2

View 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

View 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

View 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

View 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

View 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

View File

@@ -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 -}})

View File

@@ -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()

View File

@@ -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()

View File

@@ -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() {

View File

@@ -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
}

View File

@@ -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()

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -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=()
}

View File

@@ -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)
}

View File

@@ -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")
}
}

View File

@@ -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)

View File

@@ -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,
}
}

View File

@@ -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)

View File

@@ -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")
}

View File

@@ -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")

View File

@@ -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")
}

View File

@@ -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))

View File

@@ -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 {

View File

@@ -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

View File

@@ -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
}

View File

@@ -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
}
}
}

View File

@@ -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
}

View File

@@ -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 }

View File

@@ -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)
}