commit f2f0ca8d0da25116c0d313bae6ae1a7603930cfa
parent 36a475c57d23f9b04f9e0a2024d3647419095bf9
Author: Drew DeVault <sir@cmpwn.com>
Date: Wed, 10 Mar 2021 10:10:21 -0500
all: use str for path components
On Linux, and other operating systems, path components can be any
sequence of bytes other than ASCII '/', and the stdlib API was
originally designed to accomodate this.
However, this is ultimately quite cumbersome. Some examples:
- argv can also be non-UTF-8, so os::args has to be updated as well,
causing almost all Hare programs to deal with this dumb edge case
- Ditto for environment variables.
- Ditto for os::exec.
- Filenames can also contain \0, which breaks so much shit. Hare handles
this as str but not as *const char.
- path::path cannot be passed to fmt for printing without lengthly
special-case handling
This is a bit of unnecessary complexity that permeates huge swaths of
the stdlib and trickles into end-user programs. It simplifies things
greatly for everyone and for 99% of use-cases if we just abort when we
encounter non-UTF-8 paths.
That said, some programs do have a legitimate reason to support
non-UTF-8 paths, arguments, or environment variables. For this reason, I
have left the rt API intact, which understands both strings and byte
slices. Any program which has a genuine need to support this can write
syscalls or access rt's initialization vector directly. This is
non-portable - but that was a cost they were likely to bear regardless.
Diffstat:
17 files changed, 169 insertions(+), 279 deletions(-)
diff --git a/dirs/xdg.ha b/dirs/xdg.ha
@@ -3,7 +3,7 @@ use os;
use path;
use io;
-fn lookup(prog: str, var: str, default: path::path) path::path = {
+fn lookup(prog: str, var: str, default: str) str = {
match (os::getenv(var)) {
s: str => {
let path = path::join(s, prog);
@@ -34,8 +34,7 @@ fn lookup(prog: str, var: str, default: path::path) path::path = {
// Returns a directory suitable for storing config files. If 'prog' is given, a
// unique path for this program to store data will be returned.
-export fn config(prog: str) path::path =
- lookup(prog, "XDG_CONFIG_HOME", ".config");
+export fn config(prog: str) str = lookup(prog, "XDG_CONFIG_HOME", ".config");
// Returns an [fs::fs] for storing config files. If 'prog' is given, a unique
// path for this program to store data will be returned.
@@ -43,8 +42,7 @@ export fn config_fs(prog: str) *fs::fs = os::diropen(config(prog)) as *fs::fs;
// Returns a directory suitable for cache files. If 'prog' is given, a unique
// path for this program to store data will be returned.
-export fn cache(prog: str) path::path =
- lookup(prog, "XDG_CACHE_HOME", ".cache");
+export fn cache(prog: str) str = lookup(prog, "XDG_CACHE_HOME", ".cache");
// Returns an [fs::fs] for cache files. If 'prog' is given, a unique path for
// this program to store data will be returned.
@@ -52,7 +50,7 @@ export fn cache_fs(prog: str) *fs::fs = os::diropen(cache(prog)) as *fs::fs;
// Returns a directory suitable for persistent data files. If 'prog' is given, a
// unique path for this program to store data will be returned.
-export fn data(prog: str) path::path =
+export fn data(prog: str) str =
lookup(prog, "XDG_DATA_HOME", path::join(".local", "share"));
// Returns an [fs::fs] for persistent data files. If 'prog' is given, a unique
diff --git a/fs/fs.ha b/fs/fs.ha
@@ -11,11 +11,7 @@ export fn close(fs: *fs) void = {
// Opens a file. If no flags are provided, the default read/write mode is
// RDONLY.
-export fn open(
- fs: *fs,
- path: path::path,
- flags: flags...
-) (*io::stream | error) = {
+export fn open(fs: *fs, path: str, flags: flags...) (*io::stream | error) = {
return match (fs.open) {
null => io::unsupported,
f: *openfunc => f(fs, path, flags...),
@@ -29,7 +25,7 @@ export fn open(
// are discarded.
export fn create(
fs: *fs,
- path: path::path,
+ path: str,
mode: mode,
flags: flags...
) (*io::stream | error) = {
@@ -41,7 +37,7 @@ export fn create(
};
// Removes a file.
-export fn remove(fs: *fs, path: path::path) (void | error) = {
+export fn remove(fs: *fs, path: str) (void | error) = {
return match (fs.remove) {
null => io::unsupported,
f: *removefunc => f(fs, path),
@@ -51,7 +47,7 @@ export fn remove(fs: *fs, path: path::path) (void | 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::path) (*iterator | error) = {
+export fn iter(fs: *fs, path: str) (*iterator | error) = {
return match (fs.iter) {
null => io::unsupported,
f: *iterfunc => f(fs, path),
@@ -60,7 +56,7 @@ export fn iter(fs: *fs, path: 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::path) (filestat | error) = {
+export fn stat(fs: *fs, path: str) (filestat | error) = {
return match (fs.stat) {
null => io::unsupported,
f: *statfunc => f(fs, path),
@@ -70,7 +66,7 @@ export fn stat(fs: *fs, path: path::path) (filestat | error) = {
// Opens a new filesystem for a subdirectory. The subdirectory must be closed
// separately from the parent filesystem, and its lifetime can outlive that of
// its parent.
-export fn subdir(fs: *fs, path: path::path) (*fs | error) = {
+export fn subdir(fs: *fs, path: str) (*fs | error) = {
return match (fs.subdir) {
null => io::unsupported,
f: *subdirfunc => f(fs, path),
@@ -78,7 +74,7 @@ export fn subdir(fs: *fs, path: path::path) (*fs | error) = {
};
// Creates a directory.
-export fn mkdir(fs: *fs, path: path::path) (void | error) = {
+export fn mkdir(fs: *fs, path: str) (void | error) = {
return match (fs.mkdir) {
null => io::unsupported,
f: *mkdirfunc => f(fs, path),
@@ -86,9 +82,9 @@ export fn mkdir(fs: *fs, path: path::path) (void | error) = {
};
// Makes a directory, and all non-extant directories in its path.
-export fn mkdirs(fs: *fs, path: path::path) (void | error) = {
+export fn mkdirs(fs: *fs, path: str) (void | error) = {
let parent = path::dirname(path);
- if (!path::equal(path, parent)) {
+ if (path != parent) {
match (mkdirs(fs, parent)) {
exists => void,
err: error => return err,
@@ -100,7 +96,7 @@ export fn mkdirs(fs: *fs, path: path::path) (void | error) = {
// Removes a directory. The target directory must be empty; see [rmdirall] to
// remove its contents as well.
-export fn rmdir(fs: *fs, path: path::path) (void | error) = {
+export fn rmdir(fs: *fs, path: str) (void | error) = {
return match (fs.rmdir) {
null => io::unsupported,
f: *rmdirfunc => f(fs, path),
@@ -108,23 +104,22 @@ export fn rmdir(fs: *fs, path: path::path) (void | error) = {
};
// Removes a directory, and anything in it.
-export fn rmdirall(fs: *fs, path: path::path) (void | error) = {
+export fn rmdirall(fs: *fs, path: str) (void | error) = {
let it = iter(fs, path)?;
for (true) match (next(it)) {
ent: dirent => {
- if (path::equal(ent.name, ".")
- || path::equal(ent.name, "..")) {
+ if (ent.name == "." || ent.name == "..") {
continue;
};
switch (ent.ftype) {
mode::DIR => {
let p = path::join(path, ent.name);
- defer path::path_free(p);
+ defer free(p);
rmdirall(fs, p)?;
},
* => {
let p = path::join(path, ent.name);
- defer path::path_free(p);
+ defer free(p);
remove(fs, p)?;
},
};
@@ -136,7 +131,7 @@ export fn rmdirall(fs: *fs, path: path::path) (void | error) = {
// Creates a directory and returns a subdir for it. Some filesystems support
// doing this operation atomically, but if not, a fallback is used.
-export fn mksubdir(fs: *fs, path: path::path) (*fs | error) = {
+export fn mksubdir(fs: *fs, path: str) (*fs | error) = {
return match (fs.mksubdir) {
null => {
mkdir(fs, path)?;
@@ -147,9 +142,9 @@ 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 = {
+// ../ sequences, roots the path, and returns a new path. The caller must free
+// the return value.
+export fn resolve(fs: *fs, path: str) str = {
match (fs.resolve) {
f: *resolvefunc => return f(fs, path),
null => void,
diff --git a/fs/types.ha b/fs/types.ha
@@ -113,31 +113,21 @@ 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::path,
+ name: str,
// The type of this entry. The permission bits may be unset.
ftype: mode,
};
// Duplicates a [dirent] object. Call [dirent_free] to get rid of it later.
-export fn dirent_dup(e: dirent) dirent = {
- let new = e;
- new.name = match (new.name) {
- s: str => strings::dup(s),
- b: []u8 => {
- let n: []u8 = [];
- append(n, ...b);
- n;
- },
- };
+export fn dirent_dup(e: *dirent) dirent = {
+ let new = *e;
+ new.name = strings::dup(e.name);
return new;
};
// Frees a [dirent] object which was duplicated with [dirent_dup].
-export fn dirent_free(e: dirent) void = match (e.name) {
- s: str => free(s),
- b: []u8 => free(b),
-};
+export fn dirent_free(e: *dirent) void = free(e.name);
// Flags to use for opening a file. Not all operating systems support all flags;
// at a minimum, RDONLY, WRONLY, RDWR, and CREATE will be supported.
@@ -161,24 +151,24 @@ export type flags = enum int {
};
export type closefunc = fn(fs: *fs) void;
-export type removefunc = fn(fs: *fs, path: path::path) (void | 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);
-export type mkdirfunc = fn(fs: *fs, path: path::path) (void | error);
-export type rmdirfunc = 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;
+export type removefunc = fn(fs: *fs, path: str) (void | error);
+export type iterfunc = fn(fs: *fs, path: str) (*iterator | error);
+export type statfunc = fn(fs: *fs, path: str) (filestat | error);
+export type subdirfunc = fn(fs: *fs, path: str) (*fs | error);
+export type mkdirfunc = fn(fs: *fs, path: str) (void | error);
+export type rmdirfunc = fn(fs: *fs, path: str) (void | error);
+export type mksubdirfunc = fn(fs: *fs, path: str) (*fs | error);
+export type resolvefunc = fn(fs: *fs, path: str) str;
export type openfunc = fn(
fs: *fs,
- path: path::path,
+ path: str,
flags: flags...
) (*io::stream | error);
export type createfunc = fn(
fs: *fs,
- path: path::path,
+ path: str,
mode: mode,
flags: flags...
) (*io::stream | error);
diff --git a/fs/util.ha b/fs/util.ha
@@ -78,12 +78,12 @@ 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::path) ([]dirent | error) = {
+export fn readdir(fs: *fs, path: str) ([]dirent | error) = {
let i = iter(fs, path)?;
let ents: []dirent = [];
for (true) {
match (next(i)) {
- d: dirent => append(ents, dirent_dup(d)),
+ d: dirent => append(ents, dirent_dup(&d)),
void => break,
};
};
@@ -93,6 +93,6 @@ export fn readdir(fs: *fs, path: path::path) ([]dirent | error) = {
// Frees a slice of [dirent]s.
export fn dirents_free(d: []dirent) void = {
for (let i = 0z; i < len(d); i += 1) {
- dirent_free(d[i]);
+ dirent_free(&d[i]);
};
};
diff --git a/hare/module/context.ha b/hare/module/context.ha
@@ -13,10 +13,10 @@ export type context = struct {
fs: *fs::fs,
// List of paths to search, generally populated from HAREPATH plus some
// baked-in default.
- paths: []path::path,
+ paths: []str,
// Path to the Hare cache, generally populated from HARECACHE and
// defaulting to $XDG_CACHE_HOME/hare.
- cache: path::path,
+ cache: str,
// Build tags to apply to this context.
tags: []tag,
};
@@ -26,9 +26,9 @@ export fn context_init(tags: []tag) context = {
let ctx = context {
fs = os::cwd,
tags = tags,
- paths: []path::path = match (os::getenv("HAREPATH")) {
+ paths: []str = match (os::getenv("HAREPATH")) {
void => {
- let path: []path::path = alloc([
+ let path: []str = alloc([
DEFAULT_HAREPATH,
dirs::data("hare"),
]);
@@ -36,7 +36,7 @@ export fn context_init(tags: []tag) context = {
},
s: str => {
let sl = strings::split(s, ":");
- let path: []path::path = alloc([], len(sl) + 1);
+ let path: []str = alloc([], len(sl) + 1);
let i = 0z;
for (i < len(sl); i += 1) {
append(path, sl[i]);
@@ -46,7 +46,7 @@ export fn context_init(tags: []tag) context = {
path;
},
},
- cache: path::path = match (os::getenv("HARECACHE")) {
+ cache: str = match (os::getenv("HARECACHE")) {
void => dirs::cache("hare"),
s: str => s,
},
@@ -57,11 +57,11 @@ export fn context_init(tags: []tag) context = {
// Converts an identifier to a partial path (e.g. foo::bar becomes foo/bar). The
// return value must be freed by the caller.
-export fn ident_path(name: ast::ident) path::path = {
+export fn ident_path(name: ast::ident) str = {
let p = path::join(name[0]);
for (let i = 1z; i < len(name); i += 1) {
let q = path::join(p, name[i]);
- path::path_free(p);
+ free(p);
p = q;
};
return p;
@@ -70,6 +70,6 @@ export fn ident_path(name: ast::ident) path::path = {
@test fn ident_path() void = {
let ident: ast::ident = ["foo", "bar", "baz"];
let p = ident_path(ident);
- defer path::path_free(p);
- assert(path::equal(p, "foo/bar/baz"));
+ defer free(p);
+ assert(p == "foo/bar/baz");
};
diff --git a/hare/module/scan.ha b/hare/module/scan.ha
@@ -15,7 +15,7 @@ use strio;
// Scans the files in a directory for eligible build inputs and returns a
// [version] which includes all applicable files and their dependencies.
-export fn scan(ctx: *context, path: path::path) (version | error) = {
+export fn scan(ctx: *context, path: str) (version | error) = {
let sha = sha256::sha256();
//defer! hash::close(sha);
let iter = match (fs::iter(ctx.fs, path)) {
@@ -39,7 +39,7 @@ export fn scan(ctx: *context, path: path::path) (version | error) = {
hash::write(sha, in.hash);
return version {
hash = hash::finish(sha),
- basedir = path::dup(path::dirname(path)),
+ basedir = strings::dup(path::dirname(path)),
depends = deps,
inputs = inputs,
};
@@ -48,7 +48,7 @@ export fn scan(ctx: *context, path: path::path) (version | error) = {
iter: *fs::iterator => iter,
};
let ver = version {
- basedir = path::dup(path),
+ basedir = strings::dup(path),
...
};
scan_directory(ctx, &ver, sha, path, iter)?;
@@ -60,7 +60,7 @@ fn scan_directory(
ctx: *context,
ver: *version,
sha: *hash::hash,
- path: path::path,
+ path: str,
iter: *fs::iterator,
) (void | error) = {
for (true) match (fs::next(iter)) {
@@ -68,9 +68,10 @@ fn scan_directory(
ent: fs::dirent => switch (ent.ftype) {
fs::mode::LINK => abort(), // TODO
fs::mode::DIR => {
- let d = ent.name: []u8;
- if (len(d) == 0 || (d[0] != '+': u32: u8
- && d[0] != '-': u32: u8)) {
+ let d = strings::to_utf8(ent.name);
+ if (len(d) == 0 || (
+ !strings::has_prefix(ent.name, "+") &&
+ !strings::has_prefix(ent.name, "-"))) {
continue;
};
if (!eligible(ctx, ent.name, true)) {
@@ -104,7 +105,7 @@ export fn lookup(ctx: *context, name: ast::ident) (version | error) = {
let ipath = ident_path(name);
for (let i = len(ctx.paths); i > 0; i -= 1) {
let cand = path::join(ctx.paths[i - 1], ipath);
- defer path::path_free(cand);
+ defer free(cand);
match (scan(ctx, cand)) {
v: version => return v,
e: error => void,
@@ -113,10 +114,9 @@ export fn lookup(ctx: *context, name: ast::ident) (version | error) = {
return module_not_found;
};
-fn eligible(ctx: *context, name: path::path, dir: bool) bool = {
- if (!utf8::valid(name: []u8)) {
- return false;
- };
+fn eligible(ctx: *context, name: str, dir: bool) bool = {
+ // TODO: Rewrite me to use string manipulation rather than byte slices
+ // This pre-dates the change from path as (str | []u8) to just str
if (!dir) {
let eligible = false;
const ext = path::extension(name);
@@ -134,7 +134,7 @@ fn eligible(ctx: *context, name: path::path, dir: bool) bool = {
// XXX: It might be nice if the stdlib offered search functions which
// support multiple needles
- let b = name: []u8;
+ let b = strings::to_utf8(name);
let p = bytes::index(b, '+': u32: u8);
let m = bytes::index(b, '-': u32: u8);
if (p is void && m is void) {
@@ -157,7 +157,7 @@ fn eligible(ctx: *context, name: path::path, dir: bool) bool = {
return tags_compat(ctx.tags, tags);
};
-fn type_for_ext(name: path::path) (filetype | void) = {
+fn type_for_ext(name: str) (filetype | void) = {
const ext = path::extension(name);
return
if (ext == ".ha") filetype::HARE
@@ -167,7 +167,7 @@ fn type_for_ext(name: path::path) (filetype | void) = {
fn scan_file(
ctx: *context,
- path: path::path,
+ path: str,
deps: *[]ast::ident,
) ([]u8 | error) = {
let f = fs::open(ctx.fs, path)?;
@@ -177,7 +177,7 @@ fn scan_file(
let tee = io::tee(f, hash::writer(sha));
defer io::close(tee);
- let lexer = lex::init(tee, path as str);
+ let lexer = lex::init(tee, path);
let imports = parse::imports(&lexer)?;
for (let i = 0z; i < len(imports); i += 1) {
let ident = match (imports[i]) {
diff --git a/hare/module/types.ha b/hare/module/types.ha
@@ -2,7 +2,6 @@ use fs;
use hare::ast;
use hare::parse;
use io;
-use path;
// The inclusive/exclusive state for a build tag.
export type tag_mode = enum {
@@ -26,7 +25,7 @@ export type manifest = struct {
// A module version: a set of possible input files for that module.
export type version = struct {
hash: []u8,
- basedir: path::path,
+ basedir: str,
depends: []ast::ident,
inputs: []input,
};
@@ -39,7 +38,7 @@ export type filetype = enum {
// An input to a module, generally a source file.
export type input = struct {
hash: []u8,
- path: path::path,
+ path: str,
ft: filetype,
stat: fs::filestat,
};
diff --git a/os/+linux/dirfdfs.ha b/os/+linux/dirfdfs.ha
@@ -92,7 +92,7 @@ export fn dirfs_clone(fs: *fs::fs, resolve: resolve...) *fs::fs = {
fn _fs_open(
fs: *fs::fs,
- path: path::path,
+ path: str,
mode: io::mode,
oh: *rt::open_how,
) (*io::stream | fs::error) = {
@@ -112,22 +112,17 @@ fn _fs_open(
oh.resolve |= rt::RESOLVE_NO_XDEV;
};
- const name: str = match (path) {
- s: str => s,
- b: []u8 => "<open([]u8)>",
- };
-
let fd = match (rt::openat2(fs.dirfd, path, oh, size(rt::open_how))) {
err: rt::errno => return errno_to_io(err),
fd: int => fd,
};
- return fdopen(fd, name, mode);
+ return fdopen(fd, path, mode);
};
fn fs_open(
fs: *fs::fs,
- path: path::path,
+ path: str,
flags: fs::flags...
) (*io::stream | fs::error) = {
let oflags = 0;
@@ -158,7 +153,7 @@ fn fs_open(
fn fs_create(
fs: *fs::fs,
- path: path::path,
+ path: str,
mode: fs::mode,
flags: fs::flags...
) (*io::stream | fs::error) = {
@@ -190,7 +185,7 @@ fn fs_create(
return _fs_open(fs, path, iomode, &oh);
};
-fn fs_remove(fs: *fs::fs, path: path::path) (void | fs::error) = {
+fn fs_remove(fs: *fs::fs, path: str) (void | fs::error) = {
let fs = fs: *os_filesystem;
match (rt::unlinkat(fs.dirfd, path, 0)) {
err: rt::errno => return errno_to_io(err),
@@ -198,7 +193,7 @@ fn fs_remove(fs: *fs::fs, path: path::path) (void | fs::error) = {
};
};
-fn fs_stat(fs: *fs::fs, path: path::path) (fs::filestat | fs::error) = {
+fn fs_stat(fs: *fs::fs, path: str) (fs::filestat | fs::error) = {
let fs = fs: *os_filesystem;
let st = rt::st { ... };
match (rt::fstatat(fs.dirfd, path, &st, rt::AT_SYMLINK_NOFOLLOW)) {
@@ -218,7 +213,7 @@ fn fs_stat(fs: *fs::fs, path: path::path) (fs::filestat | fs::error) = {
};
};
-fn fs_subdir(fs: *fs::fs, path: path::path) (*fs::fs | fs::error) = {
+fn fs_subdir(fs: *fs::fs, path: str) (*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,
@@ -234,7 +229,7 @@ fn fs_subdir(fs: *fs::fs, path: path::path) (*fs::fs | fs::error) = {
return dirfdopen(fd);
};
-fn fs_rmdir(fs: *fs::fs, path: path::path) (void | fs::error) = {
+fn fs_rmdir(fs: *fs::fs, path: str) (void | fs::error) = {
let fs = fs: *os_filesystem;
match (rt::unlinkat(fs.dirfd, path, rt::AT_REMOVEDIR)) {
err: rt::errno => return errno_to_io(err),
@@ -242,7 +237,7 @@ fn fs_rmdir(fs: *fs::fs, path: path::path) (void | fs::error) = {
};
};
-fn fs_mkdir(fs: *fs::fs, path: path::path) (void | fs::error) = {
+fn fs_mkdir(fs: *fs::fs, path: str) (void | fs::error) = {
let fs = fs: *os_filesystem;
return match (rt::mkdirat(fs.dirfd, path, 0o755)) {
err: rt::errno => switch (err) {
@@ -253,16 +248,11 @@ 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)) {
+fn resolve_part(parts: *[]str, part: str) void = {
+ if (part == ".") {
// no-op
void;
- } else if (bytes::equal(b, dotdot)) {
+ } else if (part == "..") {
// XXX: We should not have to dereference this
if (len(*parts) != 0) {
delete(parts[len(*parts) - 1]);
@@ -272,19 +262,19 @@ fn resolve_part(parts: *[]path::path, part: path::path) void = {
};
};
-fn fs_resolve(fs: *fs::fs, path: path::path) path::path = {
- let parts: []path::path = [];
+fn fs_resolve(fs: *fs::fs, path: str) str = {
+ let parts: []str = [];
if (!path::abs(path)) {
let iter = path::iter(getcwd());
for (true) match (path::next(&iter)) {
void => break,
- p: path::path => resolve_part(&parts, p),
+ p: str => resolve_part(&parts, p),
};
};
let iter = path::iter(path);
for (true) match (path::next(&iter)) {
void => break,
- p: path::path => resolve_part(&parts, p),
+ p: str => resolve_part(&parts, p),
};
return path::join(parts...);
};
@@ -305,7 +295,7 @@ type os_iterator = struct {
buf: [BUFSIZ]u8,
};
-fn fs_iter(fs: *fs::fs, path: path::path) (*fs::iterator | fs::error) = {
+fn fs_iter(fs: *fs::fs, path: str) (*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,
@@ -346,13 +336,7 @@ fn iter_next(iter: *fs::iterator) (fs::dirent | void) = {
};
let de = &iter.buf[iter.buf_pos]: *rt::dirent64;
iter.buf_pos += de.d_reclen;
- let ln = strings::c_strlen(&de.d_name: *const char);
- let name = &de.d_name: *[*]u8;
- let name = if (!utf8::valid(name[..ln])) {
- name[..ln];
- } else {
- strings::from_utf8_unsafe(name[..ln]);
- };
+ let name = strings::from_c(&de.d_name: *const char);
let ftype: fs::mode = switch (de.d_type) {
rt::DT_UNKNOWN => fs::mode::UNKNOWN,
diff --git a/os/+linux/fs.ha b/os/+linux/fs.ha
@@ -17,11 +17,11 @@ use strings;
// 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);
+export fn getcwd() str = 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) {
+export fn chdir(target: (*fs::fs | str)) (void | fs::error) = {
+ const path: str = match (target) {
fs: *fs::fs => {
assert(fs.open == &fs_open);
let fs = fs: *os_filesystem;
@@ -30,10 +30,9 @@ export fn chdir(target: (*fs::fs | ...path::path)) (void | fs::error) = {
void => void,
};
},
- s: str => strings::to_utf8(s),
- b: []u8 => b,
+ s: str => s,
};
- return match (rt::chdir(path: *[*]u8: *const char)) {
+ return match (rt::chdir(path)) {
err: rt::errno => errno_to_io(err): fs::error,
void => void,
};
@@ -43,12 +42,8 @@ export fn chdir(target: (*fs::fs | ...path::path)) (void | fs::error) = {
// have root or otherwise elevated permissions.
//
// This function is not appropriate for sandboxing.
-export fn chroot(target: path::path) (void | fs::error) = {
- const path: []u8 = match (target) {
- s: str => strings::to_utf8(s),
- b: []u8 => b,
- };
- return match (rt::chroot(path: *[*]u8: *const char)) {
+export fn chroot(target: str) (void | fs::error) = {
+ return match (rt::chroot(target)) {
err: rt::errno => errno_to_io(err): fs::error,
void => void,
};
diff --git a/os/+linux/open.ha b/os/+linux/open.ha
@@ -8,10 +8,8 @@ use path;
// [fs::flags::CLOEXEC] are used when opening the file. If you pass your own
// flags, it is recommended that you add the latter two unless you know that you
// do not want them.
-export fn open(
- path: path::path,
- flags: fs::flags...
-) (*io::stream | fs::error) = fs::open(cwd, path, flags...);
+export fn open(path: str, flags: fs::flags...) (*io::stream | fs::error) =
+ fs::open(cwd, path, flags...);
// Creates a new file and opens it for writing.
//
@@ -23,7 +21,7 @@ export fn open(
// Only the permission bits of the mode are used. If other bits are set, they
// are discarded.
export fn create(
- path: path::path,
+ path: str,
mode: fs::mode,
flags: fs::flags...
) (*io::stream | fs::error) = fs::create(cwd, path, mode, flags...);
diff --git a/os/fs.ha b/os/fs.ha
@@ -8,38 +8,34 @@ export let root: *fs::fs = null: *fs::fs;
export let cwd: *fs::fs = null: *fs::fs;
// Removes a file.
-export fn remove(path: path::path) (void | fs::error) = fs::remove(cwd, path);
+export fn remove(path: str) (void | fs::error) = fs::remove(cwd, path);
// Creates an [fs::iterator] for a given directory to read its contents.
-export fn iterdir(path: path::path) (*fs::iterator | fs::error) =
- fs::iter(cwd, path);
+export fn iterdir(path: str) (*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: path::path) ([]fs::dirent | fs::error) =
- fs::readdir(cwd, path);
+export fn readdir(path: str) ([]fs::dirent | fs::error) = fs::readdir(cwd, path);
// Returns file information for a given path.
-export fn stat(path: path::path) (fs::filestat | fs::error) = fs::stat(cwd, path);
+export fn stat(path: str) (fs::filestat | fs::error) = fs::stat(cwd, path);
// Opens a directory as a filesystem.
-export fn diropen(path: path::path) (*fs::fs | fs::error) = fs::subdir(cwd, path);
+export fn diropen(path: str) (*fs::fs | fs::error) = fs::subdir(cwd, path);
// Creates a directory.
-export fn mkdir(path: path::path) (void | fs::error) = fs::mkdir(cwd, path);
+export fn mkdir(path: str) (void | fs::error) = fs::mkdir(cwd, path);
// Creates a directory, and all non-extant directories in its path.
-export fn mkdirs(path: path::path) (void | fs::error) = fs::mkdirs(cwd, path);
+export fn mkdirs(path: str) (void | fs::error) = fs::mkdirs(cwd, path);
// Removes a directory. The target directory must be empty; see [rmdirall] to
// remove its contents as well.
-export fn rmdir(path: path::path) (void | fs::error) = fs::rmdir(cwd, path);
+export fn rmdir(path: str) (void | fs::error) = fs::rmdir(cwd, path);
// Removes a directory, and anything in it.
-export fn rmdirall(path: path::path) (void | fs::error) =
- fs::rmdirall(cwd, path);
+export fn rmdirall(path: str) (void | fs::error) = fs::rmdirall(cwd, path);
// Creates a directory and returns a subdir for it. Some filesystems support
// doing this operation atomically, but if not, a fallback is used.
-export fn mksubdir(path: path::path) (*fs::fs | fs::error) =
- fs::mksubdir(cwd, path);
+export fn mksubdir(path: str) (*fs::fs | fs::error) = fs::mksubdir(cwd, path);
diff --git a/path/iter.ha b/path/iter.ha
@@ -1,12 +1,9 @@
use bytes;
use strings;
-const pathsep: []u8 = [PATHSEP];
-
export type iflags = enum uint {
NONE = 0,
- STRING = 1 << 0,
- ABSOLUTE = 1 << 1,
+ ABSOLUTE = 1 << 0,
};
// An iterator which yields each component of a path.
@@ -15,15 +12,13 @@ export type iterator = struct {
flags: iflags,
};
+let pathsep: []u8 = [PATHSEP];
+
// Returns an iterator which yields each component of a path. If the path is
// absolute, the first component will be the root path (e.g. /).
-export fn iter(path: path) iterator = {
+export fn iter(path: str) iterator = {
let flags = iflags::NONE;
- if (path is str) {
- flags |= iflags::STRING;
- };
-
- let pb = pathbytes(path);
+ let pb = strings::to_utf8(path);
if (len(pb) > 0 && pb[0] == PATHSEP) {
flags |= iflags::ABSOLUTE;
pb = pb[1..];
@@ -39,38 +34,36 @@ export fn iter(path: path) iterator = {
};
// Returns the next path component from an iterator, or void if none remain.
-export fn next(iter: *iterator) (path | void) = {
+export fn next(iter: *iterator) (str | void) = {
if (iter.flags & iflags::ABSOLUTE == iflags::ABSOLUTE) {
iter.flags &= ~iflags::ABSOLUTE;
static assert(PATHSEP <= 0x7F);
return strings::from_utf8_unsafe(pathsep);
};
return match (bytes::next_token(&iter.tok)) {
+ b: []u8 => strings::from_utf8_unsafe(b),
void => void,
- b: []u8 => if (iter.flags & iflags::STRING == iflags::STRING)
- strings::from_utf8_unsafe(b)
- else b,
};
};
@test fn iter() void = {
assert(PATHSEP == '/': u32: u8); // meh
let i = iter("/foo/bar/baz");
- assert(equal(next(&i) as path, "/"));
- assert(equal(next(&i) as path, "foo"));
- assert(equal(next(&i) as path, "bar"));
- assert(equal(next(&i) as path, "baz"));
+ assert(next(&i) as str == "/");
+ assert(next(&i) as str == "foo");
+ assert(next(&i) as str == "bar");
+ assert(next(&i) as str == "baz");
assert(next(&i) is void);
let i = iter("foo/bar/baz/");
- assert(equal(next(&i) as path, "foo"));
- assert(equal(next(&i) as path, "bar"));
- assert(equal(next(&i) as path, "baz"));
+ assert(next(&i) as str == "foo");
+ assert(next(&i) as str == "bar");
+ assert(next(&i) as str == "baz");
assert(next(&i) is void);
let i = iter("foo");
- assert(equal(next(&i) as path, "foo"));
+ assert(next(&i) as str == "foo");
assert(next(&i) is void);
let i = iter("/");
- assert(equal(next(&i) as path, "/"));
+ assert(next(&i) as str == "/");
assert(next(&i) is void);
};
diff --git a/path/join.ha b/path/join.ha
@@ -4,16 +4,14 @@ 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 = {
+// must free the return value.
+export fn join(paths: str...) str = {
// TODO: Normalize inputs so that if they end with a / we don't double
// up on delimiters
let sink = bufio::dynamic(io::mode::WRITE);
let utf8 = true;
for (let i = 0z; i < len(paths); i += 1) {
- utf8 = utf8 && paths[i] is str;
-
- let buf = pathbytes(paths[i]);
+ let buf = strings::to_utf8(paths[i]);
let l = len(buf);
if (l == 0) continue;
for (l > 0 && buf[l - 1] == PATHSEP) {
@@ -28,42 +26,40 @@ export fn join(paths: path...) path = {
};
};
- return
- if (utf8) strings::from_utf8_unsafe(bufio::finish(sink))
- else bufio::finish(sink);
+ return strings::from_utf8_unsafe(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");
+ defer free(i);
+ assert(i == "foo");
let p = join(i, "bar", "baz");
- defer path_free(p);
- assert(p as str == "foo/bar/baz");
+ defer free(p);
+ assert(p == "foo/bar/baz");
let q = join(p, "bat", "bad");
- defer path_free(q);
- assert(q as str == "foo/bar/baz/bat/bad");
+ defer free(q);
+ assert(q == "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");
+ defer free(r);
+ assert(r == "foo/bar/baz/foo/bar/baz/bat/bad");
let p = join("foo/", "bar");
- defer path_free(p);
- assert(p as str == "foo/bar");
+ defer free(p);
+ assert(p == "foo/bar");
let p = join("foo///", "bar");
- defer path_free(p);
- assert(p as str == "foo/bar");
+ defer free(p);
+ assert(p == "foo/bar");
let p = join("foo", "", "bar");
- defer path_free(p);
- assert(p as str == "foo/bar");
+ defer free(p);
+ assert(p == "foo/bar");
let p = join("/", "foo", "bar", "baz");
- defer path_free(p);
- assert(p as str == "/foo/bar/baz");
+ defer free(p);
+ assert(p == "/foo/bar/baz");
};
diff --git a/path/names.ha b/path/names.ha
@@ -6,8 +6,8 @@ use strings;
// returns the directory in which that file resides. For a path to a directory,
// this returns the path to its parent directory. The return value is borrowed
// from the input, use [dup] to extend its lifetime.
-export fn dirname(path: path::path) path::path = {
- let b = pathbytes(normalize(path));
+export fn dirname(path: str) str = {
+ let b = strings::to_utf8(path);
let i = match (bytes::rindex(b, PATHSEP)) {
void => return path,
z: size => z,
@@ -15,76 +15,58 @@ export fn dirname(path: path::path) path::path = {
if (i == 0) {
i += 1;
};
- return
- if (path is str) strings::from_utf8_unsafe(b[..i])
- else b[..i];
+ return strings::from_utf8_unsafe(b[..i]);
};
@test fn dirname() void = {
- let p: path = "/foo/bar";
- assert(dirname(p) as str == "/foo");
- let p: path = "/foo";
- assert(dirname(p) as str == "/");
- let p: path = "/";
- assert(dirname(p) as str == "/");
- let p: path = "foo/bar";
- assert(dirname(p) as str == "foo");
- let p: path = "foo";
- assert(dirname(p) as str == "foo");
+ assert(dirname("/foo/bar") == "/foo");
+ assert(dirname("/foo") == "/");
+ assert(dirname("/") == "/");
+ assert(dirname("foo/bar") == "foo");
+ assert(dirname("foo") == "foo");
};
// Returns the final component of a given path. For a path to a file name, this
// returns the file name. For a path to a directory, this returns the directory
// name. The return value is borrowed from the input, use [dup] to extend its
// lifetime.
-export fn basename(path: path::path) path::path = {
- let b = pathbytes(normalize(path));
+export fn basename(path: str) str = {
+ let b = strings::to_utf8(path);
let i = match (bytes::rindex(b, PATHSEP)) {
void => return path,
z: size => if (z + 1 < len(b)) z + 1z else 0z,
};
- return
- if (path is str) strings::from_utf8_unsafe(b[i..])
- else b[i..];
+ return strings::from_utf8_unsafe(b[i..]);
};
@test fn basename() void = {
- let p: path = "/foo/bar";
- assert(basename(p) as str == "bar");
- let p: path = "/foo";
- assert(basename(p) as str == "foo");
- let p: path = "/";
- assert(basename(p) as str == "/");
- let p: path = "foo/bar";
- assert(basename(p) as str == "bar");
- let p: path = "foo";
- assert(basename(p) as str == "foo");
+ assert(basename("/foo/bar") == "bar");
+ assert(basename("/foo") == "foo");
+ assert(basename("/") == "/");
+ assert(basename("foo/bar") == "bar");
+ assert(basename("foo") == "foo");
};
-// Returns the file extension for a path. This presumes that the file extension
-// is a valid UTF-8 string, if not, it will return an empty string. The return
-// value is borrowed from the input, see [strings::dup] to extend its lifetime.
+// Returns the file extension for a path. The return value is borrowed from the
+// input, see [strings::dup] to extend its lifetime.
//
// The return value includes the '.' character.
//
// extension("foo/example") => ""
// extension("foo/example.txt") => ".txt"
// extension("foo/example.tar.gz") => ".tar.gz"
-export fn extension(p: path::path) str = {
- let b = pathbytes(p);
+export fn extension(p: str) str = {
+ let b = strings::to_utf8(p);
if (len(b) == 0 || b[len(b) - 1] == PATHSEP) {
return "";
};
- let b = pathbytes(basename(p));
+ let b = strings::to_utf8(basename(p));
let i = match (bytes::index(b, '.': u32: u8)) {
void => return "",
z: size => z,
};
let e = b[i..];
- return match (strings::try_from_utf8(e)) {
- utf8::invalid => "",
- s: str => s,
- };
+ return strings::from_utf8_unsafe(e);
};
@test fn extension() void = {
diff --git a/path/norm.ha b/path/norm.ha
@@ -1,6 +0,0 @@
-// Normalizes a path, merging successive path separators, and resolving . and ..
-// components. Modifies the path in-place and returns the same object.
-export fn normalize(p: path::path) path::path = {
- // TODO
- return p;
-};
diff --git a/path/util.ha b/path/util.ha
@@ -1,38 +1,8 @@
-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),
-};
-
-// Duplicates a path.
-export fn dup(p: path) path = match (p) {
- s: str => strings::dup(s),
- b: []u8 => {
- // TODO: alloc from another slice
- let new: []u8 = alloc([], len(b));
- append(new, ...b);
- new;
- },
-};
-
-// Returns true if two paths are equal.
-export fn equal(a: path, b: path) bool =
- bytes::equal(pathbytes(a), pathbytes(b));
-
// Returns true if a path is an absolute path.
-export fn abs(path: path::path) bool = {
- let b = pathbytes(path);
+export fn abs(path: str) bool = {
+ let b = strings::to_utf8(path);
if (len(b) == 0) {
return false;
};
diff --git a/temp/+linux.ha b/temp/+linux.ha
@@ -5,7 +5,7 @@ use io;
use os;
use path;
-fn get_tmpdir() path::path = os::tryenv("TMPDIR", "/tmp");
+fn get_tmpdir() str = os::tryenv("TMPDIR", "/tmp");
// Creates an unnamed temporary file. The file may or may not have a name; not
// all systems support the creation of temporary inodes which are not linked to
@@ -53,8 +53,8 @@ export fn named(
// but not that it will be removed automatically; the caller must remove it when
// they're done using it via [os::rmdir] or [os::rmdirall].
//
-// The caller must free the return value with [path::path_free].
-export fn dir() path::path = {
+// The caller must free the return value.
+export fn dir() str = {
let buf: [8]u8 = [0...];
random::buffer(buf[..]);
let name = hex::encode(buf);