github.com/tetratelabs/wazero@v1.2.1/internal/sysfs/dirfs.go (about)

     1  package sysfs
     2  
     3  import (
     4  	"io/fs"
     5  	"os"
     6  	"syscall"
     7  
     8  	"github.com/tetratelabs/wazero/internal/fsapi"
     9  	"github.com/tetratelabs/wazero/internal/platform"
    10  )
    11  
    12  func NewDirFS(dir string) fsapi.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  type dirFS struct {
    27  	fsapi.UnimplementedFS
    28  	dir string
    29  	// cleanedDir is for easier OS-specific concatenation, as it always has
    30  	// a trailing path separator.
    31  	cleanedDir string
    32  }
    33  
    34  // String implements fmt.Stringer
    35  func (d *dirFS) String() string {
    36  	return d.dir
    37  }
    38  
    39  // OpenFile implements the same method as documented on api.FS
    40  func (d *dirFS) OpenFile(path string, flag int, perm fs.FileMode) (fsapi.File, syscall.Errno) {
    41  	return OpenOSFile(d.join(path), flag, perm)
    42  }
    43  
    44  // Lstat implements the same method as documented on api.FS
    45  func (d *dirFS) Lstat(path string) (fsapi.Stat_t, syscall.Errno) {
    46  	return lstat(d.join(path))
    47  }
    48  
    49  // Stat implements the same method as documented on api.FS
    50  func (d *dirFS) Stat(path string) (fsapi.Stat_t, syscall.Errno) {
    51  	return stat(d.join(path))
    52  }
    53  
    54  // Mkdir implements the same method as documented on api.FS
    55  func (d *dirFS) Mkdir(path string, perm fs.FileMode) (errno syscall.Errno) {
    56  	err := os.Mkdir(d.join(path), perm)
    57  	if errno = platform.UnwrapOSError(err); errno == syscall.ENOTDIR {
    58  		errno = syscall.ENOENT
    59  	}
    60  	return
    61  }
    62  
    63  // Chmod implements the same method as documented on api.FS
    64  func (d *dirFS) Chmod(path string, perm fs.FileMode) syscall.Errno {
    65  	err := os.Chmod(d.join(path), perm)
    66  	return platform.UnwrapOSError(err)
    67  }
    68  
    69  // Chown implements the same method as documented on api.FS
    70  func (d *dirFS) Chown(path string, uid, gid int) syscall.Errno {
    71  	return Chown(d.join(path), uid, gid)
    72  }
    73  
    74  // Lchown implements the same method as documented on api.FS
    75  func (d *dirFS) Lchown(path string, uid, gid int) syscall.Errno {
    76  	return Lchown(d.join(path), uid, gid)
    77  }
    78  
    79  // Rename implements the same method as documented on api.FS
    80  func (d *dirFS) Rename(from, to string) syscall.Errno {
    81  	from, to = d.join(from), d.join(to)
    82  	return Rename(from, to)
    83  }
    84  
    85  // Readlink implements the same method as documented on api.FS
    86  func (d *dirFS) Readlink(path string) (string, syscall.Errno) {
    87  	// Note: do not use syscall.Readlink as that causes race on Windows.
    88  	// In any case, syscall.Readlink does almost the same logic as os.Readlink.
    89  	dst, err := os.Readlink(d.join(path))
    90  	if err != nil {
    91  		return "", platform.UnwrapOSError(err)
    92  	}
    93  	return platform.ToPosixPath(dst), 0
    94  }
    95  
    96  // Link implements the same method as documented on api.FS
    97  func (d *dirFS) Link(oldName, newName string) syscall.Errno {
    98  	err := os.Link(d.join(oldName), d.join(newName))
    99  	return platform.UnwrapOSError(err)
   100  }
   101  
   102  // Rmdir implements the same method as documented on api.FS
   103  func (d *dirFS) Rmdir(path string) syscall.Errno {
   104  	err := syscall.Rmdir(d.join(path))
   105  	return platform.UnwrapOSError(err)
   106  }
   107  
   108  // Unlink implements the same method as documented on api.FS
   109  func (d *dirFS) Unlink(path string) (err syscall.Errno) {
   110  	return Unlink(d.join(path))
   111  }
   112  
   113  // Symlink implements the same method as documented on api.FS
   114  func (d *dirFS) Symlink(oldName, link string) syscall.Errno {
   115  	// Note: do not resolve `oldName` relative to this dirFS. The link result is always resolved
   116  	// when dereference the `link` on its usage (e.g. readlink, read, etc).
   117  	// https://github.com/bytecodealliance/cap-std/blob/v1.0.4/cap-std/src/fs/dir.rs#L404-L409
   118  	err := os.Symlink(oldName, d.join(link))
   119  	return platform.UnwrapOSError(err)
   120  }
   121  
   122  // Utimens implements the same method as documented on api.FS
   123  func (d *dirFS) Utimens(path string, times *[2]syscall.Timespec, symlinkFollow bool) syscall.Errno {
   124  	return Utimens(d.join(path), times, symlinkFollow)
   125  }
   126  
   127  // Truncate implements the same method as documented on api.FS
   128  func (d *dirFS) Truncate(path string, size int64) syscall.Errno {
   129  	// Use os.Truncate as syscall.Truncate doesn't exist on Windows.
   130  	err := os.Truncate(d.join(path), size)
   131  	return platform.UnwrapOSError(err)
   132  }
   133  
   134  func (d *dirFS) join(path string) string {
   135  	switch path {
   136  	case "", ".", "/":
   137  		if d.cleanedDir == "/" {
   138  			return "/"
   139  		}
   140  		// cleanedDir includes an unnecessary delimiter for the root path.
   141  		return d.cleanedDir[:len(d.cleanedDir)-1]
   142  	}
   143  	// TODO: Enforce similar to safefilepath.FromFS(path), but be careful as
   144  	// relative path inputs are allowed. e.g. dir or path == ../
   145  	return d.cleanedDir + path
   146  }