hare

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

commit dd933fcd509a0771b277ed77467ff64c1603c13f
parent d73900c2b93a2558e9d814316a0ede90fa0e3a9d
Author: Drew DeVault <sir@cmpwn.com>
Date:   Mon,  8 Mar 2021 17:26:53 -0500

fs: add resolve

Diffstat:
Mfs/fs.ha | 11+++++++++++
Mfs/types.ha | 8+++++++-
Mos/+linux/dirfdfs.ha | 38++++++++++++++++++++++++++++++++++++++
Mos/+linux/fs.ha | 5+++++
Mrt/+linux/syscalls.ha | 19++++++++++++++-----
5 files changed, 75 insertions(+), 6 deletions(-)

diff --git a/fs/fs.ha b/fs/fs.ha @@ -87,6 +87,17 @@ export fn mksubdir(fs: *fs, path: path::path) (*fs | error) = { }; }; +// Resolves a path to its absolute, normalized value. This consoldates ./ and +// ../ sequences, roots the path, and returns a new [path::path]. The caller +// must free the return value. +export fn resolve(fs: *fs, path: path::path) path::path = { + match (fs.resolve) { + f: *resolvefunc => return f(fs, path), + null => void, + }; + abort(); // TODO +}; + // Returns the next directory entry from an interator, or void if none remain. // It is a programming error to call this again after it has returned void. The // file stat returned may only have the type bits set on the file mode; callers diff --git a/fs/types.ha b/fs/types.ha @@ -96,7 +96,7 @@ export type stat_mask = enum uint { }; // Information about a file or directory. The mask field defines what other -// fields are set; mode and path are always set. +// fields are set; mode is always set. export type filestat = struct { mask: stat_mask, mode: mode, @@ -147,6 +147,7 @@ export type statfunc = fn(fs: *fs, path: path::path) (filestat | error); export type subdirfunc = fn(fs: *fs, path: path::path) (*fs | error); export type mkdirfunc = fn(fs: *fs, path: path::path) (void | error); export type mksubdirfunc = fn(fs: *fs, path: path::path) (*fs | error); +export type resolvefunc = fn(fs: *fs, path: path::path) path::path; // An abstract implementation of a filesystem. To create a custom stream, embed // this type as the first member of a struct with user-specific data and fill @@ -180,6 +181,11 @@ export type fs = struct { // Creates a directory and returns a subdir for it. mksubdir: nullable *mksubdirfunc, + + // Resolves a path to its absolute, normalized value. If the fs + // implementation does not provide this, [resolve] presumes that + // relative paths are rooted (i.e. "foo" == "/foo"). + resolve: nullable *resolvefunc, }; export type nextfunc = fn(iter: *iterator) (dirent | void); diff --git a/os/+linux/dirfdfs.ha b/os/+linux/dirfdfs.ha @@ -1,3 +1,4 @@ +use bytes; use encoding::utf8; use fs; use io; @@ -49,6 +50,7 @@ fn static_dirfdopen(fd: int, filesystem: *os_filesystem) *fs::fs = { stat = &fs_stat, subdir = &fs_subdir, mkdir = &fs_mkdir, + resolve = &fs_resolve, ... }, dirfd = fd, @@ -197,6 +199,42 @@ fn fs_mkdir(fs: *fs::fs, path: path::path) (void | fs::error) = { }; }; +fn resolve_part(parts: *[]path::path, part: path::path) void = { + const dot = strings::to_utf8("."), dotdot = strings::to_utf8(".."); + let b = match (part) { + b: []u8 => b, + s: str => strings::to_utf8(s), + }; + if (bytes::equal(b, dot)) { + // no-op + void; + } else if (bytes::equal(b, dotdot)) { + // XXX: We should not have to dereference this + if (len(*parts) != 0) { + delete(parts[len(*parts) - 1]); + }; + } else { + append(*parts, part); + }; +}; + +fn fs_resolve(fs: *fs::fs, path: path::path) path::path = { + let parts: []path::path = []; + if (!path::abs(path)) { + let iter = path::iter(getcwd()); + for (true) match (path::next(&iter)) { + void => break, + p: path::path => resolve_part(&parts, p), + }; + }; + let iter = path::iter(path); + for (true) match (path::next(&iter)) { + void => break, + p: path::path => resolve_part(&parts, p), + }; + return path::join(parts...); +}; + fn fs_close(fs: *fs::fs) void = { let fs = fs: *os_filesystem; rt::close(fs.dirfd); diff --git a/os/+linux/fs.ha b/os/+linux/fs.ha @@ -14,6 +14,11 @@ use strings; cwd = static_dirfdopen(rt::AT_FDCWD, &cwd_fs); }; +// Returns the current working directory. The return value is statically +// allocated and must be duplicated (see [strings::dup]) before calling getcwd +// again. +export fn getcwd() path::path = strings::from_c(rt::getcwd() as *const char); + // Change the current working directory. export fn chdir(target: (*fs::fs | ...path::path)) (void | fs::error) = { const path: []u8 = match (target) { diff --git a/rt/+linux/syscalls.ha b/rt/+linux/syscalls.ha @@ -6,12 +6,13 @@ fn syscall4(u64, u64, u64, u64, u64) u64; fn syscall5(u64, u64, u64, u64, u64, u64) u64; fn syscall6(u64, u64, u64, u64, u64, u64, u64) u64; +def PATH_MAX: size = 4096z; export type path = (str | []u8 | *const char); +let pathbuf: [PATH_MAX + 1]u8 = [0...]; // NUL terminates a string and stores it in a static buffer of PATH_MAX+1 bytes // in length. fn kpath(path: path) (*const char | errno) = { - static let buf: [4097]u8 = [0...]; let path = match (path) { c: *const char => return c, s: str => { @@ -24,12 +25,12 @@ fn kpath(path: path) (*const char | errno) = { }, b: []u8 => b, }; - if (len(path) + 1 >= len(buf)) { + if (len(path) + 1 >= len(pathbuf)) { return ENAMETOOLONG; }; - memcpy(&buf, path: *[*]u8, len(path)); - buf[len(path)] = 0; - return &buf: *const char; + memcpy(&pathbuf, path: *[*]u8, len(path)); + pathbuf[len(path)] = 0; + return &pathbuf: *const char; }; export fn read(fd: int, buf: *void, count: size) (size | errno) = { @@ -253,3 +254,11 @@ export fn uname(uts: *utsname) (void | errno) = { wrap_return(syscall1(SYS_uname, uts: uintptr: u64))?; return; }; + +// The return value is statically allocated and must be duplicated before +// calling any other syscall. +export fn getcwd() (*const char | errno) = { + static let pathbuf: [PATH_MAX + 1]u8 = [0...]; + wrap_return(syscall1(SYS_getcwd, &pathbuf: *[*]u8: uintptr: u64))?; + return &pathbuf: *const char; +};