commit dd933fcd509a0771b277ed77467ff64c1603c13f
parent d73900c2b93a2558e9d814316a0ede90fa0e3a9d
Author: Drew DeVault <sir@cmpwn.com>
Date: Mon, 8 Mar 2021 17:26:53 -0500
fs: add resolve
Diffstat:
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;
+};