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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 74 additions and 36 deletions

View File

@ -47,11 +47,11 @@ func (f FolderConfiguration) Filesystem() fs.Filesystem {
// cfg.Folders["default"].Filesystem() should be valid.
var opts []fs.Option
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...)
if !f.CaseSensitiveFS {
filesystem = fs.NewCaseFilesystem(filesystem, opts...)
filesystem = fs.NewCaseFilesystem(filesystem)
}
return filesystem
}

View File

@ -26,24 +26,26 @@ var (
errNotRelative = errors.New("not a relative path")
)
func WithJunctionsAsDirs() Option {
return Option{
apply: func(fs Filesystem) {
if basic, ok := fs.(*BasicFilesystem); !ok {
l.Warnln("WithJunctionsAsDirs must only be used with FilesystemTypeBasic")
} else {
basic.junctionsAsDirs = true
}
},
id: "junctionsAsDirs",
type OptionJunctionsAsDirs struct{}
func (o *OptionJunctionsAsDirs) apply(fs Filesystem) {
if basic, ok := fs.(*BasicFilesystem); !ok {
l.Warnln("WithJunctionsAsDirs must only be used with FilesystemTypeBasic")
} else {
basic.junctionsAsDirs = true
}
}
func (o *OptionJunctionsAsDirs) String() string {
return "junctionsAsDirs"
}
// 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.
type BasicFilesystem struct {
root string
junctionsAsDirs bool
options []Option
}
func newBasicFilesystem(root string, opts ...Option) *BasicFilesystem {
@ -82,7 +84,8 @@ func newBasicFilesystem(root string, opts ...Option) *BasicFilesystem {
}
fs := &BasicFilesystem{
root: root,
root: root,
options: opts,
}
for _, opt := range opts {
opt.apply(fs)
@ -311,6 +314,10 @@ func (f *BasicFilesystem) URI() string {
return strings.TrimPrefix(f.root, `\\?\`)
}
func (f *BasicFilesystem) Options() []Option {
return f.options
}
func (f *BasicFilesystem) SameFile(fi1, fi2 FileInfo) bool {
// Like os.SameFile, we always return false unless fi1 and fi2 were created
// by this package's Stat/Lstat method.

View File

@ -55,22 +55,22 @@ type caseFilesystemRegistry struct {
startCleaner sync.Once
}
func newFSKey(fs Filesystem, opts ...Option) fskey {
func newFSKey(fs Filesystem) fskey {
k := fskey{
fstype: fs.Type(),
uri: fs.URI(),
}
if len(opts) > 0 {
k.opts = opts[0].id
if opts := fs.Options(); len(opts) > 0 {
k.opts = opts[0].String()
for _, o := range opts[1:] {
k.opts += "&" + o.id
k.opts += "&" + o.String()
}
}
return k
}
func (r *caseFilesystemRegistry) get(fs Filesystem, opts ...Option) Filesystem {
k := newFSKey(fs, opts...)
func (r *caseFilesystemRegistry) get(fs Filesystem) Filesystem {
k := newFSKey(fs)
// 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
@ -136,10 +136,8 @@ type caseFilesystem struct {
// 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
// used if the filesystem is known to already behave case-sensitively.
func NewCaseFilesystem(fs Filesystem, opts ...Option) Filesystem {
return wrapFilesystem(fs, func(fs Filesystem) Filesystem {
return globalCaseFilesystemRegistry.get(fs, opts...)
})
func NewCaseFilesystem(fs Filesystem) Filesystem {
return wrapFilesystem(fs, globalCaseFilesystemRegistry.get)
}
func (f *caseFilesystem) Chmod(name string, mode FileMode) error {

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) Type() FilesystemType { return fs.fsType }
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) {
return nil, nil, fs.err
}

View File

@ -640,6 +640,10 @@ func (fs *fakefs) URI() string {
return fs.uri
}
func (fs *fakefs) Options() []Option {
return nil
}
func (fs *fakefs) SameFile(fi1, fi2 FileInfo) bool {
// BUG: real systems base file sameness on path, inodes, etc
// we try our best, but FileInfo just doesn't have enough data

View File

@ -47,6 +47,7 @@ type Filesystem interface {
Usage(name string) (Usage, error)
Type() FilesystemType
URI() string
Options() []Option
SameFile(fi1, fi2 FileInfo) bool
}
@ -178,9 +179,15 @@ var IsPermission = os.IsPermission
// IsPathSeparator is the equivalent of os.IsPathSeparator
var IsPathSeparator = os.IsPathSeparator
type Option struct {
apply func(Filesystem)
id string
// Option modifies a filesystem at creation. An option might be specific
// to a filesystem-type.
//
// 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 {

View File

@ -63,10 +63,20 @@ type WalkFunc func(path string, info FileInfo, err error) error
type walkFilesystem struct {
Filesystem
checkInfiniteRecursion bool
}
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.
@ -89,11 +99,13 @@ func (f *walkFilesystem) walk(path string, info FileInfo, walkFn WalkFunc, ances
return nil
}
if !ancestors.Contains(info) {
ancestors.Push(info)
defer ancestors.Pop()
} else {
return walkFn(path, info, ErrInfiniteRecursion)
if f.checkInfiniteRecursion {
if !ancestors.Contains(info) {
ancestors.Push(info)
defer ancestors.Pop()
} else {
return walkFn(path, info, ErrInfiniteRecursion)
}
}
names, err := f.DirNames(path)
@ -131,6 +143,9 @@ func (f *walkFilesystem) Walk(root string, walkFn WalkFunc) error {
if err != nil {
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)
}

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")
}
fs := NewFilesystem(fsType, uri, WithJunctionsAsDirs())
fs := NewFilesystem(fsType, uri, new(OptionJunctionsAsDirs))
if err := fs.MkdirAll("target/foo", 0); err != nil {
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")
}
fs := NewFilesystem(fsType, uri, WithJunctionsAsDirs())
fs := NewFilesystem(fsType, uri, new(OptionJunctionsAsDirs))
if err := fs.MkdirAll("target/foo", 0); err != nil {
t.Fatal(err)

View File

@ -97,6 +97,10 @@ func (s singleFileFS) Open(name string) (fs.File, error) {
return &fakeFile{s.name, s.filesize, 0}, nil
}
func (s singleFileFS) Options() []fs.Option {
return nil
}
type fakeInfo struct {
name string
size int64