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