Skip to content

Commit

Permalink
posix: Support UNC paths on windows.
Browse files Browse the repository at this point in the history
This allows us to now use 32K paths names on windows.

Fixes minio#1620
  • Loading branch information
harshavardhana committed Jun 10, 2016
1 parent 71632b3 commit 70c354e
Show file tree
Hide file tree
Showing 11 changed files with 213 additions and 47 deletions.
2 changes: 1 addition & 1 deletion config-migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func purgeV1() {
fatalIf(err, "Unable to retrieve config path.")

configFile := filepath.Join(configPath, "fsUsers.json")
os.RemoveAll(configFile)
removeAll(configFile)
}
fatalIf(errors.New(""), "Failed to migrate unrecognized config version ‘"+cv1.Version+"’.")
}
Expand Down
3 changes: 1 addition & 2 deletions object-api-getobjectinfo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import (
"crypto/md5"
"encoding/hex"
"io/ioutil"
"os"
"strconv"
"testing"
)
Expand Down Expand Up @@ -116,7 +115,7 @@ func BenchmarkGetObjectFS(b *testing.B) {
if err != nil {
b.Fatal(err)
}
defer os.RemoveAll(directory)
defer removeAll(directory)

// Create the obj.
obj, err := newFSObjects(directory)
Expand Down
2 changes: 1 addition & 1 deletion object-api-listobjects_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -577,7 +577,7 @@ func BenchmarkListObjects(b *testing.B) {
if err != nil {
b.Fatal(err)
}
defer os.RemoveAll(directory)
defer removeAll(directory)

// Create the obj.
obj, err := newFSObjects(directory)
Expand Down
2 changes: 1 addition & 1 deletion object-api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,6 @@ func (s *MySuite) TestXLAPISuite(c *C) {

func removeRootsC(c *C, roots []string) {
for _, root := range roots {
os.RemoveAll(root)
removeAll(root)
}
}
8 changes: 5 additions & 3 deletions posix-list-dir-others.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,19 @@
package main

import (
"fmt"
"io"
"os"
"strings"
)

// Return all the entries at the directory dirPath.
func readDir(dirPath string) (entries []string, err error) {
d, err := os.Open(dirPath)
d, err := os.Open(longPath(dirPath))
if err != nil {
// File is really not found.
if os.IsNotExist(err) {
fmt.Println(longPath(dirPath))
return nil, errFileNotFound
}

Expand All @@ -50,12 +52,12 @@ func readDir(dirPath string) (entries []string, err error) {
return nil, err
}
for _, fi := range fis {
// Skip special files.
// Skip special files, if found.
if hasPosixReservedPrefix(fi.Name()) {
continue
}
if fi.Mode().IsDir() {
// append "/" instead of "\" so that sorting is done as expected.
// Append "/" instead of "\" so that sorting is achieved as expected.
entries = append(entries, fi.Name()+slashSeparator)
} else if fi.Mode().IsRegular() {
entries = append(entries, fi.Name())
Expand Down
48 changes: 48 additions & 0 deletions posix-unc-path.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Minio Cloud Storage, (C) 2016 Minio, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package main

import (
"fmt"
"path/filepath"
"runtime"
"strings"
)

// longPath converts a absolute windows path to a UNC long path.
func longPath(path string) string {
if runtime.GOOS == "windows" {
// UNC can NOT use "/", so convert all to "\"
path = filepath.FromSlash(path)

// If prefix is "\\", we already have a UNC path or server.
if strings.HasPrefix(path, `\\`) {

// If already long path, just keep it
if strings.HasPrefix(path, `\\?\`) {
return path
}

// Trim "\\" from path and add UNC prefix.
return `\\?\UNC\` + strings.TrimPrefix(path, `\\`)
}
path = `\\?\` + path
fmt.Println(path)
return path
}
return path
}
122 changes: 122 additions & 0 deletions posix-utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@
package main

import (
"io"
"os"
"runtime"
"strings"
"syscall"
"unicode/utf8"
)

Expand Down Expand Up @@ -70,3 +73,122 @@ func hasPosixReservedPrefix(name string) (isReserved bool) {
}
return isReserved
}

// 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 {
// Fast path: if we can tell whether path is a directory or file, stop with success or error.
dir, err := os.Stat(longPath(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
err = mkdirAll(path[0:j-1], perm)
if err != nil {
return err
}
}

// Parent now exists; invoke Mkdir and use its result.
err = os.Mkdir(longPath(path), perm)
if err != nil {
// Handle arguments like "foo/." by
// double-checking that directory doesn't exist.
dir, err1 := os.Lstat(longPath(path))
if err1 == nil && dir.IsDir() {
return nil
}
return err
}
return nil
}

// removeAll removes path and any children it contains.
// It removes everything it can but returns the first error
// it encounters. If the path does not exist, RemoveAll
// returns nil (no error).
func removeAll(path string) error {
// Simple case: if Remove works, we're done.
err := os.Remove(longPath(path))
if err == nil || os.IsNotExist(err) {
return nil
}

// Otherwise, is this a directory we need to recurse into?
dir, serr := os.Lstat(longPath(path))
if serr != nil {
if serr, ok := serr.(*os.PathError); ok && (os.IsNotExist(serr.Err) || serr.Err == syscall.ENOTDIR) {
return nil
}
return serr
}
if !dir.IsDir() {
// Not a directory; return the error from Remove.
return err
}

// Directory.
fd, err := os.Open(longPath(path))
if err != nil {
if os.IsNotExist(err) {
// Race. It was deleted between the Lstat and Open.
// Return nil per RemoveAll's docs.
return nil
}
return err
}

// Remove contents & return first error.
err = nil
for {
names, err1 := fd.Readdirnames(1000)
for _, name := range names {
err1 = removeAll(path + string(os.PathSeparator) + name)
if err == nil {
err = err1
}
}
if err1 == io.EOF {
break
}
// If Readdirnames returned an error, use it.
if err == nil {
err = err1
}
if len(names) == 0 {
break
}
}

// Close directory, because windows won't remove opened directory.
fd.Close()

// Remove directory.
err1 := os.Remove(longPath(path))
if err1 == nil || os.IsNotExist(err1) {
return nil
}
if err == nil {
err = err1
}
return err
}
Loading

0 comments on commit 70c354e

Please sign in to comment.