Skip to content

Commit 9c365cb

Browse files
authored
Merge pull request pkg#239 from urjitbhatia/master
Adds a MkdirAll implementation for the client to bring it closer to os Filesystem interface
2 parents 6d8540e + 831654e commit 9c365cb

File tree

2 files changed

+70
-0
lines changed

2 files changed

+70
-0
lines changed

client.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"os"
88
"path"
99
"sync/atomic"
10+
"syscall"
1011
"time"
1112

1213
"github.com/kr/fs"
@@ -680,6 +681,54 @@ func (c *Client) Mkdir(path string) error {
680681
}
681682
}
682683

684+
// MkdirAll creates a directory named path, along with any necessary parents,
685+
// and returns nil, or else returns an error.
686+
// If path is already a directory, MkdirAll does nothing and returns nil.
687+
// If path contains a regular file, an error is returned
688+
func (c *Client) MkdirAll(path string) error {
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
695+
}
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])
713+
if err != nil {
714+
return err
715+
}
716+
}
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+
}
729+
return nil
730+
}
731+
683732
// applyOptions applies options functions to the Client.
684733
// If an error is encountered, option processing ceases.
685734
func (c *Client) applyOptions(opts ...ClientOption) error {

client_integration_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,27 @@ func TestClientMkdir(t *testing.T) {
260260
t.Fatal(err)
261261
}
262262
}
263+
func TestClientMkdirAll(t *testing.T) {
264+
sftp, cmd := testClient(t, READWRITE, NO_DELAY)
265+
defer cmd.Wait()
266+
defer sftp.Close()
267+
268+
dir, err := ioutil.TempDir("", "sftptest")
269+
if err != nil {
270+
t.Fatal(err)
271+
}
272+
sub := path.Join(dir, "mkdir1", "mkdir2", "mkdir3")
273+
if err := sftp.MkdirAll(sub); err != nil {
274+
t.Fatal(err)
275+
}
276+
info, err := os.Lstat(sub)
277+
if err != nil {
278+
t.Fatal(err)
279+
}
280+
if !info.IsDir() {
281+
t.Fatalf("Expected mkdirall to create dir at: %s", sub)
282+
}
283+
}
263284

264285
func TestClientOpen(t *testing.T) {
265286
sftp, cmd := testClient(t, READONLY, NO_DELAY)

0 commit comments

Comments
 (0)