Skip to content

Commit b254d61

Browse files
committed
wip: vfs,local,union: use specialized stat interface when available
Signed-off-by: Anagh Kumar Baranwal <6824881+darthShadow@users.noreply.github.com>
1 parent 4c642f9 commit b254d61

File tree

8 files changed

+373
-88
lines changed

8 files changed

+373
-88
lines changed

backend/drive/drive.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3802,16 +3802,16 @@ Use the --interactive/-i or --dry-run flag to see what would be moved beforehand
38023802
Usage:
38033803
38043804
rclone backend query drive: query
3805-
3806-
The query syntax is documented at [Google Drive Search query terms and
3805+
3806+
The query syntax is documented at [Google Drive Search query terms and
38073807
operators](https://developers.google.com/drive/api/guides/ref-search-terms).
38083808
38093809
For example:
38103810
38113811
rclone backend query drive: "'0ABc9DEFGHIJKLMNop0QRatUVW3X' in parents and name contains 'foo'"
38123812
38133813
If the query contains literal ' or \ characters, these need to be escaped with
3814-
\ characters. "'" becomes "\'" and "\" becomes "\\\", for example to match a
3814+
\ characters. "'" becomes "\'" and "\" becomes "\\\", for example to match a
38153815
file named "foo ' \.txt":
38163816
38173817
rclone backend query drive: "name = 'foo \' \\\.txt'"

backend/local/local.go

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1123,6 +1123,127 @@ func (f *Fs) Command(ctx context.Context, name string, arg []string, opt map[str
11231123
}
11241124
}
11251125

1126+
// Stat returns the DirEntry for the given name
1127+
//
1128+
// It should return fs.ErrorNotImplemented if it can't return a DirEntry
1129+
func (f *Fs) Stat(ctx context.Context, dir string, leaf string) (entry fs.DirEntry, err error) {
1130+
filter, useFilter := filter.GetConfig(ctx), filter.GetUseFilter(ctx)
1131+
1132+
fsDirPath := f.localPath(dir)
1133+
_, err = os.Stat(fsDirPath)
1134+
if err != nil {
1135+
return nil, fs.ErrorDirNotFound
1136+
}
1137+
1138+
fd, err := os.Open(fsDirPath)
1139+
if err != nil {
1140+
isPerm := os.IsPermission(err)
1141+
err = fmt.Errorf("failed to open directory %q: %w", dir, err)
1142+
fs.Errorf(dir, "%v", err)
1143+
if isPerm {
1144+
_ = accounting.Stats(ctx).Error(fserrors.NoRetryError(err))
1145+
err = nil // ignore error but fail sync
1146+
}
1147+
return nil, err
1148+
}
1149+
defer func() {
1150+
cerr := fd.Close()
1151+
if cerr != nil && err == nil {
1152+
err = fmt.Errorf("failed to close directory %q: %w", dir, cerr)
1153+
}
1154+
}()
1155+
1156+
names, err := fd.Readdirnames(-1)
1157+
if err != nil {
1158+
return nil, fmt.Errorf("failed to read directory entry: %w", err)
1159+
}
1160+
1161+
foundLeaf := false
1162+
// Find the leaf in the directory
1163+
for _, name := range names {
1164+
if name == leaf {
1165+
foundLeaf = true
1166+
break
1167+
}
1168+
}
1169+
if !foundLeaf {
1170+
return nil, fs.ErrorObjectNotFound
1171+
}
1172+
1173+
leafPath := join.FilePathJoin(fsDirPath, leaf)
1174+
// Just a stat won't work without checking the entries from readdir first because of differences in unicode handling
1175+
fi, fierr := os.Lstat(leafPath)
1176+
if fierr != nil {
1177+
// Don't report errors on any file names that are excluded
1178+
if useFilter {
1179+
newRemote := f.cleanRemote(dir, leaf)
1180+
if !filter.IncludeRemote(newRemote) {
1181+
return nil, fs.ErrorObjectNotFound
1182+
}
1183+
}
1184+
1185+
_ = accounting.Stats(ctx).Error(fserrors.NoRetryError(fierr)) // fail the sync
1186+
1187+
return nil, fmt.Errorf("%q: failed to get info about directory entry %q: %w", dir, leafPath, fierr)
1188+
}
1189+
1190+
name := fi.Name()
1191+
mode := fi.Mode()
1192+
newRemote := f.cleanRemote(dir, name)
1193+
1194+
// Follow symlinks if required
1195+
if f.opt.FollowSymlinks && (mode&os.ModeSymlink) != 0 {
1196+
localPath := join.FilePathJoin(fsDirPath, name)
1197+
fi, fierr = os.Stat(localPath)
1198+
// Quietly skip errors on excluded files and directories
1199+
if fierr != nil && useFilter && !filter.IncludeRemote(newRemote) {
1200+
return nil, fs.ErrorObjectNotFound
1201+
}
1202+
1203+
if fierr != nil {
1204+
// Check for bad symlinks and circular symlinks
1205+
if os.IsNotExist(fierr) || isCircularSymlinkError(fierr) {
1206+
_ = accounting.Stats(ctx).Error(fserrors.NoRetryError(fierr)) // fail the sync
1207+
}
1208+
return nil, fmt.Errorf("%q: following symlink for %q: %w", dir, leafPath, fierr)
1209+
}
1210+
1211+
mode = fi.Mode()
1212+
}
1213+
1214+
if fi.IsDir() {
1215+
// Ignore directories which are symlinks. These are junction points under windows which
1216+
// are kind of a souped up symlink. Unix doesn't have directories which are symlinks.
1217+
if (mode&os.ModeSymlink) == 0 && f.dev == readDevice(fi, f.opt.OneFileSystem) {
1218+
d := f.newDirectory(newRemote, fi)
1219+
return d, nil
1220+
}
1221+
1222+
} else {
1223+
// Check whether this link should be translated
1224+
if f.opt.TranslateSymlinks && fi.Mode()&os.ModeSymlink != 0 {
1225+
newRemote += fs.LinkSuffix
1226+
}
1227+
1228+
// Don't include non directory if not included
1229+
// we leave directory filtering to the layer above
1230+
if useFilter && !filter.IncludeRemote(newRemote) {
1231+
return nil, fs.ErrorObjectNotFound
1232+
}
1233+
1234+
fso, err := f.newObjectWithInfo(newRemote, fi)
1235+
if err != nil {
1236+
return nil, err
1237+
}
1238+
1239+
if fso.Storable() {
1240+
return fso, nil
1241+
}
1242+
}
1243+
1244+
return nil, fs.ErrorObjectNotFound
1245+
}
1246+
11261247
// ------------------------------------------------------------
11271248

11281249
// Fs returns the parent Fs
@@ -1768,6 +1889,7 @@ var (
17681889
_ fs.OpenWriterAter = &Fs{}
17691890
_ fs.DirSetModTimer = &Fs{}
17701891
_ fs.MkdirMetadataer = &Fs{}
1892+
_ fs.Stater = &Fs{}
17711893
_ fs.Object = &Object{}
17721894
_ fs.Metadataer = &Object{}
17731895
_ fs.SetMetadataer = &Object{}

backend/onedrive/onedrive.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,7 @@ block download of the file.
356356
357357
In this case you will see a message like this
358358
359-
server reports this file is infected with a virus - use --onedrive-av-override to download anyway: Infected (name of virus): 403 Forbidden:
359+
server reports this file is infected with a virus - use --onedrive-av-override to download anyway: Infected (name of virus): 403 Forbidden:
360360
361361
If you are 100% sure you want to download this file anyway then use
362362
the --onedrive-av-override flag, or av_override = true in the config
@@ -2988,13 +2988,13 @@ var (
29882988
_ fs.ListRer = (*Fs)(nil)
29892989
_ fs.Shutdowner = (*Fs)(nil)
29902990
_ fs.Object = (*Object)(nil)
2991-
_ fs.MimeTyper = &Object{}
2992-
_ fs.IDer = &Object{}
2991+
_ fs.MimeTyper = (*Object)(nil)
2992+
_ fs.IDer = (*Object)(nil)
29932993
_ fs.Metadataer = (*Object)(nil)
29942994
_ fs.Metadataer = (*Directory)(nil)
29952995
_ fs.SetModTimer = (*Directory)(nil)
29962996
_ fs.SetMetadataer = (*Directory)(nil)
2997-
_ fs.MimeTyper = &Directory{}
2997+
_ fs.MimeTyper = (*Directory)(nil)
29982998
_ fs.DirSetModTimer = (*Fs)(nil)
29992999
_ fs.MkdirMetadataer = (*Fs)(nil)
30003000
)

backend/union/union.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -796,6 +796,54 @@ func (f *Fs) ListR(ctx context.Context, dir string, callback fs.ListRCallback) (
796796
return callback(entries)
797797
}
798798

799+
// Stat returns the DirEntry for the given name
800+
//
801+
// It should return fs.ErrorNotImplemented if it can't return a DirEntry
802+
func (f *Fs) Stat(ctx context.Context, dir string, leaf string) (fs.DirEntry, error) {
803+
entriesList := make([][]upstream.Entry, len(f.upstreams))
804+
errs := Errors(make([]error, len(f.upstreams)))
805+
806+
multithread(len(f.upstreams), func(i int) {
807+
u := f.upstreams[i]
808+
stater, ok := u.Fs.(fs.Stater)
809+
if !ok {
810+
errs[i] = fs.ErrorNotImplemented
811+
return
812+
}
813+
entry, err := stater.Stat(ctx, dir, leaf)
814+
if err != nil {
815+
errs[i] = fmt.Errorf("%s: %w", u.Name(), err)
816+
return
817+
}
818+
uEntries := make([]upstream.Entry, 1)
819+
uEntries[0], _ = u.WrapEntry(entry)
820+
entriesList[i] = uEntries
821+
})
822+
823+
if len(errs) == len(errs.FilterNil()) {
824+
errs = errs.Map(func(e error) error {
825+
if errors.Is(e, fs.ErrorDirNotFound) {
826+
return nil
827+
}
828+
return e
829+
})
830+
if len(errs) == 0 {
831+
return nil, fs.ErrorDirNotFound
832+
}
833+
return nil, errs.Err()
834+
}
835+
836+
entries, err := f.mergeDirEntries(entriesList)
837+
if err != nil {
838+
return nil, err
839+
}
840+
if len(entries) != 1 {
841+
return nil, fmt.Errorf("internal error: expected 1 entry but got %d", len(entries))
842+
}
843+
844+
return entries[0], nil
845+
}
846+
799847
// NewObject creates a new remote union file object
800848
func (f *Fs) NewObject(ctx context.Context, remote string) (fs.Object, error) {
801849
objs := make([]*upstream.Object, len(f.upstreams))
@@ -1093,4 +1141,5 @@ var (
10931141
_ fs.ListRer = (*Fs)(nil)
10941142
_ fs.Shutdowner = (*Fs)(nil)
10951143
_ fs.CleanUpper = (*Fs)(nil)
1144+
_ fs.Stater = (*Fs)(nil)
10961145
)

fs/types.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,14 @@ type Fder interface {
218218
Fd(ctx context.Context, flags int) (uintptr, error)
219219
}
220220

221+
// Stater is an optional interface for Directory
222+
type Stater interface {
223+
// Stat returns the DirEntry for the given leaf
224+
//
225+
// It should return fs.ErrorNotImplemented if it can't return a DirEntry
226+
Stat(ctx context.Context, dir string, leaf string) (DirEntry, error)
227+
}
228+
221229
// SetModTimer is an optional interface for Directory.
222230
//
223231
// Object implements this as part of its requires set of interfaces.

0 commit comments

Comments
 (0)