Skip to content

Commit b35f229

Browse files
committed
Merge remote-tracking branch 'origin/adreed/fix-acl-api' into adreed/fix-acl-api
2 parents 8309996 + 46675f3 commit b35f229

12 files changed

+133
-12
lines changed

cmd/copy.go

+9
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,8 @@ type rawCopyCmdArgs struct {
123123
preserveSMBPermissions bool
124124
preservePermissions bool // Separate flag so that we don't get funkiness with two "flags" targeting the same boolean
125125
preserveOwner bool // works in conjunction with preserveSmbPermissions
126+
// Default true; false indicates that the destination is the target directory, rather than something we'd put a directory under (e.g. a container)
127+
asSubdir bool
126128
// Opt-in flag to persist additional SMB properties to Azure Files. Named ...info instead of ...properties
127129
// because the latter was similar enough to preserveSMBPermissions to induce user error
128130
preserveSMBInfo bool
@@ -652,6 +654,9 @@ func (raw rawCopyCmdArgs) cook() (CookedCopyCmdArgs, error) {
652654
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).
653655
}
654656

657+
// --as-subdir is OK on all sources and destinations, but additional verification has to be done down the line. (e.g. https://account.blob.core.windows.net is not a valid root)
658+
cooked.asSubdir = raw.asSubdir
659+
655660
cooked.IncludeDirectoryStubs = raw.includeDirectoryStubs || (cooked.isHNStoHNS && cooked.preservePermissions.IsTruthy())
656661

