github.com/wasilibs/wazerox@v0.0.0-20240124024944-4923be63ab5f/internal/sysfs/dirfs.go (about) 1 package sysfs 2 3 import ( 4 "io/fs" 5 "os" 6 7 experimentalsys "github.com/wasilibs/wazerox/experimental/sys" 8 "github.com/wasilibs/wazerox/internal/platform" 9 "github.com/wasilibs/wazerox/sys" 10 ) 11 12 func DirFS(dir string) experimentalsys.FS { 13 return &dirFS{ 14 dir: dir, 15 cleanedDir: ensureTrailingPathSeparator(dir), 16 } 17 } 18 19 func 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! 28 type 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 38 func (d *dirFS) String() string { 39 return d.dir 40 } 41 42 // OpenFile implements the same method as documented on sys.FS 43 func (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 48 func (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 53 func (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 58 func (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 // Chmod implements the same method as documented on sys.FS 67 func (d *dirFS) Chmod(path string, perm fs.FileMode) experimentalsys.Errno { 68 err := os.Chmod(d.join(path), perm) 69 return experimentalsys.UnwrapOSError(err) 70 } 71 72 // Rename implements the same method as documented on sys.FS 73 func (d *dirFS) Rename(from, to string) experimentalsys.Errno { 74 from, to = d.join(from), d.join(to) 75 return rename(from, to) 76 } 77 78 // Readlink implements the same method as documented on sys.FS 79 func (d *dirFS) Readlink(path string) (string, experimentalsys.Errno) { 80 // Note: do not use syscall.Readlink as that causes race on Windows. 81 // In any case, syscall.Readlink does almost the same logic as os.Readlink. 82 dst, err := os.Readlink(d.join(path)) 83 if err != nil { 84 return "", experimentalsys.UnwrapOSError(err) 85 } 86 return platform.ToPosixPath(dst), 0 87 } 88 89 // Link implements the same method as documented on sys.FS 90 func (d *dirFS) Link(oldName, newName string) experimentalsys.Errno { 91 err := os.Link(d.join(oldName), d.join(newName)) 92 return experimentalsys.UnwrapOSError(err) 93 } 94 95 // Rmdir implements the same method as documented on sys.FS 96 func (d *dirFS) Rmdir(path string) experimentalsys.Errno { 97 return rmdir(d.join(path)) 98 } 99 100 // Unlink implements the same method as documented on sys.FS 101 func (d *dirFS) Unlink(path string) (err experimentalsys.Errno) { 102 return unlink(d.join(path)) 103 } 104 105 // Symlink implements the same method as documented on sys.FS 106 func (d *dirFS) Symlink(oldName, link string) experimentalsys.Errno { 107 // Note: do not resolve `oldName` relative to this dirFS. The link result is always resolved 108 // when dereference the `link` on its usage (e.g. readlink, read, etc). 109 // https://github.com/bytecodealliance/cap-std/blob/v1.0.4/cap-std/src/fs/dir.rs#L404-L409 110 err := os.Symlink(oldName, d.join(link)) 111 return experimentalsys.UnwrapOSError(err) 112 } 113 114 // Utimens implements the same method as documented on sys.FS 115 func (d *dirFS) Utimens(path string, atim, mtim int64) experimentalsys.Errno { 116 return utimens(d.join(path), atim, mtim) 117 } 118 119 func (d *dirFS) join(path string) string { 120 switch path { 121 case "", ".", "/": 122 if d.cleanedDir == "/" { 123 return "/" 124 } 125 // cleanedDir includes an unnecessary delimiter for the root path. 126 return d.cleanedDir[:len(d.cleanedDir)-1] 127 } 128 // TODO: Enforce similar to safefilepath.FromFS(path), but be careful as 129 // relative path inputs are allowed. e.g. dir or path == ../ 130 return d.cleanedDir + path 131 }