summaryrefslogtreecommitdiff
path: root/src/kernel/syscalls.c
diff options
context:
space:
mode:
authordzwdz2022-07-09 15:24:58 +0200
committerdzwdz2022-07-09 15:24:58 +0200
commitdad8b261ac7898f4d8cf537ad288ad6a1a74d124 (patch)
tree610712b313032529195c3e5319ab45b1a7482c65 /src/kernel/syscalls.c
parent2e8e2dc1fb1aaefbe82cc4261c615428aa6250d5 (diff)
syscalls/pipe: turn into a POSIX-style api with separate rw ends
Without separate read/write ends you can't tell when there are no more writers left if you have multiple readers. Consider this piece of code: int fd = pipe(); fork(); // execution continues in 2 processes while (read(fd, &some_buf, sizeof somebuf) >= 0) { ... } Once both processes call `read()`, it's obvious that no writes are possible - all the processes that hold a reference to the pipe are currently stuck on a `read()` call, so the kernel could just make it return an error in both. But, what then? It's still possible to write to the pipe, and you can't know if the other process will do that. Thus, if you don't want to miss any output, you have to keep reading the pipe. Forever. Both processes end up stuck. Having separate read/write ends prevents that.
Diffstat (limited to 'src/kernel/syscalls.c')
-rw-r--r--src/kernel/syscalls.c39
1 files changed, 26 insertions, 13 deletions
diff --git a/src/kernel/syscalls.c b/src/kernel/syscalls.c
index c916e81..421b5ce 100644
--- a/src/kernel/syscalls.c
+++ b/src/kernel/syscalls.c
@@ -194,8 +194,10 @@ int _syscall_read(handle_t handle_num, void __user *buf, size_t len, int offset)
});
break;
case HANDLE_PIPE:
- pipe_joinqueue(h, true, process_current, buf, len);
- pipe_trytransfer(h);
+ if (pipe_joinqueue(h, true, process_current, buf, len))
+ pipe_trytransfer(h);
+ else
+ SYSCALL_RETURN(-1);
break;
default:
SYSCALL_RETURN(-1);
@@ -221,8 +223,10 @@ int _syscall_write(handle_t handle_num, const void __user *buf, size_t len, int
});
break;
case HANDLE_PIPE:
- pipe_joinqueue(h, false, process_current, (void __user *)buf, len);
- pipe_trytransfer(h);
+ if (pipe_joinqueue(h, false, process_current, (void __user *)buf, len))
+ pipe_trytransfer(h);
+ else
+ SYSCALL_RETURN(-1);
break;
default:
SYSCALL_RETURN(-1);
@@ -310,15 +314,24 @@ void __user *_syscall_memflag(void __user *addr, size_t len, int flags) {
SYSCALL_RETURN((uintptr_t)addr);
}
-handle_t _syscall_pipe(int flags) {
- if (flags) return -1;
+int _syscall_pipe(handle_t __user user_ends[2], int flags) {
+ if (flags) SYSCALL_RETURN(-1);
+ handle_t ends[2];
+ struct handle *rend, *wend;
- handle_t h = process_find_free_handle(process_current, 0);
- if (h < 0) return -1;
- process_current->handles[h] = handle_init(HANDLE_PIPE);
- assert(process_current->handles[h]->pipe.reader == NULL);
- assert(process_current->handles[h]->pipe.writer == NULL);
- SYSCALL_RETURN(h);
+ ends[0] = process_find_free_handle(process_current, 0);
+ if (ends[0] < 0) SYSCALL_RETURN(-1);
+ ends[1] = process_find_free_handle(process_current, ends[0]+1);
+ if (ends[1] < 0) SYSCALL_RETURN(-1);
+
+ rend = process_current->handles[ends[0]] = handle_init(HANDLE_PIPE);
+ wend = process_current->handles[ends[1]] = handle_init(HANDLE_PIPE);
+ wend->pipe.write_end = true;
+ wend->pipe.sister = rend;
+ rend->pipe.sister = wend;
+
+ virt_cpy_to(process_current->pages, user_ends, ends, sizeof ends);
+ SYSCALL_RETURN(0);
}
void _syscall_debug_klog(const void __user *buf, size_t len) {
@@ -366,7 +379,7 @@ int _syscall(int num, int a, int b, int c, int d) {
_syscall_memflag((userptr_t)a, b, c);
break;
case _SYSCALL_PIPE:
- _syscall_pipe(a);
+ _syscall_pipe((userptr_t)a, b);
break;
case _SYSCALL_DEBUG_KLOG:
_syscall_debug_klog((userptr_t)a, b);