mirror of
https://github.com/rclone/rclone.git
synced 2024-11-29 03:45:25 +01:00
vfs: ensure names used in cache path are legal on current os
Fixes #5360
This commit is contained in:
parent
18be4ad10d
commit
3a2f748aeb
@ -21,6 +21,7 @@ import (
|
|||||||
"github.com/rclone/rclone/fs/fserrors"
|
"github.com/rclone/rclone/fs/fserrors"
|
||||||
"github.com/rclone/rclone/fs/hash"
|
"github.com/rclone/rclone/fs/hash"
|
||||||
"github.com/rclone/rclone/fs/operations"
|
"github.com/rclone/rclone/fs/operations"
|
||||||
|
"github.com/rclone/rclone/lib/encoder"
|
||||||
"github.com/rclone/rclone/lib/file"
|
"github.com/rclone/rclone/lib/file"
|
||||||
"github.com/rclone/rclone/vfs/vfscache/writeback"
|
"github.com/rclone/rclone/vfs/vfscache/writeback"
|
||||||
"github.com/rclone/rclone/vfs/vfscommon"
|
"github.com/rclone/rclone/vfs/vfscommon"
|
||||||
@ -74,46 +75,53 @@ type AddVirtualFn func(remote string, size int64, isDir bool) error
|
|||||||
// This starts background goroutines which can be cancelled with the
|
// This starts background goroutines which can be cancelled with the
|
||||||
// context passed in.
|
// context passed in.
|
||||||
func New(ctx context.Context, fremote fs.Fs, opt *vfscommon.Options, avFn AddVirtualFn) (*Cache, error) {
|
func New(ctx context.Context, fremote fs.Fs, opt *vfscommon.Options, avFn AddVirtualFn) (*Cache, error) {
|
||||||
fName := fremote.Name()
|
// Get cache root path.
|
||||||
fRoot := filepath.FromSlash(fremote.Root())
|
// We need it in two variants: OS path as an absolute path with UNC prefix,
|
||||||
if runtime.GOOS == "windows" {
|
// OS-specific path separators, and encoded with OS-specific encoder. Standard path
|
||||||
if strings.HasPrefix(fRoot, `\\?`) {
|
// without UNC prefix, with slash path separators, and standard (internal) encoding.
|
||||||
fRoot = fRoot[3:]
|
// Care must be taken when creating OS paths so that the ':' separator following a
|
||||||
}
|
// drive letter is not encoded (e.g. into unicode fullwidth colon).
|
||||||
fRoot = strings.Replace(fRoot, ":", "", -1)
|
var err error
|
||||||
// Replace leading ':' if remote was created on the fly as ":backend:/path" as it is illegal in Windows
|
parentOSPath := config.CacheDir // Assuming string contains a local path in OS encoding
|
||||||
if fName[0] == ':' {
|
if parentOSPath, err = filepath.Abs(parentOSPath); err != nil {
|
||||||
fName = "^" + fName[1:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cacheDir := config.CacheDir
|
|
||||||
cacheDir, err := filepath.Abs(cacheDir)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "failed to make --cache-dir absolute")
|
return nil, errors.Wrap(err, "failed to make --cache-dir absolute")
|
||||||
}
|
}
|
||||||
root := file.UNCPath(filepath.Join(cacheDir, "vfs", fName, fRoot))
|
fs.Debugf(nil, "vfs cache: root is %q", parentOSPath)
|
||||||
fs.Debugf(nil, "vfs cache: root is %q", root)
|
parentPath := fromOSPath(parentOSPath)
|
||||||
metaRoot := file.UNCPath(filepath.Join(cacheDir, "vfsMeta", fName, fRoot))
|
|
||||||
fs.Debugf(nil, "vfs cache: metadata root is %q", root)
|
|
||||||
|
|
||||||
fcache, err := fscache.Get(ctx, root)
|
// Get a relative cache path representing the remote.
|
||||||
if err != nil {
|
relativeDirPath := fremote.Root() // This is a remote path in standard encoding
|
||||||
return nil, errors.Wrap(err, "failed to create cache remote")
|
if runtime.GOOS == "windows" {
|
||||||
|
if strings.HasPrefix(relativeDirPath, `//?/`) {
|
||||||
|
relativeDirPath = relativeDirPath[2:] // Trim off the "//" for the result to be a valid when appending to another path
|
||||||
}
|
}
|
||||||
fcacheMeta, err := fscache.Get(ctx, metaRoot)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "failed to create cache meta remote")
|
|
||||||
}
|
}
|
||||||
|
relativeDirPath = fremote.Name() + "/" + relativeDirPath
|
||||||
|
relativeDirOSPath := toOSPath(relativeDirPath)
|
||||||
|
|
||||||
hashType, hashOption := operations.CommonHash(ctx, fcache, fremote)
|
// Create cache root dirs
|
||||||
|
var dataOSPath, metaOSPath string
|
||||||
|
if dataOSPath, metaOSPath, err = createRootDirs(parentOSPath, relativeDirOSPath); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
fs.Debugf(nil, "vfs cache: data root is %q", dataOSPath)
|
||||||
|
fs.Debugf(nil, "vfs cache: metadata root is %q", metaOSPath)
|
||||||
|
|
||||||
|
// Get (create) cache backends
|
||||||
|
var fdata, fmeta fs.Fs
|
||||||
|
if fdata, fmeta, err = getBackends(ctx, parentPath, relativeDirPath); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
hashType, hashOption := operations.CommonHash(ctx, fdata, fremote)
|
||||||
|
|
||||||
|
// Create the cache object
|
||||||
c := &Cache{
|
c := &Cache{
|
||||||
fremote: fremote,
|
fremote: fremote,
|
||||||
fcache: fcache,
|
fcache: fdata,
|
||||||
fcacheMeta: fcacheMeta,
|
fcacheMeta: fmeta,
|
||||||
opt: opt,
|
opt: opt,
|
||||||
root: root,
|
root: dataOSPath,
|
||||||
metaRoot: metaRoot,
|
metaRoot: metaOSPath,
|
||||||
item: make(map[string]*Item),
|
item: make(map[string]*Item),
|
||||||
errItems: make(map[string]error),
|
errItems: make(map[string]error),
|
||||||
hashType: hashType,
|
hashType: hashType,
|
||||||
@ -122,12 +130,6 @@ func New(ctx context.Context, fremote fs.Fs, opt *vfscommon.Options, avFn AddVir
|
|||||||
avFn: avFn,
|
avFn: avFn,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure cache directories exist
|
|
||||||
_, err = c.mkdir("")
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "failed to make cache directory")
|
|
||||||
}
|
|
||||||
|
|
||||||
// load in the cache and metadata off disk
|
// load in the cache and metadata off disk
|
||||||
err = c.reload(ctx)
|
err = c.reload(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -146,6 +148,63 @@ func New(ctx context.Context, fremote fs.Fs, opt *vfscommon.Options, avFn AddVir
|
|||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// createDir creates a directory path, along with any necessary parents
|
||||||
|
func createDir(dir string) error {
|
||||||
|
return os.MkdirAll(dir, 0700)
|
||||||
|
}
|
||||||
|
|
||||||
|
// createRootDir creates a single cache root directory
|
||||||
|
func createRootDir(parentOSPath string, name string, relativeDirOSPath string) (path string, err error) {
|
||||||
|
path = file.UNCPath(filepath.Join(parentOSPath, name, relativeDirOSPath))
|
||||||
|
err = createDir(path)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// createRootDirs creates all cache root directories
|
||||||
|
func createRootDirs(parentOSPath string, relativeDirOSPath string) (dataOSPath string, metaOSPath string, err error) {
|
||||||
|
if dataOSPath, err = createRootDir(parentOSPath, "vfs", relativeDirOSPath); err != nil {
|
||||||
|
err = errors.Wrap(err, "failed to create data cache directory")
|
||||||
|
} else if metaOSPath, err = createRootDir(parentOSPath, "vfsMeta", relativeDirOSPath); err != nil {
|
||||||
|
err = errors.Wrap(err, "failed to create metadata cache directory")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// createItemDir creates the directory for named item in all cache roots
|
||||||
|
//
|
||||||
|
// Returns an os path for the data cache file.
|
||||||
|
func (c *Cache) createItemDir(name string) (string, error) {
|
||||||
|
parent := vfscommon.FindParent(name)
|
||||||
|
leaf := filepath.Base(name)
|
||||||
|
parentPath := c.toOSPath(parent)
|
||||||
|
err := createDir(parentPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.Wrap(err, "failed to create data cache item directory")
|
||||||
|
}
|
||||||
|
parentPathMeta := c.toOSPathMeta(parent)
|
||||||
|
err = createDir(parentPathMeta)
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.Wrap(err, "failed to create metadata cache item directory")
|
||||||
|
}
|
||||||
|
return filepath.Join(parentPath, leaf), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getBackend gets a backend for a cache root dir
|
||||||
|
func getBackend(ctx context.Context, parentPath string, name string, relativeDirPath string) (fs.Fs, error) {
|
||||||
|
path := fmt.Sprintf("%s/%s/%s", parentPath, name, relativeDirPath)
|
||||||
|
return fscache.Get(ctx, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getBackends gets backends for all cache root dirs
|
||||||
|
func getBackends(ctx context.Context, parentPath string, relativeDirPath string) (fdata fs.Fs, fmeta fs.Fs, err error) {
|
||||||
|
if fdata, err = getBackend(ctx, parentPath, "vfs", relativeDirPath); err != nil {
|
||||||
|
err = errors.Wrap(err, "failed to get data cache backend")
|
||||||
|
} else if fmeta, err = getBackend(ctx, parentPath, "vfsMeta", relativeDirPath); err != nil {
|
||||||
|
err = errors.Wrap(err, "failed to get metadata cache backend")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// clean returns the cleaned version of name for use in the index map
|
// clean returns the cleaned version of name for use in the index map
|
||||||
//
|
//
|
||||||
// name should be a remote path not an osPath
|
// name should be a remote path not an osPath
|
||||||
@ -158,33 +217,25 @@ func clean(name string) string {
|
|||||||
return name
|
return name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fromOSPath turns a OS path into a standard/remote path
|
||||||
|
func fromOSPath(osPath string) string {
|
||||||
|
return encoder.OS.ToStandardPath(filepath.ToSlash(osPath))
|
||||||
|
}
|
||||||
|
|
||||||
|
// toOSPath turns a standard/remote path into an OS path
|
||||||
|
func toOSPath(standardPath string) string {
|
||||||
|
return filepath.FromSlash(encoder.OS.FromStandardPath(standardPath))
|
||||||
|
}
|
||||||
|
|
||||||
// toOSPath turns a remote relative name into an OS path in the cache
|
// toOSPath turns a remote relative name into an OS path in the cache
|
||||||
func (c *Cache) toOSPath(name string) string {
|
func (c *Cache) toOSPath(name string) string {
|
||||||
return filepath.Join(c.root, filepath.FromSlash(name))
|
return filepath.Join(c.root, toOSPath(name))
|
||||||
}
|
}
|
||||||
|
|
||||||
// toOSPathMeta turns a remote relative name into an OS path in the
|
// toOSPathMeta turns a remote relative name into an OS path in the
|
||||||
// cache for the metadata
|
// cache for the metadata
|
||||||
func (c *Cache) toOSPathMeta(name string) string {
|
func (c *Cache) toOSPathMeta(name string) string {
|
||||||
return filepath.Join(c.metaRoot, filepath.FromSlash(name))
|
return filepath.Join(c.metaRoot, toOSPath(name))
|
||||||
}
|
|
||||||
|
|
||||||
// mkdir makes the directory for name in the cache and returns an os
|
|
||||||
// path for the file
|
|
||||||
func (c *Cache) mkdir(name string) (string, error) {
|
|
||||||
parent := vfscommon.FindParent(name)
|
|
||||||
leaf := filepath.Base(name)
|
|
||||||
parentPath := c.toOSPath(parent)
|
|
||||||
err := os.MkdirAll(parentPath, 0700)
|
|
||||||
if err != nil {
|
|
||||||
return "", errors.Wrap(err, "make cache directory failed")
|
|
||||||
}
|
|
||||||
parentPathMeta := c.toOSPathMeta(parent)
|
|
||||||
err = os.MkdirAll(parentPathMeta, 0700)
|
|
||||||
if err != nil {
|
|
||||||
return "", errors.Wrap(err, "make cache meta directory failed")
|
|
||||||
}
|
|
||||||
return filepath.Join(parentPath, leaf), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// _get gets name from the cache or creates a new one
|
// _get gets name from the cache or creates a new one
|
||||||
@ -306,7 +357,7 @@ func rename(osOldPath, osNewPath string) error {
|
|||||||
return errors.Wrapf(err, "Failed to stat destination: %s", osNewPath)
|
return errors.Wrapf(err, "Failed to stat destination: %s", osNewPath)
|
||||||
}
|
}
|
||||||
parent := vfscommon.OsFindParent(osNewPath)
|
parent := vfscommon.OsFindParent(osNewPath)
|
||||||
err = os.MkdirAll(parent, 0700)
|
err = createDir(parent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(err, "Failed to create parent dir: %s", parent)
|
return errors.Wrapf(err, "Failed to create parent dir: %s", parent)
|
||||||
}
|
}
|
||||||
@ -488,7 +539,7 @@ func (c *Cache) KickCleaner() {
|
|||||||
c.kickerMu.Unlock()
|
c.kickerMu.Unlock()
|
||||||
|
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
for c.outOfSpace == true {
|
for c.outOfSpace {
|
||||||
fs.Debugf(nil, "vfs cache: in KickCleaner, looping on c.outOfSpace")
|
fs.Debugf(nil, "vfs cache: in KickCleaner, looping on c.outOfSpace")
|
||||||
c.cond.Wait()
|
c.cond.Wait()
|
||||||
}
|
}
|
||||||
@ -510,7 +561,6 @@ func (c *Cache) removeNotInUse(item *Item, maxAge time.Duration, emptyOnly bool)
|
|||||||
} else {
|
} else {
|
||||||
fs.Debugf(nil, "vfs cache RemoveNotInUse (maxAge=%d, emptyOnly=%v): item %s not removed, freed %d bytes", maxAge, emptyOnly, item.GetName(), spaceFreed)
|
fs.Debugf(nil, "vfs cache RemoveNotInUse (maxAge=%d, emptyOnly=%v): item %s not removed, freed %d bytes", maxAge, emptyOnly, item.GetName(), spaceFreed)
|
||||||
}
|
}
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retry failed resets during purgeClean()
|
// Retry failed resets during purgeClean()
|
||||||
|
@ -121,8 +121,8 @@ func TestCacheNew(t *testing.T) {
|
|||||||
assert.Contains(t, c.fcache.Root(), filepath.Base(r.Fremote.Root()))
|
assert.Contains(t, c.fcache.Root(), filepath.Base(r.Fremote.Root()))
|
||||||
assert.Equal(t, []string(nil), itemAsString(c))
|
assert.Equal(t, []string(nil), itemAsString(c))
|
||||||
|
|
||||||
// mkdir
|
// createItemDir
|
||||||
p, err := c.mkdir("potato")
|
p, err := c.createItemDir("potato")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, "potato", filepath.Base(p))
|
assert.Equal(t, "potato", filepath.Base(p))
|
||||||
assert.Equal(t, []string(nil), itemAsString(c))
|
assert.Equal(t, []string(nil), itemAsString(c))
|
||||||
@ -238,7 +238,7 @@ func TestCacheOpens(t *testing.T) {
|
|||||||
}, itemAsString(c))
|
}, itemAsString(c))
|
||||||
}
|
}
|
||||||
|
|
||||||
// test the open, mkdir, purge, close, purge sequence
|
// test the open, createItemDir, purge, close, purge sequence
|
||||||
func TestCacheOpenMkdir(t *testing.T) {
|
func TestCacheOpenMkdir(t *testing.T) {
|
||||||
_, c, cleanup := newTestCache(t)
|
_, c, cleanup := newTestCache(t)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
@ -251,8 +251,8 @@ func TestCacheOpenMkdir(t *testing.T) {
|
|||||||
`name="sub/potato" opens=1 size=0`,
|
`name="sub/potato" opens=1 size=0`,
|
||||||
}, itemAsString(c))
|
}, itemAsString(c))
|
||||||
|
|
||||||
// mkdir
|
// createItemDir
|
||||||
p, err := c.mkdir("sub/potato")
|
p, err := c.createItemDir("sub/potato")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, "potato", filepath.Base(p))
|
assert.Equal(t, "potato", filepath.Base(p))
|
||||||
assert.Equal(t, []string{
|
assert.Equal(t, []string{
|
||||||
|
@ -29,7 +29,7 @@ import (
|
|||||||
//
|
//
|
||||||
// - Cache.toOSPath
|
// - Cache.toOSPath
|
||||||
// - Cache.toOSPathMeta
|
// - Cache.toOSPathMeta
|
||||||
// - Cache.mkdir
|
// - Cache.createItemDir
|
||||||
// - Cache.objectFingerprint
|
// - Cache.objectFingerprint
|
||||||
// - Cache.AddVirtual
|
// - Cache.AddVirtual
|
||||||
|
|
||||||
@ -511,9 +511,9 @@ func (item *Item) open(o fs.Object) (err error) {
|
|||||||
|
|
||||||
item.info.ATime = time.Now()
|
item.info.ATime = time.Now()
|
||||||
|
|
||||||
osPath, err := item.c.mkdir(item.name) // No locking in Cache
|
osPath, err := item.c.createItemDir(item.name) // No locking in Cache
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "vfs cache item: open mkdir failed")
|
return errors.Wrap(err, "vfs cache item: createItemDir failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
err = item._checkObject(o)
|
err = item._checkObject(o)
|
||||||
|
Loading…
Reference in New Issue
Block a user