summaryrefslogtreecommitdiff
path: root/usr/src/uts/common/brand/lx/syscall/lx_wait.c
blob: 3a5ba69b93b8b6b9ff195ddc89879c80b6b89c13 (plain)
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
376
377
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 * Copyright 2019 Joyent, Inc.
 */

/*
 * wait() family of functions.
 *
 * The first minor difference between the Linux and Solaris family of wait()
 * calls is that the values for WNOHANG and WUNTRACED are different. Thankfully,
 * the exit status values are identical between the two implementations.
 *
 * Things get very different and very complicated when we introduce the Linux
 * threading model.  Under linux, both threads and child processes are
 * represented as processes.  However, the behavior of wait() with respect to
 * each child varies according to the flags given to clone()
 *
 *	SIGCHLD 	The SIGCHLD signal should be sent on termination
 *	CLONE_THREAD	The child shares the same thread group as the parent
 *	CLONE_DETACHED	The parent receives no notification when the child exits
 *
 * The following flags control the Linux behavior w.r.t. the above attributes:
 *
 * 	__WALL		Wait on all children, regardless of type
 * 	__WCLONE	Wait only on non-SIGCHLD children
 * 	__WNOTHREAD	Don't wait on children of other threads in this group
 *
 * The following chart shows whether wait() returns when the child exits:
 *
 *                           default    __WCLONE    __WALL
 *           no SIGCHLD		-	    X	      X
 *              SIGCHLD		X	    -	      X
 *
 * The following chart shows whether wait() returns when the grandchild exits:
 *
 *                           default   __WNOTHREAD
 * 	no CLONE_THREAD		-	    -
 *         CLONE_THREAD		X	    -
 *
 * The CLONE_DETACHED flag is universal - when the child exits, no state is
 * stored and wait() has no effect.
 *
 * XXX Support the above combination of options, or some reasonable subset that
 *     covers at least fork() and pthread_create().
 */

#include <sys/wait.h>
#include <sys/brand.h>
#include <sys/lx_brand.h>
#include <sys/lx_types.h>
#include <sys/lx_misc.h>
#include <lx_signum.h>
#include <lx_errno.h>
#include <lx_syscall.h>

/*
 * From "uts/common/os/exit.c" and "uts/common/syscall/rusagesys.c":
 */
extern int waitid(idtype_t, id_t, k_siginfo_t *, int);
extern int rusagesys(int, void *, void *, void *, void *);

/*
 * Convert between Linux options and Solaris options, returning -1 if any
 * invalid flags are found.
 */
#define	LX_WNOHANG	0x00000001
#define	LX_WUNTRACED	0x00000002
#define	LX_WSTOPPED	LX_WUNTRACED
#define	LX_WEXITED	0x00000004
#define	LX_WCONTINUED	0x00000008
#define	LX_WNOWAIT	0x01000000

#define	LX_WNOTHREAD	0x20000000
#define	LX_WALL		0x40000000
#define	LX_WCLONE	0x80000000

#define	LX_P_ALL	0x0
#define	LX_P_PID	0x1
#define	LX_P_GID	0x2

/*
 * Split the passed waitpid/waitid options into two separate variables:
 * those for the native illumos waitid(2), and the extra Linux-specific
 * options we will handle in our brand-specific code.
 */
static int
ltos_options(uintptr_t options, int *native_options, int *extra_options)
{
	int newoptions = 0;

	if (((options) & ~(LX_WNOHANG | LX_WUNTRACED | LX_WEXITED |
	    LX_WCONTINUED | LX_WNOWAIT | LX_WNOTHREAD | LX_WALL |
	    LX_WCLONE)) != 0) {
		return (-1);
	}

	*extra_options = options & (LX_WNOTHREAD | LX_WALL | LX_WCLONE);

	if (options & LX_WNOHANG)
		newoptions |= WNOHANG;
	if (options & LX_WUNTRACED)
		newoptions |= WUNTRACED;
	if (options & LX_WEXITED)
		newoptions |= WEXITED;
	if (options & LX_WCONTINUED)
		newoptions |= WCONTINUED;
	if (options & LX_WNOWAIT)
		newoptions |= WNOWAIT;

	/*
	 * The trapped option is implicit on Linux.
	 */
	newoptions |= WTRAPPED;

	*native_options = newoptions;
	return (0);
}

static int
lx_wstat(int code, int status)
{
	int stat = 0;

	switch (code) {
	case CLD_EXITED:
		stat = status << 8;
		break;
	case CLD_DUMPED:
		stat = lx_stol_signo(status, SIGKILL) | WCOREFLG;
		break;
	case CLD_KILLED:
		stat = lx_stol_signo(status, SIGKILL);
		break;
	case CLD_TRAPPED:
	case CLD_STOPPED:
		stat = (lx_stol_status(status, SIGKILL) << 8) | WSTOPFLG;
		break;
	case CLD_CONTINUED:
		stat = WCONTFLG;
		break;
	}

	return (stat);
}

static int
lx_call_waitid(idtype_t idtype, id_t id, k_siginfo_t *sip, int native_options,
    int extra_options)
{
	lx_lwp_data_t *lwpd = ttolxlwp(curthread);
	int error;

	/*
	 * Our brand-specific waitid helper only understands a subset of
	 * the possible idtypes.  Ensure we keep to that subset here:
	 */
	if (idtype != P_ALL && idtype != P_PID && idtype != P_PGID) {
		return (EINVAL);
	}

	/*
	 * Enable the return of emulated ptrace(2) stop conditions
	 * through lx_waitid_helper, and stash the Linux-specific
	 * extra waitid() flags.
	 */
	lwpd->br_waitid_emulate = B_TRUE;
	lwpd->br_waitid_flags = extra_options;

	if ((error = waitid(idtype, id, sip, native_options)) == EINTR) {
		/*
		 * According to signal(7), the wait4(2), waitid(2), and
		 * waitpid(2) system calls are restartable.
		 */
		ttolxlwp(curthread)->br_syscall_restart = B_TRUE;
	}

	lwpd->br_waitid_emulate = B_FALSE;
	lwpd->br_waitid_flags = 0;

	return (error);
}

