This implements `platform.UtimesFile` which is similar to futimes. Before, we were using path-based functionality even though the call site was for a file descriptor. Note: right now, there's no obvious code in Go to invoke the `futimens` syscall. This means non-windows isn't implemented at nanos granularity, so ends up falling back to the path based option. Finally, this removes tests for the seldom supported updates with negative epoch time. There's little impact to this as setting times on files before 1970 isn't a typical use case. Signed-off-by: Adrian Cole <adrian@tetrate.io>
149 lines
4.0 KiB
Go
149 lines
4.0 KiB
Go
package sysfs
|
|
|
|
import (
|
|
"io/fs"
|
|
"os"
|
|
"syscall"
|
|
|
|
"github.com/tetratelabs/wazero/internal/platform"
|
|
)
|
|
|
|
func NewDirFS(dir string) FS {
|
|
return &dirFS{
|
|
dir: dir,
|
|
cleanedDir: ensureTrailingPathSeparator(dir),
|
|
}
|
|
}
|
|
|
|
func ensureTrailingPathSeparator(dir string) string {
|
|
if dir[len(dir)-1] != os.PathSeparator {
|
|
return dir + string(os.PathSeparator)
|
|
}
|
|
return dir
|
|
}
|
|
|
|
type dirFS struct {
|
|
UnimplementedFS
|
|
dir string
|
|
// cleanedDir is for easier OS-specific concatenation, as it always has
|
|
// a trailing path separator.
|
|
cleanedDir string
|
|
}
|
|
|
|
// String implements fmt.Stringer
|
|
func (d *dirFS) String() string {
|
|
return d.dir
|
|
}
|
|
|
|
// Open implements the same method as documented on fs.FS
|
|
func (d *dirFS) Open(name string) (fs.File, error) {
|
|
return fsOpen(d, name)
|
|
}
|
|
|
|
// OpenFile implements FS.OpenFile
|
|
func (d *dirFS) OpenFile(path string, flag int, perm fs.FileMode) (fs.File, error) {
|
|
return platform.OpenFile(d.join(path), flag, perm)
|
|
}
|
|
|
|
// Lstat implements FS.Lstat
|
|
func (d *dirFS) Lstat(path string, stat *platform.Stat_t) error {
|
|
return platform.Lstat(d.join(path), stat)
|
|
}
|
|
|
|
// Stat implements FS.Stat
|
|
func (d *dirFS) Stat(path string, stat *platform.Stat_t) error {
|
|
return platform.Stat(d.join(path), stat)
|
|
}
|
|
|
|
// Mkdir implements FS.Mkdir
|
|
func (d *dirFS) Mkdir(path string, perm fs.FileMode) (err error) {
|
|
err = os.Mkdir(d.join(path), perm)
|
|
if err = platform.UnwrapOSError(err); err == syscall.ENOTDIR {
|
|
err = syscall.ENOENT
|
|
}
|
|
return
|
|
}
|
|
|
|
// Chmod implements FS.Chmod
|
|
func (d *dirFS) Chmod(path string, perm fs.FileMode) error {
|
|
err := os.Chmod(d.join(path), perm)
|
|
return platform.UnwrapOSError(err)
|
|
}
|
|
|
|
// Chown implements FS.Chown
|
|
func (d *dirFS) Chown(path string, uid, gid int) error {
|
|
return platform.Chown(d.join(path), uid, gid)
|
|
}
|
|
|
|
// Lchown implements FS.Lchown
|
|
func (d *dirFS) Lchown(path string, uid, gid int) error {
|
|
return platform.Lchown(d.join(path), uid, gid)
|
|
}
|
|
|
|
// Rename implements FS.Rename
|
|
func (d *dirFS) Rename(from, to string) error {
|
|
from, to = d.join(from), d.join(to)
|
|
err := platform.Rename(from, to)
|
|
return platform.UnwrapOSError(err)
|
|
}
|
|
|
|
// Readlink implements FS.Readlink
|
|
func (d *dirFS) Readlink(path string) (string, error) {
|
|
// Note: do not use syscall.Readlink as that causes race on Windows.
|
|
// In any case, syscall.Readlink does almost the same logic as os.Readlink.
|
|
dst, err := os.Readlink(d.join(path))
|
|
if err != nil {
|
|
return "", platform.UnwrapOSError(err)
|
|
}
|
|
return platform.ToPosixPath(dst), nil
|
|
}
|
|
|
|
// Link implements FS.Link.
|
|
func (d *dirFS) Link(oldName, newName string) error {
|
|
err := os.Link(d.join(oldName), d.join(newName))
|
|
return platform.UnwrapOSError(err)
|
|
}
|
|
|
|
// Rmdir implements FS.Rmdir
|
|
func (d *dirFS) Rmdir(path string) error {
|
|
err := syscall.Rmdir(d.join(path))
|
|
return platform.UnwrapOSError(err)
|
|
}
|
|
|
|
// Unlink implements FS.Unlink
|
|
func (d *dirFS) Unlink(path string) (err error) {
|
|
return platform.Unlink(d.join(path))
|
|
}
|
|
|
|
// Symlink implements FS.Symlink
|
|
func (d *dirFS) Symlink(oldName, link string) (err error) {
|
|
// Note: do not resolve `oldName` relative to this dirFS. The link result is always resolved
|
|
// when dereference the `link` on its usage (e.g. readlink, read, etc).
|
|
// https://github.com/bytecodealliance/cap-std/blob/v1.0.4/cap-std/src/fs/dir.rs#L404-L409
|
|
err = os.Symlink(oldName, d.join(link))
|
|
return platform.UnwrapOSError(err)
|
|
}
|
|
|
|
// UtimesNano implements FS.UtimesNano
|
|
func (d *dirFS) UtimesNano(name string, atimeNsec, mtimeNsec int64) error {
|
|
return platform.UtimesNano(d.join(name), atimeNsec, mtimeNsec)
|
|
}
|
|
|
|
// Truncate implements FS.Truncate
|
|
func (d *dirFS) Truncate(path string, size int64) error {
|
|
// Use os.Truncate as syscall.Truncate doesn't exist on Windows.
|
|
err := os.Truncate(d.join(path), size)
|
|
return platform.UnwrapOSError(err)
|
|
}
|
|
|
|
func (d *dirFS) join(path string) string {
|
|
switch path {
|
|
case "", ".", "/":
|
|
// cleanedDir includes an unnecessary delimiter for the root path.
|
|
return d.cleanedDir[:len(d.cleanedDir)-1]
|
|
}
|
|
// TODO: Enforce similar to safefilepath.FromFS(path), but be careful as
|
|
// relative path inputs are allowed. e.g. dir or path == ../
|
|
return d.cleanedDir + path
|
|
}
|