aboutsummaryrefslogtreecommitdiffhomepage
path: root/internal
diff options
context:
space:
mode:
authora <[email protected]>2024-01-13 14:12:43 -0600
committerGitHub <[email protected]>2024-01-13 20:12:43 +0000
commitc839a98ff527932fd14460829142c486f4531a7b (patch)
tree3a4a9745d2bc54ff557b4439ea0fe2dbc58238e0 /internal
parentb359ca565c624b8718eac79058bff0591b250d0e (diff)
downloadcaddy-c839a98ff527932fd14460829142c486f4531a7b.tar.gz
caddy-c839a98ff527932fd14460829142c486f4531a7b.zip
filesystem: Globally declared filesystems, `fs` directive (#5833)
Diffstat (limited to 'internal')
-rw-r--r--internal/filesystems/map.go77
-rw-r--r--internal/filesystems/os.go29
2 files changed, 106 insertions, 0 deletions
diff --git a/internal/filesystems/map.go b/internal/filesystems/map.go
new file mode 100644
index 000000000..e795ed1fe
--- /dev/null
+++ b/internal/filesystems/map.go
@@ -0,0 +1,77 @@
+package filesystems
+
+import (
+ "io/fs"
+ "strings"
+ "sync"
+)
+
+const (
+ DefaultFilesystemKey = "default"
+)
+
+var DefaultFilesystem = &wrapperFs{key: DefaultFilesystemKey, FS: OsFS{}}
+
+// wrapperFs exists so can easily add to wrapperFs down the line
+type wrapperFs struct {
+ key string
+ fs.FS
+}
+
+// FilesystemMap stores a map of filesystems
+// the empty key will be overwritten to be the default key
+// it includes a default filesystem, based off the os fs
+type FilesystemMap struct {
+ m sync.Map
+}
+
+// note that the first invocation of key cannot be called in a racy context.
+func (f *FilesystemMap) key(k string) string {
+ if k == "" {
+ k = DefaultFilesystemKey
+ }
+ return k
+}
+
+// Register will add the filesystem with key to later be retrieved
+// A call with a nil fs will call unregister, ensuring that a call to Default() will never be nil
+func (f *FilesystemMap) Register(k string, v fs.FS) {
+ k = f.key(k)
+ if v == nil {
+ f.Unregister(k)
+ return
+ }
+ f.m.Store(k, &wrapperFs{key: k, FS: v})
+}
+
+// Unregister will remove the filesystem with key from the filesystem map
+// if the key is the default key, it will set the default to the osFS instead of deleting it
+// modules should call this on cleanup to be safe
+func (f *FilesystemMap) Unregister(k string) {
+ k = f.key(k)
+ if k == DefaultFilesystemKey {
+ f.m.Store(k, DefaultFilesystem)
+ } else {
+ f.m.Delete(k)
+ }
+}
+
+// Get will get a filesystem with a given key
+func (f *FilesystemMap) Get(k string) (v fs.FS, ok bool) {
+ k = f.key(k)
+ c, ok := f.m.Load(strings.TrimSpace(k))
+ if !ok {
+ if k == DefaultFilesystemKey {
+ f.m.Store(k, DefaultFilesystem)
+ return DefaultFilesystem, true
+ }
+ return nil, ok
+ }
+ return c.(fs.FS), true
+}
+
+// Default will get the default filesystem in the filesystem map
+func (f *FilesystemMap) Default() fs.FS {
+ val, _ := f.Get(DefaultFilesystemKey)
+ return val
+}
diff --git a/internal/filesystems/os.go b/internal/filesystems/os.go
new file mode 100644
index 000000000..04b4d5b40
--- /dev/null
+++ b/internal/filesystems/os.go
@@ -0,0 +1,29 @@
+package filesystems
+
+import (
+ "io/fs"
+ "os"
+ "path/filepath"
+)
+
+// OsFS is a simple fs.FS implementation that uses the local
+// file system. (We do not use os.DirFS because we do our own
+// rooting or path prefixing without being constrained to a single
+// root folder. The standard os.DirFS implementation is problematic
+// since roots can be dynamic in our application.)
+//
+// OsFS also implements fs.StatFS, fs.GlobFS, fs.ReadDirFS, and fs.ReadFileFS.
+type OsFS struct{}
+
+func (OsFS) Open(name string) (fs.File, error) { return os.Open(name) }
+func (OsFS) Stat(name string) (fs.FileInfo, error) { return os.Stat(name) }
+func (OsFS) Glob(pattern string) ([]string, error) { return filepath.Glob(pattern) }
+func (OsFS) ReadDir(name string) ([]fs.DirEntry, error) { return os.ReadDir(name) }
+func (OsFS) ReadFile(name string) ([]byte, error) { return os.ReadFile(name) }
+
+var (
+ _ fs.StatFS = (*OsFS)(nil)
+ _ fs.GlobFS = (*OsFS)(nil)
+ _ fs.ReadDirFS = (*OsFS)(nil)
+ _ fs.ReadFileFS = (*OsFS)(nil)
+)