forked from ish-app/ish
-
Notifications
You must be signed in to change notification settings - Fork 2
/
exit.c
375 lines (330 loc) · 11.5 KB
/
exit.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
#include <pthread.h>
#include <signal.h>
#include <string.h>
#include "kernel/calls.h"
#include "kernel/mm.h"
#include "kernel/futex.h"
#include "kernel/ptrace.h"
#include "fs/fd.h"
#include "fs/tty.h"
static void halt_system(void);
static bool exit_tgroup(struct task *task) {
struct tgroup *group = task->group;
list_remove(&task->group_links);
bool group_dead = list_empty(&group->threads);
if (group_dead) {
// don't need to lock the group since the only pointers to it come from:
// - other threads' current->group, but there are none left thanks to that list_empty call
// - locking pids_lock first, which do_exit did
if (group->itimer)
timer_free(group->itimer);
// The group will be removed from its group and session by reap_if_zombie,
// because fish tries to set the pgid to that of an exited but not reaped
// task.
// https://github.com/Microsoft/WSL/issues/2786
}
return group_dead;
}
void (*exit_hook)(struct task *task, int code) = NULL;
static struct task *find_new_parent(struct task *task) {
struct task *new_parent;
list_for_each_entry(&task->group->threads, new_parent, group_links) {
if (!new_parent->exiting)
return new_parent;
}
return pid_get_task(1);
}
noreturn void do_exit(int status) {
// has to happen before mm_release
addr_t clear_tid = current->clear_tid;
if (clear_tid) {
pid_t_ zero = 0;
if (user_put(clear_tid, zero) == 0)
futex_wake(clear_tid, 1);
}
// release all our resources
mm_release(current->mm);
current->mm = NULL;
fdtable_release(current->files);
current->files = NULL;
fs_info_release(current->fs);
current->fs = NULL;
// sighand must be released below so it can be protected by pids_lock
// since it can be accessed by other threads
// save things that our parent might be interested in
current->exit_code = status; // FIXME locking
struct rusage_ rusage = rusage_get_current();
lock(¤t->group->lock);
rusage_add(¤t->group->rusage, &rusage);
struct rusage_ group_rusage = current->group->rusage;
unlock(¤t->group->lock);
// the actual freeing needs pids_lock
lock(&pids_lock);
current->exiting = true;
// release the sighand
sighand_release(current->sighand);
current->sighand = NULL;
struct sigqueue *sigqueue, *sigqueue_tmp;
list_for_each_entry_safe(¤t->queue, sigqueue, sigqueue_tmp, queue) {
list_remove(&sigqueue->queue);
free(sigqueue);
}
struct task *leader = current->group->leader;
// reparent children
struct task *new_parent = find_new_parent(current);
struct task *child, *tmp;
list_for_each_entry_safe(¤t->children, child, tmp, siblings) {
child->parent = new_parent;
list_remove(&child->siblings);
list_add(&new_parent->children, &child->siblings);
}
if (exit_tgroup(current)) {
// notify parent that we died
struct task *parent = leader->parent;
if (parent == NULL) {
// init died
halt_system();
} else {
leader->zombie = true;
notify(&parent->group->child_exit);
struct siginfo_ info = {
.code = SI_KERNEL_,
.child.pid = current->pid,
.child.uid = current->uid,
.child.status = current->exit_code,
.child.utime = clock_from_timeval(group_rusage.utime),
.child.stime = clock_from_timeval(group_rusage.stime),
};
if (leader->exit_signal != 0)
send_signal(parent, leader->exit_signal, info);
}
if (exit_hook != NULL)
exit_hook(current, status);
}
vfork_notify(current);
if (current != leader)
task_destroy(current);
unlock(&pids_lock);
pthread_exit(NULL);
}
noreturn void do_exit_group(int status) {
struct tgroup *group = current->group;
lock(&pids_lock);
lock(&group->lock);
if (!group->doing_group_exit) {
group->doing_group_exit = true;
group->group_exit_code = status;
} else {
status = group->group_exit_code;
}
// kill everyone else in the group
struct task *task;
list_for_each_entry(&group->threads, task, group_links) {
deliver_signal(task, SIGKILL_, SIGINFO_NIL);
task->group->stopped = false;
notify(&task->group->stopped_cond);
}
unlock(&group->lock);
unlock(&pids_lock);
do_exit(status);
}
// always called from init process
static void halt_system(void) {
// brutally murder everything
// which will leave everything in an inconsistent state. I will solve this problem later.
for (int i = 2; i < MAX_PID; i++) {
struct task *task = pid_get_task(i);
if (task != NULL)
pthread_kill(task->thread, SIGKILL);
}
// unmount all filesystems
lock(&mounts_lock);
struct mount *mount, *tmp;
list_for_each_entry_safe(&mounts, mount, tmp, mounts) {
mount_remove(mount);
}
unlock(&mounts_lock);
}
dword_t sys_exit(dword_t status) {
STRACE("exit(%d)\n", status);
do_exit(status << 8);
}
dword_t sys_exit_group(dword_t status) {
STRACE("exit_group(%d)\n", status);
do_exit_group(status << 8);
}
#define WNOHANG_ (1 << 0)
#define WUNTRACED_ (1 << 1)
#define WEXITED_ (1 << 2)
#define WCONTINUED_ (1 << 3)
#define WNOWAIT_ (1 << 24)
#define __WALL_ (1 << 30)
#define P_ALL_ 0
#define P_PID_ 1
#define P_PGID_ 2
// returns false if the task cannot be reaped and true if the task was reaped
static bool reap_if_zombie(struct task *task, struct siginfo_ *info_out, struct rusage_ *rusage_out, int options) {
if (!task->zombie)
return false;
lock(&task->group->lock);
dword_t exit_code = task->exit_code;
if (task->group->doing_group_exit)
exit_code = task->group->group_exit_code;
info_out->child.status = exit_code;
struct rusage_ rusage = task->group->rusage;
if (!(options & WNOWAIT_)) {
lock(¤t->group->lock);
rusage_add(¤t->group->children_rusage, &rusage);
unlock(¤t->group->lock);
}
if (rusage_out != NULL)
*rusage_out = rusage;
unlock(&task->group->lock);
// WNOWAIT means don't destroy the child, instead leave it so it could be waited for again.
if (options & WNOWAIT_)
return true;
// tear down group
cond_destroy(&task->group->child_exit);
task_leave_session(task);
list_remove(&task->group->pgroup);
free(task->group);
task_destroy(task);
return true;
}
static bool notify_if_stopped(struct task *task, struct siginfo_ *info_out) {
lock(&task->group->lock);
bool stopped = task->group->stopped;
unlock(&task->group->lock);
if (!stopped || task->group->group_exit_code == 0)
return false;
dword_t exit_code = task->group->group_exit_code;
task->group->group_exit_code = 0;
info_out->child.status = exit_code;
return true;
}
static bool reap_if_needed(struct task *task, struct siginfo_ *info_out, struct rusage_ *rusage_out, int options) {
assert(task_is_leader(task));
if ((options & WUNTRACED_ && notify_if_stopped(task, info_out)) ||
(options & WEXITED_ && reap_if_zombie(task, info_out, rusage_out, options))) {
info_out->sig = SIGCHLD_;
return true;
}
lock(&task->ptrace.lock);
if (task->ptrace.stopped && task->ptrace.signal) {
// I had this code here because it made something work, but it's now
// making GDB think we support events (we don't). I can't remember what
// it fixed but until then commenting it out for now.
info_out->child.status = /* task->ptrace.trap_event << 16 |*/ task->ptrace.signal << 8 | 0x7f;
task->ptrace.signal = 0;
unlock(&task->ptrace.lock);
return true;
}
unlock(&task->ptrace.lock);
return false;
}
int do_wait(int idtype, pid_t_ id, struct siginfo_ *info, struct rusage_ *rusage, int options) {
if (idtype != P_ALL_ && idtype != P_PID_ && idtype != P_PGID_)
return _EINVAL;
if (options & ~(WNOHANG_|WUNTRACED_|WEXITED_|WCONTINUED_|WNOWAIT_|__WALL_))
return _EINVAL;
lock(&pids_lock);
int err;
bool got_signal = false;
retry:
if (idtype != P_PID_) {
// look for a zombie child
bool no_children = true;
struct task *parent;
list_for_each_entry(¤t->group->threads, parent, group_links) {
struct task *task;
list_for_each_entry(¤t->children, task, siblings) {
if (!task_is_leader(task))
continue;
if (idtype == P_PGID_ && task->group->pgid != id)
continue;
no_children = false;
info->child.pid = task->pid;
if (reap_if_needed(task, info, rusage, options))
goto found_something;
}
}
err = _ECHILD;
if (no_children)
goto error;
} else {
// check if this child is a zombie
struct task *task = pid_get_task_zombie(id);
err = _ECHILD;
if (task == NULL || task->parent == NULL || task->parent->group != current->group)
goto error;
task = task->group->leader;
info->child.pid = id;
if (reap_if_needed(task, info, rusage, options))
goto found_something;
}
// WNOHANG leaves the info in an implementation-defined state. set the pid
// to 0 so wait4 can pass that along correctly.
info->child.pid = 0;
if (options & WNOHANG_) {
info->sig = SIGCHLD_;
goto found_something;
}
err = _EINTR;
if (got_signal)
goto error;
// no matching zombie found, wait for one
if (wait_for(¤t->group->child_exit, &pids_lock, NULL)) {
// maybe we got a SIGCHLD! go through the loop one more time to make
// sure the newly exited process is returned in that case.
got_signal = true;
goto retry;
}
goto retry;
info->sig = SIGCHLD_;
found_something:
unlock(&pids_lock);
return 0;
error:
unlock(&pids_lock);
return err;
}
dword_t sys_waitid(int_t idtype, pid_t_ id, addr_t info_addr, int_t options) {
STRACE("waitid(%d, %d, %#x, %#x)", idtype, id, info_addr, options);
struct siginfo_ info = {};
int_t res = do_wait(idtype, id, &info, NULL, options);
if (res < 0 || (res == 0 && info.child.pid == 0))
return res;
if (info_addr != 0 && user_put(info_addr, info))
return _EFAULT;
return 0;
}
dword_t sys_wait4(pid_t_ id, addr_t status_addr, dword_t options, addr_t rusage_addr) {
STRACE("wait4(%d, %#x, %#x, %#x)", id, status_addr, options, rusage_addr);
if (options & WNOWAIT_)
return _EINVAL;
int idtype;
if (id > 0)
idtype = P_PID_;
else if (id == -1)
idtype = P_ALL_;
else {
idtype = P_PGID_;
if (id == 0)
id = current->group->pgid;
else
id = -id;
}
struct siginfo_ info = {.child.pid = 0xbaba};
struct rusage_ rusage;
int_t res = do_wait(idtype, id, &info, &rusage, options | WEXITED_);
if (res < 0 || (res == 0 && info.child.pid == 0))
return res;
if (status_addr != 0 && user_put(status_addr, info.child.status))
return _EFAULT;
if (rusage_addr != 0 && user_put(rusage_addr, rusage))
return _EFAULT;
return info.child.pid;
}
dword_t sys_waitpid(pid_t_ pid, addr_t status_addr, dword_t options) {
return sys_wait4(pid, status_addr, options, 0);
}