From fefcbf60faf907eb1eed65fcd1e15a2bfd368283 Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood <nick@craig-wood.com>
Date: Mon, 29 Jun 2020 15:49:19 +0100
Subject: [PATCH] sftp: use the absolute path instead of the relative path

Before this change rclone used the relative path from the current
working directory.

It appears that WS FTP doesn't like this and the openssh sftp tool
also uses absolute paths which is a good reason for switching to
absolute paths.

This change reads the current working directory at startup and bases
all file requests from there.

See: https://forum.rclone.org/t/sftp-ssh-fx-failure-directory-not-found/17436
---
 backend/sftp/sftp.go | 30 +++++++++++++++++++++---------
 1 file changed, 21 insertions(+), 9 deletions(-)

diff --git a/backend/sftp/sftp.go b/backend/sftp/sftp.go
index a3d2618e6..fceea7a3d 100644
--- a/backend/sftp/sftp.go
+++ b/backend/sftp/sftp.go
@@ -193,6 +193,7 @@ type Options struct {
 type Fs struct {
 	name         string
 	root         string
+	absRoot      string
 	opt          Options          // parsed options
 	m            configmap.Mapper // config
 	features     *fs.Features     // optional features
@@ -491,6 +492,7 @@ func NewFsWithConnection(ctx context.Context, name string, root string, m config
 	f := &Fs{
 		name:      name,
 		root:      root,
+		absRoot:   root,
 		opt:       *opt,
 		m:         m,
 		config:    sshConfig,
@@ -507,11 +509,20 @@ func NewFsWithConnection(ctx context.Context, name string, root string, m config
 	if err != nil {
 		return nil, errors.Wrap(err, "NewFs")
 	}
+	cwd, err := c.sftpClient.Getwd()
 	f.putSftpConnection(&c, nil)
+	if err != nil {
+		fs.Debugf(f, "Failed to read current directory - using relative paths: %v", err)
+	} else if !path.IsAbs(f.root) {
+		f.absRoot = path.Join(cwd, f.root)
+		fs.Debugf(f, "Using absolute root directory %q", f.absRoot)
+	}
 	if root != "" {
 		// Check to see if the root actually an existing file
+		oldAbsRoot := f.absRoot
 		remote := path.Base(root)
 		f.root = path.Dir(root)
+		f.absRoot = path.Dir(f.absRoot)
 		if f.root == "." {
 			f.root = ""
 		}
@@ -520,6 +531,7 @@ func NewFsWithConnection(ctx context.Context, name string, root string, m config
 			if err == fs.ErrorObjectNotFound || errors.Cause(err) == fs.ErrorNotAFile {
 				// File doesn't exist so return old f
 				f.root = root
+				f.absRoot = oldAbsRoot
 				return f, nil
 			}
 			return nil, err
@@ -602,7 +614,7 @@ func (f *Fs) dirExists(dir string) (bool, error) {
 // This should return ErrDirNotFound if the directory isn't
 // found.
 func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err error) {
-	root := path.Join(f.root, dir)
+	root := path.Join(f.absRoot, dir)
 	ok, err := f.dirExists(root)
 	if err != nil {
 		return nil, errors.Wrap(err, "List failed")
@@ -683,7 +695,7 @@ func (f *Fs) PutStream(ctx context.Context, in io.Reader, src fs.ObjectInfo, opt
 // directories above that
 func (f *Fs) mkParentDir(remote string) error {
 	parent := path.Dir(remote)
-	return f.mkdir(path.Join(f.root, parent))
+	return f.mkdir(path.Join(f.absRoot, parent))
 }
 
 // mkdir makes the directory and parents using native paths
@@ -719,7 +731,7 @@ func (f *Fs) mkdir(dirPath string) error {
 
 // Mkdir makes the root directory of the Fs object
 func (f *Fs) Mkdir(ctx context.Context, dir string) error {
-	root := path.Join(f.root, dir)
+	root := path.Join(f.absRoot, dir)
 	return f.mkdir(root)
 }
 
@@ -735,7 +747,7 @@ func (f *Fs) Rmdir(ctx context.Context, dir string) error {
 		return fs.ErrorDirectoryNotEmpty
 	}
 	// Remove the directory
-	root := path.Join(f.root, dir)
+	root := path.Join(f.absRoot, dir)
 	c, err := f.getSftpConnection()
 	if err != nil {
 		return errors.Wrap(err, "Rmdir")
@@ -762,7 +774,7 @@ func (f *Fs) Move(ctx context.Context, src fs.Object, remote string) (fs.Object,
 	}
 	err = c.sftpClient.Rename(
 		srcObj.path(),
-		path.Join(f.root, remote),
+		path.Join(f.absRoot, remote),
 	)
 	f.putSftpConnection(&c, err)
 	if err != nil {
@@ -789,8 +801,8 @@ func (f *Fs) DirMove(ctx context.Context, src fs.Fs, srcRemote, dstRemote string
 		fs.Debugf(srcFs, "Can't move directory - not same remote type")
 		return fs.ErrorCantDirMove
 	}
-	srcPath := path.Join(srcFs.root, srcRemote)
-	dstPath := path.Join(f.root, dstRemote)
+	srcPath := path.Join(srcFs.absRoot, srcRemote)
+	dstPath := path.Join(f.absRoot, dstRemote)
 
 	// Check if destination exists
 	ok, err := f.dirExists(dstPath)
@@ -1076,7 +1088,7 @@ func (o *Object) ModTime(ctx context.Context) time.Time {
 
 // path returns the native path of the object
 func (o *Object) path() string {
-	return path.Join(o.fs.root, o.remote)
+	return path.Join(o.fs.absRoot, o.remote)
 }
 
 // setMetadata updates the info in the object from the stat result passed in
@@ -1092,7 +1104,7 @@ func (f *Fs) stat(remote string) (info os.FileInfo, err error) {
 	if err != nil {
 		return nil, errors.Wrap(err, "stat")
 	}
-	absPath := path.Join(f.root, remote)
+	absPath := path.Join(f.absRoot, remote)
 	info, err = c.sftpClient.Stat(absPath)
 	f.putSftpConnection(&c, err)
 	return info, err