Skip to content

Commit dbe015e

Browse files
Fix ACL copying with Blob (#2525)
* Generate a DFS service client when working with Blob as well * Fix testing, default include directory stubs when sensible Also removes leftover isHNStoHNS flag from Sync. * Use HNS acct with BlobFS location * Adapt to using HNS account instead when using BlobFS * Fix tests * Hopefully final test fixes? * Revert ps cli auth tests * Accidental leftovers of manual local testing * Clean up other test reductions --------- Co-authored-by: Gauri Lamunion <51212198+gapra-msft@users.noreply.github.com>
1 parent 3f3e1f0 commit dbe015e

16 files changed

+100
-73
lines changed

cmd/copy.go

+4
Original file line numberDiff line numberDiff line change
@@ -686,6 +686,10 @@ func (raw rawCopyCmdArgs) cook() (CookedCopyCmdArgs, error) {
686686
cooked.asSubdir = raw.asSubdir
687687

688688
cooked.IncludeDirectoryStubs = raw.includeDirectoryStubs
689+
if cooked.preservePermissions.IsTruthy() && cooked.FromTo.From() == common.ELocation.Blob() {
690+
// If a user is trying to persist from Blob storage with ACLs, they probably want directories too, because ACLs only exist in HNS.
691+
cooked.IncludeDirectoryStubs = true
692+
}
689693

690694
cooked.backupMode = raw.backupMode
691695
if err = validateBackupMode(cooked.backupMode, cooked.FromTo); err != nil {

cmd/sync.go

-4
Original file line numberDiff line numberDiff line change
@@ -258,9 +258,6 @@ func (raw *rawSyncCmdArgs) cook() (cookedSyncCmdArgs, error) {
258258
// return cooked, err
259259
// }
260260
cooked.preservePermissions = common.NewPreservePermissionsOption(isUserPersistingPermissions, raw.preserveOwner, cooked.fromTo)
261-
if cooked.fromTo == common.EFromTo.BlobBlob() && cooked.preservePermissions.IsTruthy() {
262-
cooked.isHNSToHNS = true // override HNS settings, since if a user is tx'ing blob->blob and copying permissions, it's DEFINITELY going to be HNS (since perms don't exist w/o HNS).
263-
}
264261

265262
cooked.preservePOSIXProperties = raw.preservePOSIXProperties
266263
if cooked.preservePOSIXProperties && !areBothLocationsPOSIXAware(cooked.fromTo) {
@@ -380,7 +377,6 @@ type cookedSyncCmdArgs struct {
380377
fromTo common.FromTo
381378
credentialInfo common.CredentialInfo
382379
s2sSourceCredentialType common.CredentialType
383-
isHNSToHNS bool // Because DFS sources and destinations are obscured, this is necessary for folder property transfers on ADLS Gen 2.
384380

385381
// filters
386382
recursive bool

cmd/syncEnumerator.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,14 @@ func (cca *cookedSyncCmdArgs) initEnumerator(ctx context.Context) (enumerator *s
5555
}
5656
}
5757

58+
includeDirStubs := cca.fromTo.From().SupportsHnsACLs() && cca.fromTo.To().SupportsHnsACLs() && cca.preservePermissions.IsTruthy()
59+
5860
// TODO: enable symlink support in a future release after evaluating the implications
5961
// TODO: Consider passing an errorChannel so that enumeration errors during sync can be conveyed to the caller.
6062
// GetProperties is enabled by default as sync supports both upload and download.
6163
// This property only supports Files and S3 at the moment, but provided that Files sync is coming soon, enable to avoid stepping on Files sync work
6264
dest := cca.fromTo.To()
63-
sourceTraverser, err := InitResourceTraverser(cca.source, cca.fromTo.From(), &ctx, &srcCredInfo, common.ESymlinkHandlingType.Skip(), nil, cca.recursive, true, cca.isHNSToHNS, common.EPermanentDeleteOption.None(), func(entityType common.EntityType) {
65+
sourceTraverser, err := InitResourceTraverser(cca.source, cca.fromTo.From(), &ctx, &srcCredInfo, common.ESymlinkHandlingType.Skip(), nil, cca.recursive, true, includeDirStubs, common.EPermanentDeleteOption.None(), func(entityType common.EntityType) {
6466
if entityType == common.EEntityType.File() {
6567
atomic.AddUint64(&cca.atomicSourceFilesScanned, 1)
6668
}
@@ -81,7 +83,7 @@ func (cca *cookedSyncCmdArgs) initEnumerator(ctx context.Context) (enumerator *s
8183
// TODO: enable symlink support in a future release after evaluating the implications
8284
// GetProperties is enabled by default as sync supports both upload and download.
8385
// This property only supports Files and S3 at the moment, but provided that Files sync is coming soon, enable to avoid stepping on Files sync work
84-
destinationTraverser, err := InitResourceTraverser(cca.destination, cca.fromTo.To(), &ctx, &dstCredInfo, common.ESymlinkHandlingType.Skip(), nil, cca.recursive, true, cca.isHNSToHNS, common.EPermanentDeleteOption.None(), func(entityType common.EntityType) {
86+
destinationTraverser, err := InitResourceTraverser(cca.destination, cca.fromTo.To(), &ctx, &dstCredInfo, common.ESymlinkHandlingType.Skip(), nil, cca.recursive, true, includeDirStubs, common.EPermanentDeleteOption.None(), func(entityType common.EntityType) {
8587
if entityType == common.EEntityType.File() {
8688
atomic.AddUint64(&cca.atomicDestinationFilesScanned, 1)
8789
}

cmd/zc_traverser_blob.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -256,8 +256,9 @@ func (t *blobTraverser) Traverse(preprocessor objectMorpher, processor objectPro
256256
if !t.includeDeleted && (isBlob || err != nil) {
257257
return err
258258
}
259-
} else if blobURLParts.BlobName == "" && t.preservePermissions.IsTruthy() {
260-
// if the root is a container and we're copying "folders", we should persist the ACLs there too.
259+
} else if blobURLParts.BlobName == "" && (t.preservePermissions.IsTruthy() || t.isDFS) {
260+
// If the root is a container and we're copying "folders", we should persist the ACLs there too.
261+
// For DFS, we should always include the container root.
261262
if azcopyScanningLogger != nil {
262263
azcopyScanningLogger.Log(common.LogDebug, "Detected the root as a container.")
263264
}

common/util.go

+6-9
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ func GetServiceClientForLocation(loc Location,
116116
) (*ServiceClient, error) {
117117
ret := &ServiceClient{}
118118
switch loc {
119-
case ELocation.BlobFS():
119+
case ELocation.BlobFS(), ELocation.Blob(): // Since we always may need to interact with DFS while working with Blob, we should just attach both.
120120
datalakeURLParts, err := azdatalake.ParseURL(resourceURL)
121121
if err != nil {
122122
return nil, err
@@ -150,9 +150,6 @@ func GetServiceClientForLocation(loc Location,
150150

151151
ret.dsc = dsc
152152

153-
// For BlobFS, we additionally create a blob client as well. We interact with both endpoints.
154-
fallthrough
155-
case ELocation.Blob():
156153
blobURLParts, err := blob.ParseURL(resourceURL)
157154
if err != nil {
158155
return nil, err
@@ -162,23 +159,23 @@ func GetServiceClientForLocation(loc Location,
162159
// In case we are creating a blob client for a datalake target, correct the endpoint
163160
blobURLParts.Host = strings.Replace(blobURLParts.Host, ".dfs", ".blob", 1)
164161
resourceURL = blobURLParts.String()
165-
var o *blobservice.ClientOptions
162+
var bso *blobservice.ClientOptions
166163
var bsc *blobservice.Client
167164
if policyOptions != nil {
168-
o = &blobservice.ClientOptions{ClientOptions: *policyOptions}
165+
bso = &blobservice.ClientOptions{ClientOptions: *policyOptions}
169166
}
170167

171168
if credType.IsAzureOAuth() {
172-
bsc, err = blobservice.NewClient(resourceURL, cred, o)
169+
bsc, err = blobservice.NewClient(resourceURL, cred, bso)
173170
} else if credType.IsSharedKey() {
174171
var sharedKeyCred *blob.SharedKeyCredential
175172
sharedKeyCred, err = GetBlobSharedKeyCredential()
176173
if err != nil {
177174
return nil, err
178175
}
179-
bsc, err = blobservice.NewClientWithSharedKeyCredential(resourceURL, sharedKeyCred, o)
176+
bsc, err = blobservice.NewClientWithSharedKeyCredential(resourceURL, sharedKeyCred, bso)
180177
} else {
181-
bsc, err = blobservice.NewClientWithNoCredential(resourceURL, o)
178+
bsc, err = blobservice.NewClientWithNoCredential(resourceURL, bso)
182179
}
183180

184181
if err != nil {

e2etest/declarativeHelpers.go

+6-2
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,11 @@ func (tft TestFromTo) getValues(op Operation) []common.FromTo {
460460
if op == eOperation.Sync() {
461461
switch fromTo {
462462
case common.EFromTo.BlobBlob(),
463+
common.EFromTo.BlobFSBlob(),
464+
common.EFromTo.BlobBlobFS(),
465+
common.EFromTo.BlobFSBlobFS(),
466+
common.EFromTo.BlobFSLocal(),
467+
common.EFromTo.LocalBlobFS(),
463468
common.EFromTo.FileFile(),
464469
common.EFromTo.LocalBlob(),
465470
common.EFromTo.BlobLocal(),
@@ -475,8 +480,7 @@ func (tft TestFromTo) getValues(op Operation) []common.FromTo {
475480

476481
// TODO: remove this temp block
477482
// temp
478-
if fromTo.From() == common.ELocation.S3() ||
479-
fromTo.From() == common.ELocation.BlobFS() || fromTo.To() == common.ELocation.BlobFS() {
483+
if fromTo.From() == common.ELocation.S3() {
480484
continue // until we implement the declarativeResourceManagers
481485
}
482486

e2etest/declarativeResourceManagers.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ func (r *resourceLocal) createSourceSnapshot(a asserter) {
212212

213213
type resourceBlobContainer struct {
214214
accountType AccountType
215+
isBlobFS bool
215216
containerClient *container.Client
216217
rawSasURL *url.URL
217218
}
@@ -383,7 +384,7 @@ func (r *resourceBlobContainer) getParam(a asserter, stripTopDir, withSas bool,
383384
uri = bURLParts.String()
384385
}
385386

386-
if r.accountType == EAccountType.HierarchicalNamespaceEnabled() {
387+
if r.isBlobFS {
387388
uri = strings.ReplaceAll(uri, "blob", "dfs")
388389
}
389390

e2etest/declarativeRunner.go

+20-8
Original file line numberDiff line numberDiff line change
@@ -111,16 +111,16 @@ func RunScenarios(
111111
validate Validate, // TODO: do we really want the test author to have to nominate which validation should happen? Pros: better perf of tests. Cons: they have to tell us, and if they tell us wrong test may not test what they think it tests
112112
// _ interface{}, // TODO if we want it??, blockBlobsOnly or specific/all blob types
113113

114-
// It would be a pain to list out every combo by hand,
115-
// In addition to the fact that not every credential type is sensible.
116-
// Thus, the E2E framework takes in a requested set of credential types, and applies them where sensible.
117-
// This allows you to make tests use OAuth only, SAS only, etc.
114+
// It would be a pain to list out every combo by hand,
115+
// In addition to the fact that not every credential type is sensible.
116+
// Thus, the E2E framework takes in a requested set of credential types, and applies them where sensible.
117+
// This allows you to make tests use OAuth only, SAS only, etc.
118118
requestedCredentialTypesSrc []common.CredentialType,
119119
requestedCredentialTypesDst []common.CredentialType,
120120
p params,
121121
hs *hooks,
122122
fs testFiles,
123-
// TODO: do we need something here to explicitly say that we expect success or failure? For now, we are just inferring that from the elements of sourceFiles
123+
// TODO: do we need something here to explicitly say that we expect success or failure? For now, we are just inferring that from the elements of sourceFiles
124124
destAccountType AccountType,
125125
srcAccountType AccountType,
126126
scenarioSuffix string) {
@@ -140,8 +140,9 @@ func RunScenarios(
140140
}
141141

142142
seenFromTos := make(map[common.FromTo]bool)
143+
fromTos := testFromTo.getValues(op)
143144

144-
for _, fromTo := range testFromTo.getValues(op) {
145+
for _, fromTo := range fromTos {
145146
// dedupe the scenarios
146147
if _, ok := seenFromTos[fromTo]; ok {
147148
continue
@@ -166,9 +167,20 @@ func RunScenarios(
166167
subtestName += "-" + scenarioSuffix
167168
}
168169

170+
usedSrc, usedDst := srcAccountType, destAccountType
171+
if fromTo.From() == common.ELocation.BlobFS() {
172+
// switch to an account made for dfs
173+
usedSrc = EAccountType.HierarchicalNamespaceEnabled()
174+
}
175+
176+
if fromTo.To() == common.ELocation.BlobFS() {
177+
// switch to an account made for dfs
178+
usedDst = EAccountType.HierarchicalNamespaceEnabled()
179+
}
180+
169181
s := scenario{
170-
srcAccountType: srcAccountType,
171-
destAccountType: destAccountType,
182+
srcAccountType: usedSrc,
183+
destAccountType: usedDst,
172184
subtestName: subtestName,
173185
compactScenarioName: compactScenarioName,
174186
fullScenarioName: fullScenarioName,

e2etest/declarativeScenario.go

+15-16
Original file line numberDiff line numberDiff line change
@@ -107,13 +107,13 @@ func (s *scenario) Run() {
107107
// setup scenario
108108
// First, validate the accounts make sense for the source/dests
109109
if s.srcAccountType.IsBlobOnly() {
110-
s.a.Assert(s.fromTo.From(), equals(), common.ELocation.Blob())
110+
s.a.Assert(true, equals(), s.fromTo.From() == common.ELocation.Blob() || s.fromTo.From() == common.ELocation.BlobFS())
111111
}
112112

113-
if s.destAccountType.IsBlobOnly() {
113+
if s.destAccountType.IsManagedDisk() {
114114
s.a.Assert(s.destAccountType, notEquals(), EAccountType.StdManagedDisk(), "Upload is not supported in MD testing yet")
115115
s.a.Assert(s.destAccountType, notEquals(), EAccountType.OAuthManagedDisk(), "Upload is not supported in MD testing yet")
116-
s.a.Assert(s.fromTo.To(), equals(), common.ELocation.Blob())
116+
s.a.Assert(true, equals(), s.fromTo.From() == common.ELocation.Blob() || s.fromTo.From() == common.ELocation.BlobFS())
117117
}
118118

119119
// setup
@@ -261,7 +261,7 @@ func (s *scenario) assignSourceAndDest() {
261261
return &resourceLocal{common.Iff[string](s.p.destNull && !isSourceAcc, common.Dev_Null, "")}
262262
case common.ELocation.File():
263263
return &resourceAzureFileShare{accountType: accType}
264-
case common.ELocation.Blob():
264+
case common.ELocation.Blob(), common.ELocation.BlobFS():
265265
// TODO: handle the multi-container (whole account) scenario
266266
// TODO: handle wider variety of account types
267267
if accType.IsManagedDisk() {
@@ -270,10 +270,7 @@ func (s *scenario) assignSourceAndDest() {
270270
return &resourceManagedDisk{config: *mdCfg}
271271
}
272272

273-
return &resourceBlobContainer{accountType: accType}
274-
case common.ELocation.BlobFS():
275-
s.a.Error("Not implemented yet for blob FS")
276-
return &resourceDummy{}
273+
return &resourceBlobContainer{accountType: accType, isBlobFS: loc == common.ELocation.BlobFS()}
277274
case common.ELocation.S3():
278275
s.a.Error("Not implemented yet for S3")
279276
return &resourceDummy{}
@@ -484,7 +481,7 @@ func (s *scenario) getTransferInfo() (srcRoot string, dstRoot string, expectFold
484481
expectFolders = (s.fromTo.From().IsFolderAware() &&
485482
s.fromTo.To().IsFolderAware() &&
486483
s.p.allowsFolderTransfers()) ||
487-
(s.p.preserveSMBPermissions && s.FromTo() == common.EFromTo.BlobBlob()) ||
484+
(s.p.preserveSMBPermissions && s.FromTo().From().SupportsHnsACLs() && s.FromTo().To().SupportsHnsACLs()) ||
488485
(s.p.preservePOSIXProperties && (s.FromTo() == common.EFromTo.LocalBlob() || s.FromTo() == common.EFromTo.BlobBlob() || s.FromTo() == common.EFromTo.BlobLocal()))
489486
expectRootFolder := expectFolders
490487

@@ -583,7 +580,7 @@ func (s *scenario) validateProperties() {
583580
// validate all the different things
584581
s.validatePOSIXProperties(f, actual.nameValueMetadata)
585582
s.validateSymlink(f, actual.nameValueMetadata)
586-
s.validateMetadata(expected.nameValueMetadata, actual.nameValueMetadata)
583+
s.validateMetadata(f, expected.nameValueMetadata, actual.nameValueMetadata)
587584
s.validateBlobTags(expected.blobTags, actual.blobTags)
588585
s.validateContentHeaders(expected.contentHeaders, actual.contentHeaders)
589586
s.validateCreateTime(expected.creationTime, actual.creationTime)
@@ -733,20 +730,22 @@ func metadataWithProperCasing(original map[string]*string) map[string]*string {
733730
}
734731

735732
// // Individual property validation routines
736-
func (s *scenario) validateMetadata(expected, actual map[string]*string) {
733+
func (s *scenario) validateMetadata(f *testObject, expected, actual map[string]*string) {
734+
cased := metadataWithProperCasing(actual)
735+
737736
for _, v := range common.AllLinuxProperties { // properties are evaluated elsewhere
738737
delete(expected, v)
739-
delete(actual, v)
738+
delete(cased, v)
740739
}
741740

742-
s.a.Assert(len(actual), equals(), len(expected), "Both should have same number of metadata entries")
743-
cased := metadataWithProperCasing(actual)
741+
s.a.Assert(len(cased), equals(), len(expected), "Both should have same number of metadata entries")
742+
744743
for key := range expected {
745744
exValue := expected[key]
746745
actualValue, ok := cased[key]
747-
s.a.Assert(ok, equals(), true, fmt.Sprintf("expect key '%s' to be found in destination metadata", key))
746+
s.a.Assert(ok, equals(), true, fmt.Sprintf("%s: expect key '%s' to be found in destination metadata", f.name, key))
748747
if ok {
749-
s.a.Assert(exValue, equals(), actualValue, fmt.Sprintf("Expect value for key '%s' to be '%s' but found '%s'", key, *exValue, *actualValue))
748+
s.a.Assert(exValue, equals(), actualValue, fmt.Sprintf("%s: Expect value for key '%s' to be '%s' but found '%s'", f.name, key, *exValue, *actualValue))
750749
}
751750
}
752751
}

e2etest/zt_basic_cli_ps_auth_test.go

-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
1818
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
1919
// THE SOFTWARE.
20-
2120
package e2etest
2221

2322
import (

e2etest/zt_basic_copy_sync_remove_test.go

+16-6
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ func TestBasic_CopyRemoveFolderHNS(t *testing.T) {
368368
desc: "AllRemove",
369369
useAllTos: true,
370370
froms: []common.Location{
371-
common.ELocation.Blob(), // blobfs isn't technically supported; todo: support it properly rather than jank through Blob
371+
common.ELocation.BlobFS(),
372372
},
373373
tos: []common.Location{
374374
common.ELocation.Unknown(),
@@ -419,8 +419,19 @@ func TestBasic_CopyRemoveFolderHNS(t *testing.T) {
419419
}
420420

421421
func TestBasic_CopyRemoveContainer(t *testing.T) {
422+
allButBfsRemove := TestFromTo{
423+
desc: "AllRemove",
424+
useAllTos: true,
425+
froms: []common.Location{
426+
common.ELocation.Blob(), // If you have a container-level SAS and a HNS account, you can't delete the container. HNS should not be included here.
427+
common.ELocation.File(),
428+
},
429+
tos: []common.Location{
430+
common.ELocation.Unknown(),
431+
},
432+
}
422433

423-
RunScenarios(t, eOperation.Remove(), eTestFromTo.AllRemove(), eValidate.Auto(), anonymousAuthOnly, anonymousAuthOnly, params{
434+
RunScenarios(t, eOperation.Remove(), allButBfsRemove, eValidate.Auto(), anonymousAuthOnly, anonymousAuthOnly, params{
424435
recursive: true,
425436
relativeSourcePath: "",
426437
}, nil, testFiles{
@@ -438,7 +449,7 @@ func TestBasic_CopyRemoveContainerHNS(t *testing.T) {
438449
desc: "AllRemove",
439450
useAllTos: true,
440451
froms: []common.Location{
441-
common.ELocation.Blob(), // blobfs isn't technically supported; todo: support it properly rather than jank through Blob
452+
common.ELocation.BlobFS(),
442453
},
443454
tos: []common.Location{
444455
common.ELocation.Unknown(),
@@ -472,7 +483,6 @@ func TestBasic_CopyRemoveContainerHNS(t *testing.T) {
472483
_, err = fsURL.GetAccessControl(ctx, nil)
473484
a.Assert(err, notEquals(), nil)
474485
a.Assert(datalakeerror.HasCode(err, "FilesystemNotFound"), equals(), true)
475-
476486
},
477487
},
478488
testFiles{
@@ -869,7 +879,7 @@ func TestBasic_OverwriteHNSDirWithChildren(t *testing.T) {
869879
RunScenarios(
870880
t,
871881
eOperation.Copy(),
872-
eTestFromTo.Other(common.EFromTo.LocalBlobFS()),
882+
eTestFromTo.Other(common.EFromTo.BlobFSBlobFS()),
873883
eValidate.Auto(),
874884
anonymousAuthOnly,
875885
anonymousAuthOnly,
@@ -1066,7 +1076,7 @@ func TestBasic_SyncRemoveFoldersHNS(t *testing.T) {
10661076
RunScenarios(
10671077
t,
10681078
eOperation.Sync(),
1069-
eTestFromTo.Other(common.EFromTo.BlobBlob()),
1079+
eTestFromTo.Other(common.EFromTo.BlobFSBlobFS()),
10701080
eValidate.Auto(),
10711081
anonymousAuthOnly,
10721082
anonymousAuthOnly,

e2etest/zt_enumeration_filter_test.go

-15
Original file line numberDiff line numberDiff line change
@@ -162,21 +162,6 @@ func TestFilter_RemoveFolder(t *testing.T) {
162162
}, EAccountType.Standard(), EAccountType.Standard(), "")
163163
}
164164

165-
func TestFilter_RemoveContainer(t *testing.T) {
166-
167-
RunScenarios(t, eOperation.Remove(), eTestFromTo.AllRemove(), eValidate.Auto(), anonymousAuthOnly, anonymousAuthOnly, params{
168-
recursive: true,
169-
relativeSourcePath: "",
170-
}, nil, testFiles{
171-
defaultSize: "1K",
172-
shouldTransfer: []interface{}{
173-
"file1.txt",
174-
"folder1/file11.txt",
175-
"folder1/file12.txt",
176-
},
177-
}, EAccountType.Standard(), EAccountType.Standard(), "")
178-
}
179-
180165
func TestFilter_ExcludePath(t *testing.T) {
181166
RunScenarios(t, eOperation.Copy(), eTestFromTo.AllSourcesToOneDest(), eValidate.Auto(), anonymousAuthOnly, anonymousAuthOnly, params{
182167
recursive: true,

e2etest/zt_preserve_properties_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ func TestProperties_NameValueMetadataCanBeUploaded(t *testing.T) {
5757
}
5858

5959
func TestProperties_HNSACLs(t *testing.T) {
60-
RunScenarios(t, eOperation.CopyAndSync(), eTestFromTo.Other(common.EFromTo.BlobBlob()), eValidate.Auto(), anonymousAuthOnly, anonymousAuthOnly, params{
60+
RunScenarios(t, eOperation.CopyAndSync(), eTestFromTo.Other(common.EFromTo.BlobBlob(), common.EFromTo.BlobFSBlobFS(), common.EFromTo.BlobBlobFS(), common.EFromTo.BlobFSBlob()), eValidate.Auto(), anonymousAuthOnly, anonymousAuthOnly, params{
6161
recursive: true,
6262
preserveSMBPermissions: true, // this flag is deprecated, but still held over to avoid breaking.
6363
}, nil, testFiles{

0 commit comments

Comments
 (0)