···5757add_subdirectory(buildtools)
5858add_subdirectory(hosttools)
5959add_subdirectory(libelfloader/wrapgen)
6060-add_subdirectory(libsimple)
6060+# add the Linux build of libsimple now
6161+add_subdirectory(libsimple "${CMAKE_CURRENT_BINARY_DIR}/libsimple-linux")
6162add_subdirectory(external/darlingserver)
6263add_subdirectory(startup)
6364···108109 ${CMAKE_SOURCE_DIR}/${DARLING_SDK_RELATIVE_PATH}/usr/include/libxml2
109110 ${CMAKE_BINARY_DIR}/${DARLING_SDK_RELATIVE_PATH}/usr/include/libxml2
110111)
112112+113113+# add the Mach-O build of libsimple now
114114+add_subdirectory(libsimple "${CMAKE_CURRENT_BINARY_DIR}/libsimple-darling")
111115112116add_subdirectory(external/libkqueue)
113117
···11+#include "table.h"
22+#include <libsimple/lock.h>
33+#include <stddef.h>
44+#include <errno.h>
55+#include "../unistd/close.h"
66+77+#define MAX_GUARD_COUNT 1024
88+99+#define GUARD_ENTRY_EMPTY_FD (-1)
1010+#define GUARD_ENTRY_DELETED_FD (-2)
1111+1212+typedef struct guard_entry {
1313+ int fd;
1414+ guard_flags_t flags;
1515+} guard_entry_t;
1616+1717+// this is a linearly-probed, open-addressed hash table;
1818+// the hash is the FD number itself.
1919+//
2020+// basically, we find the optimal index for the FD, and if it's occupied,
2121+// we keep going until we find an unoccupied slot, wrapping around
2222+// if necessary (until we return to the optimal index).
2323+//
2424+// when deleting, we mark entries as deleted rather than empty.
2525+// yes, this incurs a performance penalty, but:
2626+// 1) we shouldn't ever have collisions, really. most systems limit userspace to 1024 FDs.
2727+// 2) we don't expect to be guarding too many descriptors (mainly just the darlingserver RPC descriptors).
2828+// 3) the additional complexity of eager deletion just isn't worth it (vs lazy deletion, which is what we're doing).
2929+static guard_entry_t guard_table[MAX_GUARD_COUNT] = {
3030+ [0 ... MAX_GUARD_COUNT - 1] = {
3131+ .fd = GUARD_ENTRY_EMPTY_FD,
3232+ .flags = guard_flag_none,
3333+ },
3434+};
3535+// TODO: this should be an RW lock
3636+static libsimple_lock_t guard_table_lock = LIBSIMPLE_LOCK_INITIALIZER;
3737+3838+// normal search
3939+static guard_entry_t* guard_table_find_locked(int fd) {
4040+ size_t start_index = fd % MAX_GUARD_COUNT;
4141+4242+ // check the optimal index first, then sequentially until the end of the table
4343+ for (size_t i = start_index; i < MAX_GUARD_COUNT; ++i) {
4444+ guard_entry_t* entry = &guard_table[i];
4545+4646+ if (entry->fd == fd) {
4747+ return entry;
4848+ }
4949+5050+ if (entry->fd == GUARD_ENTRY_EMPTY_FD) {
5151+ return NULL;
5252+ }
5353+ }
5454+5555+ // rinse and repeat starting from index 0
5656+ for (size_t i = 0; i < start_index; ++i) {
5757+ guard_entry_t* entry = &guard_table[i];
5858+5959+ if (entry->fd == fd) {
6060+ return entry;
6161+ }
6262+6363+ if (entry->fd == GUARD_ENTRY_EMPTY_FD) {
6464+ return NULL;
6565+ }
6666+ }
6767+6868+ // the table was full and our FD wasn't there
6969+ return NULL;
7070+};
7171+7272+// insertion search
7373+static guard_entry_t* guard_table_find_available_locked(int fd) {
7474+ size_t start_index = fd % MAX_GUARD_COUNT;
7575+7676+ // check the optimal index first, then sequentially until the end of the table
7777+ for (size_t i = start_index; i < MAX_GUARD_COUNT; ++i) {
7878+ guard_entry_t* entry = &guard_table[i];
7979+8080+ if (entry->fd == fd || entry->fd == GUARD_ENTRY_EMPTY_FD || entry->fd == GUARD_ENTRY_DELETED_FD) {
8181+ return entry;
8282+ }
8383+ }
8484+8585+ // rinse and repeat starting from index 0
8686+ for (size_t i = 0; i < start_index; ++i) {
8787+ guard_entry_t* entry = &guard_table[i];
8888+8989+ if (entry->fd == fd || entry->fd == GUARD_ENTRY_EMPTY_FD || entry->fd == GUARD_ENTRY_DELETED_FD) {
9090+ return entry;
9191+ }
9292+ }
9393+9494+ // the table was full and we had no available slot for our FD
9595+ return NULL;
9696+};
9797+9898+int guard_table_add(int fd, guard_flags_t flags) {
9999+#ifdef VARIANT_DYLD
100100+ return -ENOSYS;
101101+#else
102102+ int result = 0;
103103+104104+ libsimple_lock_lock(&guard_table_lock);
105105+106106+ guard_entry_t* entry = guard_table_find_locked(fd);
107107+ if (entry) {
108108+ result = -EEXIST;
109109+ } else {
110110+ entry = guard_table_find_available_locked(fd);
111111+ if (entry) {
112112+ entry->fd = fd;
113113+ entry->flags = flags;
114114+ } else {
115115+ result = -ENOMEM;
116116+ }
117117+ }
118118+119119+ libsimple_lock_unlock(&guard_table_lock);
120120+121121+ return result;
122122+#endif
123123+};
124124+125125+int guard_table_modify(int fd, guard_flags_t flags) {
126126+#ifdef VARIANT_DYLD
127127+ return -ENOSYS;
128128+#else
129129+ int result = 0;
130130+131131+ libsimple_lock_lock(&guard_table_lock);
132132+133133+ guard_entry_t* entry = guard_table_find_locked(fd);
134134+ if (entry) {
135135+ entry->flags = flags;
136136+ } else {
137137+ result = -ENOENT;
138138+ }
139139+140140+ libsimple_lock_unlock(&guard_table_lock);
141141+142142+ return result;
143143+#endif
144144+};
145145+146146+int guard_table_remove(int fd) {
147147+#ifdef VARIANT_DYLD
148148+ return -ENOSYS;
149149+#else
150150+ int result = 0;
151151+152152+ libsimple_lock_lock(&guard_table_lock);
153153+154154+ guard_entry_t* entry = guard_table_find_locked(fd);
155155+ if (entry) {
156156+ entry->fd = GUARD_ENTRY_DELETED_FD;
157157+ entry->flags = guard_flag_none;
158158+ } else {
159159+ result = -ENOENT;
160160+ }
161161+162162+ libsimple_lock_unlock(&guard_table_lock);
163163+164164+ return result;
165165+#endif
166166+};
167167+168168+int guard_table_check(int fd, guard_flags_t flags) {
169169+#ifdef VARIANT_DYLD
170170+ return 0;
171171+#else
172172+ int result = 0;
173173+ libsimple_lock_lock(&guard_table_lock);
174174+ guard_entry_t* entry = guard_table_find_locked(fd);
175175+ result = entry && (entry->flags & flags) != 0;
176176+ libsimple_lock_unlock(&guard_table_lock);
177177+ return result;
178178+#endif
179179+};
180180+181181+void guard_table_postfork_child(void) {
182182+#ifndef VARIANT_DYLD
183183+ // after forking, the lock is invalid in the child; let's reinitialize it
184184+ libsimple_lock_init(&guard_table_lock);
185185+186186+ // now let's close any descriptors marked as close-on-fork
187187+ for (size_t i = 0; i < MAX_GUARD_COUNT; ++i) {
188188+ guard_entry_t* entry = &guard_table[i];
189189+ if (entry->fd >= 0 && (entry->flags & guard_flag_close_on_fork) != 0) {
190190+ // FIXME: if the FD is in a kqueue, this won't notify the kqueue
191191+ close_internal(entry->fd);
192192+ entry->fd = GUARD_ENTRY_DELETED_FD;
193193+ entry->flags = guard_flag_none;
194194+ }
195195+ }
196196+#endif
197197+};
+40
src/kernel/emulation/linux/guarded/table.h
···11+#ifndef _DARLING_EMULATION_LINUX_GUARDED_TABLE_H_
22+#define _DARLING_EMULATION_LINUX_GUARDED_TABLE_H_
33+44+typedef enum guard_flags {
55+ guard_flag_none = 0,
66+ guard_flag_prevent_close = 1U << 0,
77+ guard_flag_close_on_fork = 1U << 1,
88+} __attribute__((flag_enum)) guard_flags_t;
99+1010+// TODO: expand this code to support different types of guards (e.g. close, dup, socket IPC, etc.).
1111+// right now, it's only checked when closing descriptors.
1212+1313+/**
1414+ * @retval `0` on success
1515+ * @retval `-EEXIST` if the entry already exists
1616+ * @retval `-ENOMEM` if no space available for new entry
1717+ */
1818+int guard_table_add(int fd, guard_flags_t flags);
1919+2020+/**
2121+ * @retval `0` on success
2222+ * @retval `-ENOENT` if the entry doesn't exist
2323+ */
2424+int guard_table_modify(int fd, guard_flags_t flags);
2525+2626+/**
2727+ * @retval `0` on success
2828+ * @retval `-ENOENT` if the entry doesn't exist
2929+ */
3030+int guard_table_remove(int fd);
3131+3232+/**
3333+ * @returns `0` if FD is unguarded for all the specified guard flags, 1 otherwise (if it is guarded for at least one).
3434+ * This includes the case when the FD isn't present in the table at all (it is simply treated as unguarded).
3535+ */
3636+int guard_table_check(int fd, guard_flags_t flags);
3737+3838+void guard_table_postfork_child(void);
3939+4040+#endif // _DARLING_EMULATION_LINUX_GUARDED_TABLE_H_
+5-2
src/kernel/emulation/linux/unistd/close.c
···66#include "../mach/lkm.h"
77#include <lkm/api.h>
88#include "../simple.h"
99+#include "../guarded/table.h"
9101011__attribute__((weak))
1112__attribute__((visibility("default")))
···2122{
2223 int ret;
23242424- if (fd == mach_driver_get_dyld_fd()) {
2525- __simple_kprintf("*** Someone tried to close the special LKM fd! ***");
2525+ if (guard_table_check(fd, guard_flag_prevent_close)) {
2626+ // we should crash, actually.
2727+ // for now, let's silently (as far as the caller is concerned) ignore it.
2828+ __simple_kprintf("*** Someone tried to close a guarded FD (%d) via close! ***", fd);
2629 return 0;
2730 }
2831
+7
src/kernel/emulation/linux/unistd/dup2.c
···55#include "../duct_errno.h"
66#include "../mach/lkm.h"
77#include <lkm/api.h>
88+#include "../simple.h"
99+#include "../guarded/table.h"
810911extern void kqueue_dup(int oldfd, int newfd);
10121113long sys_dup2(int fd_from, int fd_to)
1214{
1315 int ret;
1616+1717+ if (guard_table_check(fd_to, guard_flag_prevent_close)) {
1818+ __simple_kprintf("*** Someone tried to close a guarded FD (%d) via dup2! ***", fd_to);
1919+ __simple_abort();
2020+ }
14211522 #if defined(__NR_dup2)
1623 ret = LINUX_SYSCALL2(__NR_dup2, fd_from, fd_to);
+26-7
src/libsimple/CMakeLists.txt
···11project(libsimple)
2233-add_library(libsimple_darlingserver
33+set(libsimple_sources
44 src/lock.c
55)
6677-target_include_directories(libsimple_darlingserver PUBLIC
88- include
99-)
77+if (NOT DEFINED libsimple_linux_added)
88+ set(libsimple_linux_added TRUE PARENT_SCOPE)
99+1010+ add_library(libsimple_darlingserver "${libsimple_sources}")
1111+1212+ target_include_directories(libsimple_darlingserver PUBLIC
1313+ include
1414+ )
1515+1616+ target_compile_definitions(libsimple_darlingserver PRIVATE
1717+ LIBSIMPLE_LINUX=1
1818+ )
1919+else()
2020+ add_darling_static_library(libsimple_darling
2121+ FAT
2222+ SOURCES "${libsimple_sources}"
2323+ )
10241111-target_compile_definitions(libsimple_darlingserver PRIVATE
1212- LIBSIMPLE_LINUX=1
1313-)
2525+ target_include_directories(libsimple_darling PUBLIC
2626+ include
2727+ )
2828+2929+ target_compile_definitions(libsimple_darling PRIVATE
3030+ LIBSIMPLE_DARLING=1
3131+ )
3232+endif()
+6
src/libsimple/src/lock.c
···2727#if LIBSIMPLE_LINUX
2828 #include <sys/syscall.h>
2929 #include <unistd.h>
3030+#elif LIBSIMPLE_DARLING
3131+ extern int __linux_futex(int* uaddr, int op, int val, const struct timespec* timeout, int* uaddr2, int val3);
3032#endif
31333434+#if LIBSIMPLE_DARLING
3535+ #define linux_futex __linux_futex
3636+#else // !LIBSIMPLE_DARLING
3237static int linux_futex(int* uaddr, int op, int val, const struct timespec* timeout, int* uaddr2, int val3) {
3338#if LIBSIMPLE_LINUX
3439 return syscall(SYS_futex, uaddr, op, val, timeout, uaddr2, val3);
···3641 #error linux_futex not implemented for this platform
3742#endif
3843};
4444+#endif // !LIBSIMPLE_DARLING
39454046//
4147// libsimple_lock