dirfs.go

 1package sysfs
 2
 3import (
 4	"io/fs"
 5	"os"
 6
 7	experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
 8	"github.com/tetratelabs/wazero/internal/platform"
 9	"github.com/tetratelabs/wazero/sys"
10)
11
12func DirFS(dir string) experimentalsys.FS {
13	return &dirFS{
14		dir:        dir,
15		cleanedDir: ensureTrailingPathSeparator(dir),
16	}
17}
18
19func ensureTrailingPathSeparator(dir string) string {
20	if !os.IsPathSeparator(dir[len(dir)-1]) {
21		return dir + string(os.PathSeparator)
22	}
23	return dir
24}
25
26// dirFS is not exported because the input fields must be maintained together.
27// This is likely why os.DirFS doesn't, either!
28type dirFS struct {
29	experimentalsys.UnimplementedFS
30
31	dir string
32	// cleanedDir is for easier OS-specific concatenation, as it always has
33	// a trailing path separator.
34	cleanedDir string
35}
36
37// String implements fmt.Stringer
38func (d *dirFS) String() string {
39	return d.dir
40}
41
42// OpenFile implements the same method as documented on sys.FS
43func (d *dirFS) OpenFile(path string, flag experimentalsys.Oflag, perm fs.FileMode) (experimentalsys.File, experimentalsys.Errno) {
44	return OpenOSFile(d.join(path), flag, perm)
45}
46
47// Lstat implements the same method as documented on sys.FS
48func (d *dirFS) Lstat(path string) (sys.Stat_t, experimentalsys.Errno) {
49	return lstat(d.join(path))
50}
51
52// Stat implements the same method as documented on sys.FS
53func (d *dirFS) Stat(path string) (sys.Stat_t, experimentalsys.Errno) {
54	return stat(d.join(path))
55}
56
57// Mkdir implements the same method as documented on sys.FS
58func (d *dirFS) Mkdir(path string, perm fs.FileMode) (errno experimentalsys.Errno) {
59	err := os.Mkdir(d.join(path), perm)
60	if errno = experimentalsys.UnwrapOSError(err); errno == experimentalsys.ENOTDIR {
61		errno = experimentalsys.ENOENT
62	}
63	return
64}
65
66// Readlink implements the same method as documented on sys.FS
67func (d *dirFS) Readlink(path string) (string, experimentalsys.Errno) {
68	// Note: do not use syscall.Readlink as that causes race on Windows.
69	// In any case, syscall.Readlink does almost the same logic as os.Readlink.
70	dst, err := os.Readlink(d.join(path))
71	if err != nil {
72		return "", experimentalsys.UnwrapOSError(err)
73	}
74	return platform.ToPosixPath(dst), 0
75}
76
77// Rmdir implements the same method as documented on sys.FS
78func (d *dirFS) Rmdir(path string) experimentalsys.Errno {
79	return rmdir(d.join(path))
80}
81
82// Utimens implements the same method as documented on sys.FS
83func (d *dirFS) Utimens(path string, atim, mtim int64) experimentalsys.Errno {
84	return utimens(d.join(path), atim, mtim)
85}
86
87func (d *dirFS) join(path string) string {
88	switch path {
89	case "", ".", "/":
90		if d.cleanedDir == "/" {
91			return "/"
92		}
93		// cleanedDir includes an unnecessary delimiter for the root path.
94		return d.cleanedDir[:len(d.cleanedDir)-1]
95	}
96	// TODO: Enforce similar to safefilepath.FromFS(path), but be careful as
97	// relative path inputs are allowed. e.g. dir or path == ../
98	return d.cleanedDir + path
99}