1/*
2 * Copyright (c) 1999, 2012 Apple Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24#include "internal.h"
25
26#include <libkern/OSAtomic.h>
27#include <mach/mach_init.h>
28#include <mach/mach_vm.h>
29#include <platform/compat.h>
30
31PTHREAD_NOEXPORT void pthread_workqueue_atfork_child(void);
32PTHREAD_NOEXPORT void __pthread_fork_child_internal(pthread_t);
33
34int
35pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void))
36{
37	int res = 0;
38	size_t idx;
39	pthread_globals_t globals = _pthread_globals();
40
41	OSSpinLockLock(&globals->pthread_atfork_lock);
42	idx = globals->atfork_count++;
43
44	if (idx == 0) {
45		// Initialize pointer to inline storage.
46		globals->atfork = globals->atfork_storage;
47	} else if (idx == PTHREAD_ATFORK_INLINE_MAX) {
48		// Migrate to out-of-line storage.
49		kern_return_t kr;
50		mach_vm_address_t storage = 0;
51		mach_vm_size_t size = PTHREAD_ATFORK_MAX * sizeof(struct pthread_atfork_entry);
52		OSSpinLockUnlock(&globals->pthread_atfork_lock);
53		kr = mach_vm_map(mach_task_self(),
54				 &storage,
55				 size,
56				 vm_page_size - 1,
57				 VM_MAKE_TAG(VM_MEMORY_OS_ALLOC_ONCE)| VM_FLAGS_ANYWHERE,
58				 MEMORY_OBJECT_NULL,
59				 0,
60				 FALSE,
61				 VM_PROT_DEFAULT,
62				 VM_PROT_ALL,
63				 VM_INHERIT_DEFAULT);
64		OSSpinLockLock(&globals->pthread_atfork_lock);
65		if (kr == KERN_SUCCESS) {
66			if (globals->atfork == globals->atfork_storage) {
67				globals->atfork = storage;
68				memmove(globals->atfork, globals->atfork_storage, sizeof(globals->atfork_storage));
69				bzero(globals->atfork_storage, sizeof(globals->atfork_storage));
70			} else {
71				// Another thread did vm_map first.
72				OSSpinLockUnlock(&globals->pthread_atfork_lock);
73				mach_vm_deallocate(mach_task_self(), storage, size);
74				OSSpinLockLock(&globals->pthread_atfork_lock);
75			}
76		} else {
77			res = ENOMEM;
78		}
79	} else if (idx >= PTHREAD_ATFORK_MAX) {
80		res = ENOMEM;
81	}
82
83	if (res == 0) {
84		struct pthread_atfork_entry *e = &globals->atfork[idx];
85		e->prepare = prepare;
86		e->parent = parent;
87		e->child = child;
88	}
89	OSSpinLockUnlock(&globals->pthread_atfork_lock);
90
91	return res;
92}
93
94// Called before the fork(2) system call is made in the parent process.
95// Iterate pthread_atfork prepare handlers.
96void
97_pthread_fork_prepare(void)
98{
99	pthread_globals_t globals = _pthread_globals();
100
101	OSSpinLockLock(&globals->pthread_atfork_lock);
102
103	size_t idx;
104	for (idx = globals->atfork_count; idx > 0; --idx) {
105		struct pthread_atfork_entry *e = &globals->atfork[idx-1];
106		if (e->prepare != NULL) {
107			e->prepare();
108		}
109	}
110
111	OSSpinLockLock(&globals->psaved_self_global_lock);
112	globals->psaved_self = pthread_self();
113	OSSpinLockLock(&globals->psaved_self->lock);
114}
115
116// Called after the fork(2) system call returns to the parent process.
117// Iterate pthread_atfork parent handlers.
118void
119_pthread_fork_parent(void)
120{
121	pthread_globals_t globals = _pthread_globals();
122
123	OSSpinLockUnlock(&globals->psaved_self->lock);
124	OSSpinLockUnlock(&globals->psaved_self_global_lock);
125
126	size_t idx;
127	for (idx = 0; idx < globals->atfork_count; ++idx) {
128		struct pthread_atfork_entry *e = &globals->atfork[idx];
129		if (e->parent != NULL) {
130			e->parent();
131		}
132	}
133	OSSpinLockUnlock(&globals->pthread_atfork_lock);
134}
135
136// Called after the fork(2) system call returns to the new child process.
137// Clean up data structures of other threads which no longer exist in the child.
138// Make the current thread the main thread.
139void
140_pthread_fork_child(void)
141{
142	pthread_globals_t globals = _pthread_globals();
143	globals->psaved_self_global_lock = OS_SPINLOCK_INIT;
144	__pthread_fork_child_internal(globals->psaved_self);
145	__is_threaded = 0;
146	pthread_workqueue_atfork_child();
147}
148
149// Iterate pthread_atfork child handlers.
150void
151_pthread_fork_child_postinit(void)
152{
153	pthread_globals_t globals = _pthread_globals();
154	size_t idx;
155	for (idx = 0; idx < globals->atfork_count; ++idx) {
156		struct pthread_atfork_entry *e = &globals->atfork[idx];
157		if (e->child != NULL) {
158			e->child();
159		}
160	}
161	globals->pthread_atfork_lock = OS_SPINLOCK_INIT;
162}
163