diff --git a/sdk/storage/Azure.Storage.DataMovement.Files.Shares/CHANGELOG.md b/sdk/storage/Azure.Storage.DataMovement.Files.Shares/CHANGELOG.md index 812ec986f4dd..3e49f7a85cb2 100644 --- a/sdk/storage/Azure.Storage.DataMovement.Files.Shares/CHANGELOG.md +++ b/sdk/storage/Azure.Storage.DataMovement.Files.Shares/CHANGELOG.md @@ -31,6 +31,7 @@ - Changed `FromClient` methods to `static` methods. ### Bugs Fixed +- Fixed File Attributes with ReadOnly does not transfer / copy correctly bug #2167 ### Other Changes diff --git a/sdk/storage/Azure.Storage.DataMovement.Files.Shares/assets.json b/sdk/storage/Azure.Storage.DataMovement.Files.Shares/assets.json index 9881fa206fb1..969cac62b2e1 100644 --- a/sdk/storage/Azure.Storage.DataMovement.Files.Shares/assets.json +++ b/sdk/storage/Azure.Storage.DataMovement.Files.Shares/assets.json @@ -2,5 +2,5 @@ "AssetsRepo": "Azure/azure-sdk-assets", "AssetsRepoPrefixPath": "net", "TagPrefix": "net/storage/Azure.Storage.DataMovement.Files.Shares", - "Tag": "net/storage/Azure.Storage.DataMovement.Files.Shares_1510e7b5ae" + "Tag": "net/storage/Azure.Storage.DataMovement.Files.Shares_147f0416cd" } diff --git a/sdk/storage/Azure.Storage.DataMovement.Files.Shares/src/ShareFileStorageResource.cs b/sdk/storage/Azure.Storage.DataMovement.Files.Shares/src/ShareFileStorageResource.cs index 5046111ad6a9..d09d06a40fb7 100644 --- a/sdk/storage/Azure.Storage.DataMovement.Files.Shares/src/ShareFileStorageResource.cs +++ b/sdk/storage/Azure.Storage.DataMovement.Files.Shares/src/ShareFileStorageResource.cs @@ -80,6 +80,12 @@ internal async Task CreateAsync( IDictionary metadata = _options?.GetFileMetadata(properties?.RawProperties); string filePermission = _options?.GetFilePermission(properties?.RawProperties); FileSmbProperties smbProperties = _options?.GetFileSmbProperties(properties, _destinationPermissionKey); + // if transfer is not empty and File Attribute contains ReadOnly, we should not set it before creating the file. + if ((properties == null || properties.ResourceLength > 0) && IsReadOnlySet(smbProperties.FileAttributes)) + { + smbProperties.FileAttributes = default; + } + await ShareFileClient.CreateAsync( maxSize: maxSize, httpHeaders: httpHeaders, @@ -90,6 +96,11 @@ await ShareFileClient.CreateAsync( cancellationToken: cancellationToken).ConfigureAwait(false); } + private bool IsReadOnlySet(NtfsFileAttributes? fileAttributes) + { + return fileAttributes?.HasFlag(NtfsFileAttributes.ReadOnly) ?? false; + } + protected override async Task CompleteTransferAsync( bool overwrite, StorageResourceCompleteTransferOptions completeTransferOptions, @@ -97,13 +108,15 @@ protected override async Task CompleteTransferAsync( { CancellationHelper.ThrowIfCancellationRequested(cancellationToken); - // Call Set Properties if FileChangedOn is to be preserved or manually set - // as it can be changed during a transfer. - if (_options?._isFileChangedOnSet == false || _options?.FileChangedOn != null) + StorageResourceItemProperties sourceProperties = completeTransferOptions?.SourceProperties; + FileSmbProperties smbProperties = _options?.GetFileSmbProperties(sourceProperties); + // Call Set Properties + // if transfer is not empty and original File Attribute contains ReadOnly + // or if FileChangedOn is to be preserved or manually set + if (((sourceProperties == null || sourceProperties.ResourceLength > 0) && IsReadOnlySet(smbProperties.FileAttributes)) + || (_options?._isFileChangedOnSet == false || _options?.FileChangedOn != null)) { - StorageResourceItemProperties sourceProperties = completeTransferOptions?.SourceProperties; ShareFileHttpHeaders httpHeaders = _options?.GetShareFileHttpHeaders(sourceProperties?.RawProperties); - FileSmbProperties smbProperties = _options?.GetFileSmbProperties(sourceProperties); await ShareFileClient.SetHttpHeadersAsync(new() { HttpHeaders = httpHeaders, diff --git a/sdk/storage/Azure.Storage.DataMovement.Files.Shares/tests/ShareFileStartTransferCopyTests.cs b/sdk/storage/Azure.Storage.DataMovement.Files.Shares/tests/ShareFileStartTransferCopyTests.cs index dab95c9776c5..9a6e28b1bbec 100644 --- a/sdk/storage/Azure.Storage.DataMovement.Files.Shares/tests/ShareFileStartTransferCopyTests.cs +++ b/sdk/storage/Azure.Storage.DataMovement.Files.Shares/tests/ShareFileStartTransferCopyTests.cs @@ -132,6 +132,7 @@ private async Task CreateFileClientWithPermissionKeyAsync( ShareClientOptions options = null, Stream contents = null, TransferPropertiesTestType propertiesType = TransferPropertiesTestType.Default, + NtfsFileAttributes fileAttribute = _defaultFileAttributes, CancellationToken cancellationToken = default) { objectName ??= GetNewObjectName(); @@ -145,14 +146,14 @@ private async Task CreateFileClientWithPermissionKeyAsync( string permissionKey = default; if (propertiesType == TransferPropertiesTestType.Preserve) { - PermissionInfo permissionInfo = await container.CreatePermissionAsync(new ShareFilePermission() { Permission = _defaultPermissions } ); + PermissionInfo permissionInfo = await container.CreatePermissionAsync(new ShareFilePermission() { Permission = _defaultPermissions }); permissionKey = permissionInfo.FilePermissionKey; } await fileClient.CreateAsync( maxSize: objectLength.Value, new ShareFileCreateOptions() { - HttpHeaders = new ShareFileHttpHeaders() + HttpHeaders = new ShareFileHttpHeaders() { ContentLanguage = _defaultContentLanguage, ContentDisposition = _defaultContentDisposition, @@ -161,7 +162,7 @@ await fileClient.CreateAsync( Metadata = _defaultMetadata, SmbProperties = new FileSmbProperties() { - FileAttributes = _defaultFileAttributes, + FileAttributes = fileAttribute, FilePermissionKey = permissionKey, FileCreatedOn = _defaultFileCreatedOn, FileChangedOn = _defaultFileChangedOn, @@ -236,7 +237,7 @@ protected override StorageResourceItem GetDestinationStorageResourceItem( ContentLanguage = _defaultContentLanguage, CacheControl = _defaultCacheControl, ContentType = _defaultContentType, - FileMetadata = _defaultMetadata, + FileMetadata = _defaultMetadata, FileAttributes = _defaultFileAttributes, FileCreatedOn = _defaultFileCreatedOn, FileChangedOn = _defaultFileChangedOn, @@ -416,6 +417,111 @@ await VerifyPropertiesCopyAsync( cancellationToken: cancellationTokenSource.Token); } + [RecordedTest] + [Combinatorial] + public async Task ShareFileToShareFile_ManuallySetFileAttributes( + [Values(NtfsFileAttributes.ReadOnly, NtfsFileAttributes.Hidden, NtfsFileAttributes.Archive, (NtfsFileAttributes.ReadOnly | NtfsFileAttributes.Archive), (NtfsFileAttributes.ReadOnly | NtfsFileAttributes.Hidden))] NtfsFileAttributes fileAttribute, + [Values(0, 1024)] long fileLength) + { + // Arrange + await using IDisposingContainer source = await GetSourceDisposingContainerAsync(); + await using IDisposingContainer destination = await GetDestinationDisposingContainerAsync(); + + // Create source + ShareFileClient sourceClient = await CreateFileClientWithPermissionKeyAsync( + container: source.Container, + objectLength: fileLength, + createResource: true); + StorageResourceItem sourceResource = GetSourceStorageResourceItem(sourceClient); + + // Create destination + ShareFileClient destinationClient = await GetDestinationObjectClientAsync( + container: destination.Container, + createResource: false); + // Manually setting destination File Attributes + ShareFileStorageResourceOptions destFileAttributeOptions = new(); + destFileAttributeOptions.FileAttributes = fileAttribute; + StorageResourceItem destinationResource = new ShareFileStorageResource(destinationClient, destFileAttributeOptions); + + TransferOptions options = new TransferOptions(); + TestEventsRaised testEventsRaised = new TestEventsRaised(options); + TransferManager transferManager = new TransferManager(); + + // Act - Start transfer and await for completion. + TransferOperation transfer = await transferManager.StartTransferAsync( + sourceResource, + destinationResource, + options); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30)); + await TestTransferWithTimeout.WaitForCompletionAsync( + transfer, + testEventsRaised, + cancellationTokenSource.Token); + + // Assert + Assert.IsTrue(transfer.HasCompleted); + Assert.AreEqual(TransferState.Completed, transfer.Status.State); + await testEventsRaised.AssertSingleCompletedCheck(); + using Stream sourceStream = await sourceClient.OpenReadAsync(cancellationToken: cancellationTokenSource.Token); + using Stream destinationStream = await destinationClient.OpenReadAsync(cancellationToken: cancellationTokenSource.Token); + Assert.AreEqual(sourceStream, destinationStream); + // Verify destination File Attributes + ShareFileProperties destinationProperties = await destinationClient.GetPropertiesAsync(cancellationToken: cancellationTokenSource.Token); + Assert.AreEqual(fileAttribute, destinationProperties.SmbProperties.FileAttributes); + } + + [RecordedTest] + [Combinatorial] + public async Task ShareFileToShareFile_PreserveFromSourceFileAttributes( + [Values(NtfsFileAttributes.ReadOnly, NtfsFileAttributes.Hidden, NtfsFileAttributes.Archive, (NtfsFileAttributes.ReadOnly | NtfsFileAttributes.Archive), (NtfsFileAttributes.ReadOnly | NtfsFileAttributes.Hidden))] NtfsFileAttributes fileAttribute, + [Values(0, 1024)] long fileLength) + { + // Arrange + await using IDisposingContainer source = await GetSourceDisposingContainerAsync(); + await using IDisposingContainer destination = await GetDestinationDisposingContainerAsync(); + + // Create source + ShareFileClient sourceClient = await CreateFileClientWithPermissionKeyAsync( + container: source.Container, + objectLength: fileLength, + createResource: true, + fileAttribute: fileAttribute); + // Manually setting source File Attributes + ShareFileStorageResourceOptions sourceFileAttributeOptions = new(); + sourceFileAttributeOptions.FileAttributes = fileAttribute; + StorageResourceItem sourceResource = new ShareFileStorageResource(sourceClient, sourceFileAttributeOptions); + + // Create destination + ShareFileClient destinationClient = await GetDestinationObjectClientAsync( + container: destination.Container, + createResource: false); + // No options set, so preserve from source + StorageResourceItem destinationResource = new ShareFileStorageResource(destinationClient); + + TransferOptions options = new TransferOptions(); + TestEventsRaised testEventsRaised = new TestEventsRaised(options); + TransferManager transferManager = new TransferManager(); + + // Act - Start transfer and await for completion. + TransferOperation transfer = await transferManager.StartTransferAsync( + sourceResource, + destinationResource, + options); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30)); + await TestTransferWithTimeout.WaitForCompletionAsync( + transfer, + testEventsRaised, + cancellationTokenSource.Token); + + // Assert + Assert.IsTrue(transfer.HasCompleted); + Assert.AreEqual(TransferState.Completed, transfer.Status.State); + await testEventsRaised.AssertSingleCompletedCheck(); + using Stream sourceStream = await sourceClient.OpenReadAsync(cancellationToken: cancellationTokenSource.Token); + using Stream destinationStream = await destinationClient.OpenReadAsync(cancellationToken: cancellationTokenSource.Token); + Assert.AreEqual(sourceStream, destinationStream); + } + [RecordedTest] public async Task ShareFileToShareFile_PermissionKeyDefault() {