commit 887c7c8b6e038e7aa0470a6e33ac878b37ed29fb
parent 55d0d59d33a8b6f0b699253f8a9efe553fe6ad65
Author: Drew DeVault <sir@cmpwn.com>
Date: Tue, 22 Jun 2021 14:25:35 -0400
fs: add fs::rename, fs::move
fs::move is not done yet; missing a fallback implementation.
Signed-off-by: Drew DeVault <sir@cmpwn.com>
Diffstat:
6 files changed, 82 insertions(+), 6 deletions(-)
diff --git a/fs/fs.ha b/fs/fs.ha
@@ -45,6 +45,28 @@ export fn remove(fs: *fs, path: str) (void | error) = {
};
};
+// Renames a file. This generally only works if the source and destination path
+// are both on the same filesystem. See [[move]] for an implementation which
+// falls back on a "copy & remove" procedure in this situation.
+export fn rename(fs: *fs, oldpath: str, newpath: str) (void | error) = {
+ return match (fs.rename) {
+ null => errors::unsupported,
+ f: *renamefunc => f(fs, oldpath, newpath),
+ };
+};
+
+// Moves a file. This will use [[rename]] if possible, and will fall back to
+// copy and remove if necessary.
+export fn move(fs: *fs, oldpath: str, newpath: str) (void | error) = {
+ match (rename(fs, oldpath, newpath)) {
+ cannotrename => void, // Fallback
+ errors::unsupported => void, // Fallback
+ void => return, // Success
+ err: error => return err,
+ };
+ abort(); // TODO: Fallback
+};
+
// 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.
diff --git a/fs/types.ha b/fs/types.ha
@@ -8,6 +8,12 @@ use time;
// For example, opening a file with [[iter]].
export type wrongtype = !void;
+// Returned from [[rename]] if this rename is not possible due to technical
+// constraints, such as if it would cause a file to move between filesystems. In
+// this situation, other operations (such as copy & remove) may succeed if
+// attempted.
+export type cannotrename = !void;
+
// All possible fs error types.
export type error = !(
errors::noentry |
@@ -16,6 +22,7 @@ export type error = !(
errors::busy |
errors::invalid |
wrongtype |
+ cannotrename |
io::error);
// File mode information. These bits do not necessarily reflect the underlying
@@ -159,6 +166,7 @@ export type flags = enum int {
export type closefunc = fn(fs: *fs) void;
export type removefunc = fn(fs: *fs, path: str) (void | error);
+export type renamefunc = fn(fs: *fs, oldpath: str, newpath: 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);
@@ -198,6 +206,9 @@ export type fs = struct {
// Removes a file.
remove: nullable *removefunc,
+ // Renames a file.
+ rename: nullable *renamefunc,
+
// Returns an iterator for a path, which yields the contents of a
// directory. Pass empty string to yield from the root.
//
diff --git a/fs/util.ha b/fs/util.ha
@@ -5,6 +5,8 @@ use strings;
// Returns a human-friendly representation of an error.
export fn strerror(err: error) const str = match (err) {
+ wrongtype => "Wrong entry type for requested operation",
+ cannotrename => "Unable to perform rename operation (try move instead)",
errors::noentry => "File or directory not found",
errors::noaccess => "Permission denied",
errors::exists => "File or directory exists",
diff --git a/os/+linux/dirfdfs.ha b/os/+linux/dirfdfs.ha
@@ -66,6 +66,7 @@ fn static_dirfdopen(fd: int, filesystem: *os_filesystem) *fs::fs = {
open = &fs_open,
create = &fs_create,
remove = &fs_remove,
+ rename = &fs_rename,
iter = &fs_iter,
stat = &fs_stat,
subdir = &fs_subdir,
@@ -213,6 +214,14 @@ fn fs_remove(fs: *fs::fs, path: str) (void | fs::error) = {
};
};
+fn fs_rename(fs: *fs::fs, oldpath: str, newpath: str) (void | fs::error) = {
+ let fs = fs: *os_filesystem;
+ match (rt::renameat(fs.dirfd, oldpath, fs.dirfd, newpath, 0)) {
+ err: rt::errno => return errno_to_fs(err),
+ void => void,
+ };
+};
+
fn fs_stat(fs: *fs::fs, path: str) (fs::filestat | fs::error) = {
let fs = fs: *os_filesystem;
let st = rt::st { ... };
diff --git a/os/fs.ha b/os/fs.ha
@@ -8,6 +8,17 @@ export let cwd: *fs::fs = null: *fs::fs;
// Removes a file.
export fn remove(path: str) (void | fs::error) = fs::remove(cwd, path);
+// Renames a file. This generally only works if the source and destination path
+// are both on the same filesystem. See [[move]] for an implementation which
+// falls back on a "copy & remove" procedure in this situation.
+export fn rename(oldpath: str, newpath: str) (void | fs::error) =
+ fs::rename(cwd, oldpath, newpath);
+
+// Moves a file. This will use [[rename]] if possible, and will fall back to
+// copy and remove if necessary.
+export fn move(oldpath: str, newpath: str) (void | fs::error) =
+ fs::move(cwd, oldpath, newpath);
+
// Creates an [[fs::iterator]] for a given directory to read its contents.
export fn iter(path: str) (*fs::iterator | fs::error) = fs::iter(cwd, path);
diff --git a/rt/+linux/syscalls.ha b/rt/+linux/syscalls.ha
@@ -10,9 +10,7 @@ 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) = {
+fn copy_kpath(path: path, buf: []u8) (*const char | errno) = {
let path = match (path) {
c: *const char => return c,
s: str => {
@@ -28,9 +26,15 @@ fn kpath(path: path) (*const char | errno) = {
if (len(path) + 1 >= len(pathbuf)) {
return ENAMETOOLONG;
};
- memcpy(&pathbuf, path: *[*]u8, len(path));
- pathbuf[len(path)] = 0;
- return &pathbuf: *const char;
+ memcpy(buf: *[*]u8, path: *[*]u8, len(path));
+ buf[len(path)] = 0;
+ return buf: *[*]u8: *const char;
+};
+
+// 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) = {
+ return copy_kpath(path, pathbuf);
};
export fn read(fd: int, buf: *void, count: size) (size | errno) = {
@@ -121,6 +125,23 @@ export fn fchownat(dirfd: int, path: path, uid: uint, gid: uint, flags: int) (vo
return;
};
+export fn renameat(
+ olddirfd: int,
+ oldpath: str,
+ newdirfd: int,
+ newpath: str,
+ flags: uint,
+) (void | errno) = {
+ let oldpath = kpath(oldpath)?;
+ static let newpathbuf: [PATH_MAX + 1]u8 = [0...];
+ let newpath = copy_kpath(newpath, newpathbuf)?;
+ wrap_return(syscall5(SYS_renameat2,
+ olddirfd: u64, oldpath: uintptr: u64,
+ newdirfd: u64, newpath: uintptr: u64,
+ flags: u64))?;
+ return;
+};
+
export fn dup(fd: int) (int | errno) = {
return wrap_return(syscall1(SYS_dup, fd: u64))?: int;
};