commit 7cb3a253f43c12a746be2f377ff420ca4d2734dd
parent cd68fa6697115b04b302dde790754585b31d299a
Author: Drew DeVault <sir@cmpwn.com>
Date: Thu, 25 Feb 2021 20:03:48 -0500
path: new module
Diffstat:
10 files changed, 114 insertions(+), 30 deletions(-)
diff --git a/fs/fs.ha b/fs/fs.ha
@@ -9,7 +9,7 @@ export fn close(fs: *fs) void = {
};
// Opens a file.
-export fn open(fs: *fs, path: path, mode: io::mode) (*io::stream | error) = {
+export fn open(fs: *fs, path: path::path, mode: io::mode) (*io::stream | error) = {
return match (fs.open) {
null => io::unsupported,
f: *openfunc => f(fs, path, mode),
@@ -17,7 +17,7 @@ export fn open(fs: *fs, path: path, mode: io::mode) (*io::stream | error) = {
};
// Creates a new file. The default file permissions are implementation defined.
-export fn create(fs: *fs, path: path, mode: io::mode) (*io::stream | error) = {
+export fn create(fs: *fs, path: path::path, mode: io::mode) (*io::stream | error) = {
return match (fs.create) {
null => io::unsupported,
f: *createfunc => f(fs, path, mode),
@@ -27,7 +27,7 @@ export fn create(fs: *fs, path: path, mode: io::mode) (*io::stream | error) = {
// Returns an iterator for a path, which yields the contents of a directory.
// Pass empty string to yield from the root. The order in which entries are
// returned is undefined.
-export fn iter(fs: *fs, path: path) (*iterator | error) = {
+export fn iter(fs: *fs, path: path::path) (*iterator | error) = {
return match (fs.iter) {
null => io::unsupported,
f: *iterfunc => f(fs, path),
@@ -36,7 +36,7 @@ export fn iter(fs: *fs, path: path) (*iterator | error) = {
// Obtains information about a file or directory. If the target is a symlink,
// information is returned about the link, not its target.
-export fn stat(fs: *fs, path: path) (filestat | error) = {
+export fn stat(fs: *fs, path: path::path) (filestat | error) = {
return match (fs.stat) {
null => io::unsupported,
f: *statfunc => f(fs, path),
@@ -44,7 +44,7 @@ export fn stat(fs: *fs, path: path) (filestat | error) = {
};
// Opens a new filesystem for a subdirectory.
-export fn subdir(fs: *fs, path: path) (*fs | error) = {
+export fn subdir(fs: *fs, path: path::path) (*fs | error) = {
return match (fs.subdir) {
null => io::unsupported,
f: *subdirfunc => f(fs, path),
diff --git a/fs/types.ha b/fs/types.ha
@@ -1,8 +1,6 @@
use io;
use strings;
-
-// A path or path component.
-export type path = (str | []u8);
+use path;
// An entry was requested which does not exist.
export type noentry = void!;
@@ -108,7 +106,7 @@ export type filestat = struct {
export type dirent = struct {
// The name of this entry. Not fully qualified: for example,
// "foo/bar/baz.txt" would store "baz.txt" here.
- name: path,
+ name: path::path,
// The type of this entry. The permission bits may be unset.
ftype: mode,
@@ -135,11 +133,11 @@ export fn dirent_free(e: dirent) void = match (e.name) {
};
export type closefunc = fn(fs: *fs) void;
-export type openfunc = fn(fs: *fs, path: path, mode: io::mode) (*io::stream | error);
-export type createfunc = fn(fs: *fs, path: path, mode: io::mode) (*io::stream | error);
-export type iterfunc = fn(fs: *fs, path: path) (*iterator | error);
-export type statfunc = fn(fs: *fs, path: path) (filestat | error);
-export type subdirfunc = fn(fs: *fs, path: path) (*fs | error);
+export type openfunc = fn(fs: *fs, path: path::path, mode: io::mode) (*io::stream | error);
+export type createfunc = fn(fs: *fs, path: path::path, mode: io::mode) (*io::stream | error);
+export type iterfunc = fn(fs: *fs, path: path::path) (*iterator | error);
+export type statfunc = fn(fs: *fs, path: path::path) (filestat | error);
+export type subdirfunc = fn(fs: *fs, path: path::path) (*fs | error);
// 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
diff --git a/fs/util.ha b/fs/util.ha
@@ -1,4 +1,5 @@
use io;
+use path;
use strings;
// Returns a human-friendly representation of an error.
@@ -77,7 +78,7 @@ export fn is_socket(mode: mode) bool = mode & mode::SOCK == mode::SOCK;
// Reads all entries from a directory. The caller must free the return value
// with [dirents_free].
-export fn readdir(fs: *fs, path: path) ([]dirent | error) = {
+export fn readdir(fs: *fs, path: path::path) ([]dirent | error) = {
let i = iter(fs, path)?;
let ents: []dirent = [];
for (true) {
diff --git a/os/+linux/dirfdfs.ha b/os/+linux/dirfdfs.ha
@@ -1,6 +1,7 @@
use encoding::utf8;
use fs;
use io;
+use path;
use rt;
use strings;
@@ -54,7 +55,7 @@ fn static_dirfdopen(fd: int, filesystem: *os_filesystem) *fs::fs = {
return &filesystem.fs;
};
-fn pathbytes(path: fs::path) *const char = {
+fn pathbytes(path: path::path) *const char = {
let p = match (path) {
s: str => strings::to_utf8(s),
b: []u8 => b,
@@ -89,7 +90,7 @@ export fn dirfs_clone(fs: *fs::fs, resolve: resolve...) *fs::fs = {
fn _fs_open(
fs: *fs::fs,
- path: fs::path,
+ path: path::path,
mode: io::mode,
oh: *rt::open_how,
) (*io::stream | fs::error) = {
@@ -123,7 +124,7 @@ fn _fs_open(
return fdopen(fd, name, mode);
};
-fn fs_open(fs: *fs::fs, path: fs::path, mode: io::mode) (*io::stream | fs::error) = {
+fn fs_open(fs: *fs::fs, path: path::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;
@@ -140,7 +141,7 @@ fn fs_open(fs: *fs::fs, path: fs::path, mode: io::mode) (*io::stream | fs::error
return _fs_open(fs, path, mode, &oh);
};
-fn fs_create(fs: *fs::fs, path: fs::path, mode: io::mode) (*io::stream | fs::error) = {
+fn fs_create(fs: *fs::fs, path: path::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;
@@ -157,7 +158,7 @@ fn fs_create(fs: *fs::fs, path: fs::path, mode: io::mode) (*io::stream | fs::err
return _fs_open(fs, path, mode, &oh);
};
-fn fs_stat(fs: *fs::fs, path: fs::path) (fs::filestat | fs::error) = {
+fn fs_stat(fs: *fs::fs, path: path::path) (fs::filestat | fs::error) = {
let fs = fs: *os_filesystem;
let st = rt::st { ... };
match (rt::fstatat(fs.dirfd, pathbytes(path),
@@ -178,7 +179,7 @@ fn fs_stat(fs: *fs::fs, path: fs::path) (fs::filestat | fs::error) = {
};
};
-fn fs_subdir(fs: *fs::fs, path: fs::path) (*fs::fs | fs::error) = {
+fn fs_subdir(fs: *fs::fs, path: path::path) (*fs::fs | fs::error) = {
let fs = fs: *os_filesystem;
let oh = rt::open_how {
flags = (rt::O_RDONLY | rt::O_CLOEXEC | rt::O_DIRECTORY): u64,
@@ -210,7 +211,7 @@ type os_iterator = struct {
buf: [BUFSIZ]u8,
};
-fn fs_iter(fs: *fs::fs, path: fs::path) (*fs::iterator | fs::error) = {
+fn fs_iter(fs: *fs::fs, path: path::path) (*fs::iterator | fs::error) = {
let fs = fs: *os_filesystem;
let oh = rt::open_how {
flags = (rt::O_RDONLY | rt::O_CLOEXEC | rt::O_DIRECTORY): u64,
diff --git a/os/+linux/fs.ha b/os/+linux/fs.ha
@@ -1,4 +1,5 @@
use fs;
+use path;
use rt;
use strings;
@@ -13,7 +14,7 @@ use strings;
};
// Change the current working directory.
-export fn chdir(target: (*fs::fs | ...fs::path)) (void | fs::error) = {
+export fn chdir(target: (*fs::fs | ...path::path)) (void | fs::error) = {
const path: []u8 = match (target) {
fs: *fs::fs => {
assert(fs.open == &fs_open);
@@ -36,7 +37,7 @@ export fn chdir(target: (*fs::fs | ...fs::path)) (void | fs::error) = {
// have root or otherwise elevated permissions.
//
// This function is not appropriate for sandboxing.
-export fn chroot(target: fs::path) (void | fs::error) = {
+export fn chroot(target: path::path) (void | fs::error) = {
const path: []u8 = match (target) {
s: str => strings::to_utf8(s),
b: []u8 => b,
diff --git a/os/+linux/open.ha b/os/+linux/open.ha
@@ -1,5 +1,6 @@
use fs;
use io;
+use path;
use rt;
use strings;
@@ -27,7 +28,7 @@ export type flags = enum int {
//
// If no flags are provided, the default for +linux is RDONLY | NOCTTY | CLOEXEC.
export fn open(
- path: fs::path,
+ path: path::path,
flag: flags...
) (*io::stream | io::error) = {
const name: str = match (path) {
@@ -63,7 +64,7 @@ export fn open(
// Equivalent to [open], but allows the caller to specify a file mode for the
// new file. If no other flag is given, the default read/write mode is WRONLY.
export fn create(
- path: fs::path,
+ path: path::path,
mode: uint,
flag: flags...
) (*io::stream | io::error) = {
diff --git a/os/fs.ha b/os/fs.ha
@@ -1,4 +1,5 @@
use fs;
+use path;
// Provides an implementation of [fs::fs] for the host filesystem.
export let root: *fs::fs = null: *fs::fs;
@@ -7,16 +8,16 @@ export let root: *fs::fs = null: *fs::fs;
export let cwd: *fs::fs = null: *fs::fs;
// Creates an [fs::iterator] for a given directory to read its contents.
-export fn iterdir(path: fs::path) (*fs::iterator | fs::error) =
+export fn iterdir(path: path::path) (*fs::iterator | fs::error) =
fs::iter(cwd, path);
// Reads all entries from a directory. The caller must free the return value
// with [fs::dirents_free].
-export fn readdir(path: fs::path) ([]fs::dirent | fs::error) =
+export fn readdir(path: path::path) ([]fs::dirent | fs::error) =
fs::readdir(cwd, path);
// Returns file information for a given path.
-export fn stat(path: fs::path) (fs::filestat | fs::error) = fs::stat(cwd, path);
+export fn stat(path: path::path) (fs::filestat | fs::error) = fs::stat(cwd, path);
// Opens a directory as a filesystem.
-export fn diropen(path: fs::path) (*fs::fs | fs::error) = fs::subdir(cwd, path);
+export fn diropen(path: path::path) (*fs::fs | fs::error) = fs::subdir(cwd, path);
diff --git a/path/+linux.ha b/path/+linux.ha
@@ -0,0 +1,2 @@
+// Path separator, platform-specific.
+export def PATHSEP: u8 = '/': u32: u8;
diff --git a/path/join.ha b/path/join.ha
@@ -0,0 +1,59 @@
+use bufio;
+use strings;
+use io;
+
+// Joins together several path components with the path separator. The caller
+// must free the return value with [path_free].
+export fn join(paths: path...) path = {
+ // TODO: Normalize inputs so that if they end with a / we don't double
+ // up on delimiters
+ let sink = bufio::dynamic();
+ let utf8 = true;
+ for (let i = 0z; i < len(paths); i += 1) {
+ utf8 = utf8 && paths[i] is str;
+
+ let buf = pathbytes(paths[i]);
+ let l = len(buf);
+ for (buf[l - 1] == PATHSEP) {
+ l -= 1;
+ };
+ for (let q = 0z; q < l) {
+ let w = io::write(sink, buf[q..l]) as size;
+ q += w;
+ };
+ if (i + 1 < len(paths)) {
+ assert(io::write(sink, [PATHSEP]) as size == 1);
+ };
+ };
+
+ return
+ if (utf8) strings::from_utf8_unsafe(bufio::finish(sink))
+ else bufio::finish(sink);
+};
+
+@test fn join() void = {
+ assert(PATHSEP == '/': u32: u8); // TODO: meh
+ let i = join("foo");
+ defer path_free(i);
+ assert(i as str == "foo");
+
+ let p = join(i, "bar", "baz");
+ defer path_free(p);
+ assert(p as str == "foo/bar/baz");
+
+ let q = join(p, "bat", "bad");
+ defer path_free(q);
+ assert(q as str == "foo/bar/baz/bat/bad");
+
+ let r = join(p, q);
+ defer path_free(r);
+ assert(r as str == "foo/bar/baz/foo/bar/baz/bat/bad");
+
+ let p = join("foo/", "bar");
+ defer path_free(p);
+ assert(p as str == "foo/bar");
+
+ let p = join("foo///", "bar");
+ defer path_free(p);
+ assert(p as str == "foo/bar");
+};
diff --git a/path/util.ha b/path/util.ha
@@ -0,0 +1,20 @@
+use bytes;
+use strings;
+
+// A path or path component.
+export type path = (str | []u8);
+
+fn pathbytes(path: path) []u8 = match (path) {
+ s: str => strings::to_utf8(s),
+ b: []u8 => b,
+};
+
+// Frees a path.
+export fn path_free(p: path) void = match (p) {
+ s: str => free(s),
+ b: []u8 => free(b),
+};
+
+// Returns true if two paths are equal.
+export fn equal(a: path, b: path) bool =
+ bytes::equal(pathbytes(a), pathbytes(b));