1/*
2 * Copyright 2004-2006, Axel Dörfler, axeld@pinc-software.de. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include <fork.h>
8
9#include <unistd.h>
10#include <stdlib.h>
11#include <errno.h>
12
13#include <errno_private.h>
14#include <locks.h>
15#include <libroot_private.h>
16#include <runtime_loader.h>
17#include <syscalls.h>
18
19
20typedef struct fork_hook {
21	struct fork_hook *next;
22	void (*function)(void);
23} fork_hook;
24
25#define FORK_LOCK_NAME "fork lock"
26
27static fork_hook *sPrepareHooks, *sParentHooks, *sChildHooks;
28static fork_hook *sLastParentHook, *sLastChildHook;
29static mutex sForkLock = MUTEX_INITIALIZER(FORK_LOCK_NAME);
30
31extern thread_id __main_thread_id;
32
33
34/**	Adds a hook to the specified list.
35 *	If \a _lastHook is NULL, the hook will be added at the head of the list,
36 *	else it will be added at the tail of the list.
37 *	Since this function allocates memory, it can fail, and returns B_NO_MEMORY
38 *	in that case. It returns B_OK on success.
39 */
40
41static status_t
42add_fork_hook(fork_hook **_hooks, fork_hook **_lastHook, void (*function)(void))
43{
44	fork_hook *hook = (fork_hook *)malloc(sizeof(struct fork_hook));
45	if (hook == NULL)
46		return B_NO_MEMORY;
47
48	hook->function = function;
49
50	if (_lastHook) {
51		// add hook at the end of the list
52
53		if (*_hooks == NULL) {
54			// first entry of this list
55			*_hooks = hook;
56			*_lastHook = hook;
57		} else {
58			// any other item
59#if 0
60			if (*_lastHook == NULL) {
61				// search for last hook (need if an item was added to the beginning only --
62				// this can only be the case if this function is called directly, though)
63				fork_hook *last = *_hooks;
64				while (last->next)
65					last = last->next;
66
67				*_lastHook = last;
68			}
69#endif
70
71			(*_lastHook)->next = hook;
72			*_lastHook = hook;
73		}
74
75		hook->next = NULL;
76	} else {
77		// add hook at the beginning of the list
78		hook->next = *_hooks;
79		*_hooks = hook;
80	}
81
82	return B_OK;
83}
84
85
86/**	Calls all hooks in the specified list in ascending order.
87 */
88
89static void
90call_fork_hooks(fork_hook *hook)
91{
92	while (hook) {
93		hook->function();
94		hook = hook->next;
95	}
96}
97
98
99/**	Private support function that registers the hooks that will be executed
100 *	before and after the team is fork()ed.
101 *	It is called from pthread_atfork() and atfork().
102 */
103
104status_t
105__register_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void))
106{
107	status_t status = mutex_lock(&sForkLock);
108	if (status != B_OK)
109		return status;
110
111	if (prepare)
112		status = add_fork_hook(&sPrepareHooks, NULL, prepare);
113
114	if (status == B_OK && parent)
115		status = add_fork_hook(&sParentHooks, &sLastParentHook, parent);
116
117	if (status == B_OK && child)
118		status = add_fork_hook(&sChildHooks, &sLastChildHook, child);
119
120	mutex_unlock(&sForkLock);
121	return status;
122}
123
124
125pid_t
126fork(void)
127{
128	thread_id thread;
129	status_t status;
130
131	status = mutex_lock(&sForkLock);
132	if (status != B_OK)
133		return status;
134
135	// call preparation hooks
136	call_fork_hooks(sPrepareHooks);
137
138	thread = _kern_fork();
139	if (thread < 0) {
140		// something went wrong
141		mutex_unlock(&sForkLock);
142		__set_errno(thread);
143		return -1;
144	}
145
146	if (thread == 0) {
147		// we are the child
148		// ToDo: initialize child
149		__main_thread_id = find_thread(NULL);
150		mutex_init(&sForkLock, FORK_LOCK_NAME);
151			// TODO: The lock is already initialized and we in the fork()ing
152			// process we should make sure that it is in a consistent state when
153			// calling the kernel.
154		__gRuntimeLoader->reinit_after_fork();
155		__reinit_pwd_backend_after_fork();
156
157		call_fork_hooks(sChildHooks);
158	} else {
159		// we are the parent
160		call_fork_hooks(sParentHooks);
161		mutex_unlock(&sForkLock);
162	}
163
164	return thread;
165}
166
167
168pid_t
169vfork(void)
170{
171	return fork();
172}
173
174