From da4a84c4fbe59fb57754ec73fd3ce2d5c928890e Mon Sep 17 00:00:00 2001 From: aneesh-n <99904+aneesh-n@users.noreply.github.com> Date: Tue, 3 Dec 2024 17:59:01 +0530 Subject: [PATCH] Add support for ads in archiver --- internal/archiver/archiver_unix.go | 64 +++++++++++++ internal/archiver/archiver_windows.go | 128 ++++++++++++++++++++++++++ 2 files changed, 192 insertions(+) create mode 100644 internal/archiver/archiver_unix.go create mode 100644 internal/archiver/archiver_windows.go diff --git a/internal/archiver/archiver_unix.go b/internal/archiver/archiver_unix.go new file mode 100644 index 000000000..471d99dc7 --- /dev/null +++ b/internal/archiver/archiver_unix.go @@ -0,0 +1,64 @@ +//go:build !windows +// +build !windows + +package archiver + +import ( + "os" + + "github.com/restic/restic/internal/fs" + "github.com/restic/restic/internal/restic" +) + +// preProcessTargets performs preprocessing of the targets before the loop. +// It is a no-op on non-windows OS as we do not need to do an +// extra iteration on the targets before the loop. +// We process each target inside the loop. +func preProcessTargets(_ fs.FS, _ *[]string) { + // no-op +} + +// processTarget processes each target in the loop. +// In case of non-windows OS it uses the passed filesys to clean the target. +func processTarget(filesys fs.FS, target string) string { + return filesys.Clean(target) +} + +// preProcessPaths processes paths before looping. +func (arch *Archiver) preProcessPaths(_ string, names []string) (paths []string) { + // In case of non-windows OS this is no-op as we process the paths within the loop + // and avoid the extra looping before hand. + return names +} + +// processPath processes the path in the loop. +func (arch *Archiver) processPath(dir string, name string) (path string) { + //In case of non-windows OS we prepare the path in the loop. + return arch.FS.Join(dir, name) +} + +// getNameFromPathname gets the name from pathname. +// In case for non-windows the pathname is same as the name. +func getNameFromPathname(pathname string) (name string) { + return pathname +} + +// processTargets is no-op for non-windows OS +func (arch *Archiver) processTargets(_ string, _ string, _ string, fiMain os.FileInfo) (fi os.FileInfo, shouldReturn bool, fn futureNode, excluded bool, err error) { + return fiMain, false, futureNode{}, false, nil +} + +// incrementNewFiles increments the new files count +func (c *ChangeStats) incrementNewFiles(_ *restic.Node) { + c.New++ +} + +// incrementNewFiles increments the unchanged files count +func (c *ChangeStats) incrementUnchangedFiles(_ *restic.Node) { + c.Unchanged++ +} + +// incrementNewFiles increments the changed files count +func (c *ChangeStats) incrementChangedFiles(_ *restic.Node) { + c.Changed++ +} diff --git a/internal/archiver/archiver_windows.go b/internal/archiver/archiver_windows.go new file mode 100644 index 000000000..552f1a8b0 --- /dev/null +++ b/internal/archiver/archiver_windows.go @@ -0,0 +1,128 @@ +package archiver + +import ( + "path/filepath" + + "github.com/restic/restic/internal/debug" + "github.com/restic/restic/internal/errors" + "github.com/restic/restic/internal/fs" + "github.com/restic/restic/internal/restic" +) + +// preProcessTargets performs preprocessing of the targets before the loop. +// For Windows, it cleans each target and it also adds ads stream for each +// target to the targets array. +// We read the ADS from each file and add them as independent Nodes with +// the full ADS name as the name of the file. +// During restore the ADS files are restored using the ADS name and that +// automatically attaches them as ADS to the main file. +func preProcessTargets(filesys fs.FS, targets *[]string) { + for _, target := range *targets { + if target != "" && filesys.VolumeName(target) == target { + // special case to allow users to also specify a volume name "C:" instead of a path "C:\" + target = target + filesys.Separator() + } else { + target = filesys.Clean(target) + } + addADSStreams(target, targets) + } +} + +// processTarget processes each target in the loop. +// In case of windows the clean up of target is already done +// in preProcessTargets before the loop, hence this is no-op. +func processTarget(_ fs.FS, target string) string { + return target +} + +// getNameFromPathname gets the name from pathname. +// In case for windows the pathname is the full path, so it need to get the base name. +func getNameFromPathname(pathname string) (name string) { + return filepath.Base(pathname) +} + +// preProcessPaths processes paths before looping. +func (arch *Archiver) preProcessPaths(dir string, names []string) (paths []string) { + // In case of windows we want to add the ADS paths as well before sorting. + return arch.getPathsIncludingADS(dir, names) +} + +// processPath processes the path in the loop. +func (arch *Archiver) processPath(_ string, name string) (path string) { + // In case of windows we have already prepared the paths before the loop. + // Hence this is a no-op. + return name +} + +// getPathsIncludingADS iterates all passed path names and adds the ads +// contained in those paths before returning all full paths including ads +func (arch *Archiver) getPathsIncludingADS(dir string, names []string) []string { + paths := make([]string, 0, len(names)) + + for _, name := range names { + pathname := arch.FS.Join(dir, name) + paths = append(paths, pathname) + addADSStreams(pathname, &paths) + } + return paths +} + +// addADSStreams gets the ads streams if any in the pathname passed and adds them to the passed paths +func addADSStreams(pathname string, paths *[]string) { + success, adsStreams, err := restic.GetADStreamNames(pathname) + if success { + streamCount := len(adsStreams) + if streamCount > 0 { + debug.Log("ADS Streams for file: %s, streams: %v", pathname, adsStreams) + for i := 0; i < streamCount; i++ { + adsStream := adsStreams[i] + adsPath := pathname + adsStream + *paths = append(*paths, adsPath) + } + } + } else if err != nil { + debug.Log("No ADS found for path: %s, err: %v", pathname, err) + } +} + +// processTargets in windows performs Lstat for the ADS files since the file info would not be available for them yet. +func (arch *Archiver) processTargets(target string, targetMain string, abstarget string, fiMain fs.ExtendedFileInfo) (fi *fs.ExtendedFileInfo, shouldReturn bool, fn futureNode, excluded bool, err error) { + if target != targetMain { + //If this is an ADS file we need to Lstat again for the file info. + fi, err = arch.FS.Lstat(target) + if err != nil { + debug.Log("lstat() for %v returned error: %v", target, err) + err = arch.error(abstarget, err) + if err != nil { + return nil, true, futureNode{}, false, errors.WithStack(err) + } + //If this is an ads file, shouldReturn should be true because we want to + // skip the remaining processing of the file. + return nil, true, futureNode{}, true, nil + } + } else { + fi = &fiMain + } + return fi, false, futureNode{}, false, nil +} + +// incrementNewFiles increments the new files count +func (c *ChangeStats) incrementNewFiles(node *restic.Node) { + if node.IsMainFile() { + c.New++ + } +} + +// incrementNewFiles increments the unchanged files count +func (c *ChangeStats) incrementUnchangedFiles(node *restic.Node) { + if node.IsMainFile() { + c.Unchanged++ + } +} + +// incrementNewFiles increments the changed files count +func (c *ChangeStats) incrementChangedFiles(node *restic.Node) { + if node.IsMainFile() { + c.Changed++ + } +}