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