commit 1f8efea5c528a53c0204a62554b3d0944c8474d3
parent d80f53d088041206f139e2f80afab95612f3c341
Author: Drew DeVault <sir@cmpwn.com>
Date: Wed, 24 Feb 2021 15:35:12 -0500
os: initial pass on dirfd filesystem
Diffstat:
8 files changed, 225 insertions(+), 36 deletions(-)
diff --git a/fs/util.ha b/fs/util.ha
@@ -1,4 +1,3 @@
-use fmt;
use io;
use strio;
@@ -13,43 +12,44 @@ export fn errstr(err: error) const str = match (err) {
// is statically allocated, use [strings::dup] to duplicate it or it will be
// overwritten on subsequent calls.
export fn mode_str(m: mode) const str = {
- // TODO: This could be more efficient without fmt
- static let buf: [11]u8 = [0...];
- let sink = strio::fixed(buf);
- fmt::fprintf(sink, "{}{}{}{}{}{}{}{}{}{}",
- if (m & mode::DIR == mode::DIR) "d"
- else if (m & mode::FIFO == mode::FIFO) "p"
- else if (m & mode::SOCK == mode::SOCK) "s"
- else if (m & mode::BLK == mode::BLK) "b"
- else if (m & mode::LINK == mode::LINK) "l"
- else if (m & mode::CHR == mode::CHR) "c"
- else "-",
- if (m & mode::USER_R == mode::USER_R) "r" else "-",
- if (m & mode::USER_W == mode::USER_W) "w" else "-",
- if (m & mode::SETUID == mode::SETUID) "s"
- else if (m & mode::USER_X == mode::USER_X) "x"
- else "-",
- if (m & mode::GROUP_R == mode::GROUP_R) "r" else "-",
- if (m & mode::GROUP_W == mode::GROUP_W) "w" else "-",
- if (m & mode::SETGID == mode::SETGID) "s"
- else if (m & mode::GROUP_X == mode::GROUP_X) "x"
- else "-",
- if (m & mode::OTHER_R == mode::OTHER_R) "r" else "-",
- if (m & mode::OTHER_W == mode::OTHER_W) "w" else "-",
- if (m & mode::STICKY == mode::STICKY) "t"
- else if (m & mode::OTHER_X == mode::OTHER_X) "x"
- else "-",
- );
- return strio::string(sink);
+ // TODO: Rewrite me to avoid circular dependency on fmt
+ abort();
+ //static let buf: [11]u8 = [0...];
+ //let sink = strio::fixed(buf);
+ //fmt::fprintf(sink, "{}{}{}{}{}{}{}{}{}{}",
+ // if (m & mode::DIR == mode::DIR) "d"
+ // else if (m & mode::FIFO == mode::FIFO) "p"
+ // else if (m & mode::SOCK == mode::SOCK) "s"
+ // else if (m & mode::BLK == mode::BLK) "b"
+ // else if (m & mode::LINK == mode::LINK) "l"
+ // else if (m & mode::CHR == mode::CHR) "c"
+ // else "-",
+ // if (m & mode::USER_R == mode::USER_R) "r" else "-",
+ // if (m & mode::USER_W == mode::USER_W) "w" else "-",
+ // if (m & mode::SETUID == mode::SETUID) "s"
+ // else if (m & mode::USER_X == mode::USER_X) "x"
+ // else "-",
+ // if (m & mode::GROUP_R == mode::GROUP_R) "r" else "-",
+ // if (m & mode::GROUP_W == mode::GROUP_W) "w" else "-",
+ // if (m & mode::SETGID == mode::SETGID) "s"
+ // else if (m & mode::GROUP_X == mode::GROUP_X) "x"
+ // else "-",
+ // if (m & mode::OTHER_R == mode::OTHER_R) "r" else "-",
+ // if (m & mode::OTHER_W == mode::OTHER_W) "w" else "-",
+ // if (m & mode::STICKY == mode::STICKY) "t"
+ // else if (m & mode::OTHER_X == mode::OTHER_X) "x"
+ // else "-",
+ // );
+ //return strio::string(sink);
};
-@test fn mode_str() void = {
- assert(mode_str(0o777: mode) == "-rwxrwxrwx");
- assert(mode_str(mode::DIR | 0o755: mode) == "drwxr-xr-x");
- assert(mode_str(0o755: mode | mode::SETUID) == "-rwsr-xr-x");
- assert(mode_str(0o644: mode) == "-rw-r--r--");
- assert(mode_str(0: mode) == "----------");
-};
+//@test fn mode_str() void = {
+// assert(mode_str(0o777: mode) == "-rwxrwxrwx");
+// assert(mode_str(mode::DIR | 0o755: mode) == "drwxr-xr-x");
+// assert(mode_str(0o755: mode | mode::SETUID) == "-rwsr-xr-x");
+// assert(mode_str(0o644: mode) == "-rw-r--r--");
+// assert(mode_str(0: mode) == "----------");
+//};
// Returns the permission bits of a file mode.
export fn mode_perm(m: mode) mode = (m: uint & 0o777u): mode;
diff --git a/os/+linux/dirfdfs.ha b/os/+linux/dirfdfs.ha
@@ -0,0 +1,142 @@
+use fs;
+use io;
+use rt;
+use strings;
+
+// Controls how symlinks are followed (or not) in a dirfd filesystem. Support
+// for this feature varies, you should gate usage of this enum behind a build
+// tag.
+//
+// TODO: Document these
+export type resolve = enum {
+ NONE,
+ BENEATH,
+ IN_ROOT,
+ NO_SYMLINKS,
+ NO_XDEV,
+};
+
+type os_filesystem = struct {
+ fs: fs::fs,
+ dirfd: int,
+ resolve: resolve,
+};
+
+fn static_dirfdopen(fd: int, filesystem: *os_filesystem) *fs::fs = {
+ *filesystem = os_filesystem {
+ fs = fs::fs {
+ open = &fs_open,
+ create = &fs_create,
+ iter = &fs_iter,
+ stat = &fs_stat,
+ subdir = &fs_subdir,
+ ...
+ },
+ dirfd = fd,
+ };
+ return &filesystem.fs;
+};
+
+export fn dirfdopen(fd: int) *fs::fs = {
+ let fs = alloc(os_filesystem { ... });
+ return static_dirfdopen(fd, fs);
+};
+
+// Clones a dirfd filesystem, optionally adding additional [resolve]
+// constraints.
+export fn dirfs_clone(fs: *fs::fs, resolve: resolve...) *fs::fs = {
+ assert(fs.open == &fs_open);
+ let fs = fs: *os_filesystem;
+ let new = alloc(*fs);
+ for (let i = 0z; i < len(resolve); i += 1) {
+ new.resolve |= resolve[i];
+ };
+ new.dirfd = rt::dup(new.dirfd) as int;
+ return &new.fs;
+};
+
+fn _fs_open(
+ fs: *fs::fs,
+ path: fs::path,
+ mode: io::mode,
+ oh: *rt::open_how,
+) (*io::stream | fs::error) = {
+ let fs = fs: *os_filesystem;
+
+ oh.resolve = 0u64;
+ if (fs.resolve & resolve::BENEATH == resolve::BENEATH) {
+ oh.resolve |= rt::RESOLVE_BENEATH;
+ };
+ if (fs.resolve & resolve::IN_ROOT == resolve::IN_ROOT) {
+ oh.resolve |= rt::RESOLVE_IN_ROOT;
+ };
+ if (fs.resolve & resolve::NO_SYMLINKS == resolve::NO_SYMLINKS) {
+ oh.resolve |= rt::RESOLVE_NO_SYMLINKS;
+ };
+ if (fs.resolve & resolve::NO_XDEV == resolve::NO_XDEV) {
+ oh.resolve |= rt::RESOLVE_NO_XDEV;
+ };
+
+ const p: []u8 = match (path) {
+ s: str => strings::to_utf8(s),
+ b: []u8 => b,
+ };
+ const name: str = match (path) {
+ s: str => s,
+ b: []u8 => "<open([]u8)>",
+ };
+
+ let fd = match (rt::openat2(fs.dirfd, p: *[*]u8: *const char,
+ oh, size(rt::open_how))) {
+ err: rt::errno => return errno_to_io(err),
+ fd: int => fd,
+ };
+
+ return fdopen(fd, name, mode);
+};
+
+fn fs_open(fs: *fs::fs, path: fs::path, mode: io::mode) (*io::stream | fs::error) = {
+ let oflags = rt::O_NOCTTY | rt::O_CLOEXEC;
+ if (mode & io::mode::RDWR == io::mode::RDWR) {
+ oflags |= rt::O_RDWR;
+ } else if (mode & io::mode::READ == io::mode::READ) {
+ oflags |= rt::O_RDONLY;
+ } else if (mode & io::mode::WRITE == io::mode::WRITE) {
+ oflags |= rt::O_WRONLY;
+ };
+
+ let oh = rt::open_how {
+ flags = oflags: u64,
+ ...
+ };
+ return _fs_open(fs, path, mode, &oh);
+};
+
+fn fs_create(fs: *fs::fs, path: fs::path, mode: io::mode) (*io::stream | fs::error) = {
+ let oflags = rt::O_NOCTTY | rt::O_CLOEXEC | rt::O_CREATE;
+ if (mode & io::mode::RDWR == io::mode::RDWR) {
+ oflags |= rt::O_RDWR;
+ } else if (mode & io::mode::READ == io::mode::READ) {
+ oflags |= rt::O_RDONLY;
+ } else if (mode & io::mode::WRITE == io::mode::WRITE) {
+ oflags |= rt::O_WRONLY;
+ };
+
+ let oh = rt::open_how {
+ flags = oflags: u64,
+ ...
+ };
+ return _fs_open(fs, path, mode, &oh);
+};
+
+fn fs_iter(fs: *fs::fs, path: fs::path) (*fs::iterator | fs::error) = {
+ abort();
+};
+
+fn fs_stat(fs: *fs::fs, path: fs::path) (fs::filestat | fs::error) = {
+ abort();
+};
+
+fn fs_subdir(fs: *fs::fs, path: fs::path) (*fs::fs | fs::error) = {
+ abort();
+};
diff --git a/os/+linux/fs.ha b/os/+linux/fs.ha
@@ -0,0 +1,12 @@
+use fs;
+use rt;
+
+@init fn init() void = {
+ static let root_fs = os_filesystem { ... };
+ let dirfd = rt::open("/": *const char,
+ rt::O_RDONLY | rt::O_DIRECTORY, 0u) as int;
+ root = static_dirfdopen(dirfd, &root_fs);
+
+ static let cwd_fs = os_filesystem { ... };
+ cwd = static_dirfdopen(rt::AT_FDCWD, &cwd_fs);
+};
diff --git a/os/fs.ha b/os/fs.ha
@@ -0,0 +1,7 @@
+use fs;
+
+// Provides an implementation of [fs::fs] for the host filesystem.
+export let root: *fs::fs = null: *fs::fs;
+
+// Provides an implementation of [fs::fs] for the current working directory.
+export let cwd: *fs::fs = null: *fs::fs;
diff --git a/rt/+linux/syscallno+aarch64.ha b/rt/+linux/syscallno+aarch64.ha
@@ -289,4 +289,5 @@ export def SYS_fsmount: u64 = 432;
export def SYS_fspick: u64 = 433;
export def SYS_pidfd_open: u64 = 434;
export def SYS_clone3: u64 = 435;
+export def SYS_openat2: u64 = 437;
export def SYS_faccessat2: u64 = 439;
diff --git a/rt/+linux/syscallno+x86_64.ha b/rt/+linux/syscallno+x86_64.ha
@@ -343,4 +343,5 @@ export def SYS_fsopen: u64 = 430;
export def SYS_fsconfig: u64 = 431;
export def SYS_fsmount: u64 = 432;
export def SYS_fspick: u64 = 433;
+export def SYS_openat2: u64 = 437;
export def SYS_faccessat2: u64 = 439;
diff --git a/rt/+linux/syscalls.ha b/rt/+linux/syscalls.ha
@@ -21,6 +21,20 @@ export fn open(path: *const char, flags: int, mode: uint) (int | errno) = {
path: uintptr: u64, flags: u64, mode: u64))?: int;
};
+export fn openat2(
+ dirfd: int,
+ path: *const char,
+ how: *open_how,
+ how_sz: size,
+) (int | errno) = {
+ return wrap_return(syscall4(SYS_openat2, dirfd: u64,
+ path: uintptr: u64, how: uintptr: u64, how_sz: u64))?: int;
+};
+
+export fn dup(fd: int) (int | errno) = {
+ return wrap_return(syscall1(SYS_dup, fd: u64))?: int;
+};
+
export fn close(fd: int) (void | errno) = {
wrap_return(syscall1(SYS_close, fd: u64))?;
return;
diff --git a/rt/+linux/types.ha b/rt/+linux/types.ha
@@ -230,3 +230,15 @@ export def CLOCK_REALTIME_ALARM: int = 8;
export def CLOCK_BOOTTIME_ALARM: int = 9;
export def CLOCK_SGI_CYCLE: int = 10;
export def CLOCK_TAI: int = 11;
+
+export type open_how = struct {
+ flags: u64,
+ mode: u64,
+ resolve: u64,
+};
+
+export def RESOLVE_NO_XDEV: u64 = 0x01;
+export def RESOLVE_NO_MAGICLINKS: u64 = 0x02;
+export def RESOLVE_NO_SYMLINKS: u64 = 0x04;
+export def RESOLVE_BENEATH: u64 = 0x08;
+export def RESOLVE_IN_ROOT: u64 = 0x10;