-
Notifications
You must be signed in to change notification settings - Fork 338
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
Snapshotting root of drive fails when Volume Shadow Copy enabled #3842
Comments
Same case on windows server 2019, doesn't seem to happen on Windows 10 on my other pc. |
After running with debugging, I may know the issue.
I believe I have ran into an issue like this before, it is caused by the shadow volume path not ending in
Notice the trailing |
I tried to recompile with just a quick fix in kopia/fs/localfs/local_fs_os.go Lines 114 to 116 in 4cf9582
|
I've traced the source of the error to the kopia/fs/localfs/local_fs_os.go Lines 66 to 69 in 2b92388
The first time this is called on a VSS snapshot, the kopia/fs/localfs/local_fs_os.go Lines 25 to 33 in 2b92388
The underlying error is returned by I managed to write a patch that fixes this issue for me, but I'm not entirely convinced this is the right part of the code to fix it in. I'd love if someone more familiar with this part of the code could look into this more... diff --git a/fs/localfs/local_fs_os.go b/fs/localfs/local_fs_os.go
index e790b5b3..2a3ffbb2 100644
--- a/fs/localfs/local_fs_os.go
+++ b/fs/localfs/local_fs_os.go
@@ -66,7 +66,21 @@ func (it *filesystemDirectoryIterator) Close() {
func (fsd *filesystemDirectory) Iterate(ctx context.Context) (fs.DirectoryIterator, error) {
fullPath := fsd.fullPath()
- f, direrr := os.Open(fullPath) //nolint:gosec
+ // Trying to list the shadow volume root directory will fail unless it contains a trailing \
+ // We detect that here and fix it
+ var openPath = fullPath
+ if runtime.GOOS == "windows" {
+ const shadowVolumePrefix = `\\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy`
+ if strings.HasPrefix(fullPath, shadowVolumePrefix) {
+ remainder := fullPath[len(shadowVolumePrefix):]
+ // Check if we're at the root of the shadow volume, any subdirectories should contain a path separator
+ if !strings.ContainsRune(remainder, os.PathSeparator) {
+ openPath = fullPath + string(os.PathSeparator)
+ }
+ }
+ }
+
+ f, direrr := os.Open(openPath) //nolint:gosec
if direrr != nil {
return nil, errors.Wrap(direrr, "unable to read directory")
} |
I also encountered this bug when attempting to create a backup of an entire volume using a Volume Shadow Copy, and then I found your bug report. Thank you for the detailed analysis. At first, I wondered why you didn't choose a similar construct as in the kopia/fs/localfs/local_fs_os.go Lines 113 to 123 in 89c8eb4
However, when I tried to apply the same solution, I realized why: As you correctly analyzed, the problem occurs when calling Nonetheless, I slightly modified your proposed solution, as I don't see the necessity for introducing the remainder variable. --- a/fs/localfs/local_fs_os.go
+++ b/fs/localfs/local_fs_os.go
@@ -66,12 +66,24 @@ func (it *filesystemDirectoryIterator) Close() {
func (fsd *filesystemDirectory) Iterate(ctx context.Context) (fs.DirectoryIterator, error) {
fullPath := fsd.fullPath()
+ if runtime.GOOS == "windows" {
+ const shadowVolumePrefix = `\\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy`
+
+ if strings.HasPrefix(fullPath, shadowVolumePrefix) &&
+ !strings.HasSuffix(fullPath, string(filepath.Separator)) {
+ fullPath = fullPath + string(filepath.Separator)
+ }
+ }
+
f, direrr := os.Open(fullPath) //nolint:gosec
if direrr != nil {
return nil, errors.Wrap(direrr, "unable to read directory")
}
- childPrefix := fullPath + string(filepath.Separator)
+ childPrefix := fullPath
+ if !strings.HasSuffix(childPrefix, string(filepath.Separator)) {
+ childPrefix += string(filepath.Separator)
+ }
return &filesystemDirectoryIterator{dirHandle: f, childPrefix: childPrefix}, nil
}
However, in principle, I wonder if this is really the ideal place: Wouldn't it be better to patch Lines 51 to 53 in 89c8eb4
and ensure that the return value ends with a trailing backslash if |
Thanks for the additional eyes,
The remainder part ensures we only apply the trailing path separator to the top level shadow volume root directory, no directory below it requires it. The remainder code is checking if we're at the root or in a subdirectory. I separated openPath and fullPath because only the Open call needs the trailing slash, all the rest of Kopia's code doesn't expect a trailing slash there. I don't know if it would actually break anything, but I didn't see a reason to change the behavior. This is the same reason I didn't modify the fullPath function itself. |
After checking your patch again, I think your childPrefix check actually handles the second part of my comment. For some reason I was under the impression that fullPath got used or returned somewhere by itself, but since it's just being used for childPrefix, your check should be sufficient. The first part of my comment is still valid I believe. |
In case it helps anyone, this seems to work fine in 0.16.1 (and also at least 0.13.0). It might be helpful to track down what changed in 0.17 that causes it to break as well. |
I just tested with 0.16.1 and got the same error, and the |
Sorry, I got confused looking at which versions I had previously installed. 0.13.0 is apparently the version I briefly installed last year and decided to wait until VSS got built in support before giving Kopia a real try. I have 3 policies1 setup to snapshot Running with 0.17.0 it does look the same as this bug:
The same command with 0.16.1 finishes without error. From
or
I never did configure the external script method and instead waited for the built in support from #3543. Footnotes
|
Oh, that's very interesting! I was able to reproduce your results using the official published v0.16.1 binary, I was originally building it myself from the v0.16.1 git tag, which results in the broken behavior! I see the Go version was updated to 1.22 between v0.16.1 and v0.17.0, and I'm also using Go 1.22 locally. I wonder if that's the culprit? In the Go 1.22 changelog, I see various changes in the
The 3rd one might be the culprit seeing as the error seems to be stemming from the |
I've confirmed that this is a regression in Go 1.22. Reverting 09415e0 and downgrading to Go 1.21.10 (along with a few fix ups) allowed me to build 0.17.0 on Go 1.21, and snapshotting using Shadow Copy works correctly. There seems to be a bug with similar symptoms filed for Go here: golang/go#61907 Sorry for the ping @jkowalski, but would you or another of the active contributors be willing to file an upstream issue for this? Also wonder what can be done in Kopia for now, reverting 09415e0 was fairly seamless and only required 2 or 3 merge conflicts + and extra fix up of a newer int range loop. |
The range loop is kind of a big change so this is not exactly easy to rollback :) On top of that we've upgraded linter and have other changes. I'll talk to @julio-lopez to see what to do here. I think I would prefer to fix this forward and stay on go 1.22, but would definitely need help from folks who are able to actually debug and test this (I don't use Windows much, let alone VSS). (I have to say Windows filesystem API is full of magic and features that most people don't even realize are even there). |
Yes, ideally this would be fixed upstream in Go since it seems to be a regression in the stdlib, and then Kopia can just bump to that version in the next release cycle, but I'm not sure how much Windows magic edge cases the Go team is willing to handle. There are also Microsoft programs which exhibit the same error (see #3842 (comment)) so it could be argued that the previous behavior was incorrect and this is actually the intended behavior, in which case it would need a special case in Kopia to handle it. |
I think special-casing in Kopia sounds good. We already have I think some of the code ideas in this thread make sense to put in a PR so we can review it and merge. Longer term I think it makes sense to completely ditch |
Hi, I've opened a pull request with a fix at #3891 |
Snapshotting fails with
ERROR upload error: The parameter is incorrect.
when snapshotting the root of the drive with--enable-volume-shadow-copy
set toalways
. Snapshotting a sub directory in the root works correctly. All commands were run with administrator privileges.The text was updated successfully, but these errors were encountered: