Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to follow symbolic links #3863

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions cmd/restic/cmd_backup.go
Expand Up @@ -96,6 +96,7 @@ type BackupOptions struct {
FilesFromRaw []string
TimeStamp string
WithAtime bool
FollowSymLinks bool
IgnoreInode bool
IgnoreCtime bool
UseFsSnapshot bool
Expand Down Expand Up @@ -138,6 +139,7 @@ func init() {
f.StringArrayVar(&backupOptions.FilesFromRaw, "files-from-raw", nil, "read the files to backup from `file` (can be combined with file args; can be specified multiple times)")
f.StringVar(&backupOptions.TimeStamp, "time", "", "`time` of the backup (ex. '2012-11-01 22:08:41') (default: now)")
f.BoolVar(&backupOptions.WithAtime, "with-atime", false, "store the atime for all files and directories")
f.BoolVar(&backupOptions.FollowSymLinks, "follow-symlinks", false, "treat symbolic links as transparent and dereference their destination")
f.BoolVar(&backupOptions.IgnoreInode, "ignore-inode", false, "ignore inode number changes when checking for modified files")
f.BoolVar(&backupOptions.IgnoreCtime, "ignore-ctime", false, "ignore ctime changes when checking for modified files")
f.BoolVarP(&backupOptions.DryRun, "dry-run", "n", false, "do not upload or write any data, just show what would be done")
Expand Down Expand Up @@ -679,6 +681,7 @@ func runBackup(opts BackupOptions, gopts GlobalOptions, term *termstatus.Termina
sc.Select = selectFilter
sc.Error = progressReporter.ScannerError
sc.Result = progressReporter.ReportTotal
sc.FollowSymLinks = opts.FollowSymLinks

if !gopts.JSON {
progressPrinter.V("start scan on %v", targets)
Expand All @@ -689,6 +692,7 @@ func runBackup(opts BackupOptions, gopts GlobalOptions, term *termstatus.Termina
arch.SelectByName = selectByNameFilter
arch.Select = selectFilter
arch.WithAtime = opts.WithAtime
arch.FollowSymLinks = opts.FollowSymLinks
success := true
arch.Error = func(item string, err error) error {
success = false
Expand Down
28 changes: 19 additions & 9 deletions internal/archiver/archiver.go
Expand Up @@ -83,6 +83,10 @@ type Archiver struct {
// default.
WithAtime bool

// When symbolic links are encountered, dereference them and access the
// content they point to instead.
FollowSymLinks bool

// Flags controlling change detection. See doc/040_backup.rst for details.
ChangeIgnoreFlags uint
}
Expand Down Expand Up @@ -141,15 +145,16 @@ func (o Options) ApplyDefaults() Options {
// New initializes a new archiver.
func New(repo restic.Repository, fs fs.FS, opts Options) *Archiver {
arch := &Archiver{
Repo: repo,
SelectByName: func(item string) bool { return true },
Select: func(item string, fi os.FileInfo) bool { return true },
FS: fs,
Options: opts.ApplyDefaults(),
Repo: repo,
SelectByName: func(item string) bool { return true },
Select: func(item string, fi os.FileInfo) bool { return true },
FS: fs,
Options: opts.ApplyDefaults(),

CompleteItem: func(string, *restic.Node, *restic.Node, ItemStats, time.Duration) {},
StartFile: func(string) {},
CompleteBlob: func(string, uint64) {},
CompleteItem: func(string, *restic.Node, *restic.Node, ItemStats, time.Duration) {},
StartFile: func(string) {},
CompleteBlob: func(string, uint64) {},
FollowSymLinks: false,
}

return arch
Expand Down Expand Up @@ -367,7 +372,12 @@ func (arch *Archiver) Save(ctx context.Context, snPath, target string, previous
}

// get file info and run remaining select functions that require file information
fi, err := arch.FS.Lstat(target)
var fi os.FileInfo
if arch.FollowSymLinks {
fi, err = arch.FS.Stat(target)
} else {
fi, err = arch.FS.Lstat(target)
}
if err != nil {
debug.Log("lstat() for %v returned error: %v", target, err)
err = arch.error(abstarget, err)
Expand Down
30 changes: 19 additions & 11 deletions internal/archiver/scanner.go
Expand Up @@ -14,21 +14,23 @@ import (
// stats concerning the files and folders found. Select is used to decide which
// items should be included. Error is called when an error occurs.
type Scanner struct {
FS fs.FS
SelectByName SelectByNameFunc
Select SelectFunc
Error ErrorFunc
Result func(item string, s ScanStats)
FS fs.FS
SelectByName SelectByNameFunc
Select SelectFunc
Error ErrorFunc
Result func(item string, s ScanStats)
FollowSymLinks bool
}

// NewScanner initializes a new Scanner.
func NewScanner(fs fs.FS) *Scanner {
return &Scanner{
FS: fs,
SelectByName: func(item string) bool { return true },
Select: func(item string, fi os.FileInfo) bool { return true },
Error: func(item string, err error) error { return err },
Result: func(item string, s ScanStats) {},
FS: fs,
SelectByName: func(item string) bool { return true },
Select: func(item string, fi os.FileInfo) bool { return true },
Error: func(item string, err error) error { return err },
Result: func(item string, s ScanStats) {},
FollowSymLinks: false,
}
}

Expand Down Expand Up @@ -109,7 +111,13 @@ func (s *Scanner) scan(ctx context.Context, stats ScanStats, target string) (Sca
}

// get file information
fi, err := s.FS.Lstat(target)
var fi os.FileInfo
var err error
if s.FollowSymLinks {
fi, err = s.FS.Stat(target)
} else {
fi, err = s.FS.Lstat(target)
}
if err != nil {
return stats, s.Error(target, err)
}
Expand Down