657662
if err = crossValidateSymlinksAndPermissions(cooked.FollowSymlinks, cooked.preservePermissions.IsTruthy()); err != nil {
@@ -1093,6 +1098,9 @@ type CookedCopyCmdArgs struct {
10931098
// Whether to enable Windows special privileges
10941099
backupMode bool
10951100

1101+
// Whether to rename/share the root
1102+
asSubdir bool
1103+
10961104
// whether user wants to preserve full properties during service to service copy, the default value is true.
10971105
// For S3 and Azure File non-single file source, as list operation doesn't return full properties of objects/files,
10981106
// to preserve full properties AzCopy needs to send one additional request per object/file.
@@ -1846,6 +1854,7 @@ func init() {
18461854
cpCmd.PersistentFlags().BoolVar(&raw.noGuessMimeType, "no-guess-mime-type", false, "Prevents AzCopy from detecting the content-type based on the extension or content of the file.")
18471855
cpCmd.PersistentFlags().BoolVar(&raw.preserveLastModifiedTime, "preserve-last-modified-time", false, "Only available when destination is file system.")
18481856
cpCmd.PersistentFlags().BoolVar(&raw.preserveSMBPermissions, "preserve-smb-permissions", false, "False by default. Preserves SMB ACLs between aware resources (Windows and Azure Files). For downloads, you will also need the --backup flag to restore permissions where the new Owner will not be the user running AzCopy. This flag applies to both files and folders, unless a file-only filter is specified (e.g. include-pattern).")
1857+
cpCmd.PersistentFlags().BoolVar(&raw.asSubdir, "as-subdir", true, "True by default. Places folder sources as subdirectories under the destination.")
18491858
cpCmd.PersistentFlags().BoolVar(&raw.preserveOwner, common.PreserveOwnerFlagName, common.PreserveOwnerDefault, "Only has an effect in downloads, and only when --preserve-smb-permissions is used. If true (the default), the file Owner and Group are preserved in downloads. If set to false, --preserve-smb-permissions will still preserve ACLs but Owner and Group will be based on the user running AzCopy")
18501859
cpCmd.PersistentFlags().BoolVar(&raw.preserveSMBInfo, "preserve-smb-info", true, "For SMB-aware locations, flag will be set to true by default. Preserves SMB property info (last write time, creation time, attribute bits) between SMB-aware resources (Windows and Azure Files). Only the attribute bits supported by Azure Files will be transferred; any others will be ignored. This flag applies to both files and folders, unless a file-only filter is specified (e.g. include-pattern). The info transferred for folders is the same as that for files, except for Last Write Time which is never preserved for folders.")
18511860
cpCmd.PersistentFlags().BoolVar(&raw.forceIfReadOnly, "force-if-read-only", false, "When overwriting an existing file on Windows or Azure Files, force the overwrite to work even if the existing file has its read-only attribute set")

cmd/copyEnumeratorInit.go

+18-7
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,12 @@ func (cca *CookedCopyCmdArgs) initEnumerator(jobPartOrder common.CopyJobPartOrde
7676
return nil, err
7777
}
7878

79-
// Ensure we're only copying from a directory with a trailing wildcard or recursive.
79+
// Ensure we're only copying a directory under valid conditions
8080
isSourceDir := traverser.IsDirectory(true)
81-
if isSourceDir && !cca.Recursive && !cca.StripTopDir {
81+
if isSourceDir &&
82+
!cca.Recursive && // Copies the folder & everything under it
83+
!cca.StripTopDir { // Copies only everything under it
84+
// todo: dir only transfer, also todo: support syncing the root folder's acls on sync.
8285
return nil, errors.New("cannot use directory as source without --recursive or a trailing wildcard (/*)")
8386
}
8487

@@ -113,6 +116,10 @@ func (cca *CookedCopyCmdArgs) initEnumerator(jobPartOrder common.CopyJobPartOrde
113116
return nil, errors.New("cannot transfer individual files/folders to the root of a service. Add a container or directory to the destination URL")
114117
}
115118

119+
if srcLevel == ELocationLevel.Container() && dstLevel == ELocationLevel.Service() && !cca.asSubdir {
120+
return nil, errors.New("cannot use --as-subdir=false with a service level destination")
121+
}
122+
116123
// When copying a container directly to a container, strip the top directory
117124
if srcLevel == ELocationLevel.Container() && dstLevel == ELocationLevel.Container() && cca.FromTo.From().IsRemote() && cca.FromTo.To().IsRemote() {
118125
cca.StripTopDir = true
@@ -255,8 +262,8 @@ func (cca *CookedCopyCmdArgs) initEnumerator(jobPartOrder common.CopyJobPartOrde
255262
}
256263
}
257264

258-
srcRelPath := cca.MakeEscapedRelativePath(true, isDestDir, object)
259-
dstRelPath := cca.MakeEscapedRelativePath(false, isDestDir, object)
265+
srcRelPath := cca.MakeEscapedRelativePath(true, isDestDir, cca.asSubdir, object)
266+
dstRelPath := cca.MakeEscapedRelativePath(false, isDestDir, cca.asSubdir, object)
260267

261268
transfer, shouldSendToSte := object.ToNewCopyTransfer(
262269
cca.autoDecompress && cca.FromTo.IsDownload(),
@@ -572,7 +579,7 @@ func pathEncodeRules(path string, fromTo common.FromTo, disableAutoDecoding bool
572579
return path
573580
}
574581

575-
func (cca *CookedCopyCmdArgs) MakeEscapedRelativePath(source bool, dstIsDir bool, object StoredObject) (relativePath string) {
582+
func (cca *CookedCopyCmdArgs) MakeEscapedRelativePath(source bool, dstIsDir bool, asSubdir bool, object StoredObject) (relativePath string) {
576583
// write straight to /dev/null, do not determine a indirect path
577584
if !source && cca.Destination.Value == common.Dev_Null {
578585
return "" // ignore path encode rules
@@ -602,8 +609,12 @@ func (cca *CookedCopyCmdArgs) MakeEscapedRelativePath(source bool, dstIsDir bool
602609
return pathEncodeRules(relativePath, cca.FromTo, cca.disableAutoDecoding, source)
603610
}
604611

605-
// If it's out here, the object is contained in a folder, or was found via a wildcard, or object.isSourceRootFolder == true
612+
// user is not placing the source as a subdir
613+
if object.isSourceRootFolder() && !asSubdir {
614+
relativePath = ""
615+
}
606616

617+
// If it's out here, the object is contained in a folder, or was found via a wildcard, or object.isSourceRootFolder == true
607618
if object.isSourceRootFolder() {
608619
relativePath = "" // otherwise we get "/" from the line below, and that breaks some clients, e.g. blobFS
609620
} else {
@@ -612,7 +623,7 @@ func (cca *CookedCopyCmdArgs) MakeEscapedRelativePath(source bool, dstIsDir bool
612623

613624
if common.IffString(source, object.ContainerName, object.DstContainerName) != "" {
614625
relativePath = `/` + common.IffString(source, object.ContainerName, object.DstContainerName) + relativePath
615-
} else if !source && !cca.StripTopDir {
626+
} else if !source && !cca.StripTopDir && cca.asSubdir { // Avoid doing this where the root is shared or renamed.
616627
// We ONLY need to do this adjustment to the destination.
617628
// The source SAS has already been removed. No need to convert it to a URL or whatever.
618629
// Save to a directory

cmd/zt_copy_s2smigration_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ func getDefaultRawCopyInput(src, dst string) rawCopyCmdArgs {
8989
s2sInvalidMetadataHandleOption: defaultS2SInvalideMetadataHandleOption.String(),
9090
forceWrite: common.EOverwriteOption.True().String(),
9191
preserveOwner: common.PreserveOwnerDefault,
92+
asSubdir: true,
9293
}
9394
}
9495

cmd/zt_scenario_helpers_for_test.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ import (
3636
gcpUtils "cloud.google.com/go/storage"
3737

3838
"github.com/Azure/azure-storage-azcopy/v10/azbfs"
39-
minio "github.com/minio/minio-go"
39+
"github.com/minio/minio-go"
4040

4141
"github.com/Azure/azure-storage-azcopy/v10/common"
4242
"github.com/Azure/azure-storage-blob-go/azblob"
@@ -848,6 +848,7 @@ func getDefaultCopyRawInput(src string, dst string) rawCopyCmdArgs {
848848
s2sInvalidMetadataHandleOption: defaultS2SInvalideMetadataHandleOption.String(),
849849
forceWrite: common.EOverwriteOption.True().String(),
850850
preserveOwner: common.PreserveOwnerDefault,
851+
asSubdir: true,
851852
}
852853
}
853854

e2etest/declarativeHelpers.go

+1
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ func (a *testingAsserter) CompactScenarioName() string {
140140

141141
type params struct {
142142
recursive bool
143+
invertedAsSubdir bool // this flag is INVERTED, because it is TRUE by default. todo: use pointers instead?
143144
includePath string
144145
includePattern string
145146
includeAfter string

e2etest/declarativeScenario.go

+10-3
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,8 @@ func (s *scenario) runAzCopy() {
187187
result, wasClean, err := r.ExecuteAzCopyCommand(
188188
s.operation,
189189
s.state.source.getParam(s.stripTopDir, srcUseSas, tf.objectTarget),
190-
s.state.dest.getParam(false, useSas, tf.objectTarget),
190+
// Prefer the destination target over the object target itself.
191+
s.state.dest.getParam(false, useSas, common.IffString(tf.destTarget != "", tf.destTarget, tf.objectTarget)),
191192
afterStart, s.chToStdin)
192193

193194
if !wasClean {
@@ -296,20 +297,26 @@ func (s *scenario) getTransferInfo() (srcRoot string, dstRoot string, expectFold
296297
// compute dest, taking into account our stripToDir rules
297298
addedDirAtDest = ""
298299
areBothContainerLike := s.state.source.isContainerLike() && s.state.dest.isContainerLike()
300+
301+
tf := s.GetTestFiles()
299302
if s.stripTopDir || s.operation == eOperation.Sync() || areBothContainerLike {
300303
// Sync always acts like stripTopDir is true.
301304
// For copies between two container-like locations, we don't expect the root directory to be transferred, regardless of stripTopDir.
302305
// Yes, this is arguably inconsistent. But its the way its always been, and it does seem to match user expectations for copies
303306
// of that kind.
304307
expectRootFolder = false
305308
} else if s.fromTo.From().IsLocal() {
306-
if s.GetTestFiles().objectTarget == "" {
309+
if tf.objectTarget == "" && tf.destTarget == "" {
307310
addedDirAtDest = filepath.Base(srcRoot)
311+
} else if tf.destTarget != "" {
312+
addedDirAtDest = tf.destTarget
308313
}
309314
dstRoot = fmt.Sprintf("%s%c%s", dstRoot, os.PathSeparator, addedDirAtDest)
310315
} else {
311-
if s.GetTestFiles().objectTarget == "" {
316+
if tf.objectTarget == "" && tf.destTarget == "" {
312317
addedDirAtDest = path.Base(srcRoot)
318+
} else if tf.destTarget != "" {
319+
addedDirAtDest = tf.destTarget
313320
}
314321
dstRoot = fmt.Sprintf("%s/%s", dstRoot, addedDirAtDest)
315322
}

e2etest/declarativeTestFiles.go

+3
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,7 @@ func folder(n string, properties ...withPropertyProvider) *testObject {
307307
type testFiles struct {
308308
defaultSize string // how big should the files be? Applies to those files that don't specify individual sizes. Uses the same K, M, G suffixes as benchmark mode's size-per-file
309309
objectTarget string // should we target only a single file/folder?
310+
destTarget string // do we want to copy under a folder or rename?
310311
sourcePublic azblob.PublicAccessType // should the source blob container be public? (ONLY APPLIES TO BLOB.)
311312

312313
// The files/folders that we expect to be transferred. Elements of the list must be strings or testObject's.
@@ -329,6 +330,7 @@ func (tf testFiles) cloneShouldTransfers() testFiles {
329330
return testFiles{
330331
defaultSize: tf.defaultSize,
331332
objectTarget: tf.objectTarget,
333+
destTarget: tf.destTarget,
332334
sourcePublic: tf.sourcePublic,
333335
shouldTransfer: tf.shouldTransfer,
334336
}
@@ -339,6 +341,7 @@ func (tf testFiles) DeepCopy() testFiles {
339341
ret.defaultSize = tf.defaultSize
340342

341343
ret.objectTarget = tf.objectTarget
344+
ret.destTarget = tf.destTarget
342345
ret.sourcePublic = tf.sourcePublic
343346
ret.shouldTransfer = tf.copyList(tf.shouldTransfer)
344347
ret.shouldIgnore = tf.copyList(tf.shouldIgnore)

e2etest/runner.go

+1
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ func (t *TestRunner) SetAllFlags(p params) {
6868

6969
// TODO: TODO: nakulkar-msft there will be many more to add here
7070
set("recursive", p.recursive, false)
71+
set("as-subdir", !p.invertedAsSubdir, true)
7172
set("include-path", p.includePath, "")
7273
set("exclude-path", p.excludePath, "")
7374
set("include-pattern", p.includePattern, "")

e2etest/zt_basic_copy_sync_remove_test.go

+27
Original file line numberDiff line numberDiff line change
@@ -306,3 +306,30 @@ func TestBasic_CopyToWrongBlobType(t *testing.T) {
306306
}
307307
}
308308
}
309+
310+
func TestBasic_CopyWithShareRoot(t *testing.T) {
311+
RunScenarios(
312+
t,
313+
eOperation.Copy(), // Sync already shares the root by default.
314+
eTestFromTo.AllUploads(),
315+
eValidate.Auto(),
316+
params{
317+
recursive: true,
318+
invertedAsSubdir: true,
319+
},
320+
nil,
321+
testFiles{
322+
defaultSize: "1K",
323+
destTarget: "newName",
324+
325+
shouldTransfer: []interface{}{
326+
folder(""),
327+
f("asdf.txt"),
328+
folder("a"),
329+
f("a/asdf.txt"),
330+
},
331+
},
332+
EAccountType.Standard(),
333+
"",
334+
)
335+
}

e2etest/zt_preserve_smb_properties_test.go

+46
Original file line numberDiff line numberDiff line change
@@ -215,3 +215,49 @@ func TestProperties_SMBPermsAndFlagsWithSync(t *testing.T) {
215215
shouldIgnore: recreateFiles,
216216
}, EAccountType.Standard(), "")
217217
}
218+
219+
func TestProperties_SMBWithCopyWithShareRoot(t *testing.T) {
220+
fileSDDL, err := AdjustSDDLToLocal(SampleSDDL, SampleSDDLPlaceHolder)
221+
if err != nil {
222+
t.Error(err)
223+
}
224+
225+
rootSDDL, err := AdjustSDDLToLocal(RootSampleSDDL, SampleSDDLPlaceHolder)
226+
if err != nil {
227+
t.Error(err)
228+
}
229+
230+
folderSDDL, err := AdjustSDDLToLocal(FolderSampleSDDL, SampleSDDLPlaceHolder)
231+
if err != nil {
232+
t.Error(err)
233+
}
234+
235+
RunScenarios(
236+
t,
237+
eOperation.Copy(), // Sync already shares the root by default.
238+
eTestFromTo.Other(common.EFromTo.LocalFile()),
239+
eValidate.Auto(),
240+
params{
241+
recursive: true,
242+
invertedAsSubdir: true,
243+
preserveSMBPermissions: true,
244+
preserveSMBInfo: true,
245+
},
246+
nil,
247+
testFiles{
248+
defaultSize: "1K",
249+
destTarget: "newName",
250+
251+
shouldTransfer: []interface{}{
252+
folder("", with{smbAttributes: 2, smbPermissionsSddl: rootSDDL}),
253+
f("asdf.txt", with{smbAttributes: 2, smbPermissionsSddl: fileSDDL}),
254+
folder("a", with{smbAttributes: 2, smbPermissionsSddl: folderSDDL}),
255+
f("a/asdf.txt", with{smbAttributes: 2, smbPermissionsSddl: fileSDDL}),
256+
folder("a/b", with{smbAttributes: 2, smbPermissionsSddl: folderSDDL}),
257+
f("a/b/asdf.txt", with{smbAttributes: 2, smbPermissionsSddl: fileSDDL}),
258+
},
259+
},
260+
EAccountType.Standard(),
261+
"",
262+
)
263+
}

ste/downloader-azureFiles_windows.go

+8
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import (
1111
"syscall"
1212
"unsafe"
1313

14+
"github.com/Azure/azure-storage-file-go/azfile"
15+
1416
"github.com/Azure/azure-storage-azcopy/v10/common"
1517
"github.com/Azure/azure-storage-azcopy/v10/sddl"
1618
"github.com/Azure/azure-storage-file-go/azfile"
@@ -94,6 +96,12 @@ var globalSetAclMu = &sync.Mutex{}
9496
func (a *azureFilesDownloader) PutSDDL(sip ISMBPropertyBearingSourceInfoProvider, txInfo TransferInfo) error {
9597
// Let's start by getting our SDDL and parsing it.
9698
sddlString, err := sip.GetSDDL()
99+
100+
// There's nothing to set.
101+
if sddlString == "" {
102+
return nil
103+
}
104+
97105
// TODO: be better at handling these errors.
98106
// GetSDDL will fail on a file-level SAS token.
99107
if err != nil {

ste/sender-azureFile.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,13 @@ func (u *azureFileSenderBase) addPermissionsToHeaders(info TransferInfo, destUrl
272272
// If we didn't do the workaround, then let's get the SDDL and put it later.
273273
if u.headersToApply.PermissionKey == nil || *u.headersToApply.PermissionKey == "" {
274274
pString, err := sddlSIP.GetSDDL()
275-
u.headersToApply.PermissionString = &pString
275+
276+
// Sending "" to the service is invalid, but the service will return it sometimes (e.g. on file shares)
277+
// Thus, we'll let the files SDK fill in "inherit" for us, so the service is happy.
278+
if pString != "" {
279+
u.headersToApply.PermissionString = &pString
280+
}
281+
276282
if err != nil {
277283
return "Getting permissions", err
278284
}

0 commit comments

Comments
 (0)