github.com/ncw/rclone@v1.48.1-0.20190724201158-a35aa1360e3e/backend/local/local.go (about) 1 // Package local provides a filesystem interface 2 package local 3 4 import ( 5 "bytes" 6 "context" 7 "fmt" 8 "io" 9 "io/ioutil" 10 "os" 11 "path" 12 "path/filepath" 13 "regexp" 14 "runtime" 15 "strings" 16 "sync" 17 "time" 18 "unicode/utf8" 19 20 "github.com/ncw/rclone/fs" 21 "github.com/ncw/rclone/fs/accounting" 22 "github.com/ncw/rclone/fs/config/configmap" 23 "github.com/ncw/rclone/fs/config/configstruct" 24 "github.com/ncw/rclone/fs/fserrors" 25 "github.com/ncw/rclone/fs/hash" 26 "github.com/ncw/rclone/lib/file" 27 "github.com/ncw/rclone/lib/readers" 28 "github.com/pkg/errors" 29 ) 30 31 // Constants 32 const devUnset = 0xdeadbeefcafebabe // a device id meaning it is unset 33 const linkSuffix = ".rclonelink" // The suffix added to a translated symbolic link 34 const useReadDir = (runtime.GOOS == "windows" || runtime.GOOS == "plan9") // these OSes read FileInfos directly 35 36 // Register with Fs 37 func init() { 38 fsi := &fs.RegInfo{ 39 Name: "local", 40 Description: "Local Disk", 41 NewFs: NewFs, 42 Options: []fs.Option{{ 43 Name: "nounc", 44 Help: "Disable UNC (long path names) conversion on Windows", 45 Examples: []fs.OptionExample{{ 46 Value: "true", 47 Help: "Disables long file names", 48 }}, 49 }, { 50 Name: "copy_links", 51 Help: "Follow symlinks and copy the pointed to item.", 52 Default: false, 53 NoPrefix: true, 54 ShortOpt: "L", 55 Advanced: true, 56 }, { 57 Name: "links", 58 Help: "Translate symlinks to/from regular files with a '" + linkSuffix + "' extension", 59 Default: false, 60 NoPrefix: true, 61 ShortOpt: "l", 62 Advanced: true, 63 }, { 64 Name: "skip_links", 65 Help: `Don't warn about skipped symlinks. 66 This flag disables warning messages on skipped symlinks or junction 67 points, as you explicitly acknowledge that they should be skipped.`, 68 Default: false, 69 NoPrefix: true, 70 Advanced: true, 71 }, { 72 Name: "no_unicode_normalization", 73 Help: `Don't apply unicode normalization to paths and filenames (Deprecated) 74 75 This flag is deprecated now. Rclone no longer normalizes unicode file 76 names, but it compares them with unicode normalization in the sync 77 routine instead.`, 78 Default: false, 79 Advanced: true, 80 }, { 81 Name: "no_check_updated", 82 Help: `Don't check to see if the files change during upload 83 84 Normally rclone checks the size and modification time of files as they 85 are being uploaded and aborts with a message which starts "can't copy 86 - source file is being updated" if the file changes during upload. 87 88 However on some file systems this modification time check may fail (eg 89 [Glusterfs #2206](https://github.com/ncw/rclone/issues/2206)) so this 90 check can be disabled with this flag.`, 91 Default: false, 92 Advanced: true, 93 }, { 94 Name: "one_file_system", 95 Help: "Don't cross filesystem boundaries (unix/macOS only).", 96 Default: false, 97 NoPrefix: true, 98 ShortOpt: "x", 99 Advanced: true, 100 }, { 101 Name: "case_sensitive", 102 Help: `Force the filesystem to report itself as case sensitive. 103 104 Normally the local backend declares itself as case insensitive on 105 Windows/macOS and case sensitive for everything else. Use this flag 106 to override the default choice.`, 107 Default: false, 108 Advanced: true, 109 }, { 110 Name: "case_insensitive", 111 Help: `Force the filesystem to report itself as case insensitive 112 113 Normally the local backend declares itself as case insensitive on 114 Windows/macOS and case sensitive for everything else. Use this flag 115 to override the default choice.`, 116 Default: false, 117 Advanced: true, 118 }}, 119 } 120 fs.Register(fsi) 121 } 122 123 // Options defines the configuration for this backend 124 type Options struct { 125 FollowSymlinks bool `config:"copy_links"` 126 TranslateSymlinks bool `config:"links"` 127 SkipSymlinks bool `config:"skip_links"` 128 NoUTFNorm bool `config:"no_unicode_normalization"` 129 NoCheckUpdated bool `config:"no_check_updated"` 130 NoUNC bool `config:"nounc"` 131 OneFileSystem bool `config:"one_file_system"` 132 CaseSensitive bool `config:"case_sensitive"` 133 CaseInsensitive bool `config:"case_insensitive"` 134 } 135 136 // Fs represents a local filesystem rooted at root 137 type Fs struct { 138 name string // the name of the remote 139 root string // The root directory (OS path) 140 opt Options // parsed config options 141 features *fs.Features // optional features 142 dev uint64 // device number of root node 143 precisionOk sync.Once // Whether we need to read the precision 144 precision time.Duration // precision of local filesystem 145 wmu sync.Mutex // used for locking access to 'warned'. 146 warned map[string]struct{} // whether we have warned about this string 147 // do os.Lstat or os.Stat 148 lstat func(name string) (os.FileInfo, error) 149 dirNames *mapper // directory name mapping 150 objectHashesMu sync.Mutex // global lock for Object.hashes 151 } 152 153 // Object represents a local filesystem object 154 type Object struct { 155 fs *Fs // The Fs this object is part of 156 remote string // The remote path - properly UTF-8 encoded - for rclone 157 path string // The local path - may not be properly UTF-8 encoded - for OS 158 size int64 // file metadata - always present 159 mode os.FileMode 160 modTime time.Time 161 hashes map[hash.Type]string // Hashes 162 translatedLink bool // Is this object a translated link 163 } 164 165 // ------------------------------------------------------------ 166 167 var errLinksAndCopyLinks = errors.New("can't use -l/--links with -L/--copy-links") 168 169 // NewFs constructs an Fs from the path 170 func NewFs(name, root string, m configmap.Mapper) (fs.Fs, error) { 171 // Parse config into Options struct 172 opt := new(Options) 173 err := configstruct.Set(m, opt) 174 if err != nil { 175 return nil, err 176 } 177 if opt.TranslateSymlinks && opt.FollowSymlinks { 178 return nil, errLinksAndCopyLinks 179 } 180 181 if opt.NoUTFNorm { 182 fs.Errorf(nil, "The --local-no-unicode-normalization flag is deprecated and will be removed") 183 } 184 185 f := &Fs{ 186 name: name, 187 opt: *opt, 188 warned: make(map[string]struct{}), 189 dev: devUnset, 190 lstat: os.Lstat, 191 dirNames: newMapper(), 192 } 193 f.root = f.cleanPath(root) 194 f.features = (&fs.Features{ 195 CaseInsensitive: f.caseInsensitive(), 196 CanHaveEmptyDirectories: true, 197 }).Fill(f) 198 if opt.FollowSymlinks { 199 f.lstat = os.Stat 200 } 201 202 // Check to see if this points to a file 203 fi, err := f.lstat(f.root) 204 if err == nil { 205 f.dev = readDevice(fi, f.opt.OneFileSystem) 206 } 207 if err == nil && f.isRegular(fi.Mode()) { 208 // It is a file, so use the parent as the root 209 f.root = filepath.Dir(f.root) 210 // return an error with an fs which points to the parent 211 return f, fs.ErrorIsFile 212 } 213 return f, nil 214 } 215 216 // Determine whether a file is a 'regular' file, 217 // Symlinks are regular files, only if the TranslateSymlink 218 // option is in-effect 219 func (f *Fs) isRegular(mode os.FileMode) bool { 220 if !f.opt.TranslateSymlinks { 221 return mode.IsRegular() 222 } 223 224 // fi.Mode().IsRegular() tests that all mode bits are zero 225 // Since symlinks are accepted, test that all other bits are zero, 226 // except the symlink bit 227 return mode&os.ModeType&^os.ModeSymlink == 0 228 } 229 230 // Name of the remote (as passed into NewFs) 231 func (f *Fs) Name() string { 232 return f.name 233 } 234 235 // Root of the remote (as passed into NewFs) 236 func (f *Fs) Root() string { 237 return f.root 238 } 239 240 // String converts this Fs to a string 241 func (f *Fs) String() string { 242 return fmt.Sprintf("Local file system at %s", f.root) 243 } 244 245 // Features returns the optional features of this Fs 246 func (f *Fs) Features() *fs.Features { 247 return f.features 248 } 249 250 // caseInsensitive returns whether the remote is case insensitive or not 251 func (f *Fs) caseInsensitive() bool { 252 if f.opt.CaseSensitive { 253 return false 254 } 255 if f.opt.CaseInsensitive { 256 return true 257 } 258 // FIXME not entirely accurate since you can have case 259 // sensitive Fses on darwin and case insensitive Fses on linux. 260 // Should probably check but that would involve creating a 261 // file in the remote to be most accurate which probably isn't 262 // desirable. 263 return runtime.GOOS == "windows" || runtime.GOOS == "darwin" 264 } 265 266 // translateLink checks whether the remote is a translated link 267 // and returns a new path, removing the suffix as needed, 268 // It also returns whether this is a translated link at all 269 // 270 // for regular files, dstPath is returned unchanged 271 func translateLink(remote, dstPath string) (newDstPath string, isTranslatedLink bool) { 272 isTranslatedLink = strings.HasSuffix(remote, linkSuffix) 273 newDstPath = strings.TrimSuffix(dstPath, linkSuffix) 274 return newDstPath, isTranslatedLink 275 } 276 277 // newObject makes a half completed Object 278 // 279 // if dstPath is empty then it is made from remote 280 func (f *Fs) newObject(remote, dstPath string) *Object { 281 translatedLink := false 282 283 if dstPath == "" { 284 dstPath = f.cleanPath(filepath.Join(f.root, remote)) 285 } 286 remote = f.cleanRemote(remote) 287 288 if f.opt.TranslateSymlinks { 289 // Possibly receive a new name for dstPath 290 dstPath, translatedLink = translateLink(remote, dstPath) 291 } 292 293 return &Object{ 294 fs: f, 295 remote: remote, 296 path: dstPath, 297 translatedLink: translatedLink, 298 } 299 } 300 301 // Return an Object from a path 302 // 303 // May return nil if an error occurred 304 func (f *Fs) newObjectWithInfo(remote, dstPath string, info os.FileInfo) (fs.Object, error) { 305 o := f.newObject(remote, dstPath) 306 if info != nil { 307 o.setMetadata(info) 308 } else { 309 err := o.lstat() 310 if err != nil { 311 if os.IsNotExist(err) { 312 return nil, fs.ErrorObjectNotFound 313 } 314 if os.IsPermission(err) { 315 return nil, fs.ErrorPermissionDenied 316 } 317 return nil, err 318 } 319 // Handle the odd case, that a symlink was specified by name without the link suffix 320 if o.fs.opt.TranslateSymlinks && o.mode&os.ModeSymlink != 0 && !o.translatedLink { 321 return nil, fs.ErrorObjectNotFound 322 } 323 324 } 325 if o.mode.IsDir() { 326 return nil, errors.Wrapf(fs.ErrorNotAFile, "%q", remote) 327 } 328 return o, nil 329 } 330 331 // NewObject finds the Object at remote. If it can't be found 332 // it returns the error ErrorObjectNotFound. 333 func (f *Fs) NewObject(ctx context.Context, remote string) (fs.Object, error) { 334 return f.newObjectWithInfo(remote, "", nil) 335 } 336 337 // List the objects and directories in dir into entries. The 338 // entries can be returned in any order but should be for a 339 // complete directory. 340 // 341 // dir should be "" to list the root, and should not have 342 // trailing slashes. 343 // 344 // This should return ErrDirNotFound if the directory isn't 345 // found. 346 func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err error) { 347 348 dir = f.dirNames.Load(dir) 349 fsDirPath := f.cleanPath(filepath.Join(f.root, dir)) 350 remote := f.cleanRemote(dir) 351 _, err = os.Stat(fsDirPath) 352 if err != nil { 353 return nil, fs.ErrorDirNotFound 354 } 355 356 fd, err := os.Open(fsDirPath) 357 if err != nil { 358 isPerm := os.IsPermission(err) 359 err = errors.Wrapf(err, "failed to open directory %q", dir) 360 fs.Errorf(dir, "%v", err) 361 if isPerm { 362 accounting.Stats.Error(fserrors.NoRetryError(err)) 363 err = nil // ignore error but fail sync 364 } 365 return nil, err 366 } 367 defer func() { 368 cerr := fd.Close() 369 if cerr != nil && err == nil { 370 err = errors.Wrapf(cerr, "failed to close directory %q:", dir) 371 } 372 }() 373 374 for { 375 var fis []os.FileInfo 376 if useReadDir { 377 // Windows and Plan9 read the directory entries with the stat information in which 378 // shouldn't fail because of unreadable entries. 379 fis, err = fd.Readdir(1024) 380 if err == io.EOF && len(fis) == 0 { 381 break 382 } 383 } else { 384 // For other OSes we read the names only (which shouldn't fail) then stat the 385 // individual ourselves so we can log errors but not fail the directory read. 386 var names []string 387 names, err = fd.Readdirnames(1024) 388 if err == io.EOF && len(names) == 0 { 389 break 390 } 391 if err == nil { 392 for _, name := range names { 393 namepath := filepath.Join(fsDirPath, name) 394 fi, fierr := os.Lstat(namepath) 395 if fierr != nil { 396 err = errors.Wrapf(err, "failed to read directory %q", namepath) 397 fs.Errorf(dir, "%v", fierr) 398 accounting.Stats.Error(fserrors.NoRetryError(fierr)) // fail the sync 399 continue 400 } 401 fis = append(fis, fi) 402 } 403 } 404 } 405 if err != nil { 406 return nil, errors.Wrap(err, "failed to read directory entry") 407 } 408 409 for _, fi := range fis { 410 name := fi.Name() 411 mode := fi.Mode() 412 newRemote := path.Join(remote, name) 413 newPath := filepath.Join(fsDirPath, name) 414 // Follow symlinks if required 415 if f.opt.FollowSymlinks && (mode&os.ModeSymlink) != 0 { 416 fi, err = os.Stat(newPath) 417 if os.IsNotExist(err) { 418 // Skip bad symlinks 419 err = fserrors.NoRetryError(errors.Wrap(err, "symlink")) 420 fs.Errorf(newRemote, "Listing error: %v", err) 421 accounting.Stats.Error(err) 422 continue 423 } 424 if err != nil { 425 return nil, err 426 } 427 mode = fi.Mode() 428 } 429 if fi.IsDir() { 430 // Ignore directories which are symlinks. These are junction points under windows which 431 // are kind of a souped up symlink. Unix doesn't have directories which are symlinks. 432 if (mode&os.ModeSymlink) == 0 && f.dev == readDevice(fi, f.opt.OneFileSystem) { 433 d := fs.NewDir(f.dirNames.Save(newRemote, f.cleanRemote(newRemote)), fi.ModTime()) 434 entries = append(entries, d) 435 } 436 } else { 437 // Check whether this link should be translated 438 if f.opt.TranslateSymlinks && fi.Mode()&os.ModeSymlink != 0 { 439 newRemote += linkSuffix 440 } 441 fso, err := f.newObjectWithInfo(newRemote, newPath, fi) 442 if err != nil { 443 return nil, err 444 } 445 if fso.Storable() { 446 entries = append(entries, fso) 447 } 448 } 449 } 450 } 451 return entries, nil 452 } 453 454 // cleanRemote makes string a valid UTF-8 string for remote strings. 455 // 456 // Any invalid UTF-8 characters will be replaced with utf8.RuneError 457 // It also normalises the UTF-8 and converts the slashes if necessary. 458 func (f *Fs) cleanRemote(name string) string { 459 if !utf8.ValidString(name) { 460 f.wmu.Lock() 461 if _, ok := f.warned[name]; !ok { 462 fs.Logf(f, "Replacing invalid UTF-8 characters in %q", name) 463 f.warned[name] = struct{}{} 464 } 465 f.wmu.Unlock() 466 name = string([]rune(name)) 467 } 468 name = filepath.ToSlash(name) 469 return name 470 } 471 472 // mapper maps raw to cleaned directory names 473 type mapper struct { 474 mu sync.RWMutex // mutex to protect the below 475 m map[string]string // map of un-normalised directory names 476 } 477 478 func newMapper() *mapper { 479 return &mapper{ 480 m: make(map[string]string), 481 } 482 } 483 484 // Lookup a directory name to make a local name (reverses 485 // cleanDirName) 486 // 487 // FIXME this is temporary before we make a proper Directory object 488 func (m *mapper) Load(in string) string { 489 m.mu.RLock() 490 out, ok := m.m[in] 491 m.mu.RUnlock() 492 if ok { 493 return out 494 } 495 return in 496 } 497 498 // Cleans a directory name recording if it needed to be altered 499 // 500 // FIXME this is temporary before we make a proper Directory object 501 func (m *mapper) Save(in, out string) string { 502 if in != out { 503 m.mu.Lock() 504 m.m[out] = in 505 m.mu.Unlock() 506 } 507 return out 508 } 509 510 // Put the Object to the local filesystem 511 func (f *Fs) Put(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) { 512 remote := src.Remote() 513 // Temporary Object under construction - info filled in by Update() 514 o := f.newObject(remote, "") 515 err := o.Update(ctx, in, src, options...) 516 if err != nil { 517 return nil, err 518 } 519 return o, nil 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 // Mkdir creates the directory if it doesn't exist 528 func (f *Fs) Mkdir(ctx context.Context, dir string) error { 529 // FIXME: https://github.com/syncthing/syncthing/blob/master/lib/osutil/mkdirall_windows.go 530 root := f.cleanPath(filepath.Join(f.root, dir)) 531 err := os.MkdirAll(root, 0777) 532 if err != nil { 533 return err 534 } 535 if dir == "" { 536 fi, err := f.lstat(root) 537 if err != nil { 538 return err 539 } 540 f.dev = readDevice(fi, f.opt.OneFileSystem) 541 } 542 return nil 543 } 544 545 // Rmdir removes the directory 546 // 547 // If it isn't empty it will return an error 548 func (f *Fs) Rmdir(ctx context.Context, dir string) error { 549 root := f.cleanPath(filepath.Join(f.root, dir)) 550 return os.Remove(root) 551 } 552 553 // Precision of the file system 554 func (f *Fs) Precision() (precision time.Duration) { 555 f.precisionOk.Do(func() { 556 f.precision = f.readPrecision() 557 }) 558 return f.precision 559 } 560 561 // Read the precision 562 func (f *Fs) readPrecision() (precision time.Duration) { 563 // Default precision of 1s 564 precision = time.Second 565 566 // Create temporary file and test it 567 fd, err := ioutil.TempFile("", "rclone") 568 if err != nil { 569 // If failed return 1s 570 // fmt.Println("Failed to create temp file", err) 571 return time.Second 572 } 573 path := fd.Name() 574 // fmt.Println("Created temp file", path) 575 err = fd.Close() 576 if err != nil { 577 return time.Second 578 } 579 580 // Delete it on return 581 defer func() { 582 // fmt.Println("Remove temp file") 583 _ = os.Remove(path) // ignore error 584 }() 585 586 // Find the minimum duration we can detect 587 for duration := time.Duration(1); duration < time.Second; duration *= 10 { 588 // Current time with delta 589 t := time.Unix(time.Now().Unix(), int64(duration)) 590 err := os.Chtimes(path, t, t) 591 if err != nil { 592 // fmt.Println("Failed to Chtimes", err) 593 break 594 } 595 596 // Read the actual time back 597 fi, err := os.Stat(path) 598 if err != nil { 599 // fmt.Println("Failed to Stat", err) 600 break 601 } 602 603 // If it matches - have found the precision 604 // fmt.Println("compare", fi.ModTime(ctx), t) 605 if fi.ModTime().Equal(t) { 606 // fmt.Println("Precision detected as", duration) 607 return duration 608 } 609 } 610 return 611 } 612 613 // Purge deletes all the files and directories 614 // 615 // Optional interface: Only implement this if you have a way of 616 // deleting all the files quicker than just running Remove() on the 617 // result of List() 618 func (f *Fs) Purge(ctx context.Context) error { 619 fi, err := f.lstat(f.root) 620 if err != nil { 621 return err 622 } 623 if !fi.Mode().IsDir() { 624 return errors.Errorf("can't purge non directory: %q", f.root) 625 } 626 return os.RemoveAll(f.root) 627 } 628 629 // Move src to this remote using server side move operations. 630 // 631 // This is stored with the remote path given 632 // 633 // It returns the destination Object and a possible error 634 // 635 // Will only be called if src.Fs().Name() == f.Name() 636 // 637 // If it isn't possible then return fs.ErrorCantMove 638 func (f *Fs) Move(ctx context.Context, src fs.Object, remote string) (fs.Object, error) { 639 srcObj, ok := src.(*Object) 640 if !ok { 641 fs.Debugf(src, "Can't move - not same remote type") 642 return nil, fs.ErrorCantMove 643 } 644 645 // Temporary Object under construction 646 dstObj := f.newObject(remote, "") 647 648 // Check it is a file if it exists 649 err := dstObj.lstat() 650 if os.IsNotExist(err) { 651 // OK 652 } else if err != nil { 653 return nil, err 654 } else if !dstObj.fs.isRegular(dstObj.mode) { 655 // It isn't a file 656 return nil, errors.New("can't move file onto non-file") 657 } 658 659 // Create destination 660 err = dstObj.mkdirAll() 661 if err != nil { 662 return nil, err 663 } 664 665 // Do the move 666 err = os.Rename(srcObj.path, dstObj.path) 667 if os.IsNotExist(err) { 668 // race condition, source was deleted in the meantime 669 return nil, err 670 } else if os.IsPermission(err) { 671 // not enough rights to write to dst 672 return nil, err 673 } else if err != nil { 674 // not quite clear, but probably trying to move a file across file system 675 // boundaries. Copying might still work. 676 fs.Debugf(src, "Can't move: %v: trying copy", err) 677 return nil, fs.ErrorCantMove 678 } 679 680 // Update the info 681 err = dstObj.lstat() 682 if err != nil { 683 return nil, err 684 } 685 686 return dstObj, nil 687 } 688 689 // DirMove moves src, srcRemote to this remote at dstRemote 690 // using server side move operations. 691 // 692 // Will only be called if src.Fs().Name() == f.Name() 693 // 694 // If it isn't possible then return fs.ErrorCantDirMove 695 // 696 // If destination exists then return fs.ErrorDirExists 697 func (f *Fs) DirMove(ctx context.Context, src fs.Fs, srcRemote, dstRemote string) error { 698 srcFs, ok := src.(*Fs) 699 if !ok { 700 fs.Debugf(srcFs, "Can't move directory - not same remote type") 701 return fs.ErrorCantDirMove 702 } 703 srcPath := f.cleanPath(filepath.Join(srcFs.root, srcRemote)) 704 dstPath := f.cleanPath(filepath.Join(f.root, dstRemote)) 705 706 // Check if destination exists 707 _, err := os.Lstat(dstPath) 708 if !os.IsNotExist(err) { 709 return fs.ErrorDirExists 710 } 711 712 // Create parent of destination 713 dstParentPath := filepath.Dir(dstPath) 714 err = os.MkdirAll(dstParentPath, 0777) 715 if err != nil { 716 return err 717 } 718 719 // Do the move 720 err = os.Rename(srcPath, dstPath) 721 if os.IsNotExist(err) { 722 // race condition, source was deleted in the meantime 723 return err 724 } else if os.IsPermission(err) { 725 // not enough rights to write to dst 726 return err 727 } else if err != nil { 728 // not quite clear, but probably trying to move directory across file system 729 // boundaries. Copying might still work. 730 fs.Debugf(src, "Can't move dir: %v: trying copy", err) 731 return fs.ErrorCantDirMove 732 } 733 return nil 734 } 735 736 // Hashes returns the supported hash sets. 737 func (f *Fs) Hashes() hash.Set { 738 return hash.Supported 739 } 740 741 // ------------------------------------------------------------ 742 743 // Fs returns the parent Fs 744 func (o *Object) Fs() fs.Info { 745 return o.fs 746 } 747 748 // Return a string version 749 func (o *Object) String() string { 750 if o == nil { 751 return "<nil>" 752 } 753 return o.remote 754 } 755 756 // Remote returns the remote path 757 func (o *Object) Remote() string { 758 return o.remote 759 } 760 761 // Hash returns the requested hash of a file as a lowercase hex string 762 func (o *Object) Hash(ctx context.Context, r hash.Type) (string, error) { 763 // Check that the underlying file hasn't changed 764 oldtime := o.modTime 765 oldsize := o.size 766 err := o.lstat() 767 if err != nil { 768 return "", errors.Wrap(err, "hash: failed to stat") 769 } 770 771 o.fs.objectHashesMu.Lock() 772 hashes := o.hashes 773 hashValue, hashFound := o.hashes[r] 774 o.fs.objectHashesMu.Unlock() 775 776 if !o.modTime.Equal(oldtime) || oldsize != o.size || hashes == nil || !hashFound { 777 var in io.ReadCloser 778 779 if !o.translatedLink { 780 in, err = file.Open(o.path) 781 } else { 782 in, err = o.openTranslatedLink(0, -1) 783 } 784 if err != nil { 785 return "", errors.Wrap(err, "hash: failed to open") 786 } 787 hashes, err = hash.StreamTypes(in, hash.NewHashSet(r)) 788 closeErr := in.Close() 789 if err != nil { 790 return "", errors.Wrap(err, "hash: failed to read") 791 } 792 if closeErr != nil { 793 return "", errors.Wrap(closeErr, "hash: failed to close") 794 } 795 hashValue = hashes[r] 796 o.fs.objectHashesMu.Lock() 797 if o.hashes == nil { 798 o.hashes = hashes 799 } else { 800 o.hashes[r] = hashValue 801 } 802 o.fs.objectHashesMu.Unlock() 803 } 804 return hashValue, nil 805 } 806 807 // Size returns the size of an object in bytes 808 func (o *Object) Size() int64 { 809 return o.size 810 } 811 812 // ModTime returns the modification time of the object 813 func (o *Object) ModTime(ctx context.Context) time.Time { 814 return o.modTime 815 } 816 817 // SetModTime sets the modification time of the local fs object 818 func (o *Object) SetModTime(ctx context.Context, modTime time.Time) error { 819 var err error 820 if o.translatedLink { 821 err = lChtimes(o.path, modTime, modTime) 822 } else { 823 err = os.Chtimes(o.path, modTime, modTime) 824 } 825 if err != nil { 826 return err 827 } 828 // Re-read metadata 829 return o.lstat() 830 } 831 832 // Storable returns a boolean showing if this object is storable 833 func (o *Object) Storable() bool { 834 // Check for control characters in the remote name and show non storable 835 for _, c := range o.Remote() { 836 if c >= 0x00 && c < 0x20 || c == 0x7F { 837 fs.Logf(o.fs, "Can't store file with control characters: %q", o.Remote()) 838 return false 839 } 840 } 841 mode := o.mode 842 if mode&os.ModeSymlink != 0 && !o.fs.opt.TranslateSymlinks { 843 if !o.fs.opt.SkipSymlinks { 844 fs.Logf(o, "Can't follow symlink without -L/--copy-links") 845 } 846 return false 847 } else if mode&(os.ModeNamedPipe|os.ModeSocket|os.ModeDevice) != 0 { 848 fs.Logf(o, "Can't transfer non file/directory") 849 return false 850 } else if mode&os.ModeDir != 0 { 851 // fs.Debugf(o, "Skipping directory") 852 return false 853 } 854 return true 855 } 856 857 // localOpenFile wraps an io.ReadCloser and updates the md5sum of the 858 // object that is read 859 type localOpenFile struct { 860 o *Object // object that is open 861 in io.ReadCloser // handle we are wrapping 862 hash *hash.MultiHasher // currently accumulating hashes 863 fd *os.File // file object reference 864 } 865 866 // Read bytes from the object - see io.Reader 867 func (file *localOpenFile) Read(p []byte) (n int, err error) { 868 if !file.o.fs.opt.NoCheckUpdated { 869 // Check if file has the same size and modTime 870 fi, err := file.fd.Stat() 871 if err != nil { 872 return 0, errors.Wrap(err, "can't read status of source file while transferring") 873 } 874 if file.o.size != fi.Size() { 875 return 0, errors.Errorf("can't copy - source file is being updated (size changed from %d to %d)", file.o.size, fi.Size()) 876 } 877 if !file.o.modTime.Equal(fi.ModTime()) { 878 return 0, errors.Errorf("can't copy - source file is being updated (mod time changed from %v to %v)", file.o.modTime, fi.ModTime()) 879 } 880 } 881 882 n, err = file.in.Read(p) 883 if n > 0 { 884 // Hash routines never return an error 885 _, _ = file.hash.Write(p[:n]) 886 } 887 return 888 } 889 890 // Close the object and update the hashes 891 func (file *localOpenFile) Close() (err error) { 892 err = file.in.Close() 893 if err == nil { 894 if file.hash.Size() == file.o.Size() { 895 file.o.fs.objectHashesMu.Lock() 896 file.o.hashes = file.hash.Sums() 897 file.o.fs.objectHashesMu.Unlock() 898 } 899 } 900 return err 901 } 902 903 // Returns a ReadCloser() object that contains the contents of a symbolic link 904 func (o *Object) openTranslatedLink(offset, limit int64) (lrc io.ReadCloser, err error) { 905 // Read the link and return the destination it as the contents of the object 906 linkdst, err := os.Readlink(o.path) 907 if err != nil { 908 return nil, err 909 } 910 return readers.NewLimitedReadCloser(ioutil.NopCloser(strings.NewReader(linkdst[offset:])), limit), nil 911 } 912 913 // Open an object for read 914 func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (in io.ReadCloser, err error) { 915 var offset, limit int64 = 0, -1 916 hashes := hash.Supported 917 for _, option := range options { 918 switch x := option.(type) { 919 case *fs.SeekOption: 920 offset = x.Offset 921 case *fs.RangeOption: 922 offset, limit = x.Decode(o.size) 923 case *fs.HashesOption: 924 hashes = x.Hashes 925 default: 926 if option.Mandatory() { 927 fs.Logf(o, "Unsupported mandatory option: %v", option) 928 } 929 } 930 } 931 932 // Handle a translated link 933 if o.translatedLink { 934 return o.openTranslatedLink(offset, limit) 935 } 936 937 fd, err := file.Open(o.path) 938 if err != nil { 939 return 940 } 941 wrappedFd := readers.NewLimitedReadCloser(fd, limit) 942 if offset != 0 { 943 // seek the object 944 _, err = fd.Seek(offset, io.SeekStart) 945 // don't attempt to make checksums 946 return wrappedFd, err 947 } 948 hash, err := hash.NewMultiHasherTypes(hashes) 949 if err != nil { 950 return nil, err 951 } 952 // Update the md5sum as we go along 953 in = &localOpenFile{ 954 o: o, 955 in: wrappedFd, 956 hash: hash, 957 fd: fd, 958 } 959 return in, nil 960 } 961 962 // mkdirAll makes all the directories needed to store the object 963 func (o *Object) mkdirAll() error { 964 dir := filepath.Dir(o.path) 965 return os.MkdirAll(dir, 0777) 966 } 967 968 type nopWriterCloser struct { 969 *bytes.Buffer 970 } 971 972 func (nwc nopWriterCloser) Close() error { 973 // noop 974 return nil 975 } 976 977 // Update the object from in with modTime and size 978 func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) error { 979 var out io.WriteCloser 980 981 hashes := hash.Supported 982 for _, option := range options { 983 switch x := option.(type) { 984 case *fs.HashesOption: 985 hashes = x.Hashes 986 } 987 } 988 989 err := o.mkdirAll() 990 if err != nil { 991 return err 992 } 993 994 var symlinkData bytes.Buffer 995 // If the object is a regular file, create it. 996 // If it is a translated link, just read in the contents, and 997 // then create a symlink 998 if !o.translatedLink { 999 f, err := file.OpenFile(o.path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) 1000 if err != nil { 1001 return err 1002 } 1003 // Pre-allocate the file for performance reasons 1004 err = preAllocate(src.Size(), f) 1005 if err != nil { 1006 fs.Debugf(o, "Failed to pre-allocate: %v", err) 1007 } 1008 out = f 1009 } else { 1010 out = nopWriterCloser{&symlinkData} 1011 } 1012 1013 // Calculate the hash of the object we are reading as we go along 1014 hash, err := hash.NewMultiHasherTypes(hashes) 1015 if err != nil { 1016 return err 1017 } 1018 in = io.TeeReader(in, hash) 1019 1020 _, err = io.Copy(out, in) 1021 closeErr := out.Close() 1022 if err == nil { 1023 err = closeErr 1024 } 1025 1026 if o.translatedLink { 1027 if err == nil { 1028 // Remove any current symlink or file, if one exists 1029 if _, err := os.Lstat(o.path); err == nil { 1030 if removeErr := os.Remove(o.path); removeErr != nil { 1031 fs.Errorf(o, "Failed to remove previous file: %v", removeErr) 1032 return removeErr 1033 } 1034 } 1035 // Use the contents for the copied object to create a symlink 1036 err = os.Symlink(symlinkData.String(), o.path) 1037 } 1038 1039 // only continue if symlink creation succeeded 1040 if err != nil { 1041 return err 1042 } 1043 } 1044 1045 if err != nil { 1046 fs.Logf(o, "Removing partially written file on error: %v", err) 1047 if removeErr := os.Remove(o.path); removeErr != nil { 1048 fs.Errorf(o, "Failed to remove partially written file: %v", removeErr) 1049 } 1050 return err 1051 } 1052 1053 // All successful so update the hashes 1054 o.fs.objectHashesMu.Lock() 1055 o.hashes = hash.Sums() 1056 o.fs.objectHashesMu.Unlock() 1057 1058 // Set the mtime 1059 err = o.SetModTime(ctx, src.ModTime(ctx)) 1060 if err != nil { 1061 return err 1062 } 1063 1064 // ReRead info now that we have finished 1065 return o.lstat() 1066 } 1067 1068 // OpenWriterAt opens with a handle for random access writes 1069 // 1070 // Pass in the remote desired and the size if known. 1071 // 1072 // It truncates any existing object 1073 func (f *Fs) OpenWriterAt(ctx context.Context, remote string, size int64) (fs.WriterAtCloser, error) { 1074 // Temporary Object under construction 1075 o := f.newObject(remote, "") 1076 1077 err := o.mkdirAll() 1078 if err != nil { 1079 return nil, err 1080 } 1081 1082 if o.translatedLink { 1083 return nil, errors.New("can't open a symlink for random writing") 1084 } 1085 1086 out, err := file.OpenFile(o.path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) 1087 if err != nil { 1088 return nil, err 1089 } 1090 // Pre-allocate the file for performance reasons 1091 err = preAllocate(size, out) 1092 if err != nil { 1093 fs.Debugf(o, "Failed to pre-allocate: %v", err) 1094 } 1095 return out, nil 1096 } 1097 1098 // setMetadata sets the file info from the os.FileInfo passed in 1099 func (o *Object) setMetadata(info os.FileInfo) { 1100 // Don't overwrite the info if we don't need to 1101 // this avoids upsetting the race detector 1102 if o.size != info.Size() { 1103 o.size = info.Size() 1104 } 1105 if !o.modTime.Equal(info.ModTime()) { 1106 o.modTime = info.ModTime() 1107 } 1108 if o.mode != info.Mode() { 1109 o.mode = info.Mode() 1110 } 1111 } 1112 1113 // Stat a Object into info 1114 func (o *Object) lstat() error { 1115 info, err := o.fs.lstat(o.path) 1116 if err == nil { 1117 o.setMetadata(info) 1118 } 1119 return err 1120 } 1121 1122 // Remove an object 1123 func (o *Object) Remove(ctx context.Context) error { 1124 return remove(o.path) 1125 } 1126 1127 // cleanPathFragment cleans an OS path fragment which is part of a 1128 // bigger path and not necessarily absolute 1129 func cleanPathFragment(s string) string { 1130 if s == "" { 1131 return s 1132 } 1133 s = filepath.Clean(s) 1134 if runtime.GOOS == "windows" { 1135 s = strings.Replace(s, `/`, `\`, -1) 1136 } 1137 return s 1138 } 1139 1140 // cleanPath cleans and makes absolute the path passed in and returns 1141 // an OS path. 1142 // 1143 // The input might be in OS form or rclone form or a mixture, but the 1144 // output is in OS form. 1145 // 1146 // On windows it makes the path UNC also and replaces any characters 1147 // Windows can't deal with with their replacements. 1148 func (f *Fs) cleanPath(s string) string { 1149 s = cleanPathFragment(s) 1150 if runtime.GOOS == "windows" { 1151 if !filepath.IsAbs(s) && !strings.HasPrefix(s, "\\") { 1152 s2, err := filepath.Abs(s) 1153 if err == nil { 1154 s = s2 1155 } 1156 } 1157 if !f.opt.NoUNC { 1158 // Convert to UNC 1159 s = uncPath(s) 1160 } 1161 s = cleanWindowsName(f, s) 1162 } else { 1163 if !filepath.IsAbs(s) { 1164 s2, err := filepath.Abs(s) 1165 if err == nil { 1166 s = s2 1167 } 1168 } 1169 } 1170 return s 1171 } 1172 1173 // Pattern to match a windows absolute path: "c:\" and similar 1174 var isAbsWinDrive = regexp.MustCompile(`^[a-zA-Z]\:\\`) 1175 1176 // uncPath converts an absolute Windows path 1177 // to a UNC long path. 1178 func uncPath(s string) string { 1179 // UNC can NOT use "/", so convert all to "\" 1180 s = strings.Replace(s, `/`, `\`, -1) 1181 1182 // If prefix is "\\", we already have a UNC path or server. 1183 if strings.HasPrefix(s, `\\`) { 1184 // If already long path, just keep it 1185 if strings.HasPrefix(s, `\\?\`) { 1186 return s 1187 } 1188 1189 // Trim "\\" from path and add UNC prefix. 1190 return `\\?\UNC\` + strings.TrimPrefix(s, `\\`) 1191 } 1192 if isAbsWinDrive.MatchString(s) { 1193 return `\\?\` + s 1194 } 1195 return s 1196 } 1197 1198 // cleanWindowsName will clean invalid Windows characters replacing them with _ 1199 func cleanWindowsName(f *Fs, name string) string { 1200 original := name 1201 var name2 string 1202 if strings.HasPrefix(name, `\\?\`) { 1203 name2 = `\\?\` 1204 name = strings.TrimPrefix(name, `\\?\`) 1205 } 1206 if strings.HasPrefix(name, `//?/`) { 1207 name2 = `//?/` 1208 name = strings.TrimPrefix(name, `//?/`) 1209 } 1210 // Colon is allowed as part of a drive name X:\ 1211 colonAt := strings.Index(name, ":") 1212 if colonAt > 0 && colonAt < 3 && len(name) > colonAt+1 { 1213 // Copy to name2, which is unfiltered 1214 name2 += name[0 : colonAt+1] 1215 name = name[colonAt+1:] 1216 } 1217 1218 name2 += strings.Map(func(r rune) rune { 1219 switch r { 1220 case '<', '>', '"', '|', '?', '*', ':': 1221 return '_' 1222 } 1223 return r 1224 }, name) 1225 1226 if name2 != original && f != nil { 1227 f.wmu.Lock() 1228 if _, ok := f.warned[name]; !ok { 1229 fs.Logf(f, "Replacing invalid characters in %q to %q", name, name2) 1230 f.warned[name] = struct{}{} 1231 } 1232 f.wmu.Unlock() 1233 } 1234 return name2 1235 } 1236 1237 // Check the interfaces are satisfied 1238 var ( 1239 _ fs.Fs = &Fs{} 1240 _ fs.Purger = &Fs{} 1241 _ fs.PutStreamer = &Fs{} 1242 _ fs.Mover = &Fs{} 1243 _ fs.DirMover = &Fs{} 1244 _ fs.OpenWriterAter = &Fs{} 1245 _ fs.Object = &Object{} 1246 )