Skip to content

Commit 831654e

Browse files
committed
Client.MkdirAll - mimic os.MkdirAll code for consistency & perf
1 parent 9f2445d commit 831654e

File tree

2 files changed

+41
-16
lines changed

2 files changed

+41
-16
lines changed

client.go

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,11 @@ package sftp
33
import (
44
"bytes"
55
"encoding/binary"
6-
"fmt"
76
"io"
87
"os"
98
"path"
10-
"strings"
119
"sync/atomic"
10+
"syscall"
1211
"time"
1312

1413
"github.com/kr/fs"
@@ -687,24 +686,46 @@ func (c *Client) Mkdir(path string) error {
687686
// If path is already a directory, MkdirAll does nothing and returns nil.
688687
// If path contains a regular file, an error is returned
689688
func (c *Client) MkdirAll(path string) error {
690-
parts := ""
691-
for _, p := range strings.Split(path, "/") {
692-
if p == "" {
693-
continue
694-
}
695-
parts += "/" + p
696-
dir, err := c.Stat(parts)
697-
if err == nil {
698-
if !dir.IsDir() {
699-
return fmt.Errorf("Found a non-directory file on path: %s", parts)
700-
}
701-
continue
689+
// Most of this code mimics https://golang.org/src/os/path.go?s=514:561#L13
690+
// Fast path: if we can tell whether path is a directory or file, stop with success or error.
691+
dir, err := c.Stat(path)
692+
if err == nil {
693+
if dir.IsDir() {
694+
return nil
702695
}
703-
err = c.Mkdir(parts)
696+
return &os.PathError{Op: "mkdir", Path: path, Err: syscall.ENOTDIR}
697+
}
698+
699+
// Slow path: make sure parent exists and then call Mkdir for path.
700+
i := len(path)
701+
for i > 0 && os.IsPathSeparator(path[i-1]) { // Skip trailing path separator.
702+
i--
703+
}
704+
705+
j := i
706+
for j > 0 && !os.IsPathSeparator(path[j-1]) { // Scan backward over element.
707+
j--
708+
}
709+
710+
if j > 1 {
711+
// Create parent
712+
err = c.MkdirAll(path[0 : j-1])
704713
if err != nil {
705714
return err
706715
}
707716
}
717+
718+
// Parent now exists; invoke Mkdir and use its result.
719+
err = c.Mkdir(path)
720+
if err != nil {
721+
// Handle arguments like "foo/." by
722+
// double-checking that directory doesn't exist.
723+
dir, err1 := c.Lstat(path)
724+
if err1 == nil && dir.IsDir() {
725+
return nil
726+
}
727+
return err
728+
}
708729
return nil
709730
}
710731

client_integration_test.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,9 +273,13 @@ func TestClientMkdirAll(t *testing.T) {
273273
if err := sftp.MkdirAll(sub); err != nil {
274274
t.Fatal(err)
275275
}
276-
if _, err := os.Lstat(sub); err != nil {
276+
info, err := os.Lstat(sub)
277+
if err != nil {
277278
t.Fatal(err)
278279
}
280+
if !info.IsDir() {
281+
t.Fatalf("Expected mkdirall to create dir at: %s", sub)
282+
}
279283
}
280284

281285
func TestClientOpen(t *testing.T) {

0 commit comments

Comments
 (0)