github.com/bananabytelabs/wazero@v0.0.0-20240105073314-54b22a776da8/internal/sysfs/dirfs.go (about)

     1  package sysfs
     2  
     3  import (
     4  	"io/fs"
     5  	"os"
     6  
     7  	experimentalsys "github.com/bananabytelabs/wazero/experimental/sys"
     8  	"github.com/bananabytelabs/wazero/internal/platform"
     9  	"github.com/bananabytelabs/wazero/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  }