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 )