lib/fs: Expose fs option on interface (fixes #7385, ref #7381) (#7389)

This commit is contained in:
Simon Frei
2021-03-11 15:23:56 +01:00
committed by GitHub
parent cdef503db6
commit 3938b61c3f
9 changed files with 74 additions and 36 deletions
+2 -2
View File
@@ -47,11 +47,11 @@ func (f FolderConfiguration) Filesystem() fs.Filesystem {
// cfg.Folders["default"].Filesystem() should be valid. // cfg.Folders["default"].Filesystem() should be valid.
var opts []fs.Option var opts []fs.Option
if f.FilesystemType == fs.FilesystemTypeBasic && f.JunctionsAsDirs { if f.FilesystemType == fs.FilesystemTypeBasic && f.JunctionsAsDirs {
opts = append(opts, fs.WithJunctionsAsDirs()) opts = append(opts, new(fs.OptionJunctionsAsDirs))
} }
filesystem := fs.NewFilesystem(f.FilesystemType, f.Path, opts...) filesystem := fs.NewFilesystem(f.FilesystemType, f.Path, opts...)
if !f.CaseSensitiveFS { if !f.CaseSensitiveFS {
filesystem = fs.NewCaseFilesystem(filesystem, opts...) filesystem = fs.NewCaseFilesystem(filesystem)
} }
return filesystem return filesystem
} }
+18 -11
View File
@@ -26,24 +26,26 @@ var (
errNotRelative = errors.New("not a relative path") errNotRelative = errors.New("not a relative path")
) )
func WithJunctionsAsDirs() Option { type OptionJunctionsAsDirs struct{}
return Option{
apply: func(fs Filesystem) { func (o *OptionJunctionsAsDirs) apply(fs Filesystem) {
if basic, ok := fs.(*BasicFilesystem); !ok { if basic, ok := fs.(*BasicFilesystem); !ok {
l.Warnln("WithJunctionsAsDirs must only be used with FilesystemTypeBasic") l.Warnln("WithJunctionsAsDirs must only be used with FilesystemTypeBasic")
} else { } else {
basic.junctionsAsDirs = true basic.junctionsAsDirs = true
}
},
id: "junctionsAsDirs",
} }
} }
func (o *OptionJunctionsAsDirs) String() string {
return "junctionsAsDirs"
}
// The BasicFilesystem implements all aspects by delegating to package os. // The BasicFilesystem implements all aspects by delegating to package os.
// All paths are relative to the root and cannot (should not) escape the root directory. // All paths are relative to the root and cannot (should not) escape the root directory.
type BasicFilesystem struct { type BasicFilesystem struct {
root string root string
junctionsAsDirs bool junctionsAsDirs bool
options []Option
} }
func newBasicFilesystem(root string, opts ...Option) *BasicFilesystem { func newBasicFilesystem(root string, opts ...Option) *BasicFilesystem {
@@ -82,7 +84,8 @@ func newBasicFilesystem(root string, opts ...Option) *BasicFilesystem {
} }
fs := &BasicFilesystem{ fs := &BasicFilesystem{
root: root, root: root,
options: opts,
} }
for _, opt := range opts { for _, opt := range opts {
opt.apply(fs) opt.apply(fs)
@@ -311,6 +314,10 @@ func (f *BasicFilesystem) URI() string {
return strings.TrimPrefix(f.root, `\\?\`) return strings.TrimPrefix(f.root, `\\?\`)
} }
func (f *BasicFilesystem) Options() []Option {
return f.options
}
func (f *BasicFilesystem) SameFile(fi1, fi2 FileInfo) bool { func (f *BasicFilesystem) SameFile(fi1, fi2 FileInfo) bool {
// Like os.SameFile, we always return false unless fi1 and fi2 were created // Like os.SameFile, we always return false unless fi1 and fi2 were created
// by this package's Stat/Lstat method. // by this package's Stat/Lstat method.
+8 -10
View File
@@ -55,22 +55,22 @@ type caseFilesystemRegistry struct {
startCleaner sync.Once startCleaner sync.Once
} }
func newFSKey(fs Filesystem, opts ...Option) fskey { func newFSKey(fs Filesystem) fskey {
k := fskey{ k := fskey{
fstype: fs.Type(), fstype: fs.Type(),
uri: fs.URI(), uri: fs.URI(),
} }
if len(opts) > 0 { if opts := fs.Options(); len(opts) > 0 {
k.opts = opts[0].id k.opts = opts[0].String()
for _, o := range opts[1:] { for _, o := range opts[1:] {
k.opts += "&" + o.id k.opts += "&" + o.String()
} }
} }
return k return k
} }
func (r *caseFilesystemRegistry) get(fs Filesystem, opts ...Option) Filesystem { func (r *caseFilesystemRegistry) get(fs Filesystem) Filesystem {
k := newFSKey(fs, opts...) k := newFSKey(fs)
// Use double locking when getting a caseFs. In the common case it will // Use double locking when getting a caseFs. In the common case it will
// already exist and we take the read lock fast path. If it doesn't, we // already exist and we take the read lock fast path. If it doesn't, we
@@ -136,10 +136,8 @@ type caseFilesystem struct {
// from the real path. It is safe to use with any filesystem, i.e. also a // from the real path. It is safe to use with any filesystem, i.e. also a
// case-sensitive one. However it will add some overhead and thus shouldn't be // case-sensitive one. However it will add some overhead and thus shouldn't be
// used if the filesystem is known to already behave case-sensitively. // used if the filesystem is known to already behave case-sensitively.
func NewCaseFilesystem(fs Filesystem, opts ...Option) Filesystem { func NewCaseFilesystem(fs Filesystem) Filesystem {
return wrapFilesystem(fs, func(fs Filesystem) Filesystem { return wrapFilesystem(fs, globalCaseFilesystemRegistry.get)
return globalCaseFilesystemRegistry.get(fs, opts...)
})
} }
func (f *caseFilesystem) Chmod(name string, mode FileMode) error { func (f *caseFilesystem) Chmod(name string, mode FileMode) error {
+4 -1
View File
@@ -45,7 +45,10 @@ func (fs *errorFilesystem) Roots() ([]string, error) { retur
func (fs *errorFilesystem) Usage(name string) (Usage, error) { return Usage{}, fs.err } func (fs *errorFilesystem) Usage(name string) (Usage, error) { return Usage{}, fs.err }
func (fs *errorFilesystem) Type() FilesystemType { return fs.fsType } func (fs *errorFilesystem) Type() FilesystemType { return fs.fsType }
func (fs *errorFilesystem) URI() string { return fs.uri } func (fs *errorFilesystem) URI() string { return fs.uri }
func (fs *errorFilesystem) SameFile(fi1, fi2 FileInfo) bool { return false } func (fs *errorFilesystem) Options() []Option {
return nil
}
func (fs *errorFilesystem) SameFile(fi1, fi2 FileInfo) bool { return false }
func (fs *errorFilesystem) Watch(path string, ignore Matcher, ctx context.Context, ignorePerms bool) (<-chan Event, <-chan error, error) { func (fs *errorFilesystem) Watch(path string, ignore Matcher, ctx context.Context, ignorePerms bool) (<-chan Event, <-chan error, error) {
return nil, nil, fs.err return nil, nil, fs.err
} }
+4
View File
@@ -640,6 +640,10 @@ func (fs *fakefs) URI() string {
return fs.uri return fs.uri
} }
func (fs *fakefs) Options() []Option {
return nil
}
func (fs *fakefs) SameFile(fi1, fi2 FileInfo) bool { func (fs *fakefs) SameFile(fi1, fi2 FileInfo) bool {
// BUG: real systems base file sameness on path, inodes, etc // BUG: real systems base file sameness on path, inodes, etc
// we try our best, but FileInfo just doesn't have enough data // we try our best, but FileInfo just doesn't have enough data
+10 -3
View File
@@ -47,6 +47,7 @@ type Filesystem interface {
Usage(name string) (Usage, error) Usage(name string) (Usage, error)
Type() FilesystemType Type() FilesystemType
URI() string URI() string
Options() []Option
SameFile(fi1, fi2 FileInfo) bool SameFile(fi1, fi2 FileInfo) bool
} }
@@ -178,9 +179,15 @@ var IsPermission = os.IsPermission
// IsPathSeparator is the equivalent of os.IsPathSeparator // IsPathSeparator is the equivalent of os.IsPathSeparator
var IsPathSeparator = os.IsPathSeparator var IsPathSeparator = os.IsPathSeparator
type Option struct { // Option modifies a filesystem at creation. An option might be specific
apply func(Filesystem) // to a filesystem-type.
id string //
// String is used to detect options with the same effect, i.e. must be different
// for options with different effects. Meaning if an option has parameters, a
// representation of those must be part of the returned string.
type Option interface {
String() string
apply(Filesystem)
} }
func NewFilesystem(fsType FilesystemType, uri string, opts ...Option) Filesystem { func NewFilesystem(fsType FilesystemType, uri string, opts ...Option) Filesystem {
+22 -7
View File
@@ -63,10 +63,20 @@ type WalkFunc func(path string, info FileInfo, err error) error
type walkFilesystem struct { type walkFilesystem struct {
Filesystem Filesystem
checkInfiniteRecursion bool
} }
func NewWalkFilesystem(next Filesystem) Filesystem { func NewWalkFilesystem(next Filesystem) Filesystem {
return &walkFilesystem{next} fs := &walkFilesystem{
Filesystem: next,
}
for _, opt := range next.Options() {
if _, ok := opt.(*OptionJunctionsAsDirs); ok {
fs.checkInfiniteRecursion = true
break
}
}
return fs
} }
// walk recursively descends path, calling walkFn. // walk recursively descends path, calling walkFn.
@@ -89,11 +99,13 @@ func (f *walkFilesystem) walk(path string, info FileInfo, walkFn WalkFunc, ances
return nil return nil
} }
if !ancestors.Contains(info) { if f.checkInfiniteRecursion {
ancestors.Push(info) if !ancestors.Contains(info) {
defer ancestors.Pop() ancestors.Push(info)
} else { defer ancestors.Pop()
return walkFn(path, info, ErrInfiniteRecursion) } else {
return walkFn(path, info, ErrInfiniteRecursion)
}
} }
names, err := f.DirNames(path) names, err := f.DirNames(path)
@@ -131,6 +143,9 @@ func (f *walkFilesystem) Walk(root string, walkFn WalkFunc) error {
if err != nil { if err != nil {
return walkFn(root, nil, err) return walkFn(root, nil, err)
} }
ancestors := &ancestorDirList{fs: f.Filesystem} var ancestors *ancestorDirList
if f.checkInfiniteRecursion {
ancestors = &ancestorDirList{fs: f.Filesystem}
}
return f.walk(root, info, walkFn, ancestors) return f.walk(root, info, walkFn, ancestors)
} }
+2 -2
View File
@@ -57,7 +57,7 @@ func testWalkTraverseDirJunct(t *testing.T, fsType FilesystemType, uri string) {
t.Skip("Directory junctions are available and tested on windows only") t.Skip("Directory junctions are available and tested on windows only")
} }
fs := NewFilesystem(fsType, uri, WithJunctionsAsDirs()) fs := NewFilesystem(fsType, uri, new(OptionJunctionsAsDirs))
if err := fs.MkdirAll("target/foo", 0); err != nil { if err := fs.MkdirAll("target/foo", 0); err != nil {
t.Fatal(err) t.Fatal(err)
@@ -90,7 +90,7 @@ func testWalkInfiniteRecursion(t *testing.T, fsType FilesystemType, uri string)
t.Skip("Infinite recursion detection is tested on windows only") t.Skip("Infinite recursion detection is tested on windows only")
} }
fs := NewFilesystem(fsType, uri, WithJunctionsAsDirs()) fs := NewFilesystem(fsType, uri, new(OptionJunctionsAsDirs))
if err := fs.MkdirAll("target/foo", 0); err != nil { if err := fs.MkdirAll("target/foo", 0); err != nil {
t.Fatal(err) t.Fatal(err)
+4
View File
@@ -97,6 +97,10 @@ func (s singleFileFS) Open(name string) (fs.File, error) {
return &fakeFile{s.name, s.filesize, 0}, nil return &fakeFile{s.name, s.filesize, 0}, nil
} }
func (s singleFileFS) Options() []fs.Option {
return nil
}
type fakeInfo struct { type fakeInfo struct {
name string name string
size int64 size int64