summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--man/duplex.256
-rw-r--r--src/cmd/mv.c17
-rw-r--r--src/cmd/tmpfs.c71
-rw-r--r--src/kernel/handle.h1
-rw-r--r--src/kernel/syscalls.c32
-rw-r--r--src/kernel/vfs/request.c1
-rw-r--r--src/kernel/vfs/request.h4
-rw-r--r--src/libc/include/stdio.h2
-rw-r--r--src/libc/stdio/misc.c30
-rw-r--r--src/libc/syscall.c4
-rw-r--r--src/libk/include/camellia/flags.h2
-rw-r--r--src/libk/include/camellia/syscalls.h3
-rw-r--r--src/libk/include/camellia/types.h3
13 files changed, 215 insertions, 11 deletions
diff --git a/man/duplex.2 b/man/duplex.2
new file mode 100644
index 0000000..192e72c
--- /dev/null
+++ b/man/duplex.2
@@ -0,0 +1,56 @@
+.Dd Jul 17, 2024
+.Dt DUPLEX 2
+.Os Camellia
+.Sh NAME
+.Nm duplex
+.Nd rename a file
+.Sh SYNOPSIS
+.In camellia/syscalls.h
+.Ft long
+.Fn _sys_duplex "hid_t from" "hid_t to" "int flags"
+.Sh DESCRIPTION
+.Nm
+is used for operations on two files or locations within the same filesystem
+\(em renames, links, etc.
+.Fa flags
+is a bitmask with the following options:
+.Bl -tag -width DUPLEX_REMOVE
+.It Dv DUPLEX_REMOVE
+Remove the source file after the operation \(em in other words, perform a move,
+instead of creating a new link.
+.El
+.Sh SEE ALSO
+.Xr open 2 ,
+.Xr rename 3
+.Sh BUGS
+.Xr rename 3 isn't atomic,
+as the destination file needs to be created before the duplex call.
+This could be fixed by having
+.Xr open 2
+accept a (hypothetical)
+.Dv OPEN_MOVETARGET
+flag.
+.Pp
+There are some cases where a
+.Nm
+call across different filesystems fails despite "making sense".
+.Sh RATIONALE
+Accepting two handles is the only sensible way to handle moving files,
+considering
+.Xr open 2 Ns 's
+role as a
+.Dq narrow waist .
+Having a generic
+.Nm
+call for all operations on two handles within a single filesystem seems to
+make sense.
+.Pp
+One other similar approach would be to have a way to send arbitrary handles
+to open files
+.Pq which I already plan to implement ,
+and to also have a syscall that can
+.Dq unwrap
+a handle controlled by the current process.
+Then that syscall would effectively be able to do everything
+.Nm
+can \(em but that seems more complex and awkward to use.
diff --git a/src/cmd/mv.c b/src/cmd/mv.c
new file mode 100644
index 0000000..e7bcc46
--- /dev/null
+++ b/src/cmd/mv.c
@@ -0,0 +1,17 @@
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+
+int
+main(int argc, const char *argv[])
+{
+ if (argc != 3) {
+ fprintf(stderr, "usage: mv from to\n");
+ return 1;
+ }
+
+ if (rename(argv[1], argv[2]) < 0) {
+ fprintf(stderr, "mv: %s\n", strerror(errno));
+ return 1;
+ }
+}
diff --git a/src/cmd/tmpfs.c b/src/cmd/tmpfs.c
index 6d58790..a031a44 100644
--- a/src/cmd/tmpfs.c
+++ b/src/cmd/tmpfs.c
@@ -1,3 +1,4 @@
+#include <assert.h>
#include <camellia/flags.h>
#include <camellia/fs/dir.h>
#include <camellia/fs/misc.h>
@@ -38,8 +39,10 @@ static struct node *tmpfs_open(const char *path, struct ufs_request *req);
static struct node *node_getchild(struct node *parent, const char *name, size_t len);
/** Corresponds to close(); drops a reference. */
static void node_close(struct node *node);
+static long node_move(struct node *from, struct node *to);
/** Removes a file. It's kept in memory until all the open handles are closed. */
static long node_remove(struct node *node);
+static void node_sanity(struct node *node, struct node **from);
static struct node *node_getchild(struct node *parent, const char *name, size_t len) {
@@ -100,27 +103,84 @@ static struct node *tmpfs_open(const char *path, struct ufs_request *req) {
static void node_close(struct node *node) {
node->open--;
- if (!node->ref && node != &node_root && node->open == 0) {
+ if (node->ref == NULL && node != &node_root && node->open == 0) {
+ if (node->name) {
+ free(node->name);
+ }
free(node->buf);
free(node);
}
}
+static long node_move(struct node *from, struct node *to) {
+ if (from == &node_root || to == &node_root) {
+ return -1;
+ }
+
+ assert(from && to);
+ /* 1. unlink from */
+ if (from->ref) {
+ assert(*from->ref == from);
+ if (from->sibling) {
+ from->sibling->ref = from->ref;
+ *from->sibling->ref = from->sibling;
+ } else {
+ *from->ref = NULL;
+ }
+ from->sibling = NULL;
+ from->ref = NULL;
+ } else {
+ assert(from->sibling == NULL);
+ }
+
+ /* 2. link *from in place of *to
+ * note that there's still a reference to *to in the handle */
+ if (to->ref) {
+ assert(*to->ref == to);
+ from->sibling = to->sibling;
+ if (from->sibling) {
+ from->sibling->ref = &from->sibling;
+ }
+ from->ref = to->ref;
+ *from->ref = from;
+
+ to->ref = NULL;
+ to->sibling = NULL;
+ }
+ from->name = to->name;
+ from->namelen = to->namelen;
+ to->name = NULL;
+ to->namelen = 0;
+ return 0;
+}
+
static long node_remove(struct node *node) {
if (node == &node_root) return -1;
if (!node->ref) return -1;
if (node->child) return -ENOTEMPTY;
*node->ref = node->sibling;
node->ref = NULL;
+ node->sibling = NULL;
node_close(node);
return 0;
}
+static void node_sanity(struct node *node, struct node **from) {
+ if (node == NULL) return;
+ if (node != &node_root && node->ref != from) {
+ assert(false);
+ }
+ node_sanity(node->sibling, &node->sibling);
+ node_sanity(node->child, &node->child);
+}
+
int main(void) {
const size_t buflen = 4096;
char *buf = malloc(buflen);
if (!buf) return -1;
+ node_sanity(&node_root, NULL);
+
for (;;) {
struct ufs_request req;
hid_t reqh = ufs_wait(buf, buflen, &req);
@@ -189,10 +249,19 @@ int main(void) {
_sys_fs_respond(reqh, NULL, -1, 0);
break;
+ case VFSOP_DUPLEX:
+ if (req.flags == DUPLEX_REMOVE) {
+ _sys_fs_respond(reqh, NULL, node_move(req.id, req.id2), 0);
+ } else {
+ _sys_fs_respond(reqh, NULL, -ENOSYS, 0);
+ }
+ break;
+
default:
_sys_fs_respond(reqh, NULL, -1, 0);
break;
}
+ node_sanity(&node_root, NULL);
}
return 1;
}
diff --git a/src/kernel/handle.h b/src/kernel/handle.h
index af9d45a..abbfa68 100644
--- a/src/kernel/handle.h
+++ b/src/kernel/handle.h
@@ -19,6 +19,7 @@ struct Handle {
enum handle_type type;
VfsBackend *backend; /* HANDLE_FILE | HANDLE_FS_FRONT */
void __user *file_id; /* only applicable to HANDLE_FILE */
+ // TODO store flags instead
bool readable, writeable; /* HANDLE_FILE | HANDLE_PIPE */
VfsReq *req; /* HANDLE_FS_REQ */
struct {
diff --git a/src/kernel/syscalls.c b/src/kernel/syscalls.c
index 3fc09fa..23a8883 100644
--- a/src/kernel/syscalls.c
+++ b/src/kernel/syscalls.c
@@ -456,6 +456,37 @@ hid_t _sys_getnull(int flags) {
SYSCALL_RETURN((0 <= hid) ? hid : -EMFILE);
}
+long _sys_duplex(hid_t from, hid_t to, int flags) {
+ if ((flags & ~DUPLEX_REMOVE) != 0) {
+ SYSCALL_RETURN(-ENOSYS);
+ }
+
+ Handle *fromh = hs_get(proc_cur->hs, from);
+ Handle *toh = hs_get(proc_cur->hs, to);
+ if (fromh == NULL || toh == NULL) {
+ SYSCALL_RETURN(-EBADF);
+ }
+ if (fromh->type != HANDLE_FILE || toh->type != HANDLE_FILE) {
+ SYSCALL_RETURN(-ENOSYS);
+ }
+ if (fromh->backend != toh->backend) {
+ SYSCALL_RETURN(-ENOSYS);
+ }
+ if (!fromh->readable || !fromh->writeable || !toh->writeable) {
+ SYSCALL_RETURN(-EACCES);
+ }
+
+ vfsreq_dispatchcopy((VfsReq) {
+ .type = VFSOP_DUPLEX,
+ .id = fromh->file_id,
+ .id2 = toh->file_id,
+ .flags = flags,
+ .caller = proc_cur,
+ .backend = fromh->backend,
+ });
+ return -1; // dummy
+}
+
long _sys_execbuf(void __user *ubuf, size_t len) {
if (len == 0) SYSCALL_RETURN(0);
static_assert(EXECBUF_MAX_LEN <= KMALLOC_MAX);
@@ -512,6 +543,7 @@ long _syscall(long num, long a, long b, long c, long d, long e) {
break; case _SYS_GETPROCFS: _sys_getprocfs(a);
break; case _SYS_TIME: _sys_time(a);
break; case _SYS_GETNULL: _sys_getnull(a);
+ break; case _SYS_DUPLEX: _sys_duplex(a, b, c);
break; case _SYS_EXECBUF: _sys_execbuf((userptr_t)a, b);
break; case _SYS_DEBUG_KLOG: _sys_debug_klog((userptr_t)a, b);
break;
diff --git a/src/kernel/vfs/request.c b/src/kernel/vfs/request.c
index 92b904f..3f6fda7 100644
--- a/src/kernel/vfs/request.c
+++ b/src/kernel/vfs/request.c
@@ -166,6 +166,7 @@ vfsback_useraccept(VfsReq *req)
res.len = len;
res.capacity = req->output.len;
res.id = req->id;
+ res.id2 = req->id2;
res.offset = req->offset;
res.flags = req->flags;
res.op = req->type;
diff --git a/src/kernel/vfs/request.h b/src/kernel/vfs/request.h
index 45ec687..d6facfd 100644
--- a/src/kernel/vfs/request.h
+++ b/src/kernel/vfs/request.h
@@ -50,9 +50,7 @@ struct VfsReq {
size_t len;
} output;
- // TODO why doesn't this just have a reference to the handle?
-
- void __user *id; // handle.file.id
+ void __user *id, *id2; // handle.file.id
long offset;
int flags;
diff --git a/src/libc/include/stdio.h b/src/libc/include/stdio.h
index 159bdca..d533025 100644
--- a/src/libc/include/stdio.h
+++ b/src/libc/include/stdio.h
@@ -83,7 +83,7 @@ int putchar(int c);
off_t lseek(int fd, off_t off, int whence);
int remove(const char *path);
-int rename(const char *old, const char *new);
+int rename(const char *oldpath, const char *newpath);
#define L_tmpnam (5 + 16 + 1)
char *tmpnam(char *s);
diff --git a/src/libc/stdio/misc.c b/src/libc/stdio/misc.c
index 7e8e746..6cd5b35 100644
--- a/src/libc/stdio/misc.c
+++ b/src/libc/stdio/misc.c
@@ -1,3 +1,6 @@
+#include <camellia.h>
+#include <camellia/flags.h>
+#include <camellia/syscalls.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
@@ -30,11 +33,28 @@ int remove(const char *path) {
return unlink(path);
}
-// TODO! VFSOP_MOVE
-int rename(const char *old, const char *new) {
- (void)old; (void)new;
- errno = ENOSYS;
- return -1;
+int rename(const char *oldpath, const char *newpath) {
+ // TODO require a duplex flag in open()
+ hid_t from, to;
+ from = camellia_open(oldpath, OPEN_RW);
+ if (from < 0) {
+ return -1;
+ }
+ to = camellia_open(newpath, OPEN_WRITE | OPEN_CREATE);
+ if (to < 0) {
+ close(from);
+ return -1;
+ }
+
+ long ret = _sys_duplex(from, to, DUPLEX_REMOVE);
+ close(from);
+ close(to);
+ if (ret < 0) {
+ errno = -ret;
+ return -1;
+ } else {
+ return 0;
+ }
}
// TODO tmpnam
diff --git a/src/libc/syscall.c b/src/libc/syscall.c
index f44c775..c6e3762 100644
--- a/src/libc/syscall.c
+++ b/src/libc/syscall.c
@@ -98,6 +98,10 @@ hid_t _sys_getnull(int flags) {
return (hid_t)_syscall(_SYS_GETNULL, (long)flags, 0, 0, 0, 0);
}
+long _sys_duplex(hid_t from, hid_t to, int flags) {
+ return _syscall(_SYS_DUPLEX, (long)from, (long)to, (long)flags, 0, 0);
+}
+
long _sys_execbuf(void __user *buf, size_t len) {
return _syscall(_SYS_EXECBUF, (long)buf, (long)len, 0, 0, 0);
}
diff --git a/src/libk/include/camellia/flags.h b/src/libk/include/camellia/flags.h
index 472215b..1585ace 100644
--- a/src/libk/include/camellia/flags.h
+++ b/src/libk/include/camellia/flags.h
@@ -30,3 +30,5 @@
* The idea is that if all flags which allow modifying the filesystem state require
* OPEN_WRITE to be set, filesystem handlers could just check for the OPEN_WRITE flag. */
#define OPEN_CREATE 4
+
+#define DUPLEX_REMOVE 1
diff --git a/src/libk/include/camellia/syscalls.h b/src/libk/include/camellia/syscalls.h
index 3e7576e..16576e0 100644
--- a/src/libk/include/camellia/syscalls.h
+++ b/src/libk/include/camellia/syscalls.h
@@ -23,6 +23,7 @@
#define _SYS_GETPROCFS 22
#define _SYS_TIME 23
#define _SYS_GETNULL 24
+#define _SYS_DUPLEX 25
#define _SYS_EXECBUF 100
#define _SYS_DEBUG_KLOG 101
@@ -89,6 +90,8 @@ uint64_t _sys_time(int flags);
hid_t _sys_getnull(int flags);
+long _sys_duplex(hid_t from, hid_t to, int flags);
+
/* see shared/execbuf.h */
long _sys_execbuf(void __user *buf, size_t len);
diff --git a/src/libk/include/camellia/types.h b/src/libk/include/camellia/types.h
index 65200d7..3117877 100644
--- a/src/libk/include/camellia/types.h
+++ b/src/libk/include/camellia/types.h
@@ -20,13 +20,14 @@ enum vfs_op {
VFSOP_GETSIZE,
VFSOP_REMOVE,
VFSOP_CLOSE,
+ VFSOP_DUPLEX,
};
struct ufs_request {
enum vfs_op op;
size_t len; // how much was put in *buf
size_t capacity; // how much output can be accepted by the caller
- void __user *id; // file id (returned by the open handler, passed to other calls)
+ void __user *id, *id2; // file id (returned by the open handler, passed to other calls)
long offset;
int flags;
};