Skip to content
This repository was archived by the owner on Jul 3, 2025. It is now read-only.

Commit c799866

Browse files
authored
refactor(fsx): introduce RealPathMapper, OverlayFS (#21)
This diff modifies how fsx works by introducing two new concepts: 1. the `OverlayFS`, which encapsulates the common logic used by `ChdirFS` and `ContainedFS` into a single codebase; 2. the `RealPathMapper`, which implements the path mapping policies used by `ChdirFS` and `ContainedFS`. As a result, we can re-implement (and deprecate) `ChdirFS` and `ContainedFS` in light of these two new concepts. More in detail: 1. the `ChdirFS` path mapping policy is implemented by the new `ChdirPathMapper` type; 2. the `ContainedFS` by the new `ContainedDirPathMapper` type. This change is important because it allows us to clarify the semantics of the base path used by a `RealPathMapper` in terms of whether such a base directory is relative or absolute. For `ChdirPathMapper` and `ContainedDirPathMapper`, we define two constructors: one of them ensures the base directory is absolute, while the other one does not bother with that. In turn, this is important because of Unix domain sockets, where there are limitations on the maximum socket name (i.e., file path) length. Thus, when using absolute paths in `rbmk sh`, it is easier to hit the limit and not being able to create the sockets. This seems to suggest that we could switch to use relative paths *iff* we guarantee that `rbmk COMMAND` would not `chdir` for any `COMMAND` in `rbmk` except the `sh` command itself. However, baking this assumption in `fsx` would have been quite optimistic and backward, and made `fsx` itself harder to reuse. On the contrary, by making the choice explicit in the constructors, we clearly document (with code that one needs to invoke) what are the expectations in terms of changing paths and, all in all, it seems explicit is generally better than implict.
1 parent dc5ccc6 commit c799866

File tree

7 files changed

+1075
-291
lines changed

7 files changed

+1075
-291
lines changed

fsx/chdirfs.go

Lines changed: 6 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -6,118 +6,19 @@
66

77
package fsx
88

9-
import (
10-
"io/fs"
11-
"net"
12-
"path/filepath"
13-
"time"
14-
)
15-
169
// NewChdirFS creates a new [FS] where each file name is
1710
// prefixed with the given directory path.
11+
//
12+
// Deprecated: use [NewOverlayFS] with [NewRelativeChdirPathMapper] instead.
1813
func NewChdirFS(dep FS, path string) *ChdirFS {
19-
return &ChdirFS{basepath: path, dep: dep}
14+
return &ChdirFS{NewOverlayFS(dep, NewRelativeChdirPathMapper(path))}
2015
}
2116

2217
// ChdirFS is the [FS] type returned by [NewChdirFS].
2318
//
2419
// The zero value IS NOT ready to use; construct using [NewChdirFS].
25-
type ChdirFS struct {
26-
// basepath is the base path.
27-
basepath string
28-
29-
// dep is the dependency [FS].
30-
dep FS
31-
}
32-
33-
// Ensure [basePathFS] implements [FS].
34-
var _ FS = &ChdirFS{}
35-
36-
// realPath returns the real path of a given file name or an error.
37-
func (rfs *ChdirFS) realPath(name string) string {
38-
return filepath.Join(rfs.basepath, name)
39-
}
40-
41-
// Chmod implements [FS].
42-
func (rfs *ChdirFS) Chmod(name string, mode fs.FileMode) error {
43-
return rfs.dep.Chmod(rfs.realPath(name), mode)
44-
}
45-
46-
// Chown implements [FS].
47-
func (rfs *ChdirFS) Chown(name string, uid, gid int) error {
48-
return rfs.dep.Chown(rfs.realPath(name), uid, gid)
49-
}
50-
51-
// Chtimes implements [FS].
52-
func (rfs *ChdirFS) Chtimes(name string, atime, mtime time.Time) error {
53-
return rfs.dep.Chtimes(rfs.realPath(name), atime, mtime)
54-
}
55-
56-
// Create implements [FS].
57-
func (rfs *ChdirFS) Create(name string) (File, error) {
58-
return rfs.dep.Create(rfs.realPath(name))
59-
}
60-
61-
// DialUnix implements [FS].
6220
//
63-
// See also the limitations documented in the top-level package docs.
64-
func (rfs *ChdirFS) DialUnix(name string) (net.Conn, error) {
65-
return rfs.dep.DialUnix(rfs.realPath(name))
66-
}
67-
68-
// ListenUnix implements [FS].
69-
//
70-
// See also the limitations documented in the top-level package docs.
71-
func (rfs *ChdirFS) ListenUnix(name string) (net.Listener, error) {
72-
return rfs.dep.ListenUnix(rfs.realPath(name))
73-
}
74-
75-
// Lstat implements [FS].
76-
func (rfs *ChdirFS) Lstat(name string) (fs.FileInfo, error) {
77-
return rfs.dep.Lstat(rfs.realPath(name))
78-
}
79-
80-
// Mkdir implements [FS].
81-
func (rfs *ChdirFS) Mkdir(name string, mode fs.FileMode) error {
82-
return rfs.dep.Mkdir(rfs.realPath(name), mode)
83-
}
84-
85-
// MkdirAll implements [FS].
86-
func (rfs *ChdirFS) MkdirAll(name string, mode fs.FileMode) error {
87-
return rfs.dep.MkdirAll(rfs.realPath(name), mode)
88-
}
89-
90-
// Open implements [FS].
91-
func (rfs *ChdirFS) Open(name string) (File, error) {
92-
return rfs.dep.Open(rfs.realPath(name))
93-
}
94-
95-
// OpenFile implements [FS].
96-
func (rfs *ChdirFS) OpenFile(name string, flag int, mode fs.FileMode) (File, error) {
97-
return rfs.dep.OpenFile(rfs.realPath(name), flag, mode)
98-
}
99-
100-
// ReadDir implements [FS].
101-
func (rfs *ChdirFS) ReadDir(name string) ([]fs.DirEntry, error) {
102-
return rfs.dep.ReadDir(rfs.realPath(name))
103-
}
104-
105-
// Remove implements [FS].
106-
func (rfs *ChdirFS) Remove(name string) error {
107-
return rfs.dep.Remove(rfs.realPath(name))
108-
}
109-
110-
// RemoveAll implements [FS].
111-
func (rfs *ChdirFS) RemoveAll(name string) error {
112-
return rfs.dep.RemoveAll(rfs.realPath(name))
113-
}
114-
115-
// Rename implements [FS].
116-
func (rfs *ChdirFS) Rename(oldname, newname string) error {
117-
return rfs.dep.Rename(rfs.realPath(oldname), rfs.realPath(newname))
118-
}
119-
120-
// Stat implements [FS].
121-
func (rfs *ChdirFS) Stat(name string) (fs.FileInfo, error) {
122-
return rfs.dep.Stat(rfs.realPath(name))
21+
// Deprecated: use [NewOverlayFS] with [NewRelativeChdirPathMapper] instead.
22+
type ChdirFS struct {
23+
*OverlayFS
12324
}

fsx/containedfs.go

Lines changed: 6 additions & 185 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,6 @@
66

77
package fsx
88

9-
import (
10-
"io/fs"
11-
"net"
12-
"path/filepath"
13-
"strings"
14-
"time"
15-
)
16-
179
// NewRelativeFS is a deprecated alias for [NewContainedFS].
1810
//
1911
// Deprecated: use [NewContainedFS] instead.
@@ -37,188 +29,17 @@ type RelativeFS = ContainedFS
3729
// Note: This implementation cannot prevent symlink traversal
3830
// attacks. The caller must ensure the base directory does not
3931
// contain symlinks if this is a security requirement.
32+
//
33+
// Deprecated: use [NewOverlayFS] with [NewRelativeContainedDirPathMapper] instead.
4034
func NewContainedFS(dep FS, path string) *ContainedFS {
41-
return &ContainedFS{basepath: path, dep: dep}
35+
return &ContainedFS{NewOverlayFS(dep, NewRelativeContainedDirPathMapper(path))}
4236
}
4337

4438
// ContainedFS is the [FS] type returned by [NewContainedFS].
4539
//
4640
// The zero value IS NOT ready to use; construct using [NewContainedFS].
47-
type ContainedFS struct {
48-
// basepath is the base path.
49-
basepath string
50-
51-
// dep is the dependency [FS].
52-
dep FS
53-
}
54-
55-
// Ensure [basePathFS] implements [FS].
56-
var _ FS = &ContainedFS{}
57-
58-
// realPath returns the real path of a given file name or an error.
59-
func (rfs *ContainedFS) realPath(name string) (string, error) {
60-
// 1. entirely reject absolute path names
61-
if filepath.IsAbs(name) {
62-
return "", fs.ErrNotExist
63-
}
64-
65-
// 2. clean the path and make sure it is not outside the base path
66-
bpath := filepath.Clean(rfs.basepath)
67-
fullpath := filepath.Clean(filepath.Join(bpath, name))
68-
if !strings.HasPrefix(fullpath, bpath) {
69-
return name, fs.ErrNotExist
70-
}
71-
return fullpath, nil
72-
}
73-
74-
// Chmod implements [FS].
75-
func (rfs *ContainedFS) Chmod(name string, mode fs.FileMode) error {
76-
name, err := rfs.realPath(name)
77-
if err != nil {
78-
return &fs.PathError{Op: "chmod", Path: name, Err: err}
79-
}
80-
return rfs.dep.Chmod(name, mode)
81-
}
82-
83-
// Chown implements [FS].
84-
func (rfs *ContainedFS) Chown(name string, uid, gid int) error {
85-
name, err := rfs.realPath(name)
86-
if err != nil {
87-
return &fs.PathError{Op: "chown", Path: name, Err: err}
88-
}
89-
return rfs.dep.Chown(name, uid, gid)
90-
}
91-
92-
// Chtimes implements [FS].
93-
func (rfs *ContainedFS) Chtimes(name string, atime, mtime time.Time) error {
94-
name, err := rfs.realPath(name)
95-
if err != nil {
96-
return &fs.PathError{Op: "chtimes", Path: name, Err: err}
97-
}
98-
return rfs.dep.Chtimes(name, atime, mtime)
99-
}
100-
101-
// Create implements [FS].
102-
func (rfs *ContainedFS) Create(name string) (File, error) {
103-
name, err := rfs.realPath(name)
104-
if err != nil {
105-
return nil, &fs.PathError{Op: "create", Path: name, Err: err}
106-
}
107-
return rfs.dep.Create(name)
108-
}
109-
110-
// DialUnix implements [FS].
111-
//
112-
// See also the limitations documented in the top-level package docs.
113-
func (rfs *ContainedFS) DialUnix(name string) (net.Conn, error) {
114-
name, err := rfs.realPath(name)
115-
if err != nil {
116-
return nil, &fs.PathError{Op: "dialunix", Path: name, Err: err}
117-
}
118-
return rfs.dep.DialUnix(name)
119-
}
120-
121-
// ListenUnix implements [FS].
12241
//
123-
// See also the limitations documented in the top-level package docs.
124-
func (rfs *ContainedFS) ListenUnix(name string) (net.Listener, error) {
125-
name, err := rfs.realPath(name)
126-
if err != nil {
127-
return nil, &fs.PathError{Op: "listenunix", Path: name, Err: err}
128-
}
129-
return rfs.dep.ListenUnix(name)
130-
}
131-
132-
// Lstat implements [FS].
133-
func (rfs *ContainedFS) Lstat(name string) (fs.FileInfo, error) {
134-
name, err := rfs.realPath(name)
135-
if err != nil {
136-
return nil, &fs.PathError{Op: "lstat", Path: name, Err: err}
137-
}
138-
return rfs.dep.Lstat(name)
139-
}
140-
141-
// Mkdir implements [FS].
142-
func (rfs *ContainedFS) Mkdir(name string, mode fs.FileMode) error {
143-
name, err := rfs.realPath(name)
144-
if err != nil {
145-
return &fs.PathError{Op: "mkdir", Path: name, Err: err}
146-
}
147-
return rfs.dep.Mkdir(name, mode)
148-
}
149-
150-
// MkdirAll implements [FS].
151-
func (rfs *ContainedFS) MkdirAll(name string, mode fs.FileMode) error {
152-
name, err := rfs.realPath(name)
153-
if err != nil {
154-
return &fs.PathError{Op: "mkdir", Path: name, Err: err}
155-
}
156-
return rfs.dep.MkdirAll(name, mode)
157-
}
158-
159-
// Open implements [FS].
160-
func (rfs *ContainedFS) Open(name string) (File, error) {
161-
name, err := rfs.realPath(name)
162-
if err != nil {
163-
return nil, &fs.PathError{Op: "open", Path: name, Err: err}
164-
}
165-
return rfs.dep.Open(name)
166-
}
167-
168-
// OpenFile implements [FS].
169-
func (rfs *ContainedFS) OpenFile(name string, flag int, mode fs.FileMode) (File, error) {
170-
name, err := rfs.realPath(name)
171-
if err != nil {
172-
return nil, &fs.PathError{Op: "openfile", Path: name, Err: err}
173-
}
174-
return rfs.dep.OpenFile(name, flag, mode)
175-
}
176-
177-
// ReadDir implements [FS].
178-
func (rfs *ContainedFS) ReadDir(name string) ([]fs.DirEntry, error) {
179-
name, err := rfs.realPath(name)
180-
if err != nil {
181-
return nil, &fs.PathError{Op: "readdir", Path: name, Err: err}
182-
}
183-
return rfs.dep.ReadDir(name)
184-
}
185-
186-
// Remove implements [FS].
187-
func (rfs *ContainedFS) Remove(name string) error {
188-
name, err := rfs.realPath(name)
189-
if err != nil {
190-
return &fs.PathError{Op: "remove", Path: name, Err: err}
191-
}
192-
return rfs.dep.Remove(name)
193-
}
194-
195-
// RemoveAll implements [FS].
196-
func (rfs *ContainedFS) RemoveAll(name string) error {
197-
name, err := rfs.realPath(name)
198-
if err != nil {
199-
return &fs.PathError{Op: "removeall", Path: name, Err: err}
200-
}
201-
return rfs.dep.RemoveAll(name)
202-
}
203-
204-
// Rename implements [FS].
205-
func (rfs *ContainedFS) Rename(oldname, newname string) error {
206-
oldname, err := rfs.realPath(oldname)
207-
if err != nil {
208-
return &fs.PathError{Op: "rename", Path: oldname, Err: err}
209-
}
210-
newname, err = rfs.realPath(newname)
211-
if err != nil {
212-
return &fs.PathError{Op: "rename", Path: newname, Err: err}
213-
}
214-
return rfs.dep.Rename(oldname, newname)
215-
}
216-
217-
// Stat implements [FS].
218-
func (rfs *ContainedFS) Stat(name string) (fs.FileInfo, error) {
219-
name, err := rfs.realPath(name)
220-
if err != nil {
221-
return nil, &fs.PathError{Op: "stat", Path: name, Err: err}
222-
}
223-
return rfs.dep.Stat(name)
42+
// Deprecated: use [NewOverlayFS] with [NewRelativeContainedDirPathMapper] instead.
43+
type ContainedFS struct {
44+
*OverlayFS
22445
}

fsx/fsx.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ const (
4444
O_APPEND = fsmodel.O_APPEND
4545
)
4646

47-
// IsNotExist combines the [os.ErrNotExist] check with
47+
// IsNotExist combines the [os.IsNotExist] check with
4848
// checking for the [fs.ErrNotExist] error.
4949
func IsNotExist(err error) bool {
5050
return errors.Is(err, fs.ErrNotExist) || os.IsNotExist(err)

0 commit comments

Comments
 (0)