hare

The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

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:
Mfs/util.ha | 72++++++++++++++++++++++++++++++++++++------------------------------------
Aos/+linux/dirfdfs.ha | 142+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aos/+linux/fs.ha | 12++++++++++++
Aos/fs.ha | 7+++++++
Mrt/+linux/syscallno+aarch64.ha | 1+
Mrt/+linux/syscallno+x86_64.ha | 1+
Mrt/+linux/syscalls.ha | 14++++++++++++++
Mrt/+linux/types.ha | 12++++++++++++
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;