VirtualBox

source: vbox/trunk/src/VBox/Runtime/r3/posix/process-posix.cpp@ 29698

Last change on this file since 29698 was 29636, checked in by vboxsync, 15 years ago

Runtime/process-posix: consume less stack during RTProcCreateEx

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 18.4 KB
Line 
1/* $Id: process-posix.cpp 29636 2010-05-18 13:43:55Z vboxsync $ */
2/** @file
3 * IPRT - Process, POSIX.
4 */
5
6/*
7 * Copyright (C) 2006-2010 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * The contents of this file may alternatively be used under the terms
18 * of the Common Development and Distribution License Version 1.0
19 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
20 * VirtualBox OSE distribution, in which case the provisions of the
21 * CDDL are applicable instead of those of the GPL.
22 *
23 * You may elect to license modified versions of this file under the
24 * terms and conditions of either the GPL or the CDDL or both.
25 */
26
27
28
29/*******************************************************************************
30* Header Files *
31*******************************************************************************/
32#define LOG_GROUP RTLOGGROUP_PROCESS
33#include <unistd.h>
34#include <stdlib.h>
35#include <errno.h>
36#include <sys/types.h>
37#include <sys/stat.h>
38#include <sys/wait.h>
39#include <fcntl.h>
40#include <signal.h>
41#if defined(RT_OS_LINUX) || defined(RT_OS_SOLARIS)
42# include <crypt.h>
43# include <pwd.h>
44# include <shadow.h>
45#endif
46#if defined(RT_OS_LINUX) || defined(RT_OS_OS2)
47# define HAVE_POSIX_SPAWN 1
48#endif
49#ifdef HAVE_POSIX_SPAWN
50# include <spawn.h>
51#endif
52#ifdef RT_OS_DARWIN
53# include <mach-o/dyld.h>
54#endif
55
56#include <iprt/process.h>
57#include "internal/iprt.h"
58
59#include <iprt/assert.h>
60#include <iprt/env.h>
61#include <iprt/err.h>
62#include <iprt/file.h>
63#include <iprt/pipe.h>
64#include <iprt/socket.h>
65#include <iprt/string.h>
66#include <iprt/mem.h>
67#include "internal/process.h"
68
69
70/**
71 * Check the credentials and return the gid/uid of user.
72 *
73 * @param pszUser username
74 * @param pszPasswd password
75 * @param gid where to store the GID of the user
76 * @param uid where to store the UID of the user
77 * @returns IPRT status code
78 */
79static int rtCheckCredentials(const char *pszUser, const char *pszPasswd, gid_t *gid, uid_t *uid)
80{
81#if defined(RT_OS_LINUX)
82 struct passwd *pw;
83
84 pw = getpwnam(pszUser);
85 if (!pw)
86 return VERR_PERMISSION_DENIED;
87
88 if (!pszPasswd)
89 pszPasswd = "";
90
91 struct spwd *spwd;
92 /* works only if /etc/shadow is accessible */
93 spwd = getspnam(pszUser);
94 if (spwd)
95 pw->pw_passwd = spwd->sp_pwdp;
96
97 /* be reentrant */
98 struct crypt_data *data = (struct crypt_data*)RTMemTmpAllocZ(sizeof(*data));
99 char *pszEncPasswd = crypt_r(pszPasswd, pw->pw_passwd, data);
100 if (strcmp(pszEncPasswd, pw->pw_passwd))
101 return VERR_PERMISSION_DENIED;
102 RTMemTmpFree(data);
103
104 *gid = pw->pw_gid;
105 *uid = pw->pw_uid;
106 return VINF_SUCCESS;
107
108#elif defined(RT_OS_SOLARIS)
109 struct passwd *ppw, pw;
110 char szBuf[1024];
111
112 if (getpwnam_r(pszUser, &pw, szBuf, sizeof(szBuf), &ppw) != 0 || ppw == NULL)
113 return VERR_PERMISSION_DENIED;
114
115 if (!pszPasswd)
116 pszPasswd = "";
117
118 struct spwd spwd;
119 char szPwdBuf[1024];
120 /* works only if /etc/shadow is accessible */
121 if (getspnam_r(pszUser, &spwd, szPwdBuf, sizeof(szPwdBuf)) != NULL)
122 ppw->pw_passwd = spwd.sp_pwdp;
123
124 char *pszEncPasswd = crypt(pszPasswd, ppw->pw_passwd);
125 if (strcmp(pszEncPasswd, ppw->pw_passwd))
126 return VERR_PERMISSION_DENIED;
127
128 *gid = ppw->pw_gid;
129 *uid = ppw->pw_uid;
130 return VINF_SUCCESS;
131
132#else
133 return VERR_PERMISSION_DENIED;
134#endif
135}
136
137
138RTR3DECL(int) RTProcCreate(const char *pszExec, const char * const *papszArgs, RTENV Env, unsigned fFlags, PRTPROCESS pProcess)
139{
140 return RTProcCreateEx(pszExec, papszArgs, Env, fFlags,
141 NULL, NULL, NULL, /* standard handles */
142 NULL /*pszAsUser*/, NULL /* pszPassword*/,
143 pProcess);
144}
145
146
147RTR3DECL(int) RTProcCreateEx(const char *pszExec, const char * const *papszArgs, RTENV hEnv, uint32_t fFlags,
148 PCRTHANDLE phStdIn, PCRTHANDLE phStdOut, PCRTHANDLE phStdErr, const char *pszAsUser,
149 const char *pszPassword, PRTPROCESS phProcess)
150{
151 int rc;
152
153 /*
154 * Input validation
155 */
156 AssertPtrReturn(pszExec, VERR_INVALID_POINTER);
157 AssertReturn(*pszExec, VERR_INVALID_PARAMETER);
158 AssertReturn(!(fFlags & ~(RTPROC_FLAGS_DAEMONIZE_DEPRECATED | RTPROC_FLAGS_DETACHED | RTPROC_FLAGS_SERVICE)), VERR_INVALID_PARAMETER);
159 AssertReturn(!(fFlags & RTPROC_FLAGS_DETACHED) || !phProcess, VERR_INVALID_PARAMETER);
160 AssertReturn(hEnv != NIL_RTENV, VERR_INVALID_PARAMETER);
161 const char * const *papszEnv = RTEnvGetExecEnvP(hEnv);
162 AssertPtrReturn(papszEnv, VERR_INVALID_HANDLE);
163 AssertPtrReturn(papszArgs, VERR_INVALID_PARAMETER);
164 /** @todo search the PATH (add flag for this). */
165 AssertPtrNullReturn(pszAsUser, VERR_INVALID_POINTER);
166 AssertReturn(!pszAsUser || *pszAsUser, VERR_INVALID_PARAMETER);
167 AssertReturn(!pszPassword || pszAsUser, VERR_INVALID_PARAMETER);
168 AssertPtrNullReturn(pszPassword, VERR_INVALID_POINTER);
169
170 /*
171 * Get the file descriptors for the handles we've been passed.
172 */
173 PCRTHANDLE paHandles[3] = { phStdIn, phStdOut, phStdErr };
174 int aStdFds[3] = { -1, -1, -1 };
175 for (int i = 0; i < 3; i++)
176 {
177 if (paHandles[i])
178 {
179 AssertPtrReturn(paHandles[i], VERR_INVALID_POINTER);
180 switch (paHandles[i]->enmType)
181 {
182 case RTHANDLETYPE_FILE:
183 aStdFds[i] = paHandles[i]->u.hFile != NIL_RTFILE
184 ? (int)RTFileToNative(paHandles[i]->u.hFile)
185 : -2 /* close it */;
186 break;
187
188 case RTHANDLETYPE_PIPE:
189 aStdFds[i] = paHandles[i]->u.hPipe != NIL_RTPIPE
190 ? (int)RTPipeToNative(paHandles[i]->u.hPipe)
191 : -2 /* close it */;
192 break;
193
194 case RTHANDLETYPE_SOCKET:
195 aStdFds[i] = paHandles[i]->u.hSocket != NIL_RTSOCKET
196 ? (int)RTSocketToNative(paHandles[i]->u.hSocket)
197 : -2 /* close it */;
198 break;
199
200 default:
201 AssertMsgFailedReturn(("%d: %d\n", i, paHandles[i]->enmType), VERR_INVALID_PARAMETER);
202 }
203 /** @todo check the close-on-execness of these handles? */
204 }
205 }
206
207 for (int i = 0; i < 3; i++)
208 if (aStdFds[i] == i)
209 aStdFds[i] = -1;
210
211 for (int i = 0; i < 3; i++)
212 AssertMsgReturn(aStdFds[i] < 0 || aStdFds[i] > i,
213 ("%i := %i not possible because we're lazy\n", i, aStdFds[i]),
214 VERR_NOT_SUPPORTED);
215
216 /*
217 * Resolve the user id if specified.
218 */
219 uid_t uid = ~(uid_t)0;
220 gid_t gid = ~(gid_t)0;
221 if (pszAsUser)
222 {
223 rc = rtCheckCredentials(pszAsUser, pszPassword, &gid, &uid);
224 if (RT_FAILURE(rc))
225 return rc;
226 }
227
228 /*
229 * Check for execute access to the file.
230 */
231 if (access(pszExec, X_OK))
232 {
233 rc = RTErrConvertFromErrno(errno);
234 AssertMsgFailed(("'%s' %Rrc!\n", pszExec, rc));
235 return rc;
236 }
237
238 /*
239 * Spawn the child.
240 *
241 * HACK ALERT! Put the process into a new process group with pgid = pid
242 * to make sure it differs from that of the parent process to ensure that
243 * the IPRT waipit call doesn't race anyone (read XPCOM) doing group wide
244 * waits.
245 */
246 pid_t pid = -1;
247#ifdef HAVE_POSIX_SPAWN
248 /** @todo OS/2: implement DETACHED (BACKGROUND stuff), see VbglR3Daemonize. */
249 /** @todo Try do the detach thing with posix spawn. */
250 if ( !(fFlags & (RTPROC_FLAGS_DAEMONIZE_DEPRECATED | RTPROC_FLAGS_DETACHED))
251 && uid == ~(uid_t)0
252 && gid == ~(gid_t)0
253 )
254 {
255 /* Spawn attributes. */
256 posix_spawnattr_t Attr;
257 rc = posix_spawnattr_init(&Attr);
258 if (!rc)
259 {
260# ifndef RT_OS_OS2 /* We don't need this on OS/2 and I don't recall if it's actually implemented. */
261 rc = posix_spawnattr_setflags(&Attr, POSIX_SPAWN_SETPGROUP);
262 Assert(rc == 0);
263 if (!rc)
264 {
265 rc = posix_spawnattr_setpgroup(&Attr, 0 /* pg == child pid */);
266 Assert(rc == 0);
267 }
268# endif
269
270 /* File changes. */
271 posix_spawn_file_actions_t FileActions;
272 posix_spawn_file_actions_t *pFileActions = NULL;
273 if (aStdFds[0] != -1 || aStdFds[1] != -1 || aStdFds[2] != -1)
274 {
275 rc = posix_spawn_file_actions_init(&FileActions);
276 if (!rc)
277 {
278 pFileActions = &FileActions;
279 for (int i = 0; i < 3; i++)
280 {
281 int fd = aStdFds[i];
282 if (fd == -2)
283 rc = posix_spawn_file_actions_addclose(&FileActions, i);
284 else if (fd >= 0 && fd != i)
285 {
286 rc = posix_spawn_file_actions_adddup2(&FileActions, fd, i);
287 if (!rc)
288 {
289 for (int j = i + 1; j < 3; j++)
290 if (aStdFds[j] == fd)
291 {
292 fd = -1;
293 break;
294 }
295 if (fd >= 0)
296 rc = posix_spawn_file_actions_addclose(&FileActions, fd);
297 }
298 }
299 if (rc)
300 break;
301 }
302 }
303 }
304
305 if (!rc)
306 rc = posix_spawn(&pid, pszExec, pFileActions, &Attr, (char * const *)papszArgs,
307 (char * const *)papszEnv);
308
309 /* cleanup */
310 int rc2 = posix_spawnattr_destroy(&Attr); Assert(rc2 == 0); NOREF(rc2);
311 if (pFileActions)
312 {
313 rc2 = posix_spawn_file_actions_destroy(pFileActions);
314 Assert(rc2 == 0);
315 }
316
317 /* return on success.*/
318 if (!rc)
319 {
320 if (phProcess)
321 *phProcess = pid;
322 return VINF_SUCCESS;
323 }
324 }
325 }
326 else
327#endif
328 {
329 pid = fork();
330 if (!pid)
331 {
332 setpgid(0, 0); /* see comment above */
333
334 /*
335 * Change group and user if requested.
336 */
337#if 1 /** @todo This needs more work, see suplib/hardening. */
338 if (gid != ~(gid_t)0)
339 {
340 if (setgid(gid))
341 exit(126);
342 }
343
344 if (uid != ~(uid_t)0)
345 {
346 if (setuid(uid))
347 exit(126);
348 }
349#endif
350
351 /*
352 * Apply changes to the standard file descriptor and stuff.
353 */
354 for (int i = 0; i < 3; i++)
355 {
356 int fd = aStdFds[i];
357 if (fd == -2)
358 close(i);
359 else if (fd >= 0)
360 {
361 int rc2 = dup2(fd, i);
362 if (rc2 != i)
363 exit(125);
364 for (int j = i + 1; j < 3; j++)
365 if (aStdFds[j] == fd)
366 {
367 fd = -1;
368 break;
369 }
370 if (fd >= 0)
371 close(fd);
372 }
373 }
374
375 /*
376 * Daemonize the process if requested.
377 */
378 if (fFlags & (RTPROC_FLAGS_DAEMONIZE_DEPRECATED | RTPROC_FLAGS_DETACHED))
379 {
380 rc = RTProcDaemonizeUsingFork(true /*fNoChDir*/,
381 !(fFlags & RTPROC_FLAGS_DAEMONIZE_DEPRECATED) /*fNoClose*/,
382 NULL /* pszPidFile */);
383 if (RT_FAILURE(rc))
384 {
385 /* parent */
386 AssertReleaseMsgFailed(("RTProcDaemonize returns %Rrc errno=%d\n", rc, errno));
387 exit(127);
388 }
389 /* daemonized child */
390 }
391
392 /*
393 * Finally, execute the requested program.
394 */
395 rc = execve(pszExec, (char * const *)papszArgs, (char * const *)papszEnv);
396 AssertReleaseMsgFailed(("execve returns %d errno=%d\n", rc, errno));
397 exit(127);
398 }
399 if (pid > 0)
400 {
401 if (phProcess)
402 *phProcess = pid;
403 return VINF_SUCCESS;
404 }
405 rc = errno;
406 }
407
408
409 return VERR_NOT_IMPLEMENTED;
410}
411
412
413RTR3DECL(int) RTProcWait(RTPROCESS Process, unsigned fFlags, PRTPROCSTATUS pProcStatus)
414{
415 int rc;
416 do rc = RTProcWaitNoResume(Process, fFlags, pProcStatus);
417 while (rc == VERR_INTERRUPTED);
418 return rc;
419}
420
421RTR3DECL(int) RTProcWaitNoResume(RTPROCESS Process, unsigned fFlags, PRTPROCSTATUS pProcStatus)
422{
423 /*
424 * Validate input.
425 */
426 if (Process <= 0)
427 {
428 AssertMsgFailed(("Invalid Process=%d\n", Process));
429 return VERR_INVALID_PARAMETER;
430 }
431 if (fFlags & ~(RTPROCWAIT_FLAGS_NOBLOCK | RTPROCWAIT_FLAGS_BLOCK))
432 {
433 AssertMsgFailed(("Invalid flags %#x\n", fFlags));
434 return VERR_INVALID_PARAMETER;
435 }
436
437 /*
438 * Performe the wait.
439 */
440 int iStatus = 0;
441 int rc = waitpid(Process, &iStatus, fFlags & RTPROCWAIT_FLAGS_NOBLOCK ? WNOHANG : 0);
442 if (rc > 0)
443 {
444 /*
445 * Fill in the status structure.
446 */
447 if (pProcStatus)
448 {
449 if (WIFEXITED(iStatus))
450 {
451 pProcStatus->enmReason = RTPROCEXITREASON_NORMAL;
452 pProcStatus->iStatus = WEXITSTATUS(iStatus);
453 }
454 else if (WIFSIGNALED(iStatus))
455 {
456 pProcStatus->enmReason = RTPROCEXITREASON_SIGNAL;
457 pProcStatus->iStatus = WTERMSIG(iStatus);
458 }
459 else
460 {
461 Assert(!WIFSTOPPED(iStatus));
462 pProcStatus->enmReason = RTPROCEXITREASON_ABEND;
463 pProcStatus->iStatus = iStatus;
464 }
465 }
466 return VINF_SUCCESS;
467 }
468
469 /*
470 * Child running?
471 */
472 if (!rc)
473 {
474 Assert(fFlags & RTPROCWAIT_FLAGS_NOBLOCK);
475 return VERR_PROCESS_RUNNING;
476 }
477
478 /*
479 * Figure out which error to return.
480 */
481 int iErr = errno;
482 if (iErr == ECHILD)
483 return VERR_PROCESS_NOT_FOUND;
484 return RTErrConvertFromErrno(iErr);
485}
486
487
488RTR3DECL(int) RTProcTerminate(RTPROCESS Process)
489{
490 if (!kill(Process, SIGKILL))
491 return VINF_SUCCESS;
492 return RTErrConvertFromErrno(errno);
493}
494
495
496RTR3DECL(uint64_t) RTProcGetAffinityMask()
497{
498 // @todo
499 return 1;
500}
501
502
503RTR3DECL(int) RTProcDaemonizeUsingFork(bool fNoChDir, bool fNoClose, const char *pszPidfile)
504{
505 /*
506 * Fork the child process in a new session and quit the parent.
507 *
508 * - fork once and create a new session (setsid). This will detach us
509 * from the controlling tty meaning that we won't receive the SIGHUP
510 * (or any other signal) sent to that session.
511 * - The SIGHUP signal is ignored because the session/parent may throw
512 * us one before we get to the setsid.
513 * - When the parent exit(0) we will become an orphan and re-parented to
514 * the init process.
515 * - Because of the sometimes unexpected semantics of assigning the
516 * controlling tty automagically when a session leader first opens a tty,
517 * we will fork() once more to get rid of the session leadership role.
518 */
519
520 /* We start off by opening the pidfile, so that we can fail straight away
521 * if it already exists. */
522 int fdPidfile = -1;
523 if (pszPidfile != NULL)
524 {
525 /* @note the exclusive create is not guaranteed on all file
526 * systems (e.g. NFSv2) */
527 if ((fdPidfile = open(pszPidfile, O_RDWR | O_CREAT | O_EXCL, 0644)) == -1)
528 return RTErrConvertFromErrno(errno);
529 }
530
531 /* Ignore SIGHUP straight away. */
532 struct sigaction OldSigAct;
533 struct sigaction SigAct;
534 memset(&SigAct, 0, sizeof(SigAct));
535 SigAct.sa_handler = SIG_IGN;
536 int rcSigAct = sigaction(SIGHUP, &SigAct, &OldSigAct);
537
538 /* First fork, to become independent process. */
539 pid_t pid = fork();
540 if (pid == -1)
541 return RTErrConvertFromErrno(errno);
542 if (pid != 0)
543 {
544 /* Parent exits, no longer necessary. The child gets reparented
545 * to the init process. */
546 exit(0);
547 }
548
549 /* Create new session, fix up the standard file descriptors and the
550 * current working directory. */
551 pid_t newpgid = setsid();
552 int SavedErrno = errno;
553 if (rcSigAct != -1)
554 sigaction(SIGHUP, &OldSigAct, NULL);
555 if (newpgid == -1)
556 return RTErrConvertFromErrno(SavedErrno);
557
558 if (!fNoClose)
559 {
560 /* Open stdin(0), stdout(1) and stderr(2) as /dev/null. */
561 int fd = open("/dev/null", O_RDWR);
562 if (fd == -1) /* paranoia */
563 {
564 close(STDIN_FILENO);
565 close(STDOUT_FILENO);
566 close(STDERR_FILENO);
567 fd = open("/dev/null", O_RDWR);
568 }
569 if (fd != -1)
570 {
571 dup2(fd, STDIN_FILENO);
572 dup2(fd, STDOUT_FILENO);
573 dup2(fd, STDERR_FILENO);
574 if (fd > 2)
575 close(fd);
576 }
577 }
578
579 if (!fNoChDir)
580 {
581 int rcChdir = chdir("/");
582 }
583
584 /* Second fork to lose session leader status. */
585 pid = fork();
586 if (pid == -1)
587 return RTErrConvertFromErrno(errno);
588
589 if (pid != 0)
590 {
591 /* Write the pid file, this is done in the parent, before exiting. */
592 if (fdPidfile != -1)
593 {
594 char szBuf[256];
595 size_t cbPid = RTStrPrintf(szBuf, sizeof(szBuf), "%d\n", pid);
596 int rcWrite = write(fdPidfile, szBuf, cbPid);
597 close(fdPidfile);
598 }
599 exit(0);
600 }
601
602 return VINF_SUCCESS;
603}
604
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette