github.com/xhghs/rclone@v1.51.1-0.20200430155106-e186a28cced8/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 protocal 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  	return f.ftpConnection()
   194  }
   195  
   196  // Return an FTP connection to the pool
   197  //
   198  // It nils the pointed to connection out so it can't be reused
   199  //
   200  // if err is not nil then it checks the connection is alive using a
   201  // NOOP request
   202  func (f *Fs) putFtpConnection(pc **ftp.ServerConn, err error) {
   203  	if f.opt.Concurrency > 0 {
   204  		defer f.tokens.Put()
   205  	}
   206  	c := *pc
   207  	*pc = nil
   208  	if err != nil {
   209  		// If not a regular FTP error code then check the connection
   210  		_, isRegularError := errors.Cause(err).(*textproto.Error)
   211  		if !isRegularError {
   212  			nopErr := c.NoOp()
   213  			if nopErr != nil {
   214  				fs.Debugf(f, "Connection failed, closing: %v", nopErr)
   215  				_ = c.Quit()
   216  				return
   217  			}
   218  		}
   219  	}
   220  	f.poolMu.Lock()
   221  	f.pool = append(f.pool, c)
   222  	f.poolMu.Unlock()
   223  }
   224  
   225  // NewFs constructs an Fs from the path, container:path
   226  func NewFs(name, root string, m configmap.Mapper) (ff fs.Fs, err error) {
   227  	ctx := context.Background()
   228  	// defer fs.Trace(nil, "name=%q, root=%q", name, root)("fs=%v, err=%v", &ff, &err)
   229  	// Parse config into Options struct
   230  	opt := new(Options)
   231  	err = configstruct.Set(m, opt)
   232  	if err != nil {
   233  		return nil, err
   234  	}
   235  	pass, err := obscure.Reveal(opt.Pass)
   236  	if err != nil {
   237  		return nil, errors.Wrap(err, "NewFS decrypt password")
   238  	}
   239  	user := opt.User
   240  	if user == "" {
   241  		user = os.Getenv("USER")
   242  	}
   243  	port := opt.Port
   244  	if port == "" {
   245  		port = "21"
   246  	}
   247  
   248  	dialAddr := opt.Host + ":" + port
   249  	protocol := "ftp://"
   250  	if opt.TLS {
   251  		protocol = "ftps://"
   252  	}
   253  	u := protocol + path.Join(dialAddr+"/", root)
   254  	f := &Fs{
   255  		name:     name,
   256  		root:     root,
   257  		opt:      *opt,
   258  		url:      u,
   259  		user:     user,
   260  		pass:     pass,
   261  		dialAddr: dialAddr,
   262  		tokens:   pacer.NewTokenDispenser(opt.Concurrency),
   263  	}
   264  	f.features = (&fs.Features{
   265  		CanHaveEmptyDirectories: true,
   266  	}).Fill(f)
   267  	// Make a connection and pool it to return errors early
   268  	c, err := f.getFtpConnection()
   269  	if err != nil {
   270  		return nil, errors.Wrap(err, "NewFs")
   271  	}
   272  	f.putFtpConnection(&c, nil)
   273  	if root != "" {
   274  		// Check to see if the root actually an existing file
   275  		remote := path.Base(root)
   276  		f.root = path.Dir(root)
   277  		if f.root == "." {
   278  			f.root = ""
   279  		}
   280  		_, err := f.NewObject(ctx, remote)
   281  		if err != nil {
   282  			if err == fs.ErrorObjectNotFound || errors.Cause(err) == fs.ErrorNotAFile {
   283  				// File doesn't exist so return old f
   284  				f.root = root
   285  				return f, nil
   286  			}
   287  			return nil, err
   288  		}
   289  		// return an error with an fs which points to the parent
   290  		return f, fs.ErrorIsFile
   291  	}
   292  	return f, err
   293  }
   294  
   295  // translateErrorFile turns FTP errors into rclone errors if possible for a file
   296  func translateErrorFile(err error) error {
   297  	switch errX := err.(type) {
   298  	case *textproto.Error:
   299  		switch errX.Code {
   300  		case ftp.StatusFileUnavailable, ftp.StatusFileActionIgnored:
   301  			err = fs.ErrorObjectNotFound
   302  		}
   303  	}
   304  	return err
   305  }
   306  
   307  // translateErrorDir turns FTP errors into rclone errors if possible for a directory
   308  func translateErrorDir(err error) error {
   309  	switch errX := err.(type) {
   310  	case *textproto.Error:
   311  		switch errX.Code {
   312  		case ftp.StatusFileUnavailable, ftp.StatusFileActionIgnored:
   313  			err = fs.ErrorDirNotFound
   314  		}
   315  	}
   316  	return err
   317  }
   318  
   319  // entryToStandard converts an incoming ftp.Entry to Standard encoding
   320  func (f *Fs) entryToStandard(entry *ftp.Entry) {
   321  	// Skip . and .. as we don't want these encoded
   322  	if entry.Name == "." || entry.Name == ".." {
   323  		return
   324  	}
   325  	entry.Name = f.opt.Enc.ToStandardName(entry.Name)
   326  	entry.Target = f.opt.Enc.ToStandardPath(entry.Target)
   327  }
   328  
   329  // dirFromStandardPath returns dir in encoded form.
   330  func (f *Fs) dirFromStandardPath(dir string) string {
   331  	// Skip . and .. as we don't want these encoded
   332  	if dir == "." || dir == ".." {
   333  		return dir
   334  	}
   335  	return f.opt.Enc.FromStandardPath(dir)
   336  }
   337  
   338  // findItem finds a directory entry for the name in its parent directory
   339  func (f *Fs) findItem(remote string) (entry *ftp.Entry, err error) {
   340  	// defer fs.Trace(remote, "")("o=%v, err=%v", &o, &err)
   341  	fullPath := path.Join(f.root, remote)
   342  	if fullPath == "" || fullPath == "." || fullPath == "/" {
   343  		// if root, assume exists and synthesize an entry
   344  		return &ftp.Entry{
   345  			Name: "",
   346  			Type: ftp.EntryTypeFolder,
   347  			Time: time.Now(),
   348  		}, nil
   349  	}
   350  	dir := path.Dir(fullPath)
   351  	base := path.Base(fullPath)
   352  
   353  	c, err := f.getFtpConnection()
   354  	if err != nil {
   355  		return nil, errors.Wrap(err, "findItem")
   356  	}
   357  	files, err := c.List(f.dirFromStandardPath(dir))
   358  	f.putFtpConnection(&c, err)
   359  	if err != nil {
   360  		return nil, translateErrorFile(err)
   361  	}
   362  	for _, file := range files {
   363  		f.entryToStandard(file)
   364  		if file.Name == base {
   365  			return file, nil
   366  		}
   367  	}
   368  	return nil, nil
   369  }
   370  
   371  // NewObject finds the Object at remote.  If it can't be found
   372  // it returns the error fs.ErrorObjectNotFound.
   373  func (f *Fs) NewObject(ctx context.Context, remote string) (o fs.Object, err error) {
   374  	// defer fs.Trace(remote, "")("o=%v, err=%v", &o, &err)
   375  	entry, err := f.findItem(remote)
   376  	if err != nil {
   377  		return nil, err
   378  	}
   379  	if entry != nil && entry.Type != ftp.EntryTypeFolder {
   380  		o := &Object{
   381  			fs:     f,
   382  			remote: remote,
   383  		}
   384  		info := &FileInfo{
   385  			Name:    remote,
   386  			Size:    entry.Size,
   387  			ModTime: entry.Time,
   388  		}
   389  		o.info = info
   390  
   391  		return o, nil
   392  	}
   393  	return nil, fs.ErrorObjectNotFound
   394  }
   395  
   396  // dirExists checks the directory pointed to by remote exists or not
   397  func (f *Fs) dirExists(remote string) (exists bool, err error) {
   398  	entry, err := f.findItem(remote)
   399  	if err != nil {
   400  		return false, errors.Wrap(err, "dirExists")
   401  	}
   402  	if entry != nil && entry.Type == ftp.EntryTypeFolder {
   403  		return true, nil
   404  	}
   405  	return false, nil
   406  }
   407  
   408  // List the objects and directories in dir into entries.  The
   409  // entries can be returned in any order but should be for a
   410  // complete directory.
   411  //
   412  // dir should be "" to list the root, and should not have
   413  // trailing slashes.
   414  //
   415  // This should return ErrDirNotFound if the directory isn't
   416  // found.
   417  func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err error) {
   418  	// defer log.Trace(dir, "dir=%q", dir)("entries=%v, err=%v", &entries, &err)
   419  	c, err := f.getFtpConnection()
   420  	if err != nil {
   421  		return nil, errors.Wrap(err, "list")
   422  	}
   423  
   424  	var listErr error
   425  	var files []*ftp.Entry
   426  
   427  	resultchan := make(chan []*ftp.Entry, 1)
   428  	errchan := make(chan error, 1)
   429  	go func() {
   430  		result, err := c.List(f.dirFromStandardPath(path.Join(f.root, dir)))
   431  		f.putFtpConnection(&c, err)
   432  		if err != nil {
   433  			errchan <- err
   434  			return
   435  		}
   436  		resultchan <- result
   437  	}()
   438  
   439  	// Wait for List for up to Timeout seconds
   440  	timer := time.NewTimer(fs.Config.Timeout)
   441  	select {
   442  	case listErr = <-errchan:
   443  		timer.Stop()
   444  		return nil, translateErrorDir(listErr)
   445  	case files = <-resultchan:
   446  		timer.Stop()
   447  	case <-timer.C:
   448  		// if timer fired assume no error but connection dead
   449  		fs.Errorf(f, "Timeout when waiting for List")
   450  		return nil, errors.New("Timeout when waiting for List")
   451  	}
   452  
   453  	// Annoyingly FTP returns success for a directory which
   454  	// doesn't exist, so check it really doesn't exist if no
   455  	// entries found.
   456  	if len(files) == 0 {
   457  		exists, err := f.dirExists(dir)
   458  		if err != nil {
   459  			return nil, errors.Wrap(err, "list")
   460  		}
   461  		if !exists {
   462  			return nil, fs.ErrorDirNotFound
   463  		}
   464  	}
   465  	for i := range files {
   466  		object := files[i]
   467  		f.entryToStandard(object)
   468  		newremote := path.Join(dir, object.Name)
   469  		switch object.Type {
   470  		case ftp.EntryTypeFolder:
   471  			if object.Name == "." || object.Name == ".." {
   472  				continue
   473  			}
   474  			d := fs.NewDir(newremote, object.Time)
   475  			entries = append(entries, d)
   476  		default:
   477  			o := &Object{
   478  				fs:     f,
   479  				remote: newremote,
   480  			}
   481  			info := &FileInfo{
   482  				Name:    newremote,
   483  				Size:    object.Size,
   484  				ModTime: object.Time,
   485  			}
   486  			o.info = info
   487  			entries = append(entries, o)
   488  		}
   489  	}
   490  	return entries, nil
   491  }
   492  
   493  // Hashes are not supported
   494  func (f *Fs) Hashes() hash.Set {
   495  	return 0
   496  }
   497  
   498  // Precision shows Modified Time not supported
   499  func (f *Fs) Precision() time.Duration {
   500  	return fs.ModTimeNotSupported
   501  }
   502  
   503  // Put in to the remote path with the modTime given of the given size
   504  //
   505  // May create the object even if it returns an error - if so
   506  // will return the object and the error, otherwise will return
   507  // nil and the error
   508  func (f *Fs) Put(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) {
   509  	// fs.Debugf(f, "Trying to put file %s", src.Remote())
   510  	err := f.mkParentDir(src.Remote())
   511  	if err != nil {
   512  		return nil, errors.Wrap(err, "Put mkParentDir failed")
   513  	}
   514  	o := &Object{
   515  		fs:     f,
   516  		remote: src.Remote(),
   517  	}
   518  	err = o.Update(ctx, in, src, options...)
   519  	return o, err
   520  }
   521  
   522  // PutStream uploads to the remote path with the modTime given of indeterminate size
   523  func (f *Fs) PutStream(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) {
   524  	return f.Put(ctx, in, src, options...)
   525  }
   526  
   527  // getInfo reads the FileInfo for a path
   528  func (f *Fs) getInfo(remote string) (fi *FileInfo, err error) {
   529  	// defer fs.Trace(remote, "")("fi=%v, err=%v", &fi, &err)
   530  	dir := path.Dir(remote)
   531  	base := path.Base(remote)
   532  
   533  	c, err := f.getFtpConnection()
   534  	if err != nil {
   535  		return nil, errors.Wrap(err, "getInfo")
   536  	}
   537  	files, err := c.List(f.dirFromStandardPath(dir))
   538  	f.putFtpConnection(&c, err)
   539  	if err != nil {
   540  		return nil, translateErrorFile(err)
   541  	}
   542  
   543  	for i := range files {
   544  		file := files[i]
   545  		f.entryToStandard(file)
   546  		if file.Name == base {
   547  			info := &FileInfo{
   548  				Name:    remote,
   549  				Size:    file.Size,
   550  				ModTime: file.Time,
   551  				IsDir:   file.Type == ftp.EntryTypeFolder,
   552  			}
   553  			return info, nil
   554  		}
   555  	}
   556  	return nil, fs.ErrorObjectNotFound
   557  }
   558  
   559  // mkdir makes the directory and parents using unrooted paths
   560  func (f *Fs) mkdir(abspath string) error {
   561  	abspath = path.Clean(abspath)
   562  	if abspath == "." || abspath == "/" {
   563  		return nil
   564  	}
   565  	fi, err := f.getInfo(abspath)
   566  	if err == nil {
   567  		if fi.IsDir {
   568  			return nil
   569  		}
   570  		return fs.ErrorIsFile
   571  	} else if err != fs.ErrorObjectNotFound {
   572  		return errors.Wrapf(err, "mkdir %q failed", abspath)
   573  	}
   574  	parent := path.Dir(abspath)
   575  	err = f.mkdir(parent)
   576  	if err != nil {
   577  		return err
   578  	}
   579  	c, connErr := f.getFtpConnection()
   580  	if connErr != nil {
   581  		return errors.Wrap(connErr, "mkdir")
   582  	}
   583  	err = c.MakeDir(f.dirFromStandardPath(abspath))
   584  	f.putFtpConnection(&c, err)
   585  	switch errX := err.(type) {
   586  	case *textproto.Error:
   587  		switch errX.Code {
   588  		case ftp.StatusFileUnavailable: // dir already exists: see issue #2181
   589  			err = nil
   590  		case 521: // dir already exists: error number according to RFC 959: issue #2363
   591  			err = nil
   592  		}
   593  	}
   594  	return err
   595  }
   596  
   597  // mkParentDir makes the parent of remote if necessary and any
   598  // directories above that
   599  func (f *Fs) mkParentDir(remote string) error {
   600  	parent := path.Dir(remote)
   601  	return f.mkdir(path.Join(f.root, parent))
   602  }
   603  
   604  // Mkdir creates the directory if it doesn't exist
   605  func (f *Fs) Mkdir(ctx context.Context, dir string) (err error) {
   606  	// defer fs.Trace(dir, "")("err=%v", &err)
   607  	root := path.Join(f.root, dir)
   608  	return f.mkdir(root)
   609  }
   610  
   611  // Rmdir removes the directory (container, bucket) if empty
   612  //
   613  // Return an error if it doesn't exist or isn't empty
   614  func (f *Fs) Rmdir(ctx context.Context, dir string) error {
   615  	c, err := f.getFtpConnection()
   616  	if err != nil {
   617  		return errors.Wrap(translateErrorFile(err), "Rmdir")
   618  	}
   619  	err = c.RemoveDir(f.dirFromStandardPath(path.Join(f.root, dir)))
   620  	f.putFtpConnection(&c, err)
   621  	return translateErrorDir(err)
   622  }
   623  
   624  // Move renames a remote file object
   625  func (f *Fs) Move(ctx context.Context, src fs.Object, remote string) (fs.Object, error) {
   626  	srcObj, ok := src.(*Object)
   627  	if !ok {
   628  		fs.Debugf(src, "Can't move - not same remote type")
   629  		return nil, fs.ErrorCantMove
   630  	}
   631  	err := f.mkParentDir(remote)
   632  	if err != nil {
   633  		return nil, errors.Wrap(err, "Move mkParentDir failed")
   634  	}
   635  	c, err := f.getFtpConnection()
   636  	if err != nil {
   637  		return nil, errors.Wrap(err, "Move")
   638  	}
   639  	err = c.Rename(
   640  		f.opt.Enc.FromStandardPath(path.Join(srcObj.fs.root, srcObj.remote)),
   641  		f.opt.Enc.FromStandardPath(path.Join(f.root, remote)),
   642  	)
   643  	f.putFtpConnection(&c, err)
   644  	if err != nil {
   645  		return nil, errors.Wrap(err, "Move Rename failed")
   646  	}
   647  	dstObj, err := f.NewObject(ctx, remote)
   648  	if err != nil {
   649  		return nil, errors.Wrap(err, "Move NewObject failed")
   650  	}
   651  	return dstObj, nil
   652  }
   653  
   654  // DirMove moves src, srcRemote to this remote at dstRemote
   655  // using server side move operations.
   656  //
   657  // Will only be called if src.Fs().Name() == f.Name()
   658  //
   659  // If it isn't possible then return fs.ErrorCantDirMove
   660  //
   661  // If destination exists then return fs.ErrorDirExists
   662  func (f *Fs) DirMove(ctx context.Context, src fs.Fs, srcRemote, dstRemote string) error {
   663  	srcFs, ok := src.(*Fs)
   664  	if !ok {
   665  		fs.Debugf(srcFs, "Can't move directory - not same remote type")
   666  		return fs.ErrorCantDirMove
   667  	}
   668  	srcPath := path.Join(srcFs.root, srcRemote)
   669  	dstPath := path.Join(f.root, dstRemote)
   670  
   671  	// Check if destination exists
   672  	fi, err := f.getInfo(dstPath)
   673  	if err == nil {
   674  		if fi.IsDir {
   675  			return fs.ErrorDirExists
   676  		}
   677  		return fs.ErrorIsFile
   678  	} else if err != fs.ErrorObjectNotFound {
   679  		return errors.Wrapf(err, "DirMove getInfo failed")
   680  	}
   681  
   682  	// Make sure the parent directory exists
   683  	err = f.mkdir(path.Dir(dstPath))
   684  	if err != nil {
   685  		return errors.Wrap(err, "DirMove mkParentDir dst failed")
   686  	}
   687  
   688  	// Do the move
   689  	c, err := f.getFtpConnection()
   690  	if err != nil {
   691  		return errors.Wrap(err, "DirMove")
   692  	}
   693  	err = c.Rename(
   694  		f.dirFromStandardPath(srcPath),
   695  		f.dirFromStandardPath(dstPath),
   696  	)
   697  	f.putFtpConnection(&c, err)
   698  	if err != nil {
   699  		return errors.Wrapf(err, "DirMove Rename(%q,%q) failed", srcPath, dstPath)
   700  	}
   701  	return nil
   702  }
   703  
   704  // ------------------------------------------------------------
   705  
   706  // Fs returns the parent Fs
   707  func (o *Object) Fs() fs.Info {
   708  	return o.fs
   709  }
   710  
   711  // String version of o
   712  func (o *Object) String() string {
   713  	if o == nil {
   714  		return "<nil>"
   715  	}
   716  	return o.remote
   717  }
   718  
   719  // Remote returns the remote path
   720  func (o *Object) Remote() string {
   721  	return o.remote
   722  }
   723  
   724  // Hash returns the hash of an object returning a lowercase hex string
   725  func (o *Object) Hash(ctx context.Context, t hash.Type) (string, error) {
   726  	return "", hash.ErrUnsupported
   727  }
   728  
   729  // Size returns the size of an object in bytes
   730  func (o *Object) Size() int64 {
   731  	return int64(o.info.Size)
   732  }
   733  
   734  // ModTime returns the modification time of the object
   735  func (o *Object) ModTime(ctx context.Context) time.Time {
   736  	return o.info.ModTime
   737  }
   738  
   739  // SetModTime sets the modification time of the object
   740  func (o *Object) SetModTime(ctx context.Context, modTime time.Time) error {
   741  	return nil
   742  }
   743  
   744  // Storable returns a boolean as to whether this object is storable
   745  func (o *Object) Storable() bool {
   746  	return true
   747  }
   748  
   749  // ftpReadCloser implements io.ReadCloser for FTP objects.
   750  type ftpReadCloser struct {
   751  	rc  io.ReadCloser
   752  	c   *ftp.ServerConn
   753  	f   *Fs
   754  	err error // errors found during read
   755  }
   756  
   757  // Read bytes into p
   758  func (f *ftpReadCloser) Read(p []byte) (n int, err error) {
   759  	n, err = f.rc.Read(p)
   760  	if err != nil && err != io.EOF {
   761  		f.err = err // store any errors for Close to examine
   762  	}
   763  	return
   764  }
   765  
   766  // Close the FTP reader and return the connection to the pool
   767  func (f *ftpReadCloser) Close() error {
   768  	var err error
   769  	errchan := make(chan error, 1)
   770  	go func() {
   771  		errchan <- f.rc.Close()
   772  	}()
   773  	// Wait for Close for up to 60 seconds
   774  	timer := time.NewTimer(60 * time.Second)
   775  	select {
   776  	case err = <-errchan:
   777  		timer.Stop()
   778  	case <-timer.C:
   779  		// if timer fired assume no error but connection dead
   780  		fs.Errorf(f.f, "Timeout when waiting for connection Close")
   781  		return nil
   782  	}
   783  	// if errors while reading or closing, dump the connection
   784  	if err != nil || f.err != nil {
   785  		_ = f.c.Quit()
   786  	} else {
   787  		f.f.putFtpConnection(&f.c, nil)
   788  	}
   789  	// mask the error if it was caused by a premature close
   790  	switch errX := err.(type) {
   791  	case *textproto.Error:
   792  		switch errX.Code {
   793  		case ftp.StatusTransfertAborted, ftp.StatusFileUnavailable:
   794  			err = nil
   795  		}
   796  	}
   797  	return err
   798  }
   799  
   800  // Open an object for read
   801  func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (rc io.ReadCloser, err error) {
   802  	// defer fs.Trace(o, "")("rc=%v, err=%v", &rc, &err)
   803  	path := path.Join(o.fs.root, o.remote)
   804  	var offset, limit int64 = 0, -1
   805  	for _, option := range options {
   806  		switch x := option.(type) {
   807  		case *fs.SeekOption:
   808  			offset = x.Offset
   809  		case *fs.RangeOption:
   810  			offset, limit = x.Decode(o.Size())
   811  		default:
   812  			if option.Mandatory() {
   813  				fs.Logf(o, "Unsupported mandatory option: %v", option)
   814  			}
   815  		}
   816  	}
   817  	c, err := o.fs.getFtpConnection()
   818  	if err != nil {
   819  		return nil, errors.Wrap(err, "open")
   820  	}
   821  	fd, err := c.RetrFrom(o.fs.opt.Enc.FromStandardPath(path), uint64(offset))
   822  	if err != nil {
   823  		o.fs.putFtpConnection(&c, err)
   824  		return nil, errors.Wrap(err, "open")
   825  	}
   826  	rc = &ftpReadCloser{rc: readers.NewLimitedReadCloser(fd, limit), c: c, f: o.fs}
   827  	return rc, nil
   828  }
   829  
   830  // Update the already existing object
   831  //
   832  // Copy the reader into the object updating modTime and size
   833  //
   834  // The new object may have been created if an error is returned
   835  func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (err error) {
   836  	// defer fs.Trace(o, "src=%v", src)("err=%v", &err)
   837  	path := path.Join(o.fs.root, o.remote)
   838  	// remove the file if upload failed
   839  	remove := func() {
   840  		// Give the FTP server a chance to get its internal state in order after the error.
   841  		// The error may have been local in which case we closed the connection.  The server
   842  		// may still be dealing with it for a moment. A sleep isn't ideal but I haven't been
   843  		// able to think of a better method to find out if the server has finished - ncw
   844  		time.Sleep(1 * time.Second)
   845  		removeErr := o.Remove(ctx)
   846  		if removeErr != nil {
   847  			fs.Debugf(o, "Failed to remove: %v", removeErr)
   848  		} else {
   849  			fs.Debugf(o, "Removed after failed upload: %v", err)
   850  		}
   851  	}
   852  	c, err := o.fs.getFtpConnection()
   853  	if err != nil {
   854  		return errors.Wrap(err, "Update")
   855  	}
   856  	err = c.Stor(o.fs.opt.Enc.FromStandardPath(path), in)
   857  	if err != nil {
   858  		_ = c.Quit() // toss this connection to avoid sync errors
   859  		remove()
   860  		return errors.Wrap(err, "update stor")
   861  	}
   862  	o.fs.putFtpConnection(&c, nil)
   863  	o.info, err = o.fs.getInfo(path)
   864  	if err != nil {
   865  		return errors.Wrap(err, "update getinfo")
   866  	}
   867  	return nil
   868  }
   869  
   870  // Remove an object
   871  func (o *Object) Remove(ctx context.Context) (err error) {
   872  	// defer fs.Trace(o, "")("err=%v", &err)
   873  	path := path.Join(o.fs.root, o.remote)
   874  	// Check if it's a directory or a file
   875  	info, err := o.fs.getInfo(path)
   876  	if err != nil {
   877  		return err
   878  	}
   879  	if info.IsDir {
   880  		err = o.fs.Rmdir(ctx, o.remote)
   881  	} else {
   882  		c, err := o.fs.getFtpConnection()
   883  		if err != nil {
   884  			return errors.Wrap(err, "Remove")
   885  		}
   886  		err = c.Delete(o.fs.opt.Enc.FromStandardPath(path))
   887  		o.fs.putFtpConnection(&c, err)
   888  	}
   889  	return err
   890  }
   891  
   892  // Check the interfaces are satisfied
   893  var (
   894  	_ fs.Fs          = &Fs{}
   895  	_ fs.Mover       = &Fs{}
   896  	_ fs.DirMover    = &Fs{}
   897  	_ fs.PutStreamer = &Fs{}
   898  	_ fs.Object      = &Object{}
   899  )