Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[UX][GitHub] azcopy cp fails if the destination path is at the root of the file system (i.e. /) #2588

Merged
merged 10 commits into from
Mar 13, 2024
16 changes: 12 additions & 4 deletions common/writeThoughFile.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
package common

import (
"fmt"
"os"
"regexp"
"strings"
Expand All @@ -44,19 +45,26 @@ var RootShareRegex = regexp.MustCompile(`(^\/\/[^\/]*\/?$)`)

func isRootPath(s string) bool {
shortParentDir := strings.ReplaceAll(ToShortPath(s), OS_PATH_SEPARATOR, AZCOPY_PATH_SEPARATOR_STRING)
return RootDriveRegex.MatchString(shortParentDir) ||
RootShareRegex.MatchString(shortParentDir) ||
return RootDriveRegex.MatchString(shortParentDir) ||
RootShareRegex.MatchString(shortParentDir) ||
strings.EqualFold(shortParentDir, "/")
}


func CreateParentDirectoryIfNotExist(destinationPath string, tracker FolderCreationTracker) error {
// If we're pointing at the root of a drive, don't try because it won't work.
if isRootPath(destinationPath) {
return nil
}

lastIndex := strings.LastIndex(destinationPath, DeterminePathSeparator(destinationPath))
pathSeparator := DeterminePathSeparator(destinationPath)
lastIndex := strings.LastIndex(destinationPath, pathSeparator)

// LastIndex() will return -1 if path separator was not found, we should handle this gracefully
// instead of allowing AzCopy to crash with an out-of-bounds error.
if lastIndex == -1 {
return fmt.Errorf("error: Path separator (%s) not found in destination path. On Linux, this may occur if the destination is the root file, such as '/'. If this is the case, please consider changing your destination path.", pathSeparator)
}

directory := destinationPath[:lastIndex]
return CreateDirectoryIfNotExist(directory, tracker)
}
Expand Down
67 changes: 67 additions & 0 deletions common/writeThroughFile_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright © Microsoft <wastore@microsoft.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package common_test

import (
"github.com/Azure/azure-storage-azcopy/v10/common"
"github.com/Azure/azure-storage-azcopy/v10/ste"
"github.com/stretchr/testify/assert"
"os"
"testing"
)

func TestCreateParentDirectoryIfNotExist(t *testing.T) {
a := assert.New(t)

// set up job part manager
plan := &ste.JobPartPlanHeader{}
fpo := common.EFolderPropertiesOption.AllFolders()

tracker := ste.NewFolderCreationTracker(fpo, plan)
fileName := "stuff.txt"

// when destination path is defined as "/" in linux, the source file becomes the destination path string
// AzCopy reaches out of bounds error, returns user friendly error
err := common.CreateParentDirectoryIfNotExist(fileName, tracker)
pathSep := common.DeterminePathSeparator(fileName)
a.Errorf(err, "error: Path separator "+pathSep+" not found in destination path. On Linux, this may occur if the destination is the root file, such as '/'. If this is the case, please consider changing your destination path.")

// in the case where destination file is specified with root "/" and source file name "stuff.txt" in linux,
// the system will fail to create the directory
err = common.CreateParentDirectoryIfNotExist("/"+fileName, tracker)
a.Errorf(err, "mkdir : The system cannot find the path specified.")

// when relative path provided (i.e., "/stuff.txt", or "stuff.txt") in windows,
// full path is passed as destination string and destination path string will be "C://path/to/file/stuff.txt"
// this will safely complete and return nil
file, err := os.Create(fileName)
a.NoError(err)
defer os.Remove(file.Name())
defer file.Close()

// Get the file path
path, err := os.Getwd()
a.NoError(err)

fullPath := path + "\\" + fileName
err = common.CreateParentDirectoryIfNotExist(fullPath, tracker)
a.Nil(err)
}
2 changes: 1 addition & 1 deletion ste/testJobPartTransferManager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@ func (t *testJobPartTransferManager) GetOverwritePrompter() *overwritePrompter {
}

func (t *testJobPartTransferManager) GetFolderCreationTracker() FolderCreationTracker {
panic("implement me")
return t.jobPartMgr.getFolderCreationTracker()
}

func (t *testJobPartTransferManager) ShouldLog(level common.LogLevel) bool {
Expand Down
Loading