diff options
Diffstat (limited to 'src/linux/clone.c')
-rw-r--r-- | src/linux/clone.c | 65 |
1 files changed, 65 insertions, 0 deletions
diff --git a/src/linux/clone.c b/src/linux/clone.c new file mode 100644 index 0000000..257c1ce --- /dev/null +++ b/src/linux/clone.c @@ -0,0 +1,65 @@ +#define _GNU_SOURCE +#include <stdarg.h> +#include <unistd.h> +#include <sched.h> +#include "pthread_impl.h" +#include "syscall.h" +#include "lock.h" +#include "fork_impl.h" + +struct clone_start_args { + int (*func)(void *); + void *arg; + sigset_t sigmask; +}; + +static int clone_start(void *arg) +{ + struct clone_start_args *csa = arg; + __post_Fork(0); + __restore_sigs(&csa->sigmask); + return csa->func(csa->arg); +} + +int clone(int (*func)(void *), void *stack, int flags, void *arg, ...) +{ + struct clone_start_args csa; + va_list ap; + pid_t *ptid = 0, *ctid = 0; + void *tls = 0; + + /* Flags that produce an invalid thread/TLS state are disallowed. */ + int badflags = CLONE_THREAD | CLONE_SETTLS | CLONE_CHILD_CLEARTID; + + if ((flags & badflags) || !stack) + return __syscall_ret(-EINVAL); + + va_start(ap, arg); + if (flags & (CLONE_PIDFD | CLONE_PARENT_SETTID | CLONE_CHILD_SETTID)) + ptid = va_arg(ap, pid_t *); + if (flags & CLONE_CHILD_SETTID) { + tls = va_arg(ap, void *); + ctid = va_arg(ap, pid_t *); + } + va_end(ap); + + /* If CLONE_VM is used, it's impossible to give the child a consistent + * thread structure. In this case, the best we can do is assume the + * caller is content with an extremely restrictive execution context + * like the one vfork() would provide. */ + if (flags & CLONE_VM) return __syscall_ret( + __clone(func, stack, flags, arg, ptid, tls, ctid)); + + __block_all_sigs(&csa.sigmask); + LOCK(__abort_lock); + + /* Setup the a wrapper start function for the child process to do + * mimic _Fork in producing a consistent execution state. */ + csa.func = func; + csa.arg = arg; + int ret = __clone(clone_start, stack, flags, &csa, ptid, tls, ctid); + + __post_Fork(ret); + __restore_sigs(&csa.sigmask); + return __syscall_ret(ret); +} |