rclone/lib/file/mkdir_windows_go122_test.go

123 lines
6.5 KiB
Go
Raw Normal View History

//go:build windows && go1.22
package file
import (
"fmt"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func checkMkdirAll(t *testing.T, path string, valid bool, errormsgs ...string) {
if valid {
assert.NoError(t, os.MkdirAll(path, 0777))
} else {
err := os.MkdirAll(path, 0777)
assert.Error(t, err)
ok := false
for _, msg := range errormsgs {
if err.Error() == msg {
ok = true
}
}
assert.True(t, ok, fmt.Sprintf("Error message '%v' didn't match any of %v", err, errormsgs))
}
}
func checkMkdirAllSubdirs(t *testing.T, path string, valid bool, errormsgs ...string) {
checkMkdirAll(t, path, valid, errormsgs...)
checkMkdirAll(t, path+`\`, valid, errormsgs...)
checkMkdirAll(t, path+`\parent`, valid, errormsgs...)
checkMkdirAll(t, path+`\parent\`, valid, errormsgs...)
checkMkdirAll(t, path+`\parent\child`, valid, errormsgs...)
checkMkdirAll(t, path+`\parent\child\`, valid, errormsgs...)
}
// Testing paths on existing drive
func TestMkdirAllOnDrive(t *testing.T) {
path := t.TempDir()
dir, err := os.Stat(path)
require.NoError(t, err)
require.True(t, dir.IsDir())
drive := filepath.VolumeName(path)
checkMkdirAll(t, drive, true, "")
checkMkdirAll(t, drive+`\`, true, "")
checkMkdirAll(t, `\\?\`+drive, false, fmt.Sprintf(`mkdir \\?\%s: Access is denied.`, drive)) // This isn't actually a valid Windows path, it worked under go1.21.3 but fails under go1.21.4 and newer
checkMkdirAll(t, `\\?\`+drive+`\`, true, "")
checkMkdirAllSubdirs(t, path, true, "")
checkMkdirAllSubdirs(t, `\\?\`+path, true, "")
}
// Testing paths on unused drive
// This covers the cases that we wanted to improve with our own custom version
// of golang's os.MkdirAll, introduced in PR #5401. Before go1.22 the original
// os.MkdirAll would recurse extended-length paths down to the "\\?" prefix and
// return the noninformative error:
// "mkdir \\?: The filename, directory name, or volume label syntax is incorrect."
// Our version stopped the recursion at drive's root directory, and reported,
// before go1.21.4:
// "mkdir \\?\A:\: The system cannot find the path specified."
// or, starting with go1.21.4:
// "mkdir \\?\A:: The system cannot find the path specified."
// See https://github.com/rclone/rclone/pull/5401.
// Starting with go1.22 golang's os.MkdirAll have similar improvements that made
// our custom version no longer necessary.
func TestMkdirAllOnUnusedDrive(t *testing.T) {
letter := FindUnusedDriveLetter()
require.NotEqual(t, letter, 0)
drive := string(letter) + ":"
checkMkdirAll(t, drive, false, fmt.Sprintf(`mkdir %s: The system cannot find the path specified.`, drive))
checkMkdirAll(t, drive+`\`, false, fmt.Sprintf(`mkdir %s\: The system cannot find the path specified.`, drive))
checkMkdirAll(t, drive+`\parent`, false, fmt.Sprintf(`mkdir %s\parent: The system cannot find the path specified.`, drive))
checkMkdirAll(t, drive+`\parent\`, false, fmt.Sprintf(`mkdir %s\parent\: The system cannot find the path specified.`, drive))
checkMkdirAll(t, drive+`\parent\child`, false, fmt.Sprintf(`mkdir %s\parent: The system cannot find the path specified.`, drive))
checkMkdirAll(t, drive+`\parent\child\`, false, fmt.Sprintf(`mkdir %s\parent: The system cannot find the path specified.`, drive))
drive = `\\?\` + drive
checkMkdirAll(t, drive, false, fmt.Sprintf(`mkdir %s: The system cannot find the file specified.`, drive))
checkMkdirAll(t, drive+`\`, false, fmt.Sprintf(`mkdir %s\: The system cannot find the path specified.`, drive))
checkMkdirAll(t, drive+`\parent`, false, fmt.Sprintf(`mkdir %s\parent: The system cannot find the path specified.`, drive))
checkMkdirAll(t, drive+`\parent\`, false, fmt.Sprintf(`mkdir %s\parent\: The system cannot find the path specified.`, drive))
checkMkdirAll(t, drive+`\parent\child`, false, fmt.Sprintf(`mkdir %s\parent: The system cannot find the path specified.`, drive))
checkMkdirAll(t, drive+`\parent\child\`, false, fmt.Sprintf(`mkdir %s\parent: The system cannot find the path specified.`, drive))
}
// Testing paths on unknown network host
// This covers more cases that we wanted to improve in our custom version of
// golang's os.MkdirAll, extending that explained in TestMkdirAllOnUnusedDrive.
// With our first fix, stopping it from recursing extended-length paths down to
// the "\\?" prefix, it would now stop at `\\?\UNC`, because that is what
// filepath.VolumeName returns (which is wrong, that is not a volume name!),
// and still return a noninformative error:
// "mkdir \\?\UNC\\: The filename, directory name, or volume label syntax is incorrect."
// Our version stopped the recursion at level before this, and reports:
// "mkdir \\?\UNC\0.0.0.0: The specified path is invalid."
// See https://github.com/rclone/rclone/pull/6420.
// Starting with go1.22 golang's os.MkdirAll have similar improvements that made
// our custom version no longer necessary.
func TestMkdirAllOnUnusedNetworkHost(t *testing.T) {
sharePath := `\\0.0.0.0\share`
checkMkdirAll(t, sharePath, false, fmt.Sprintf(`mkdir %s: The format of the specified network name is invalid.`, sharePath))
checkMkdirAll(t, sharePath+`\`, false, fmt.Sprintf(`mkdir %s\: The format of the specified network name is invalid.`, sharePath))
checkMkdirAll(t, sharePath+`\parent`, false, fmt.Sprintf(`mkdir %s\parent: The format of the specified network name is invalid.`, sharePath))
checkMkdirAll(t, sharePath+`\parent\`, false, fmt.Sprintf(`mkdir %s\parent\: The format of the specified network name is invalid.`, sharePath))
checkMkdirAll(t, sharePath+`\parent\child`, false, fmt.Sprintf(`mkdir %s\parent: The format of the specified network name is invalid.`, sharePath))
checkMkdirAll(t, sharePath+`\parent\child\`, false, fmt.Sprintf(`mkdir %s\parent: The format of the specified network name is invalid.`, sharePath))
serverPath := `\\?\UNC\0.0.0.0`
sharePath = serverPath + `\share`
checkMkdirAll(t, sharePath, false, fmt.Sprintf(`mkdir %s: The specified path is invalid.`, serverPath))
checkMkdirAll(t, sharePath+`\`, false, fmt.Sprintf(`mkdir %s: The specified path is invalid.`, serverPath))
checkMkdirAll(t, sharePath+`\parent`, false, fmt.Sprintf(`mkdir %s: The specified path is invalid.`, serverPath))
checkMkdirAll(t, sharePath+`\parent\`, false, fmt.Sprintf(`mkdir %s: The specified path is invalid.`, serverPath))
checkMkdirAll(t, sharePath+`\parent\child`, false, fmt.Sprintf(`mkdir %s: The specified path is invalid.`, serverPath))
checkMkdirAll(t, sharePath+`\parent\child\`, false, fmt.Sprintf(`mkdir %s: The specified path is invalid.`, serverPath))
}