Skip to content

Commit

Permalink
[Storage] [DataMovement] Fixed File Attributes with ReadOnly does not…
Browse files Browse the repository at this point in the history
… transfer / copy correctly bug (#48096)

* Initial commit

* initial commit

* reverted spaces

* Removed _destinationPermissionKey

* Added changelog + addressed if property is null
  • Loading branch information
nickliu-msft authored Feb 4, 2025
1 parent 479b84f commit 13e31b3
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@ internal async Task CreateAsync(
IDictionary<string, string> 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,
Expand All @@ -90,20 +96,27 @@ 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,
CancellationToken cancellationToken = default)
{
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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ private async Task<ShareFileClient> CreateFileClientWithPermissionKeyAsync(
ShareClientOptions options = null,
Stream contents = null,
TransferPropertiesTestType propertiesType = TransferPropertiesTestType.Default,
NtfsFileAttributes fileAttribute = _defaultFileAttributes,
CancellationToken cancellationToken = default)
{
objectName ??= GetNewObjectName();
Expand All @@ -145,14 +146,14 @@ private async Task<ShareFileClient> 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,
Expand All @@ -161,7 +162,7 @@ await fileClient.CreateAsync(
Metadata = _defaultMetadata,
SmbProperties = new FileSmbProperties()
{
FileAttributes = _defaultFileAttributes,
FileAttributes = fileAttribute,
FilePermissionKey = permissionKey,
FileCreatedOn = _defaultFileCreatedOn,
FileChangedOn = _defaultFileChangedOn,
Expand Down Expand Up @@ -236,7 +237,7 @@ protected override StorageResourceItem GetDestinationStorageResourceItem(
ContentLanguage = _defaultContentLanguage,
CacheControl = _defaultCacheControl,
ContentType = _defaultContentType,
FileMetadata = _defaultMetadata,
FileMetadata = _defaultMetadata,
FileAttributes = _defaultFileAttributes,
FileCreatedOn = _defaultFileCreatedOn,
FileChangedOn = _defaultFileChangedOn,
Expand Down Expand Up @@ -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<ShareClient> source = await GetSourceDisposingContainerAsync();
await using IDisposingContainer<ShareClient> 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<ShareClient> source = await GetSourceDisposingContainerAsync();
await using IDisposingContainer<ShareClient> 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()
{
Expand Down

0 comments on commit 13e31b3

Please sign in to comment.