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 )