From 94947f2523a0f137d9b741221c5bf271eed214c9 Mon Sep 17 00:00:00 2001 From: Nick Craig-Wood Date: Sun, 29 Jan 2017 13:43:20 +0000 Subject: [PATCH] Implement -L, --copy-links flag to allow rclone to follow symlinks Fixes #40 --- docs/content/local.md | 41 +++++++++++++++++++++++++++++++++++++++++ local/local.go | 35 +++++++++++++++++++++++++++++------ 2 files changed, 70 insertions(+), 6 deletions(-) diff --git a/docs/content/local.md b/docs/content/local.md index 3858e62fe..70757a06c 100644 --- a/docs/content/local.md +++ b/docs/content/local.md @@ -79,6 +79,47 @@ file exceeds 258 characters on z, so only use this option if you have to. Here are the command line options specific to local storage +#### --copy-links, -L #### + +Normally rclone will ignore symlinks or junction points (which behave +like symlinks under Windows). + +If you supply this flag then rclone will follow the symlink and copy +the pointed to file or directory. + +This flag applies to all commands. + +For example, supposing you have a directory structure like this + +``` +$ tree /tmp/a +/tmp/a +├── b -> ../b +├── expected -> ../expected +├── one +└── two + └── three +``` + +Then you can see the difference with and without the flag like this + +``` +$ rclone ls /tmp/a + 6 one + 6 two/three +``` + +and + +``` +$ rclone -L ls /tmp/a + 4174 expected + 6 one + 6 two/three + 6 b/two + 6 b/one +``` + #### --one-file-system, -x #### This tells rclone to stay in the filesystem specified by the root and diff --git a/local/local.go b/local/local.go index ba5c9874c..65f43225c 100644 --- a/local/local.go +++ b/local/local.go @@ -21,6 +21,10 @@ import ( "github.com/pkg/errors" ) +var ( + followSymlinks = fs.BoolP("copy-links", "L", false, "Follow symlinks and copy the pointed to item.") +) + // Constants const devUnset = 0xdeadbeefcafebabe // a device id meaning it is unset @@ -54,6 +58,8 @@ type Fs struct { wmu sync.Mutex // used for locking access to 'warned'. warned map[string]struct{} // whether we have warned about this string nounc bool // Skip UNC conversion on Windows + // do os.Lstat or os.Stat + lstat func(name string) (os.FileInfo, error) } // Object represents a local filesystem object @@ -77,12 +83,16 @@ func NewFs(name, root string) (fs.Fs, error) { warned: make(map[string]struct{}), nounc: nounc == "true", dev: devUnset, + lstat: os.Lstat, } f.root = f.cleanPath(root) f.features = (&fs.Features{CaseInsensitive: f.caseInsensitive()}).Fill(f) + if *followSymlinks { + f.lstat = os.Stat + } // Check to see if this points to a file - fi, err := os.Lstat(f.root) + fi, err := f.lstat(f.root) if err == nil { f.dev = readDevice(fi) } @@ -197,12 +207,22 @@ func (f *Fs) list(out fs.ListOpts, remote string, dirpath string, level int) (su for _, fi := range fis { name := fi.Name() + mode := fi.Mode() newRemote := path.Join(remote, name) newPath := filepath.Join(dirpath, name) + // Follow symlinks if required + if *followSymlinks && (mode&os.ModeSymlink) != 0 { + fi, err = os.Stat(newPath) + if err != nil { + out.SetError(err) + return nil + } + mode = fi.Mode() + } if fi.IsDir() { // Ignore directories which are symlinks. These are junction points under windows which // are kind of a souped up symlink. Unix doesn't have directories which are symlinks. - if (fi.Mode()&os.ModeSymlink) == 0 && out.IncludeDirectory(newRemote) { + if (mode&os.ModeSymlink) == 0 && out.IncludeDirectory(newRemote) { dir := &fs.Dir{ Name: f.cleanRemote(newRemote), When: fi.ModTime(), @@ -321,7 +341,7 @@ func (f *Fs) Mkdir(dir string) error { return err } if dir == "" { - fi, err := os.Lstat(root) + fi, err := f.lstat(root) if err != nil { return err } @@ -404,7 +424,7 @@ func (f *Fs) readPrecision() (precision time.Duration) { // deleting all the files quicker than just running Remove() on the // result of List() func (f *Fs) Purge() error { - fi, err := os.Lstat(f.root) + fi, err := f.lstat(f.root) if err != nil { return err } @@ -591,7 +611,10 @@ func (o *Object) Storable() bool { fs.Debug(o, "Clearing symlink bit to allow a file with reparse points to be copied") mode &^= os.ModeSymlink } - if mode&(os.ModeSymlink|os.ModeNamedPipe|os.ModeSocket|os.ModeDevice) != 0 { + if mode&os.ModeSymlink != 0 { + fs.Debug(o, "Can't follow symlink without -L/--copy-links") + return false + } else if mode&(os.ModeNamedPipe|os.ModeSocket|os.ModeDevice) != 0 { fs.Debug(o, "Can't transfer non file/directory") return false } else if mode&os.ModeDir != 0 { @@ -713,7 +736,7 @@ func (o *Object) Update(in io.Reader, src fs.ObjectInfo) error { // Stat a Object into info func (o *Object) lstat() error { - info, err := os.Lstat(o.path) + info, err := o.fs.lstat(o.path) o.info = info return err }