-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
Back up and restore Windows Alternate Data Streams #4614
Draft
aneesh-n
wants to merge
16
commits into
restic:master
Choose a base branch
from
zmanda:windows_ads
base: master
Could not load branches
Branch not found: {{ refName }}
Could not load tags
Nothing to show
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
8e13452
Add helper and test for ADS support
aneesh-n dec0c0f
Add support for backup/restore of ADS
aneesh-n 3e5ddc5
Ensure ADS is not replaced by main file
aneesh-n 23dc45c
Ensure ADS are not counted as files
aneesh-n 33de38c
Use main file name for filtering
aneesh-n 73bd0b7
Add test cases for ADS
aneesh-n 328acd5
Fix test cases and lint
aneesh-n b611013
Fix compilation error for unix
aneesh-n b0278c8
Add file in unreleased changelog
aneesh-n fb2a1e7
Skip ADS in backup summary
aneesh-n 52ef81c
Merge branch 'master' into windows_ads
aneesh-n cf331ae
Clean up target processing in archiver
aneesh-n b8d1f49
Fix build error by passing filesys
aneesh-n 3f7c6cb
Fix lint issue
aneesh-n 3cc32c0
Refactor SaveDir in archiver
aneesh-n 1743de5
Add tests for ADS scenarios
aneesh-n File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
Enhancement: Back up and restore Windows Alternate Data Streams | ||
|
||
Restic did not back up Alternate Data Streams in Windows. Restic now backs up Alternate Data Streams (ADS) and restores them back to the main files. | ||
The Alternate Data Streams are backed up like any other normal files, and the full name of the stream is stored as the name of the file. | ||
During restore, the ADS are restored and attached to the original files as Alternate Data Streams. | ||
For progress and summary, the ADS are not counted in the file counts, but the sizes of the ADS files are counted. | ||
|
||
https://github.com/restic/restic/pull/4614 | ||
https://github.com/restic/restic/issues/1401 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
//go:build !windows | ||
// +build !windows | ||
|
||
package archiver | ||
|
||
import ( | ||
"os" | ||
|
||
"github.com/restic/restic/internal/fs" | ||
) | ||
|
||
// 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 | ||
} |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
package archiver | ||
|
||
import ( | ||
"os" | ||
"path/filepath" | ||
|
||
"github.com/restic/restic/internal/debug" | ||
"github.com/restic/restic/internal/errors" | ||
"github.com/restic/restic/internal/fs" | ||
) | ||
|
||
// 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 { | ||
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 := fs.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 os.FileInfo) (fi os.FileInfo, 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 | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
couldn't we discover the ads stream just in time when a file/dir is backed up and thereby get rid of all this preprocessing? Shouldn't be too complex to add that to
SaveDir
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the beginning, resolveRelativeTargets is called from scanner's Scan method and from archiver Snapshot method and the sorted cleanTargets obtained in that step are then passed to NewTree.
We need these ADS entries to be available before the Node creation in the tree.
We do add this in SaveDir as well, but it is also needed in resolveRelativeTargets.