From e35f22b3dfe8b2727ccf3fb94430c104ef0753cd Mon Sep 17 00:00:00 2001
From: jD91mZM2 <me@krake.one>
Date: Sun, 28 Apr 2019 17:14:45 +0200
Subject: [PATCH] WIP: pthread_atfork

WIP mainly because we *should* use thread locals, but #[thread_local]
causes segfaults.
---
 src/header/unistd/mod.rs          | 43 +++++++++++++++++++++++++++----
 tests/Makefile                    |  1 +
 tests/expected/unistd/fork.stderr |  0
 tests/expected/unistd/fork.stdout |  3 +++
 tests/unistd/fork.c               | 29 +++++++++++++++++++++
 5 files changed, 71 insertions(+), 5 deletions(-)
 create mode 100644 tests/expected/unistd/fork.stderr
 create mode 100644 tests/expected/unistd/fork.stdout
 create mode 100644 tests/unistd/fork.c

diff --git a/src/header/unistd/mod.rs b/src/header/unistd/mod.rs
index c7e483535..6145c2c79 100644
--- a/src/header/unistd/mod.rs
+++ b/src/header/unistd/mod.rs
@@ -2,6 +2,7 @@
 
 use core::{mem, ptr, slice};
 
+use alloc::collections::LinkedList;
 use c_str::CStr;
 use header::errno;
 use header::limits;
@@ -10,9 +11,9 @@ use header::sys_ioctl;
 use header::sys_time;
 use header::termios;
 use header::time::timespec;
-use platform;
 use platform::types::*;
 use platform::{Pal, Sys};
+use platform;
 
 pub use self::brk::*;
 pub use self::getopt::*;
@@ -42,6 +43,17 @@ pub const STDIN_FILENO: c_int = 0;
 pub const STDOUT_FILENO: c_int = 1;
 pub const STDERR_FILENO: c_int = 2;
 
+#[thread_local]
+pub static mut fork_hooks_static: Option<[LinkedList<extern "C" fn()>; 3]> = None;
+
+unsafe fn init_fork_hooks<'a>() -> &'a mut [LinkedList<extern "C" fn()>; 3] {
+    // Transmute the lifetime so we can return here. Should be safe as
+    // long as one does not access the original fork_hooks.
+    mem::transmute(fork_hooks_static.get_or_insert_with(|| {
+        [LinkedList::new(), LinkedList::new(), LinkedList::new()]
+    }))
+}
+
 #[no_mangle]
 pub extern "C" fn _exit(status: c_int) {
     Sys::exit(status)
@@ -214,7 +226,21 @@ pub extern "C" fn fdatasync(fildes: c_int) -> c_int {
 
 #[no_mangle]
 pub extern "C" fn fork() -> pid_t {
-    Sys::fork()
+    let fork_hooks = unsafe { init_fork_hooks() };
+    for prepare in &fork_hooks[0] {
+        prepare();
+    }
+    let pid = Sys::fork();
+    if pid == 0 {
+        for child in &fork_hooks[2] {
+            child();
+        }
+    } else if pid != -1 {
+        for parent in &fork_hooks[1] {
+            parent();
+        }
+    }
+    pid
 }
 
 #[no_mangle]
@@ -456,9 +482,16 @@ pub extern "C" fn pthread_atfork(
     parent: Option<extern "C" fn()>,
     child: Option<extern "C" fn()>,
 ) -> c_int {
-    // TODO: WIP implementation available in "atfork" branch. It's
-    // segfaulting at the thread-local stuff, both in Unix and Redox.
-    // unimplemented!();
+    let fork_hooks = unsafe { init_fork_hooks() };
+    if let Some(prepare) = prepare {
+        fork_hooks[0].push_back(prepare);
+    }
+    if let Some(parent) = parent {
+        fork_hooks[1].push_back(parent);
+    }
+    if let Some(child) = child {
+        fork_hooks[2].push_back(child);
+    }
     0
 }
 
diff --git a/tests/Makefile b/tests/Makefile
index 3530a5ca3..6ec439803 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -77,6 +77,7 @@ EXPECT_NAMES=\
 	unistd/dup \
 	unistd/exec \
 	unistd/fchdir \
+	unistd/fork \
 	unistd/fsync \
 	unistd/ftruncate \
 	unistd/getopt \
diff --git a/tests/expected/unistd/fork.stderr b/tests/expected/unistd/fork.stderr
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/expected/unistd/fork.stdout b/tests/expected/unistd/fork.stdout
new file mode 100644
index 000000000..98f331c9d
--- /dev/null
+++ b/tests/expected/unistd/fork.stdout
@@ -0,0 +1,3 @@
+Hello from prepare
+Hello from child
+Hello from parent
diff --git a/tests/unistd/fork.c b/tests/unistd/fork.c
new file mode 100644
index 000000000..eb9b84ac2
--- /dev/null
+++ b/tests/unistd/fork.c
@@ -0,0 +1,29 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "test_helpers.h"
+
+void prepare() {
+    puts("Hello from prepare");
+}
+void parent() {
+    // Make sure we print in the right order and also don't exit
+    // before the fork does.
+    int us_status = usleep(1000);
+    ERROR_IF(usleep, us_status, == -1);
+    UNEXP_IF(usleep, us_status, != 0);
+
+    puts("Hello from parent");
+}
+void child() {
+    puts("Hello from child");
+}
+
+int main(void) {
+    int status = pthread_atfork(prepare, parent, child);
+    ERROR_IF(pthread_atfork, status, == -1);
+
+    int pid = fork();
+    ERROR_IF(fork, pid, == -1);
+}
-- 
GitLab