hare

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

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:
Mfs/fs.ha | 22++++++++++++++++++++++
Mfs/types.ha | 11+++++++++++
Mfs/util.ha | 2++
Mos/+linux/dirfdfs.ha | 9+++++++++
Mos/fs.ha | 11+++++++++++
Mrt/+linux/syscalls.ha | 33+++++++++++++++++++++++++++------
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; };