Implement -L, --copy-links flag to allow rclone to follow symlinks

Fixes #40
This commit is contained in:
Nick Craig-Wood 2017-01-29 13:43:20 +00:00
parent 29c6e22024
commit 94947f2523
2 changed files with 70 additions and 6 deletions

View File

@ -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

View File

@ -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
}