forked from TrueCloudLab/rclone
Implement -L, --copy-links flag to allow rclone to follow symlinks
Fixes #40
This commit is contained in:
parent
29c6e22024
commit
94947f2523
2 changed files with 70 additions and 6 deletions
|
@ -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
|
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 ####
|
#### --one-file-system, -x ####
|
||||||
|
|
||||||
This tells rclone to stay in the filesystem specified by the root and
|
This tells rclone to stay in the filesystem specified by the root and
|
||||||
|
|
|
@ -21,6 +21,10 @@ import (
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
followSymlinks = fs.BoolP("copy-links", "L", false, "Follow symlinks and copy the pointed to item.")
|
||||||
|
)
|
||||||
|
|
||||||
// Constants
|
// Constants
|
||||||
const devUnset = 0xdeadbeefcafebabe // a device id meaning it is unset
|
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'.
|
wmu sync.Mutex // used for locking access to 'warned'.
|
||||||
warned map[string]struct{} // whether we have warned about this string
|
warned map[string]struct{} // whether we have warned about this string
|
||||||
nounc bool // Skip UNC conversion on Windows
|
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
|
// Object represents a local filesystem object
|
||||||
|
@ -77,12 +83,16 @@ func NewFs(name, root string) (fs.Fs, error) {
|
||||||
warned: make(map[string]struct{}),
|
warned: make(map[string]struct{}),
|
||||||
nounc: nounc == "true",
|
nounc: nounc == "true",
|
||||||
dev: devUnset,
|
dev: devUnset,
|
||||||
|
lstat: os.Lstat,
|
||||||
}
|
}
|
||||||
f.root = f.cleanPath(root)
|
f.root = f.cleanPath(root)
|
||||||
f.features = (&fs.Features{CaseInsensitive: f.caseInsensitive()}).Fill(f)
|
f.features = (&fs.Features{CaseInsensitive: f.caseInsensitive()}).Fill(f)
|
||||||
|
if *followSymlinks {
|
||||||
|
f.lstat = os.Stat
|
||||||
|
}
|
||||||
|
|
||||||
// Check to see if this points to a file
|
// Check to see if this points to a file
|
||||||
fi, err := os.Lstat(f.root)
|
fi, err := f.lstat(f.root)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
f.dev = readDevice(fi)
|
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 {
|
for _, fi := range fis {
|
||||||
name := fi.Name()
|
name := fi.Name()
|
||||||
|
mode := fi.Mode()
|
||||||
newRemote := path.Join(remote, name)
|
newRemote := path.Join(remote, name)
|
||||||
newPath := filepath.Join(dirpath, 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() {
|
if fi.IsDir() {
|
||||||
// Ignore directories which are symlinks. These are junction points under windows which
|
// 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.
|
// 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{
|
dir := &fs.Dir{
|
||||||
Name: f.cleanRemote(newRemote),
|
Name: f.cleanRemote(newRemote),
|
||||||
When: fi.ModTime(),
|
When: fi.ModTime(),
|
||||||
|
@ -321,7 +341,7 @@ func (f *Fs) Mkdir(dir string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if dir == "" {
|
if dir == "" {
|
||||||
fi, err := os.Lstat(root)
|
fi, err := f.lstat(root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -404,7 +424,7 @@ func (f *Fs) readPrecision() (precision time.Duration) {
|
||||||
// deleting all the files quicker than just running Remove() on the
|
// deleting all the files quicker than just running Remove() on the
|
||||||
// result of List()
|
// result of List()
|
||||||
func (f *Fs) Purge() error {
|
func (f *Fs) Purge() error {
|
||||||
fi, err := os.Lstat(f.root)
|
fi, err := f.lstat(f.root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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")
|
fs.Debug(o, "Clearing symlink bit to allow a file with reparse points to be copied")
|
||||||
mode &^= os.ModeSymlink
|
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")
|
fs.Debug(o, "Can't transfer non file/directory")
|
||||||
return false
|
return false
|
||||||
} else if mode&os.ModeDir != 0 {
|
} 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
|
// Stat a Object into info
|
||||||
func (o *Object) lstat() error {
|
func (o *Object) lstat() error {
|
||||||
info, err := os.Lstat(o.path)
|
info, err := o.fs.lstat(o.path)
|
||||||
o.info = info
|
o.info = info
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue