github.com/10XDev/rclone@v1.52.3-0.20200626220027-16af9ab76b2a/backend/ftp/ftp.go (about)

     1  // Package ftp interfaces with FTP servers
     2  package ftp
     3  
     4  import (
     5  	"context"
     6  	"crypto/tls"
     7  	"io"
     8  	"net/textproto"
     9  	"os"
    10  	"path"
    11  	"sync"
    12  	"time"
    13  
    14  	"github.com/jlaffaye/ftp"
    15  	"github.com/pkg/errors"
    16  	"github.com/rclone/rclone/fs"
    17  	"github.com/rclone/rclone/fs/config"
    18  	"github.com/rclone/rclone/fs/config/configmap"
    19  	"github.com/rclone/rclone/fs/config/configstruct"
    20  	"github.com/rclone/rclone/fs/config/obscure"
    21  	"github.com/rclone/rclone/fs/hash"
    22  	"github.com/rclone/rclone/lib/encoder"
    23  	"github.com/rclone/rclone/lib/pacer"
    24  	"github.com/rclone/rclone/lib/readers"
    25  )
    26  
    27  // Register with Fs
    28  func init() {
    29  	fs.Register(&fs.RegInfo{
    30  		Name:        "ftp",
    31  		Description: "FTP Connection",
    32  		NewFs:       NewFs,
    33  		Options: []fs.Option{{
    34  			Name:     "host",
    35  			Help:     "FTP host to connect to",
    36  			Required: true,
    37  			Examples: []fs.OptionExample{{
    38  				Value: "ftp.example.com",
    39  				Help:  "Connect to ftp.example.com",
    40  			}},
    41  		}, {
    42  			Name: "user",
    43  			Help: "FTP username, leave blank for current username, " + os.Getenv("USER"),
    44  		}, {
    45  			Name: "port",
    46  			Help: "FTP port, leave blank to use default (21)",
    47  		}, {
    48  			Name:       "pass",
    49  			Help:       "FTP password",
    50  			IsPassword: true,
    51  			Required:   true,
    52  		}, {
    53  			Name:    "tls",
    54  			Help:    "Use FTP over TLS (Implicit)",
    55  			Default: false,
    56  		}, {
    57  			Name:     "concurrency",
    58  			Help:     "Maximum number of FTP simultaneous connections, 0 for unlimited",
    59  			Default:  0,
    60  			Advanced: true,
    61  		}, {
    62  			Name:     "no_check_certificate",
    63  			Help:     "Do not verify the TLS certificate of the server",
    64  			Default:  false,
    65  			Advanced: true,
    66  		}, {
    67  			Name:     "disable_epsv",
    68  			Help:     "Disable using EPSV even if server advertises support",
    69  			Default:  false,
    70  			Advanced: true,
    71  		}, {
    72  			Name:     config.ConfigEncoding,
    73  			Help:     config.ConfigEncodingHelp,
    74  			Advanced: true,
    75  			// The FTP protocol can't handle trailing spaces (for instance
    76  			// pureftpd turns them into _)
    77  			//
    78  			// proftpd can't handle '*' in file names
    79  			// pureftpd can't handle '[', ']' or '*'
    80  			Default: (encoder.Display |
    81  				encoder.EncodeRightSpace),
    82  		}},
    83  	})
    84  }
    85  
    86  // Options defines the configuration for this backend
    87  type Options struct {
    88  	Host              string               `config:"host"`
    89  	User              string               `config:"user"`
    90  	Pass              string               `config:"pass"`
    91  	Port              string               `config:"port"`
    92  	TLS               bool                 `config:"tls"`
    93  	Concurrency       int                  `config:"concurrency"`
    94  	SkipVerifyTLSCert bool                 `config:"no_check_certificate"`
    95  	DisableEPSV       bool                 `config:"disable_epsv"`
    96  	Enc               encoder.MultiEncoder `config:"encoding"`
    97  }
    98  
    99  // Fs represents a remote FTP server
   100  type Fs struct {
   101  	name     string       // name of this remote
   102  	root     string       // the path we are working on if any
   103  	opt      Options      // parsed options
   104  	features *fs.Features // optional features
   105  	url      string
   106  	user     string
   107  	pass     string
   108  	dialAddr string
   109  	poolMu   sync.Mutex
   110  	pool     []*ftp.ServerConn
   111  	tokens   *pacer.TokenDispenser
   112  }
   113  
   114  // Object describes an FTP file
   115  type Object struct {
   116  	fs     *Fs
   117  	remote string
   118  	info   *FileInfo
   119  }
   120  
   121  // FileInfo is the metadata known about an FTP file
   122  type FileInfo struct {
   123  	Name    string
   124  	Size    uint64
   125  	ModTime time.Time
   126  	IsDir   bool
   127  }
   128  
   129  // ------------------------------------------------------------
   130  
   131  // Name of this fs
   132  func (f *Fs) Name() string {
   133  	return f.name
   134  }
   135  
   136  // Root of the remote (as passed into NewFs)
   137  func (f *Fs) Root() string {
   138  	return f.root
   139  }
   140  
   141  // String returns a description of the FS
   142  func (f *Fs) String() string {
   143  	return f.url
   144  }
   145  
   146  // Features returns the optional features of this Fs
   147  func (f *Fs) Features() *fs.Features {
   148  	return f.features
   149  }
   150  
   151  // Open a new connection to the FTP server.
   152  func (f *Fs) ftpConnection() (*ftp.ServerConn, error) {
   153  	fs.Debugf(f, "Connecting to FTP server")
   154  	ftpConfig := []ftp.DialOption{ftp.DialWithTimeout(fs.Config.ConnectTimeout)}
   155  	if f.opt.TLS {
   156  		tlsConfig := &tls.Config{
   157  			ServerName:         f.opt.Host,
   158  			InsecureSkipVerify: f.opt.SkipVerifyTLSCert,
   159  		}
   160  		ftpConfig = append(ftpConfig, ftp.DialWithTLS(tlsConfig))
   161  	}
   162  	if f.opt.DisableEPSV {
   163  		ftpConfig = append(ftpConfig, ftp.DialWithDisabledEPSV(true))
   164  	}
   165  	c, err := ftp.Dial(f.dialAddr, ftpConfig...)
   166  	if err != nil {
   167  		fs.Errorf(f, "Error while Dialing %s: %s", f.dialAddr, err)
   168  		return nil, errors.Wrap(err, "ftpConnection Dial")
   169  	}
   170  	err = c.Login(f.user, f.pass)
   171  	if err != nil {
   172  		_ = c.Quit()
   173  		fs.Errorf(f, "Error while Logging in into %s: %s", f.dialAddr, err)
   174  		return nil, errors.Wrap(err, "ftpConnection Login")
   175  	}
   176  	return c, nil
   177  }
   178  
   179  // Get an FTP connection from the pool, or open a new one
   180  func (f *Fs) getFtpConnection() (c *ftp.ServerConn, err error) {
   181  	if f.opt.Concurrency > 0 {
   182  		f.tokens.Get()
   183  	}
   184  	f.poolMu.Lock()
   185  	if len(f.pool) > 0 {
   186  		c = f.pool[0]
   187  		f.pool = f.pool[1:]
   188  	}
   189  	f.poolMu.Unlock()
   190  	if c != nil {
   191  		return c, nil
   192  	}
   193  	c, err = f.ftpConnection()
   194  	if err != nil && f.opt.Concurrency > 0 {
   195  		f.tokens.Put()
   196  	}
   197  	return c, err
   198  }
   199  
   200  // Return an FTP connection to the pool
   201  //
   202  // It nils the pointed to connection out so it can't be reused
   203  //
   204  // if err is not nil then it checks the connection is alive using a
   205  // NOOP request
   206  func (f *Fs) putFtpConnection(pc **ftp.ServerConn, err error) {
   207  	if f.opt.Concurrency > 0 {
   208  		defer f.tokens.Put()
   209  	}
   210  	if pc == nil {
   211  		return
   212  	}
   213  	c := *pc
   214  	if c == nil {
   215  		return
   216  	}
   217  	*pc = nil
   218  	if err != nil {
   219  		// If not a regular FTP error code then check the connection
   220  		_, isRegularError := errors.Cause(err).(*textproto.Error)
   221  		if !isRegularError {
   222  			nopErr := c.NoOp()
   223  			if nopErr != nil {
   224  				fs.Debugf(f, "Connection failed, closing: %v", nopErr)
   225  				_ = c.Quit()
   226  				return
   227  			}
   228  		}
   229  	}
   230  	f.poolMu.Lock()
   231  	f.pool = append(f.pool, c)
   232  	f.poolMu.Unlock()
   233  }
   234  
   235  // NewFs constructs an Fs from the path, container:path
   236  func NewFs(name, root string, m configmap.Mapper) (ff fs.Fs, err error) {
   237  	ctx := context.Background()
   238  	// defer fs.Trace(nil, "name=%q, root=%q", name, root)("fs=%v, err=%v", &ff, &err)
   239  	// Parse config into Options struct
   240  	opt := new(Options)
   241  	err = configstruct.Set(m, opt)
   242  	if err != nil {
   243  		return nil, err
   244  	}
   245  	pass, err := obscure.Reveal(opt.Pass)
   246  	if err != nil {
   247  		return nil, errors.Wrap(err, "NewFS decrypt password")
   248  	}
   249  	user := opt.User
   250  	if user == "" {
   251  		user = os.Getenv("USER")
   252  	}
   253  	port := opt.Port
   254  	if port == "" {
   255  		port = "21"
   256  	}
   257  
   258  	dialAddr := opt.Host + ":" + port
   259  	protocol := "ftp://"
   260  	if opt.TLS {
   261  		protocol = "ftps://"
   262  	}
   263  	u := protocol + path.Join(dialAddr+"/", root)
   264  	f := &Fs{
   265  		name:     name,
   266  		root:     root,
   267  		opt:      *opt,
   268  		url:      u,
   269  		user:     user,
   270  		pass:     pass,
   271  		dialAddr: dialAddr,
   272  		tokens:   pacer.NewTokenDispenser(opt.Concurrency),
   273  	}
   274  	f.features = (&fs.Features{
   275  		CanHaveEmptyDirectories: true,
   276  	}).Fill(f)
   277  	// Make a connection and pool it to return errors early
   278  	c, err := f.getFtpConnection()
   279  	if err != nil {
   280  		return nil, errors.Wrap(err, "NewFs")
   281  	}
   282  	f.putFtpConnection(&c, nil)
   283  	if root != "" {
   284  		// Check to see if the root actually an existing file
   285  		remote := path.Base(root)
   286  		f.root = path.Dir(root)
   287  		if f.root == "." {
   288  			f.root = ""
   289  		}
   290  		_, err := f.NewObject(ctx, remote)
   291  		if err != nil {
   292  			if err == fs.ErrorObjectNotFound || errors.Cause(err) == fs.ErrorNotAFile {
   293  				// File doesn't exist so return old f
   294  				f.root = root
   295  				return f, nil
   296  			}
   297  			return nil, err
   298  		}
   299  		// return an error with an fs which points to the parent
   300  		return f, fs.ErrorIsFile
   301  	}
   302  	return f, err
   303  }
   304  
   305  // translateErrorFile turns FTP errors into rclone errors if possible for a file
   306  func translateErrorFile(err error) error {
   307  	switch errX := err.(type) {
   308  	case *textproto.Error:
   309  		switch errX.Code {
   310  		case ftp.StatusFileUnavailable, ftp.StatusFileActionIgnored:
   311  			err = fs.ErrorObjectNotFound
   312  		}
   313  	}
   314  	return err
   315  }
   316  
   317  // translateErrorDir turns FTP errors into rclone errors if possible for a directory
   318  func translateErrorDir(err error) error {
   319  	switch errX := err.(type) {
   320  	case *textproto.Error:
   321  		switch errX.Code {
   322  		case ftp.StatusFileUnavailable, ftp.StatusFileActionIgnored:
   323  			err = fs.ErrorDirNotFound
   324  		}
   325  	}
   326  	return err
   327  }
   328  
   329  // entryToStandard converts an incoming ftp.Entry to Standard encoding
   330  func (f *Fs) entryToStandard(entry *ftp.Entry) {
   331  	// Skip . and .. as we don't want these encoded
   332  	if entry.Name == "." || entry.Name == ".." {
   333  		return
   334  	}
   335  	entry.Name = f.opt.Enc.ToStandardName(entry.Name)
   336  	entry.Target = f.opt.Enc.ToStandardPath(entry.Target)
   337  }
   338  
   339  // dirFromStandardPath returns dir in encoded form.
   340  func (f *Fs) dirFromStandardPath(dir string) string {
   341  	// Skip . and .. as we don't want these encoded
   342  	if dir == "." || dir == ".." {
   343  		return dir
   344  	}
   345  	return f.opt.Enc.FromStandardPath(dir)
   346  }
   347  
   348  // findItem finds a directory entry for the name in its parent directory
   349  func (f *Fs) findItem(remote string) (entry *ftp.Entry, err error) {
   350  	// defer fs.Trace(remote, "")("o=%v, err=%v", &o, &err)
   351  	fullPath := path.Join(f.root, remote)
   352  	if fullPath == "" || fullPath == "." || fullPath == "/" {
   353  		// if root, assume exists and synthesize an entry
   354  		return &ftp.Entry{
   355  			Name: "",
   356  			Type: ftp.EntryTypeFolder,
   357  			Time: time.Now(),
   358  		}, nil
   359  	}
   360  	dir := path.Dir(fullPath)
   361  	base := path.Base(fullPath)
   362  
   363  	c, err := f.getFtpConnection()
   364  	if err != nil {
   365  		return nil, errors.Wrap(err, "findItem")
   366  	}
   367  	files, err := c.List(f.dirFromStandardPath(dir))
   368  	f.putFtpConnection(&c, err)
   369  	if err != nil {
   370  		return nil, translateErrorFile(err)
   371  	}
   372  	for _, file := range files {
   373  		f.entryToStandard(file)
   374  		if file.Name == base {
   375  			return file, nil
   376  		}
   377  	}
   378  	return nil, nil
   379  }
   380  
   381  // NewObject finds the Object at remote.  If it can't be found
   382  // it returns the error fs.ErrorObjectNotFound.
   383  func (f *Fs) NewObject(ctx context.Context, remote string) (o fs.Object, err error) {
   384  	// defer fs.Trace(remote, "")("o=%v, err=%v", &o, &err)
   385  	entry, err := f.findItem(remote)
   386  	if err != nil {
   387  		return nil, err
   388  	}
   389  	if entry != nil && entry.Type != ftp.EntryTypeFolder {
   390  		o := &Object{
   391  			fs:     f,
   392  			remote: remote,
   393  		}
   394  		info := &FileInfo{
   395  			Name:    remote,
   396  			Size:    entry.Size,
   397  			ModTime: entry.Time,
   398  		}
   399  		o.info = info
   400  
   401  		return o, nil
   402  	}
   403  	return nil, fs.ErrorObjectNotFound
   404  }
   405  
   406  // dirExists checks the directory pointed to by remote exists or not
   407  func (f *Fs) dirExists(remote string) (exists bool, err error) {
   408  	entry, err := f.findItem(remote)
   409  	if err != nil {
   410  		return false, errors.Wrap(err, "dirExists")
   411  	}
   412  	if entry != nil && entry.Type == ftp.EntryTypeFolder {
   413  		return true, nil
   414  	}
   415  	return false, nil
   416  }
   417  
   418  // List the objects and directories in dir into entries.  The
   419  // entries can be returned in any order but should be for a
   420  // complete directory.
   421  //
   422  // dir should be "" to list the root, and should not have
   423  // trailing slashes.
   424  //
   425  // This should return ErrDirNotFound if the directory isn't
   426  // found.
   427  func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err error) {
   428  	// defer log.Trace(dir, "dir=%q", dir)("entries=%v, err=%v", &entries, &err)
   429  	c, err := f.getFtpConnection()
   430  	if err != nil {
   431  		return nil, errors.Wrap(err, "list")
   432  	}
   433  
   434  	var listErr error
   435  	var files []*ftp.Entry
   436  
   437  	resultchan := make(chan []*ftp.Entry, 1)
   438  	errchan := make(chan error, 1)
   439  	go func() {
   440  		result, err := c.List(f.dirFromStandardPath(path.Join(f.root, dir)))
   441  		f.putFtpConnection(&c, err)
   442  		if err != nil {
   443  			errchan <- err
   444  			return
   445  		}
   446  		resultchan <- result
   447  	}()
   448  
   449  	// Wait for List for up to Timeout seconds
   450  	timer := time.NewTimer(fs.Config.Timeout)
   451  	select {
   452  	case listErr = <-errchan:
   453  		timer.Stop()
   454  		return nil, translateErrorDir(listErr)
   455  	case files = <-resultchan:
   456  		timer.Stop()
   457  	case <-timer.C:
   458  		// if timer fired assume no error but connection dead
   459  		fs.Errorf(f, "Timeout when waiting for List")
   460  		return nil, errors.New("Timeout when waiting for List")
   461  	}
   462  
   463  	// Annoyingly FTP returns success for a directory which
   464  	// doesn't exist, so check it really doesn't exist if no
   465  	// entries found.
   466  	if len(files) == 0 {
   467  		exists, err := f.dirExists(dir)
   468  		if err != nil {
   469  			return nil, errors.Wrap(err, "list")
   470  		}
   471  		if !exists {
   472  			return nil, fs.ErrorDirNotFound
   473  		}
   474  	}
   475  	for i := range files {
   476  		object := files[i]
   477  		f.entryToStandard(object)
   478  		newremote := path.Join(dir, object.Name)
   479  		switch object.Type {
   480  		case ftp.EntryTypeFolder:
   481  			if object.Name == "." || object.Name == ".." {
   482  				continue
   483  			}
   484  			d := fs.NewDir(newremote, object.Time)
   485  			entries = append(entries, d)
   486  		default:
   487  			o := &Object{
   488  				fs:     f,
   489  				remote: newremote,
   490  			}
   491  			info := &FileInfo{
   492  				Name:    newremote,
   493  				Size:    object.Size,
   494  				ModTime: object.Time,
   495  			}
   496  			o.info = info
   497  			entries = append(entries, o)
   498  		}
   499  	}
   500  	return entries, nil
   501  }
   502  
   503  // Hashes are not supported
   504  func (f *Fs) Hashes() hash.Set {
   505  	return 0
   506  }
   507  
   508  // Precision shows Modified Time not supported
   509  func (f *Fs) Precision() time.Duration {
   510  	return fs.ModTimeNotSupported
   511  }
   512  
   513  // Put in to the remote path with the modTime given of the given size
   514  //
   515  // May create the object even if it returns an error - if so
   516  // will return the object and the error, otherwise will return
   517  // nil and the error
   518  func (f *Fs) Put(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) {
   519  	// fs.Debugf(f, "Trying to put file %s", src.Remote())
   520  	err := f.mkParentDir(src.Remote())
   521  	if err != nil {
   522  		return nil, errors.Wrap(err, "Put mkParentDir failed")
   523  	}
   524  	o := &Object{
   525  		fs:     f,
   526  		remote: src.Remote(),
   527  	}
   528  	err = o.Update(ctx, in, src, options...)
   529  	return o, err
   530  }
   531  
   532  // PutStream uploads to the remote path with the modTime given of indeterminate size
   533  func (f *Fs) PutStream(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) {
   534  	return f.Put(ctx, in, src, options...)
   535  }
   536  
   537  // getInfo reads the FileInfo for a path
   538  func (f *Fs) getInfo(remote string) (fi *FileInfo, err error) {
   539  	// defer fs.Trace(remote, "")("fi=%v, err=%v", &fi, &err)
   540  	dir := path.Dir(remote)
   541  	base := path.Base(remote)
   542  
   543  	c, err := f.getFtpConnection()
   544  	if err != nil {
   545  		return nil, errors.Wrap(err, "getInfo")
   546  	}
   547  	files, err := c.List(f.dirFromStandardPath(dir))
   548  	f.putFtpConnection(&c, err)
   549  	if err != nil {
   550  		return nil, translateErrorFile(err)
   551  	}
   552  
   553  	for i := range files {
   554  		file := files[i]
   555  		f.entryToStandard(file)
   556  		if file.Name == base {
   557  			info := &FileInfo{
   558  				Name:    remote,
   559  				Size:    file.Size,
   560  				ModTime: file.Time,
   561  				IsDir:   file.Type == ftp.EntryTypeFolder,
   562  			}
   563  			return info, nil
   564  		}
   565  	}
   566  	return nil, fs.ErrorObjectNotFound
   567  }
   568  
   569  // mkdir makes the directory and parents using unrooted paths
   570  func (f *Fs) mkdir(abspath string) error {
   571  	abspath = path.Clean(abspath)
   572  	if abspath == "." || abspath == "/" {
   573  		return nil
   574  	}
   575  	fi, err := f.getInfo(abspath)
   576  	if err == nil {
   577  		if fi.IsDir {
   578  			return nil
   579  		}
   580  		return fs.ErrorIsFile
   581  	} else if err != fs.ErrorObjectNotFound {
   582  		return errors.Wrapf(err, "mkdir %q failed", abspath)
   583  	}
   584  	parent := path.Dir(abspath)
   585  	err = f.mkdir(parent)
   586  	if err != nil {
   587  		return err
   588  	}
   589  	c, connErr := f.getFtpConnection()
   590  	if connErr != nil {
   591  		return errors.Wrap(connErr, "mkdir")
   592  	}
   593  	err = c.MakeDir(f.dirFromStandardPath(abspath))
   594  	f.putFtpConnection(&c, err)
   595  	switch errX := err.(type) {
   596  	case *textproto.Error:
   597  		switch errX.Code {
   598  		case ftp.StatusFileUnavailable: // dir already exists: see issue #2181
   599  			err = nil
   600  		case 521: // dir already exists: error number according to RFC 959: issue #2363
   601  			err = nil
   602  		}
   603  	}
   604  	return err
   605  }
   606  
   607  // mkParentDir makes the parent of remote if necessary and any
   608  // directories above that
   609  func (f *Fs) mkParentDir(remote string) error {
   610  	parent := path.Dir(remote)
   611  	return f.mkdir(path.Join(f.root, parent))
   612  }
   613  
   614  // Mkdir creates the directory if it doesn't exist
   615  func (f *Fs) Mkdir(ctx context.Context, dir string) (err error) {
   616  	// defer fs.Trace(dir, "")("err=%v", &err)
   617  	root := path.Join(f.root, dir)
   618  	return f.mkdir(root)
   619  }
   620  
   621  // Rmdir removes the directory (container, bucket) if empty
   622  //
   623  // Return an error if it doesn't exist or isn't empty
   624  func (f *Fs) Rmdir(ctx context.Context, dir string) error {
   625  	c, err := f.getFtpConnection()
   626  	if err != nil {
   627  		return errors.Wrap(translateErrorFile(err), "Rmdir")
   628  	}
   629  	err = c.RemoveDir(f.dirFromStandardPath(path.Join(f.root, dir)))
   630  	f.putFtpConnection(&c, err)
   631  	return translateErrorDir(err)
   632  }
   633  
   634  // Move renames a remote file object
   635  func (f *Fs) Move(ctx context.Context, src fs.Object, remote string) (fs.Object, error) {
   636  	srcObj, ok := src.(*Object)
   637  	if !ok {
   638  		fs.Debugf(src, "Can't move - not same remote type")
   639  		return nil, fs.ErrorCantMove
   640  	}
   641  	err := f.mkParentDir(remote)
   642  	if err != nil {
   643  		return nil, errors.Wrap(err, "Move mkParentDir failed")
   644  	}
   645  	c, err := f.getFtpConnection()
   646  	if err != nil {
   647  		return nil, errors.Wrap(err, "Move")
   648  	}
   649  	err = c.Rename(
   650  		f.opt.Enc.FromStandardPath(path.Join(srcObj.fs.root, srcObj.remote)),
   651  		f.opt.Enc.FromStandardPath(path.Join(f.root, remote)),
   652  	)
   653  	f.putFtpConnection(&c, err)
   654  	if err != nil {
   655  		return nil, errors.Wrap(err, "Move Rename failed")
   656  	}
   657  	dstObj, err := f.NewObject(ctx, remote)
   658  	if err != nil {
   659  		return nil, errors.Wrap(err, "Move NewObject failed")
   660  	}
   661  	return dstObj, nil
   662  }
   663  
   664  // DirMove moves src, srcRemote to this remote at dstRemote
   665  // using server side move operations.
   666  //
   667  // Will only be called if src.Fs().Name() == f.Name()
   668  //
   669  // If it isn't possible then return fs.ErrorCantDirMove
   670  //
   671  // If destination exists then return fs.ErrorDirExists
   672  func (f *Fs) DirMove(ctx context.Context, src fs.Fs, srcRemote, dstRemote string) error {
   673  	srcFs, ok := src.(*Fs)
   674  	if !ok {
   675  		fs.Debugf(srcFs, "Can't move directory - not same remote type")
   676  		return fs.ErrorCantDirMove
   677  	}
   678  	srcPath := path.Join(srcFs.root, srcRemote)
   679  	dstPath := path.Join(f.root, dstRemote)
   680  
   681  	// Check if destination exists
   682  	fi, err := f.getInfo(dstPath)
   683  	if err == nil {
   684  		if fi.IsDir {
   685  			return fs.ErrorDirExists
   686  		}
   687  		return fs.ErrorIsFile
   688  	} else if err != fs.ErrorObjectNotFound {
   689  		return errors.Wrapf(err, "DirMove getInfo failed")
   690  	}
   691  
   692  	// Make sure the parent directory exists
   693  	err = f.mkdir(path.Dir(dstPath))
   694  	if err != nil {
   695  		return errors.Wrap(err, "DirMove mkParentDir dst failed")
   696  	}
   697  
   698  	// Do the move
   699  	c, err := f.getFtpConnection()
   700  	if err != nil {
   701  		return errors.Wrap(err, "DirMove")
   702  	}
   703  	err = c.Rename(
   704  		f.dirFromStandardPath(srcPath),
   705  		f.dirFromStandardPath(dstPath),
   706  	)
   707  	f.putFtpConnection(&c, err)
   708  	if err != nil {
   709  		return errors.Wrapf(err, "DirMove Rename(%q,%q) failed", srcPath, dstPath)
   710  	}
   711  	return nil
   712  }
   713  
   714  // ------------------------------------------------------------
   715  
   716  // Fs returns the parent Fs
   717  func (o *Object) Fs() fs.Info {
   718  	return o.fs
   719  }
   720  
   721  // String version of o
   722  func (o *Object) String() string {
   723  	if o == nil {
   724  		return "<nil>"
   725  	}
   726  	return o.remote
   727  }
   728  
   729  // Remote returns the remote path
   730  func (o *Object) Remote() string {
   731  	return o.remote
   732  }
   733  
   734  // Hash returns the hash of an object returning a lowercase hex string
   735  func (o *Object) Hash(ctx context.Context, t hash.Type) (string, error) {
   736  	return "", hash.ErrUnsupported
   737  }
   738  
   739  // Size returns the size of an object in bytes
   740  func (o *Object) Size() int64 {
   741  	return int64(o.info.Size)
   742  }
   743  
   744  // ModTime returns the modification time of the object
   745  func (o *Object) ModTime(ctx context.Context) time.Time {
   746  	return o.info.ModTime
   747  }
   748  
   749  // SetModTime sets the modification time of the object
   750  func (o *Object) SetModTime(ctx context.Context, modTime time.Time) error {
   751  	return nil
   752  }
   753  
   754  // Storable returns a boolean as to whether this object is storable
   755  func (o *Object) Storable() bool {
   756  	return true
   757  }
   758  
   759  // ftpReadCloser implements io.ReadCloser for FTP objects.
   760  type ftpReadCloser struct {
   761  	rc  io.ReadCloser
   762  	c   *ftp.ServerConn
   763  	f   *Fs
   764  	err error // errors found during read
   765  }
   766  
   767  // Read bytes into p
   768  func (f *ftpReadCloser) Read(p []byte) (n int, err error) {
   769  	n, err = f.rc.Read(p)
   770  	if err != nil && err != io.EOF {
   771  		f.err = err // store any errors for Close to examine
   772  	}
   773  	return
   774  }
   775  
   776  // Close the FTP reader and return the connection to the pool
   777  func (f *ftpReadCloser) Close() error {
   778  	var err error
   779  	errchan := make(chan error, 1)
   780  	go func() {
   781  		errchan <- f.rc.Close()
   782  	}()
   783  	// Wait for Close for up to 60 seconds
   784  	timer := time.NewTimer(60 * time.Second)
   785  	select {
   786  	case err = <-errchan:
   787  		timer.Stop()
   788  	case <-timer.C:
   789  		// if timer fired assume no error but connection dead
   790  		fs.Errorf(f.f, "Timeout when waiting for connection Close")
   791  		f.f.putFtpConnection(nil, nil)
   792  		return nil
   793  	}
   794  	// if errors while reading or closing, dump the connection
   795  	if err != nil || f.err != nil {
   796  		_ = f.c.Quit()
   797  		f.f.putFtpConnection(nil, nil)
   798  	} else {
   799  		f.f.putFtpConnection(&f.c, nil)
   800  	}
   801  	// mask the error if it was caused by a premature close
   802  	// NB StatusAboutToSend is to work around a bug in pureftpd
   803  	// See: https://github.com/rclone/rclone/issues/3445#issuecomment-521654257
   804  	switch errX := err.(type) {
   805  	case *textproto.Error:
   806  		switch errX.Code {
   807  		case ftp.StatusTransfertAborted, ftp.StatusFileUnavailable, ftp.StatusAboutToSend:
   808  			err = nil
   809  		}
   810  	}
   811  	return err
   812  }
   813  
   814  // Open an object for read
   815  func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (rc io.ReadCloser, err error) {
   816  	// defer fs.Trace(o, "")("rc=%v, err=%v", &rc, &err)
   817  	path := path.Join(o.fs.root, o.remote)
   818  	var offset, limit int64 = 0, -1
   819  	for _, option := range options {
   820  		switch x := option.(type) {
   821  		case *fs.SeekOption:
   822  			offset = x.Offset
   823  		case *fs.RangeOption:
   824  			offset, limit = x.Decode(o.Size())
   825  		default:
   826  			if option.Mandatory() {
   827  				fs.Logf(o, "Unsupported mandatory option: %v", option)
   828  			}
   829  		}
   830  	}
   831  	c, err := o.fs.getFtpConnection()
   832  	if err != nil {
   833  		return nil, errors.Wrap(err, "open")
   834  	}
   835  	fd, err := c.RetrFrom(o.fs.opt.Enc.FromStandardPath(path), uint64(offset))
   836  	if err != nil {
   837  		o.fs.putFtpConnection(&c, err)
   838  		return nil, errors.Wrap(err, "open")
   839  	}
   840  	rc = &ftpReadCloser{rc: readers.NewLimitedReadCloser(fd, limit), c: c, f: o.fs}
   841  	return rc, nil
   842  }
   843  
   844  // Update the already existing object
   845  //
   846  // Copy the reader into the object updating modTime and size
   847  //
   848  // The new object may have been created if an error is returned
   849  func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (err error) {
   850  	// defer fs.Trace(o, "src=%v", src)("err=%v", &err)
   851  	path := path.Join(o.fs.root, o.remote)
   852  	// remove the file if upload failed
   853  	remove := func() {
   854  		// Give the FTP server a chance to get its internal state in order after the error.
   855  		// The error may have been local in which case we closed the connection.  The server
   856  		// may still be dealing with it for a moment. A sleep isn't ideal but I haven't been
   857  		// able to think of a better method to find out if the server has finished - ncw
   858  		time.Sleep(1 * time.Second)
   859  		removeErr := o.Remove(ctx)
   860  		if removeErr != nil {
   861  			fs.Debugf(o, "Failed to remove: %v", removeErr)
   862  		} else {
   863  			fs.Debugf(o, "Removed after failed upload: %v", err)
   864  		}
   865  	}
   866  	c, err := o.fs.getFtpConnection()
   867  	if err != nil {
   868  		return errors.Wrap(err, "Update")
   869  	}
   870  	err = c.Stor(o.fs.opt.Enc.FromStandardPath(path), in)
   871  	if err != nil {
   872  		_ = c.Quit() // toss this connection to avoid sync errors
   873  		remove()
   874  		o.fs.putFtpConnection(nil, err)
   875  		return errors.Wrap(err, "update stor")
   876  	}
   877  	o.fs.putFtpConnection(&c, nil)
   878  	o.info, err = o.fs.getInfo(path)
   879  	if err != nil {
   880  		return errors.Wrap(err, "update getinfo")
   881  	}
   882  	return nil
   883  }
   884  
   885  // Remove an object
   886  func (o *Object) Remove(ctx context.Context) (err error) {
   887  	// defer fs.Trace(o, "")("err=%v", &err)
   888  	path := path.Join(o.fs.root, o.remote)
   889  	// Check if it's a directory or a file
   890  	info, err := o.fs.getInfo(path)
   891  	if err != nil {
   892  		return err
   893  	}
   894  	if info.IsDir {
   895  		err = o.fs.Rmdir(ctx, o.remote)
   896  	} else {
   897  		c, err := o.fs.getFtpConnection()
   898  		if err != nil {
   899  			return errors.Wrap(err, "Remove")
   900  		}
   901  		err = c.Delete(o.fs.opt.Enc.FromStandardPath(path))
   902  		o.fs.putFtpConnection(&c, err)
   903  	}
   904  	return err
   905  }
   906  
   907  // Check the interfaces are satisfied
   908  var (
   909  	_ fs.Fs          = &Fs{}
   910  	_ fs.Mover       = &Fs{}
   911  	_ fs.DirMover    = &Fs{}
   912  	_ fs.PutStreamer = &Fs{}
   913  	_ fs.Object      = &Object{}
   914  )