diff --git a/changelog/unreleased/issue-2063 b/changelog/unreleased/issue-2063 new file mode 100644 index 000000000..3840da5be --- /dev/null +++ b/changelog/unreleased/issue-2063 @@ -0,0 +1,6 @@ +Bugfix: Allow absolute path for filename when backing up from stdin + +When backing up from stdin, handle directory path for `--stdin-filename`. +This can be used to specify the full path for the backed-up file. + +https://github.com/restic/restic/issues/2063 diff --git a/cmd/restic/cmd_backup.go b/cmd/restic/cmd_backup.go index a37d3f168..83a4038ad 100644 --- a/cmd/restic/cmd_backup.go +++ b/cmd/restic/cmd_backup.go @@ -8,6 +8,7 @@ import ( "io" "io/ioutil" "os" + "path" "path/filepath" "strconv" "strings" @@ -523,13 +524,14 @@ func runBackup(opts BackupOptions, gopts GlobalOptions, term *termstatus.Termina if !gopts.JSON { p.V("read data from stdin") } + filename := path.Join("/", opts.StdinFilename) targetFS = &fs.Reader{ ModTime: timeStamp, - Name: opts.StdinFilename, + Name: filename, Mode: 0644, ReadCloser: os.Stdin, } - targets = []string{opts.StdinFilename} + targets = []string{filename} } sc := archiver.NewScanner(targetFS) diff --git a/internal/fs/fs_reader.go b/internal/fs/fs_reader.go index 27fc46eb6..91fedb263 100644 --- a/internal/fs/fs_reader.go +++ b/internal/fs/fs_reader.go @@ -103,17 +103,32 @@ func (fs *Reader) Stat(name string) (os.FileInfo, error) { // describes the symbolic link. Lstat makes no attempt to follow the link. // If there is an error, it will be of type *PathError. func (fs *Reader) Lstat(name string) (os.FileInfo, error) { + getDirInfo := func(name string) os.FileInfo { + fi := fakeFileInfo{ + name: fs.Base(name), + size: 0, + mode: os.ModeDir | 0755, + modtime: time.Now(), + } + return fi + } + switch name { case fs.Name: return fs.fi(), nil case "/", ".": - fi := fakeFileInfo{ - name: name, - size: 0, - mode: 0755, - modtime: time.Now(), + return getDirInfo(name), nil + } + + dir := fs.Dir(fs.Name) + for { + if dir == "/" || dir == "." { + break } - return fi, nil + if name == dir { + return getDirInfo(name), nil + } + dir = fs.Dir(dir) } return nil, os.ErrNotExist diff --git a/internal/fs/fs_reader_test.go b/internal/fs/fs_reader_test.go index 93e8b35ef..bb45732f3 100644 --- a/internal/fs/fs_reader_test.go +++ b/internal/fs/fs_reader_test.go @@ -4,6 +4,7 @@ import ( "bytes" "io/ioutil" "os" + "path" "sort" "strings" "testing" @@ -151,8 +152,8 @@ func verifyDirectoryContentsFI(t testing.TB, fs FS, dir string, want []os.FileIn } func checkFileInfo(t testing.TB, fi os.FileInfo, filename string, modtime time.Time, mode os.FileMode, isdir bool) { - if fi.IsDir() { - t.Errorf("IsDir returned true, want false") + if fi.IsDir() != isdir { + t.Errorf("IsDir returned %t, want %t", fi.IsDir(), isdir) } if fi.Mode() != mode { @@ -163,8 +164,12 @@ func checkFileInfo(t testing.TB, fi os.FileInfo, filename string, modtime time.T t.Errorf("ModTime() returned wrong value, want %v, got %v", modtime, fi.ModTime()) } - if fi.Name() != filename { - t.Errorf("Name() returned wrong value, want %q, got %q", filename, fi.Name()) + if path.Base(fi.Name()) != fi.Name() { + t.Errorf("Name() returned is not base, want %q, got %q", path.Base(fi.Name()), fi.Name()) + } + + if fi.Name() != path.Base(filename) { + t.Errorf("Name() returned wrong value, want %q, got %q", path.Base(filename), fi.Name()) } } @@ -265,7 +270,7 @@ func TestFSReader(t *testing.T) { t.Fatal(err) } - checkFileInfo(t, fi, "/", time.Time{}, 0755, false) + checkFileInfo(t, fi, "/", time.Time{}, os.ModeDir|0755, true) }, }, { @@ -276,7 +281,16 @@ func TestFSReader(t *testing.T) { t.Fatal(err) } - checkFileInfo(t, fi, ".", time.Time{}, 0755, false) + checkFileInfo(t, fi, ".", time.Time{}, os.ModeDir|0755, true) + }, + }, + { + name: "dir/Lstat-error-not-exist", + f: func(t *testing.T, fs FS) { + _, err := fs.Lstat("other") + if err != os.ErrNotExist { + t.Fatal(err) + } }, }, { @@ -287,7 +301,7 @@ func TestFSReader(t *testing.T) { t.Fatal(err) } - checkFileInfo(t, fi, "/", time.Time{}, 0755, false) + checkFileInfo(t, fi, "/", time.Time{}, os.ModeDir|0755, true) }, }, { @@ -298,7 +312,7 @@ func TestFSReader(t *testing.T) { t.Fatal(err) } - checkFileInfo(t, fi, ".", time.Time{}, 0755, false) + checkFileInfo(t, fi, ".", time.Time{}, os.ModeDir|0755, true) }, }, } @@ -319,6 +333,54 @@ func TestFSReader(t *testing.T) { } } +func TestFSReaderDir(t *testing.T) { + data := test.Random(55, 1<<18+588) + now := time.Now() + + var tests = []struct { + name string + filename string + }{ + { + name: "Lstat-absolute", + filename: "/path/to/foobar", + }, + { + name: "Lstat-relative", + filename: "path/to/foobar", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + fs := &Reader{ + Name: test.filename, + ReadCloser: ioutil.NopCloser(bytes.NewReader(data)), + + Mode: 0644, + Size: int64(len(data)), + ModTime: now, + } + + dir := path.Dir(fs.Name) + for { + if dir == "/" || dir == "." { + break + } + + fi, err := fs.Lstat(dir) + if err != nil { + t.Fatal(err) + } + + checkFileInfo(t, fi, dir, time.Time{}, os.ModeDir|0755, true) + + dir = path.Dir(dir) + } + }) + } +} + func TestFSReaderMinFileSize(t *testing.T) { var tests = []struct { name string