From be5fe5e4b5efc0d8358ed4c6544ec13cf8ac3f47 Mon Sep 17 00:00:00 2001 From: vupadhye Date: Wed, 18 Feb 2026 09:56:07 -0800 Subject: [PATCH 1/5] add nil checks for file share properties --- cmd/zc_newobjectadapters.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/cmd/zc_newobjectadapters.go b/cmd/zc_newobjectadapters.go index f9e96038f5..2c2b02c470 100644 --- a/cmd/zc_newobjectadapters.go +++ b/cmd/zc_newobjectadapters.go @@ -349,18 +349,30 @@ type shareDirectoryFilePropertiesAdapter struct { } func (a shareDirectoryFilePropertiesAdapter) Metadata() common.Metadata { + if a.FileProperty == nil { + return nil + } return nil } func (a shareDirectoryFilePropertiesAdapter) LastModified() time.Time { + if a.FileProperty == nil { + return time.Time{} + } return common.IffNotNil(a.FileProperty.LastModified, time.Time{}) } func (a shareDirectoryFilePropertiesAdapter) FileLastWriteTime() time.Time { + if a.FileProperty == nil { + return time.Time{} + } return common.IffNotNil(a.FileProperty.LastWriteTime, time.Time{}) } func (a shareDirectoryFilePropertiesAdapter) FileChangeTime() time.Time { + if a.FileProperty == nil { + return time.Time{} + } return common.IffNotNil(a.FileProperty.ChangeTime, time.Time{}) } From 95b2a4585513684cf8deb2eb246bfaf313b09bc4 Mon Sep 17 00:00:00 2001 From: vupadhye Date: Wed, 18 Feb 2026 23:18:06 -0800 Subject: [PATCH 2/5] add another nil check --- cmd/zc_newobjectadapters.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmd/zc_newobjectadapters.go b/cmd/zc_newobjectadapters.go index 2c2b02c470..55134650fb 100644 --- a/cmd/zc_newobjectadapters.go +++ b/cmd/zc_newobjectadapters.go @@ -401,6 +401,9 @@ func (a shareDirectoryFilePropertiesAdapter) ContentMD5() []byte { } func (a shareDirectoryFilePropertiesAdapter) ContentLength() int64 { + if a.FileProperty == nil { + return 0 + } return common.IffNotNil(a.FileProperty.ContentLength, 0) } From 74be1287bf911a9ee18cbe2bdd78daef9b264a38 Mon Sep 17 00:00:00 2001 From: vupadhye Date: Thu, 19 Feb 2026 15:25:37 -0800 Subject: [PATCH 3/5] support for file to file sync --- cmd/syncEnumerator.go | 2 +- cmd/syncOrchestrator.go | 19 +++++++++++++++++-- cmd/syncOrchestratorModels.go | 4 ++-- cmd/syncThrottler.go | 2 ++ 4 files changed, 22 insertions(+), 5 deletions(-) diff --git a/cmd/syncEnumerator.go b/cmd/syncEnumerator.go index 9124fce958..2ef38b505a 100644 --- a/cmd/syncEnumerator.go +++ b/cmd/syncEnumerator.go @@ -533,7 +533,7 @@ func GetSyncEnumeratorWithDestComparator( } destCleanerFunc := newFpoAwareProcessor(fpo, destinationCleaner.removeImmediately) - if UseSyncOrchestrator && (cca.fromTo == common.EFromTo.S3Blob() || cca.fromTo == common.EFromTo.BlobBlob() || cca.fromTo == common.EFromTo.BlobFSBlob()) { + if UseSyncOrchestrator && (cca.fromTo == common.EFromTo.S3Blob() || cca.fromTo == common.EFromTo.BlobBlob() || cca.fromTo == common.EFromTo.BlobFSBlob() || cca.fromTo == common.EFromTo.FileFile()) { // newFpoAwareProcessor sets the destCleanerFunc to nil for a non folder aware source destination pair like S3->Blob. // But in case of SyncOrchestrator, S3->Blob sync does recursive deletion for a prefix. // This requires a valid deletion processor to be passed to the comparator. diff --git a/cmd/syncOrchestrator.go b/cmd/syncOrchestrator.go index 56011e1191..2e4749a831 100644 --- a/cmd/syncOrchestrator.go +++ b/cmd/syncOrchestrator.go @@ -38,6 +38,7 @@ import ( "time" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azfile/share" "github.com/Azure/azure-storage-azcopy/v10/common" "github.com/Azure/azure-storage-azcopy/v10/common/parallel" ) @@ -69,6 +70,8 @@ var ( "BlobNotFound", "PathNotFound", "ResourceNotFound", + "ShareNotFound", + "ShareBeingDeleted", } orchestratorOptions *SyncOrchestratorOptions @@ -200,6 +203,15 @@ func validateLocalRoot(path string) error { return nil } +// validateFileRoot validates a File root URL. +func validateFileRoot(sourcePath string) error { + _, err := share.ParseURL(sourcePath) + if err != nil { + return err + } + return nil +} + // validateS3Root returns the root object for the sync orchestrator based on the S3 source path. // It parses the S3 URL and determines the entity type (file or folder) based on the URL structure. // @@ -269,6 +281,8 @@ func validateAndGetRootObject(path string, fromTo common.FromTo) (minimalStoredO err = validateBlobRoot(path) case common.ELocation.BlobFS(): err = validateBlobFSRoot(path) + case common.ELocation.File(): + err = validateFileRoot(path) default: err = fmt.Errorf("sync orchestrator is not supported for %s source", fromTo.From().String()) } @@ -545,7 +559,7 @@ func newSyncTraverser(enumerator *syncEnumerator, dir string, comparator objectP func validate(cca *cookedSyncCmdArgs, orchestratorOptions *SyncOrchestratorOptions) error { switch cca.fromTo { - case common.EFromTo.LocalBlob(), common.EFromTo.LocalBlobFS(), common.EFromTo.LocalFile(), common.EFromTo.S3Blob(), common.EFromTo.BlobBlob(), common.EFromTo.BlobBlobFS(), common.EFromTo.BlobFSBlob(), common.EFromTo.BlobFSBlobFS(): + case common.EFromTo.LocalBlob(), common.EFromTo.LocalBlobFS(), common.EFromTo.LocalFile(), common.EFromTo.S3Blob(), common.EFromTo.BlobBlob(), common.EFromTo.BlobBlobFS(), common.EFromTo.BlobFSBlob(), common.EFromTo.BlobFSBlobFS(), common.EFromTo.FileFile(): // sync orchestrator is supported for these types default: return fmt.Errorf( @@ -557,7 +571,8 @@ func validate(cca *cookedSyncCmdArgs, orchestratorOptions *SyncOrchestratorOptio "\t- Blob->Blob\n" + "\t- Blob->BlobFS\n" + "\t- BlobFS->Blob\n" + - "\t- BlobFS->BlobFS", + "\t- BlobFS->BlobFS\n" + + "\t- File->File", ) } diff --git a/cmd/syncOrchestratorModels.go b/cmd/syncOrchestratorModels.go index c6bff9711e..f804fe1a77 100644 --- a/cmd/syncOrchestratorModels.go +++ b/cmd/syncOrchestratorModels.go @@ -77,8 +77,8 @@ func (s *SyncOrchestratorOptions) validate(from common.Location) error { return errors.New("sync orchestrator options should only be used when UseSyncOrchestrator is true") } - if from != common.ELocation.Local() && from != common.ELocation.S3() && from != common.ELocation.Blob() && from != common.ELocation.BlobFS() { - return errors.New("sync optimizations using timestamps should only be used for local to remote syncs") + if from != common.ELocation.Local() && from != common.ELocation.S3() && from != common.ELocation.Blob() && from != common.ELocation.BlobFS() && from != common.ELocation.File() { + return errors.New("sync optimizations using timestamps should only be used for supported source locations (Local, S3, Blob, BlobFS, File)") } if s.maxDirectoryDirectChildCount == 0 { diff --git a/cmd/syncThrottler.go b/cmd/syncThrottler.go index e2ba1caa8c..21514bbb3e 100644 --- a/cmd/syncThrottler.go +++ b/cmd/syncThrottler.go @@ -197,6 +197,8 @@ func GetSafeParallelismLimit(maxActiveFiles, maxChildCount int64, fromTo common. return min(limit, 48) case common.EFromTo.S3Blob(): return min(limit, 64) + case common.EFromTo.FileFile(): + return min(limit, 48) default: return min(limit, 48) } From e7f7fecbbce83628b4634a98cb2f74d95fda3a76 Mon Sep 17 00:00:00 2001 From: vupadhye Date: Fri, 20 Feb 2026 14:24:13 -0800 Subject: [PATCH 4/5] fix --- cmd/syncOrchestrator.go | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/cmd/syncOrchestrator.go b/cmd/syncOrchestrator.go index 2e4749a831..e8c75cd5f0 100644 --- a/cmd/syncOrchestrator.go +++ b/cmd/syncOrchestrator.go @@ -203,13 +203,24 @@ func validateLocalRoot(path string) error { return nil } -// validateFileRoot validates a File root URL. -func validateFileRoot(sourcePath string) error { - _, err := share.ParseURL(sourcePath) - if err != nil { - return err - } - return nil +// validateFileShareRoot validates an Azure Files share root URL +func validateFileShareRoot(sourcePath string) error { + // Parse as a URL to validate format + parsedURL, err := url.Parse(sourcePath) + if err != nil { + return err + } + + // Basic validation - should be a valid URL with file.core.windows.net host + if parsedURL.Host == "" { + return fmt.Errorf("invalid Azure Files URL: missing host") + } + + if !strings.Contains(parsedURL.Host, "file.core.windows.net") { + return fmt.Errorf("invalid Azure Files URL: expected file.core.windows.net host") + } + + return nil } // validateS3Root returns the root object for the sync orchestrator based on the S3 source path. @@ -282,7 +293,7 @@ func validateAndGetRootObject(path string, fromTo common.FromTo) (minimalStoredO case common.ELocation.BlobFS(): err = validateBlobFSRoot(path) case common.ELocation.File(): - err = validateFileRoot(path) + err = validateFileShareRoot(path) default: err = fmt.Errorf("sync orchestrator is not supported for %s source", fromTo.From().String()) } From 40b13f69886f579b4d642c7e4e1bfecf1019c516 Mon Sep 17 00:00:00 2001 From: vupadhye Date: Fri, 20 Feb 2026 15:04:58 -0800 Subject: [PATCH 5/5] minor fix --- cmd/syncOrchestrator.go | 1 - 1 file changed, 1 deletion(-) diff --git a/cmd/syncOrchestrator.go b/cmd/syncOrchestrator.go index e8c75cd5f0..f38db7b740 100644 --- a/cmd/syncOrchestrator.go +++ b/cmd/syncOrchestrator.go @@ -38,7 +38,6 @@ import ( "time" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob" - "github.com/Azure/azure-sdk-for-go/sdk/storage/azfile/share" "github.com/Azure/azure-storage-azcopy/v10/common" "github.com/Azure/azure-storage-azcopy/v10/common/parallel" )