From 5bd95b3ce1a43b59559c8e705f862f2ad1a5179f Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Sat, 15 Apr 2017 10:53:12 +0200 Subject: [PATCH] Implement MkdirAll() for Windows Closes #735 --- src/restic/fs/file.go | 38 --------------- src/restic/fs/file_unix.go | 19 ++++++++ src/restic/fs/file_windows.go | 87 +++++++++++++++++++++++++++++++++++ 3 files changed, 106 insertions(+), 38 deletions(-) create mode 100644 src/restic/fs/file_unix.go create mode 100644 src/restic/fs/file_windows.go diff --git a/src/restic/fs/file.go b/src/restic/fs/file.go index 2ba5d138e..e23da2bcb 100644 --- a/src/restic/fs/file.go +++ b/src/restic/fs/file.go @@ -4,8 +4,6 @@ import ( "io" "os" "path/filepath" - "runtime" - "strings" ) // File is an open file on a file system. @@ -21,31 +19,6 @@ type File interface { Stat() (os.FileInfo, error) } -// fixpath returns an absolute path on windows, so restic can open long file -// names. -func fixpath(name string) string { - if runtime.GOOS == "windows" { - abspath, err := filepath.Abs(name) - if err == nil { - // Check if \\?\UNC\ already exist - if strings.HasPrefix(abspath, `\\?\UNC\`) { - return abspath - } - // Check if \\?\ already exist - if strings.HasPrefix(abspath, `\\?\`) { - return abspath - } - // Check if path starts with \\ - if strings.HasPrefix(abspath, `\\`) { - return strings.Replace(abspath, `\\`, `\\?\UNC\`, 1) - } - // Normal path - return `\\?\` + abspath - } - } - return name -} - // Chmod changes the mode of the named file to mode. func Chmod(name string, mode os.FileMode) error { return os.Chmod(fixpath(name), mode) @@ -57,17 +30,6 @@ func Mkdir(name string, perm os.FileMode) error { return os.Mkdir(fixpath(name), perm) } -// MkdirAll creates a directory named path, -// along with any necessary parents, and returns nil, -// or else returns an error. -// The permission bits perm are used for all -// directories that MkdirAll creates. -// If path is already a directory, MkdirAll does nothing -// and returns nil. -func MkdirAll(path string, perm os.FileMode) error { - return os.MkdirAll(fixpath(path), perm) -} - // Readlink returns the destination of the named symbolic link. // If there is an error, it will be of type *PathError. func Readlink(name string) (string, error) { diff --git a/src/restic/fs/file_unix.go b/src/restic/fs/file_unix.go new file mode 100644 index 000000000..4c6da3964 --- /dev/null +++ b/src/restic/fs/file_unix.go @@ -0,0 +1,19 @@ +// +build !windows + +package fs + +import "os" + +// fixpath returns an absolute path on windows, so restic can open long file +// names. +func fixpath(name string) string { + return name +} + +// MkdirAll creates a directory named path, along with any necessary parents, +// and returns nil, or else returns an error. The permission bits perm are used +// for all directories that MkdirAll creates. If path is already a directory, +// MkdirAll does nothing and returns nil. +func MkdirAll(path string, perm os.FileMode) error { + return os.MkdirAll(fixpath(path), perm) +} diff --git a/src/restic/fs/file_windows.go b/src/restic/fs/file_windows.go new file mode 100644 index 000000000..8f1df7967 --- /dev/null +++ b/src/restic/fs/file_windows.go @@ -0,0 +1,87 @@ +package fs + +import ( + "os" + "path/filepath" + "strings" + "syscall" +) + +// fixpath returns an absolute path on windows, so restic can open long file +// names. +func fixpath(name string) string { + abspath, err := filepath.Abs(name) + if err == nil { + // Check if \\?\UNC\ already exist + if strings.HasPrefix(abspath, `\\?\UNC\`) { + return abspath + } + // Check if \\?\ already exist + if strings.HasPrefix(abspath, `\\?\`) { + return abspath + } + // Check if path starts with \\ + if strings.HasPrefix(abspath, `\\`) { + return strings.Replace(abspath, `\\`, `\\?\UNC\`, 1) + } + // Normal path + return `\\?\` + abspath + } + return name +} + +// MkdirAll creates a directory named path, along with any necessary parents, +// and returns nil, or else returns an error. The permission bits perm are used +// for all directories that MkdirAll creates. If path is already a directory, +// MkdirAll does nothing and returns nil. +// +// Adapted from the stdlib MkdirAll, added test for volume name. +func MkdirAll(path string, perm os.FileMode) error { + // Fast path: if we can tell whether path is a directory or file, stop with success or error. + dir, err := os.Stat(path) + if err == nil { + if dir.IsDir() { + return nil + } + return &os.PathError{ + Op: "mkdir", + Path: path, + Err: syscall.ENOTDIR, + } + } + + // Slow path: make sure parent exists and then call Mkdir for path. + i := len(path) + for i > 0 && os.IsPathSeparator(path[i-1]) { // Skip trailing path separator. + i-- + } + + j := i + for j > 0 && !os.IsPathSeparator(path[j-1]) { // Scan backward over element. + j-- + } + + if j > 1 { + // Create parent + parent := path[0 : j-1] + if parent != filepath.VolumeName(parent) { + err = MkdirAll(parent, perm) + if err != nil { + return err + } + } + } + + // Parent now exists; invoke Mkdir and use its result. + err = os.Mkdir(path, perm) + if err != nil { + // Handle arguments like "foo/." by + // double-checking that directory doesn't exist. + dir, err1 := os.Lstat(path) + if err1 == nil && dir.IsDir() { + return nil + } + return err + } + return nil +}