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