long
lx_wait4(uintptr_t p1, uintptr_t p2, uintptr_t p3, uintptr_t p4)
{
	k_siginfo_t info = { 0 };
	idtype_t idtype;
	id_t id;
	int status = 0;
	pid_t pid = (pid_t)p1;
	int error;
	int native_options, extra_options;
	int *statusp = (int *)p2;
	void *rup = (void *)p4;

	if (ltos_options(p3, &native_options, &extra_options) == -1) {
		return (set_errno(EINVAL));
	}

	if (pid > maxpid) {
		return (set_errno(ECHILD));
	}

	/*
	 * While not listed as a valid return code, Linux's wait4(2) does,
	 * in fact, get an EFAULT if either the status pointer or rusage
	 * pointer is invalid. Since a failed waitpid should leave child
	 * process in a state where a future wait4(2) will succeed, we
	 * check them by copying out the values their buffers originally
	 * contained.  (We need to do this as a failed system call should
	 * never affect the contents of a passed buffer.)
	 *
	 * This will fail if the buffers in question are write-only.
	 */
	if (statusp != NULL) {
		if (copyin(statusp, &status, sizeof (status)) != 0 ||
		    copyout(&status, statusp, sizeof (status)) != 0) {
			return (set_errno(EFAULT));
		}
	}

	/*
	 * Do the same check for the "struct rusage" pointer, which differs
	 * in size for 32- and 64-bit processes.
	 */
	if (rup != NULL) {
		struct rusage ru;
		void *krup = &ru;
		size_t rusz = sizeof (ru);
#if defined(_SYSCALL32_IMPL)
		struct rusage32 ru32;

		if (get_udatamodel() != DATAMODEL_NATIVE) {
			krup = &ru32;
			rusz = sizeof (ru32);
		}
#endif

		if (copyin(rup, krup, rusz) != 0 ||
		    copyout(krup, rup, rusz) != 0) {
			return (set_errno(EFAULT));
		}
	}

	if (pid < -1) {
		idtype = P_PGID;
		id = -pid;
	} else if (pid == -1) {
		idtype = P_ALL;
		id = 0;
	} else if (pid == 0) {
		idtype = P_PGID;
		mutex_enter(&pidlock);
		id = curproc->p_pgrp;
		mutex_exit(&pidlock);
	} else {
		idtype = P_PID;
		id = pid;
	}

	native_options |= (WEXITED | WTRAPPED);

	if ((error = lx_call_waitid(idtype, id, &info, native_options,
	    extra_options)) != 0) {
		return (set_errno(error));
	}

	/*
	 * If the WNOHANG flag was specified and no child was found return 0.
	 */
	if ((native_options & WNOHANG) && info.si_pid == 0) {
		return (0);
	}

	status = lx_wstat(info.si_code, info.si_status);

	/*
	 * Unfortunately if this attempt to copy out either the status or the
	 * rusage fails, the process will be in an inconsistent state as
	 * subsequent calls to wait for the same child will fail where they
	 * should succeed on a Linux system. This, however, is rather
	 * unlikely since we tested the validity of both above.
	 */
	if (statusp != NULL) {
		if (copyout(&status, statusp, sizeof (status)) != 0) {
			return (set_errno(EFAULT));
		}
	}

	if (rup != NULL) {
		if ((error = rusagesys(_RUSAGESYS_GETRUSAGE_CHLD, rup, NULL,
		    NULL, NULL)) != 0) {
			return (set_errno(error));
		}
	}

	return (info.si_pid);
}

long
lx_waitpid(uintptr_t p1, uintptr_t p2, uintptr_t p3)
{
	return (lx_wait4(p1, p2, p3, (uintptr_t)NULL));
}

long
lx_waitid(uintptr_t idtype, uintptr_t id, uintptr_t infop, uintptr_t opt)
{
	int error;
	int native_options, extra_options;
	k_siginfo_t info = { 0 };

	if (ltos_options(opt, &native_options, &extra_options) == -1) {
		return (set_errno(EINVAL));
	}

	if (((opt) & (LX_WEXITED | LX_WSTOPPED | LX_WCONTINUED)) == 0) {
		return (set_errno(EINVAL));
	}

	switch (idtype) {
	case LX_P_ALL:
		idtype = P_ALL;
		break;
	case LX_P_PID:
		idtype = P_PID;
		break;
	case LX_P_GID:
		idtype = P_PGID;
		break;
	default:
		return (set_errno(EINVAL));
	}

	if ((error = lx_call_waitid(idtype, id, &info, native_options,
	    extra_options)) != 0) {
		return (set_errno(error));
	}

	/*
	 * If the WNOHANG flag was specified and no child was found return 0.
	 */
	if ((native_options & WNOHANG) && info.si_pid == 0) {
		return (0);
	}

#if defined(_SYSCALL32_IMPL)
	if (get_udatamodel() != DATAMODEL_NATIVE) {
		return (stol_ksiginfo32_copyout(&info, (void *)infop));
	} else
#endif
	{
		return (stol_ksiginfo_copyout(&info, (void *)infop));
	}
}