VirtualBox

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

Last change on this file since 57871 was 57871, checked in by vboxsync, 10 years ago

process-creation-posix.cpp: darwin build fix.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 33.0 KB
Line 
1/* $Id: process-creation-posix.cpp 57871 2015-09-23 13:59:12Z vboxsync $ */
2/** @file
3 * IPRT - Process Creation, POSIX.
4 */
5
6/*
7 * Copyright (C) 2006-2015 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.215389.xyz. 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* Header Files *
30*********************************************************************************************************************************/
31#define LOG_GROUP RTLOGGROUP_PROCESS
32#include <iprt/cdefs.h>
33
34#include <unistd.h>
35#include <stdlib.h>
36#include <errno.h>
37#include <sys/types.h>
38#include <sys/stat.h>
39#include <sys/wait.h>
40#include <fcntl.h>
41#include <signal.h>
42#include <grp.h>
43#include <paths.h>
44#include <pwd.h>
45#if defined(RT_OS_LINUX) || defined(RT_OS_SOLARIS)
46# include <crypt.h>
47# include <shadow.h>
48#endif
49
50#if defined(RT_OS_LINUX) || defined(RT_OS_OS2)
51/* While Solaris has posix_spawn() of course we don't want to use it as
52 * we need to have the child in a different process contract, no matter
53 * whether it is started detached or not. */
54# define HAVE_POSIX_SPAWN 1
55#endif
56#if defined(RT_OS_DARWIN) && defined(MAC_OS_X_VERSION_MIN_REQUIRED)
57# if MAC_OS_X_VERSION_MIN_REQUIRED >= 1050
58# define HAVE_POSIX_SPAWN 1
59# endif
60#endif
61#ifdef HAVE_POSIX_SPAWN
62# include <spawn.h>
63#endif
64
65#ifdef RT_OS_DARWIN
66# include <mach-o/dyld.h>
67#endif
68#ifdef RT_OS_SOLARIS
69# include <limits.h>
70# include <sys/ctfs.h>
71# include <sys/contract/process.h>
72# include <libcontract.h>
73#endif
74
75#include <iprt/process.h>
76#include "internal/iprt.h"
77
78#include <iprt/assert.h>
79#include <iprt/env.h>
80#include <iprt/err.h>
81#include <iprt/file.h>
82#include <iprt/path.h>
83#include <iprt/pipe.h>
84#include <iprt/socket.h>
85#include <iprt/string.h>
86#include <iprt/mem.h>
87#include "internal/process.h"
88
89
90/**
91 * Check the credentials and return the gid/uid of user.
92 *
93 * @param pszUser username
94 * @param pszPasswd password
95 * @param gid where to store the GID of the user
96 * @param uid where to store the UID of the user
97 * @returns IPRT status code
98 */
99static int rtCheckCredentials(const char *pszUser, const char *pszPasswd, gid_t *pGid, uid_t *pUid)
100{
101#if defined(RT_OS_LINUX)
102 struct passwd *pw;
103
104 pw = getpwnam(pszUser);
105 if (!pw)
106 return VERR_AUTHENTICATION_FAILURE;
107
108 if (!pszPasswd)
109 pszPasswd = "";
110
111 struct spwd *spwd;
112 /* works only if /etc/shadow is accessible */
113 spwd = getspnam(pszUser);
114 if (spwd)
115 pw->pw_passwd = spwd->sp_pwdp;
116
117 /* Default fCorrect=true if no password specified. In that case, pw->pw_passwd
118 * must be NULL (no password set for this user). Fail if a password is specified
119 * but the user does not have one assigned. */
120 int fCorrect = !pszPasswd || !*pszPasswd;
121 if (pw->pw_passwd && *pw->pw_passwd)
122 {
123 struct crypt_data *data = (struct crypt_data*)RTMemTmpAllocZ(sizeof(*data));
124 /* be reentrant */
125 char *pszEncPasswd = crypt_r(pszPasswd, pw->pw_passwd, data);
126 fCorrect = pszEncPasswd && !strcmp(pszEncPasswd, pw->pw_passwd);
127 RTMemTmpFree(data);
128 }
129 if (!fCorrect)
130 return VERR_AUTHENTICATION_FAILURE;
131
132 *pGid = pw->pw_gid;
133 *pUid = pw->pw_uid;
134 return VINF_SUCCESS;
135
136#elif defined(RT_OS_SOLARIS)
137 struct passwd *ppw, pw;
138 char szBuf[1024];
139
140 if (getpwnam_r(pszUser, &pw, szBuf, sizeof(szBuf), &ppw) != 0 || ppw == NULL)
141 return VERR_AUTHENTICATION_FAILURE;
142
143 if (!pszPasswd)
144 pszPasswd = "";
145
146 struct spwd spwd;
147 char szPwdBuf[1024];
148 /* works only if /etc/shadow is accessible */
149 if (getspnam_r(pszUser, &spwd, szPwdBuf, sizeof(szPwdBuf)) != NULL)
150 ppw->pw_passwd = spwd.sp_pwdp;
151
152 char *pszEncPasswd = crypt(pszPasswd, ppw->pw_passwd);
153 if (strcmp(pszEncPasswd, ppw->pw_passwd))
154 return VERR_AUTHENTICATION_FAILURE;
155
156 *pGid = ppw->pw_gid;
157 *pUid = ppw->pw_uid;
158 return VINF_SUCCESS;
159
160#else
161 NOREF(pszUser); NOREF(pszPasswd); NOREF(pGid); NOREF(pUid);
162 return VERR_AUTHENTICATION_FAILURE;
163#endif
164}
165
166
167#ifdef RT_OS_SOLARIS
168/** @todo the error reporting of the Solaris process contract code could be
169 * a lot better, but essentially it is not meant to run into errors after
170 * the debugging phase. */
171static int rtSolarisContractPreFork(void)
172{
173 int templateFd = open64(CTFS_ROOT "/process/template", O_RDWR);
174 if (templateFd < 0)
175 return -1;
176
177 /* Set template parameters and event sets. */
178 if (ct_pr_tmpl_set_param(templateFd, CT_PR_PGRPONLY))
179 {
180 close(templateFd);
181 return -1;
182 }
183 if (ct_pr_tmpl_set_fatal(templateFd, CT_PR_EV_HWERR))
184 {
185 close(templateFd);
186 return -1;
187 }
188 if (ct_tmpl_set_critical(templateFd, 0))
189 {
190 close(templateFd);
191 return -1;
192 }
193 if (ct_tmpl_set_informative(templateFd, CT_PR_EV_HWERR))
194 {
195 close(templateFd);
196 return -1;
197 }
198
199 /* Make this the active template for the process. */
200 if (ct_tmpl_activate(templateFd))
201 {
202 close(templateFd);
203 return -1;
204 }
205
206 return templateFd;
207}
208
209static void rtSolarisContractPostForkChild(int templateFd)
210{
211 if (templateFd == -1)
212 return;
213
214 /* Clear the active template. */
215 ct_tmpl_clear(templateFd);
216 close(templateFd);
217}
218
219static void rtSolarisContractPostForkParent(int templateFd, pid_t pid)
220{
221 if (templateFd == -1)
222 return;
223
224 /* Clear the active template. */
225 int cleared = ct_tmpl_clear(templateFd);
226 close(templateFd);
227
228 /* If the clearing failed or the fork failed there's nothing more to do. */
229 if (cleared || pid <= 0)
230 return;
231
232 /* Look up the contract which was created by this thread. */
233 int statFd = open64(CTFS_ROOT "/process/latest", O_RDONLY);
234 if (statFd == -1)
235 return;
236 ct_stathdl_t statHdl;
237 if (ct_status_read(statFd, CTD_COMMON, &statHdl))
238 {
239 close(statFd);
240 return;
241 }
242 ctid_t ctId = ct_status_get_id(statHdl);
243 ct_status_free(statHdl);
244 close(statFd);
245 if (ctId < 0)
246 return;
247
248 /* Abandon this contract we just created. */
249 char ctlPath[PATH_MAX];
250 size_t len = snprintf(ctlPath, sizeof(ctlPath),
251 CTFS_ROOT "/process/%ld/ctl", (long)ctId);
252 if (len >= sizeof(ctlPath))
253 return;
254 int ctlFd = open64(ctlPath, O_WRONLY);
255 if (statFd == -1)
256 return;
257 if (ct_ctl_abandon(ctlFd) < 0)
258 {
259 close(ctlFd);
260 return;
261 }
262 close(ctlFd);
263}
264
265#endif /* RT_OS_SOLARIS */
266
267
268RTR3DECL(int) RTProcCreate(const char *pszExec, const char * const *papszArgs, RTENV Env, unsigned fFlags, PRTPROCESS pProcess)
269{
270 return RTProcCreateEx(pszExec, papszArgs, Env, fFlags,
271 NULL, NULL, NULL, /* standard handles */
272 NULL /*pszAsUser*/, NULL /* pszPassword*/,
273 pProcess);
274}
275
276
277/**
278 * Adjust the profile environment after forking the child process and changing
279 * the UID.
280 *
281 * @returns IRPT status code.
282 * @param hEnvToUse The environment we're going to use with execve.
283 * @param fFlags The process creation flags.
284 * @param hEnv The environment passed in by the user.
285 */
286static int rtProcPosixAdjustProfileEnvFromChild(RTENV hEnvToUse, uint32_t fFlags, RTENV hEnv)
287{
288 int rc = VINF_SUCCESS;
289#ifdef RT_OS_DARWIN
290 if ( RT_SUCCESS(rc)
291 && (!(fFlags & RTPROC_FLAGS_ENV_CHANGE_RECORD) || RTEnvExistEx(hEnv, "TMPDIR")) )
292 {
293 char szValue[_4K];
294 size_t cbNeeded = confstr(_CS_DARWIN_USER_TEMP_DIR, szValue, sizeof(szValue));
295 if (cbNeeded > 0 && cbNeeded < sizeof(szValue))
296 {
297 char *pszTmp;
298 rc = RTStrCurrentCPToUtf8(&pszTmp, szValue);
299 if (RT_SUCCESS(rc))
300 {
301 rc = RTEnvSetEx(hEnvToUse, "TMPDIR", pszTmp);
302 RTStrFree(pszTmp);
303 }
304 }
305 else
306 rc = VERR_BUFFER_OVERFLOW;
307 }
308#endif
309 return rc;
310}
311
312
313/**
314 * Create a very very basic environment for a user.
315 *
316 * @returns IPRT status code.
317 * @param phEnvToUse Where to return the created environment.
318 * @param pszUser The user name for the profile.
319 */
320static int rtProcPosixCreateProfileEnv(PRTENV phEnvToUse, const char *pszUser)
321{
322 struct passwd Pwd;
323 struct passwd *pPwd = NULL;
324 char achBuf[_4K];
325 int rc;
326 errno = 0;
327 if (pszUser)
328 rc = getpwnam_r(pszUser, &Pwd, achBuf, sizeof(achBuf), &pPwd);
329 else
330 rc = getpwuid_r(getuid(), &Pwd, achBuf, sizeof(achBuf), &pPwd);
331 if (rc == 0 && pPwd)
332 {
333 char *pszDir;
334 rc = RTStrCurrentCPToUtf8(&pszDir, pPwd->pw_dir);
335 if (RT_SUCCESS(rc))
336 {
337 char *pszShell;
338 rc = RTStrCurrentCPToUtf8(&pszShell, pPwd->pw_shell);
339 if (RT_SUCCESS(rc))
340 {
341 char *pszUserFree = NULL;
342 if (!pszUser)
343 {
344 rc = RTStrCurrentCPToUtf8(&pszUserFree, pPwd->pw_name);
345 if (RT_SUCCESS(rc))
346 pszUser = pszUserFree;
347 }
348 if (RT_SUCCESS(rc))
349 {
350 rc = RTEnvCreate(phEnvToUse);
351 if (RT_SUCCESS(rc))
352 {
353 RTENV hEnvToUse = *phEnvToUse;
354
355 rc = RTEnvSetEx(hEnvToUse, "HOME", pszDir);
356 if (RT_SUCCESS(rc))
357 rc = RTEnvSetEx(hEnvToUse, "SHELL", pszShell);
358 if (RT_SUCCESS(rc))
359 rc = RTEnvSetEx(hEnvToUse, "USER", pszUser);
360 if (RT_SUCCESS(rc))
361 rc = RTEnvSetEx(hEnvToUse, "LOGNAME", pszUser);
362
363 if (RT_SUCCESS(rc))
364 rc = RTEnvSetEx(hEnvToUse, "PATH", pPwd->pw_uid == 0 ? _PATH_STDPATH : _PATH_DEFPATH);
365
366 if (RT_SUCCESS(rc))
367 {
368 RTStrPrintf(achBuf, sizeof(achBuf), "%s/%s", _PATH_MAILDIR, pszUser);
369 rc = RTEnvSetEx(hEnvToUse, "MAIL", achBuf);
370 }
371
372#ifdef RT_OS_DARWIN
373 if (RT_SUCCESS(rc) && !pszUserFree)
374 {
375 size_t cbNeeded = confstr(_CS_DARWIN_USER_TEMP_DIR, achBuf, sizeof(achBuf));
376 if (cbNeeded > 0 && cbNeeded < sizeof(achBuf))
377 {
378 char *pszTmp;
379 rc = RTStrCurrentCPToUtf8(&pszTmp, achBuf);
380 if (RT_SUCCESS(rc))
381 {
382 rc = RTEnvSetEx(hEnvToUse, "TMPDIR", pszTmp);
383 RTStrFree(pszTmp);
384 }
385 }
386 else
387 rc = VERR_BUFFER_OVERFLOW;
388 }
389#endif
390
391 /** @todo load /etc/environment, /etc/profile.env and ~/.pam_environment? */
392
393 if (RT_FAILURE(rc))
394 RTEnvDestroy(hEnvToUse);
395 }
396 RTStrFree(pszUserFree);
397 }
398 RTStrFree(pszShell);
399 }
400 RTStrFree(pszDir);
401 }
402 }
403 else
404 rc = errno ? RTErrConvertFromErrno(errno) : VERR_ACCESS_DENIED;
405 return rc;
406}
407
408
409/**
410 * RTPathTraverseList callback used by RTProcCreateEx to locate the executable.
411 */
412static DECLCALLBACK(int) rtPathFindExec(char const *pchPath, size_t cchPath, void *pvUser1, void *pvUser2)
413{
414 const char *pszExec = (const char *)pvUser1;
415 char *pszRealExec = (char *)pvUser2;
416 int rc = RTPathJoinEx(pszRealExec, RTPATH_MAX, pchPath, cchPath, pszExec, RTSTR_MAX);
417 if (RT_FAILURE(rc))
418 return rc;
419 if (!access(pszRealExec, X_OK))
420 return VINF_SUCCESS;
421 if ( errno == EACCES
422 || errno == EPERM)
423 return RTErrConvertFromErrno(errno);
424 return VERR_TRY_AGAIN;
425}
426
427/**
428 * Cleans up the environment on the way out.
429 */
430static int rtProcPosixCreateReturn(int rc, RTENV hEnvToUse, RTENV hEnv)
431{
432 if (hEnvToUse != hEnv)
433 RTEnvDestroy(hEnvToUse);
434 return rc;
435}
436
437
438RTR3DECL(int) RTProcCreateEx(const char *pszExec, const char * const *papszArgs, RTENV hEnv, uint32_t fFlags,
439 PCRTHANDLE phStdIn, PCRTHANDLE phStdOut, PCRTHANDLE phStdErr, const char *pszAsUser,
440 const char *pszPassword, PRTPROCESS phProcess)
441{
442 int rc;
443
444 /*
445 * Input validation
446 */
447 AssertPtrReturn(pszExec, VERR_INVALID_POINTER);
448 AssertReturn(*pszExec, VERR_INVALID_PARAMETER);
449 AssertReturn(!(fFlags & ~RTPROC_FLAGS_VALID_MASK), VERR_INVALID_PARAMETER);
450 AssertReturn(!(fFlags & RTPROC_FLAGS_DETACHED) || !phProcess, VERR_INVALID_PARAMETER);
451 AssertReturn(hEnv != NIL_RTENV, VERR_INVALID_PARAMETER);
452 AssertPtrReturn(papszArgs, VERR_INVALID_PARAMETER);
453 AssertPtrNullReturn(pszAsUser, VERR_INVALID_POINTER);
454 AssertReturn(!pszAsUser || *pszAsUser, VERR_INVALID_PARAMETER);
455 AssertReturn(!pszPassword || pszAsUser, VERR_INVALID_PARAMETER);
456 AssertPtrNullReturn(pszPassword, VERR_INVALID_POINTER);
457#if defined(RT_OS_OS2)
458 if (fFlags & RTPROC_FLAGS_DETACHED)
459 return VERR_PROC_DETACH_NOT_SUPPORTED;
460#endif
461
462 /*
463 * Get the file descriptors for the handles we've been passed.
464 */
465 PCRTHANDLE paHandles[3] = { phStdIn, phStdOut, phStdErr };
466 int aStdFds[3] = { -1, -1, -1 };
467 for (int i = 0; i < 3; i++)
468 {
469 if (paHandles[i])
470 {
471 AssertPtrReturn(paHandles[i], VERR_INVALID_POINTER);
472 switch (paHandles[i]->enmType)
473 {
474 case RTHANDLETYPE_FILE:
475 aStdFds[i] = paHandles[i]->u.hFile != NIL_RTFILE
476 ? (int)RTFileToNative(paHandles[i]->u.hFile)
477 : -2 /* close it */;
478 break;
479
480 case RTHANDLETYPE_PIPE:
481 aStdFds[i] = paHandles[i]->u.hPipe != NIL_RTPIPE
482 ? (int)RTPipeToNative(paHandles[i]->u.hPipe)
483 : -2 /* close it */;
484 break;
485
486 case RTHANDLETYPE_SOCKET:
487 aStdFds[i] = paHandles[i]->u.hSocket != NIL_RTSOCKET
488 ? (int)RTSocketToNative(paHandles[i]->u.hSocket)
489 : -2 /* close it */;
490 break;
491
492 default:
493 AssertMsgFailedReturn(("%d: %d\n", i, paHandles[i]->enmType), VERR_INVALID_PARAMETER);
494 }
495 /** @todo check the close-on-execness of these handles? */
496 }
497 }
498
499 for (int i = 0; i < 3; i++)
500 if (aStdFds[i] == i)
501 aStdFds[i] = -1;
502
503 for (int i = 0; i < 3; i++)
504 AssertMsgReturn(aStdFds[i] < 0 || aStdFds[i] > i,
505 ("%i := %i not possible because we're lazy\n", i, aStdFds[i]),
506 VERR_NOT_SUPPORTED);
507
508 /*
509 * Resolve the user id if specified.
510 */
511 uid_t uid = ~(uid_t)0;
512 gid_t gid = ~(gid_t)0;
513 if (pszAsUser)
514 {
515 rc = rtCheckCredentials(pszAsUser, pszPassword, &gid, &uid);
516 if (RT_FAILURE(rc))
517 return rc;
518 }
519
520 /*
521 * Create the child environment if either RTPROC_FLAGS_PROFILE or
522 * RTPROC_FLAGS_ENV_CHANGE_RECORD are in effect.
523 */
524 RTENV hEnvToUse = hEnv;
525 if ( (fFlags & (RTPROC_FLAGS_ENV_CHANGE_RECORD | RTPROC_FLAGS_PROFILE))
526 && ( (fFlags & RTPROC_FLAGS_ENV_CHANGE_RECORD)
527 || hEnv == RTENV_DEFAULT) )
528 {
529 if (fFlags & RTPROC_FLAGS_PROFILE)
530 rc = rtProcPosixCreateProfileEnv(&hEnvToUse, pszAsUser);
531 else
532 rc = RTEnvClone(&hEnvToUse, RTENV_DEFAULT);
533 if (RT_SUCCESS(rc))
534 {
535 if ((fFlags & RTPROC_FLAGS_ENV_CHANGE_RECORD) && hEnv != RTENV_DEFAULT)
536 rc = RTEnvApplyChanges(hEnvToUse, hEnv);
537 if (RT_FAILURE(rc))
538 RTEnvDestroy(hEnvToUse);
539 }
540 if (RT_FAILURE(rc))
541 return rc;
542 }
543
544 /*
545 * Check for execute access to the file.
546 */
547 char szRealExec[RTPATH_MAX];
548 if (access(pszExec, X_OK))
549 {
550 rc = errno;
551 if ( !(fFlags & RTPROC_FLAGS_SEARCH_PATH)
552 || rc != ENOENT
553 || RTPathHavePath(pszExec) )
554 rc = RTErrConvertFromErrno(rc);
555 else
556 {
557 /* search */
558 char *pszPath = RTEnvDupEx(hEnvToUse, "PATH");
559 rc = RTPathTraverseList(pszPath, ':', rtPathFindExec, (void *)pszExec, &szRealExec[0]);
560 RTStrFree(pszPath);
561 if (RT_SUCCESS(rc))
562 pszExec = szRealExec;
563 else
564 rc = rc == VERR_END_OF_STRING ? VERR_FILE_NOT_FOUND : rc;
565 }
566
567 if (RT_FAILURE(rc))
568 return rtProcPosixCreateReturn(rc, hEnvToUse, hEnv);
569 }
570
571 pid_t pid = -1;
572 const char * const *papszEnv = RTEnvGetExecEnvP(hEnvToUse);
573 AssertPtrReturn(papszEnv, rtProcPosixCreateReturn(VERR_INVALID_HANDLE, hEnvToUse, hEnv));
574
575
576 /*
577 * Take care of detaching the process.
578 *
579 * HACK ALERT! Put the process into a new process group with pgid = pid
580 * to make sure it differs from that of the parent process to ensure that
581 * the IPRT waitpid call doesn't race anyone (read XPCOM) doing group wide
582 * waits. setsid() includes the setpgid() functionality.
583 * 2010-10-11 XPCOM no longer waits for anything, but it cannot hurt.
584 */
585#ifndef RT_OS_OS2
586 if (fFlags & RTPROC_FLAGS_DETACHED)
587 {
588# ifdef RT_OS_SOLARIS
589 int templateFd = -1;
590 if (!(fFlags & RTPROC_FLAGS_SAME_CONTRACT))
591 {
592 templateFd = rtSolarisContractPreFork();
593 if (templateFd == -1)
594 return rtProcPosixCreateReturn(VERR_OPEN_FAILED, hEnvToUse, hEnv);
595 }
596# endif /* RT_OS_SOLARIS */
597 pid = fork();
598 if (!pid)
599 {
600# ifdef RT_OS_SOLARIS
601 if (!(fFlags & RTPROC_FLAGS_SAME_CONTRACT))
602 rtSolarisContractPostForkChild(templateFd);
603# endif
604 setsid(); /* see comment above */
605
606 pid = -1;
607 /* Child falls through to the actual spawn code below. */
608 }
609 else
610 {
611# ifdef RT_OS_SOLARIS
612 if (!(fFlags & RTPROC_FLAGS_SAME_CONTRACT))
613 rtSolarisContractPostForkParent(templateFd, pid);
614# endif
615 if (pid > 0)
616 {
617 /* Must wait for the temporary process to avoid a zombie. */
618 int status = 0;
619 pid_t pidChild = 0;
620
621 /* Restart if we get interrupted. */
622 do
623 {
624 pidChild = waitpid(pid, &status, 0);
625 } while ( pidChild == -1
626 && errno == EINTR);
627
628 /* Assume that something wasn't found. No detailed info. */
629 if (status)
630 return rtProcPosixCreateReturn(VERR_PROCESS_NOT_FOUND, hEnvToUse, hEnv);
631 if (phProcess)
632 *phProcess = 0;
633 return rtProcPosixCreateReturn(VINF_SUCCESS, hEnvToUse, hEnv);
634 }
635 return rtProcPosixCreateReturn(RTErrConvertFromErrno(errno), hEnvToUse, hEnv);
636 }
637 }
638#endif
639
640 /*
641 * Spawn the child.
642 *
643 * Any spawn code MUST not execute any atexit functions if it is for a
644 * detached process. It would lead to running the atexit functions which
645 * make only sense for the parent. libORBit e.g. gets confused by multiple
646 * execution. Remember, there was only a fork() so far, and until exec()
647 * is successfully run there is nothing which would prevent doing anything
648 * silly with the (duplicated) file descriptors.
649 */
650#ifdef HAVE_POSIX_SPAWN
651 /** @todo OS/2: implement DETACHED (BACKGROUND stuff), see VbglR3Daemonize. */
652 if ( uid == ~(uid_t)0
653 && gid == ~(gid_t)0)
654 {
655 /* Spawn attributes. */
656 posix_spawnattr_t Attr;
657 rc = posix_spawnattr_init(&Attr);
658 if (!rc)
659 {
660 /* Indicate that process group and signal mask are to be changed,
661 and that the child should use default signal actions. */
662 rc = posix_spawnattr_setflags(&Attr, POSIX_SPAWN_SETPGROUP | POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSIGDEF);
663 Assert(rc == 0);
664
665 /* The child starts in its own process group. */
666 if (!rc)
667 {
668 rc = posix_spawnattr_setpgroup(&Attr, 0 /* pg == child pid */);
669 Assert(rc == 0);
670 }
671
672 /* Unmask all signals. */
673 if (!rc)
674 {
675 sigset_t SigMask;
676 sigemptyset(&SigMask);
677 rc = posix_spawnattr_setsigmask(&Attr, &SigMask); Assert(rc == 0);
678 }
679
680 /* File changes. */
681 posix_spawn_file_actions_t FileActions;
682 posix_spawn_file_actions_t *pFileActions = NULL;
683 if ((aStdFds[0] != -1 || aStdFds[1] != -1 || aStdFds[2] != -1) && !rc)
684 {
685 rc = posix_spawn_file_actions_init(&FileActions);
686 if (!rc)
687 {
688 pFileActions = &FileActions;
689 for (int i = 0; i < 3; i++)
690 {
691 int fd = aStdFds[i];
692 if (fd == -2)
693 rc = posix_spawn_file_actions_addclose(&FileActions, i);
694 else if (fd >= 0 && fd != i)
695 {
696 rc = posix_spawn_file_actions_adddup2(&FileActions, fd, i);
697 if (!rc)
698 {
699 for (int j = i + 1; j < 3; j++)
700 if (aStdFds[j] == fd)
701 {
702 fd = -1;
703 break;
704 }
705 if (fd >= 0)
706 rc = posix_spawn_file_actions_addclose(&FileActions, fd);
707 }
708 }
709 if (rc)
710 break;
711 }
712 }
713 }
714
715 if (!rc)
716 rc = posix_spawn(&pid, pszExec, pFileActions, &Attr, (char * const *)papszArgs,
717 (char * const *)papszEnv);
718
719 /* cleanup */
720 int rc2 = posix_spawnattr_destroy(&Attr); Assert(rc2 == 0); NOREF(rc2);
721 if (pFileActions)
722 {
723 rc2 = posix_spawn_file_actions_destroy(pFileActions);
724 Assert(rc2 == 0);
725 }
726
727 /* return on success.*/
728 if (!rc)
729 {
730 /* For a detached process this happens in the temp process, so
731 * it's not worth doing anything as this process must exit. */
732 if (fFlags & RTPROC_FLAGS_DETACHED)
733 _Exit(0);
734 if (phProcess)
735 *phProcess = pid;
736 return rtProcPosixCreateReturn(VINF_SUCCESS, hEnvToUse, hEnv);
737 }
738 }
739 /* For a detached process this happens in the temp process, so
740 * it's not worth doing anything as this process must exit. */
741 if (fFlags & RTPROC_FLAGS_DETACHED)
742 _Exit(124);
743 }
744 else
745#endif
746 {
747#ifdef RT_OS_SOLARIS
748 int templateFd = -1;
749 if (!(fFlags & RTPROC_FLAGS_SAME_CONTRACT))
750 {
751 templateFd = rtSolarisContractPreFork();
752 if (templateFd == -1)
753 return rtProcPosixCreateReturn(VERR_OPEN_FAILED, hEnvToUse, hEnv);
754 }
755#endif /* RT_OS_SOLARIS */
756 pid = fork();
757 if (!pid)
758 {
759#ifdef RT_OS_SOLARIS
760 if (!(fFlags & RTPROC_FLAGS_SAME_CONTRACT))
761 rtSolarisContractPostForkChild(templateFd);
762#endif /* RT_OS_SOLARIS */
763 if (!(fFlags & RTPROC_FLAGS_DETACHED))
764 setpgid(0, 0); /* see comment above */
765
766 /*
767 * Change group and user if requested.
768 */
769#if 1 /** @todo This needs more work, see suplib/hardening. */
770 if (pszAsUser)
771 {
772 int ret = initgroups(pszAsUser, gid);
773 if (ret)
774 {
775 if (fFlags & RTPROC_FLAGS_DETACHED)
776 _Exit(126);
777 else
778 exit(126);
779 }
780 }
781 if (gid != ~(gid_t)0)
782 {
783 if (setgid(gid))
784 {
785 if (fFlags & RTPROC_FLAGS_DETACHED)
786 _Exit(126);
787 else
788 exit(126);
789 }
790 }
791
792 if (uid != ~(uid_t)0)
793 {
794 if (setuid(uid))
795 {
796 if (fFlags & RTPROC_FLAGS_DETACHED)
797 _Exit(126);
798 else
799 exit(126);
800 }
801 }
802#endif
803
804 /*
805 * Some final profile environment tweaks, if running as user.
806 */
807 if ( (fFlags & RTPROC_FLAGS_PROFILE)
808 && pszAsUser
809 && ( (fFlags & RTPROC_FLAGS_ENV_CHANGE_RECORD)
810 || hEnv == RTENV_DEFAULT) )
811 {
812 rc = rtProcPosixAdjustProfileEnvFromChild(hEnvToUse, fFlags, hEnv);
813 papszEnv = RTEnvGetExecEnvP(hEnvToUse);
814 if (RT_FAILURE(rc) || !papszEnv)
815 {
816 if (fFlags & RTPROC_FLAGS_DETACHED)
817 _Exit(126);
818 else
819 exit(126);
820 }
821 }
822
823 /*
824 * Unset the signal mask.
825 */
826 sigset_t SigMask;
827 sigemptyset(&SigMask);
828 rc = sigprocmask(SIG_SETMASK, &SigMask, NULL);
829 Assert(rc == 0);
830
831 /*
832 * Apply changes to the standard file descriptor and stuff.
833 */
834 for (int i = 0; i < 3; i++)
835 {
836 int fd = aStdFds[i];
837 if (fd == -2)
838 close(i);
839 else if (fd >= 0)
840 {
841 int rc2 = dup2(fd, i);
842 if (rc2 != i)
843 {
844 if (fFlags & RTPROC_FLAGS_DETACHED)
845 _Exit(125);
846 else
847 exit(125);
848 }
849 for (int j = i + 1; j < 3; j++)
850 if (aStdFds[j] == fd)
851 {
852 fd = -1;
853 break;
854 }
855 if (fd >= 0)
856 close(fd);
857 }
858 }
859
860 /*
861 * Finally, execute the requested program.
862 */
863 rc = execve(pszExec, (char * const *)papszArgs, (char * const *)papszEnv);
864 if (errno == ENOEXEC)
865 {
866 /* This can happen when trying to start a shell script without the magic #!/bin/sh */
867 RTAssertMsg2Weak("Cannot execute this binary format!\n");
868 }
869 else
870 RTAssertMsg2Weak("execve returns %d errno=%d\n", rc, errno);
871 RTAssertReleasePanic();
872 if (fFlags & RTPROC_FLAGS_DETACHED)
873 _Exit(127);
874 else
875 exit(127);
876 }
877#ifdef RT_OS_SOLARIS
878 if (!(fFlags & RTPROC_FLAGS_SAME_CONTRACT))
879 rtSolarisContractPostForkParent(templateFd, pid);
880#endif /* RT_OS_SOLARIS */
881 if (pid > 0)
882 {
883 /* For a detached process this happens in the temp process, so
884 * it's not worth doing anything as this process must exit. */
885 if (fFlags & RTPROC_FLAGS_DETACHED)
886 _Exit(0);
887 if (phProcess)
888 *phProcess = pid;
889 return rtProcPosixCreateReturn(VINF_SUCCESS, hEnvToUse, hEnv);
890 }
891 /* For a detached process this happens in the temp process, so
892 * it's not worth doing anything as this process must exit. */
893 if (fFlags & RTPROC_FLAGS_DETACHED)
894 _Exit(124);
895 return rtProcPosixCreateReturn(RTErrConvertFromErrno(errno), hEnvToUse, hEnv);
896 }
897
898 return rtProcPosixCreateReturn(VERR_NOT_IMPLEMENTED, hEnvToUse, hEnv);
899}
900
901
902RTR3DECL(int) RTProcDaemonizeUsingFork(bool fNoChDir, bool fNoClose, const char *pszPidfile)
903{
904 /*
905 * Fork the child process in a new session and quit the parent.
906 *
907 * - fork once and create a new session (setsid). This will detach us
908 * from the controlling tty meaning that we won't receive the SIGHUP
909 * (or any other signal) sent to that session.
910 * - The SIGHUP signal is ignored because the session/parent may throw
911 * us one before we get to the setsid.
912 * - When the parent exit(0) we will become an orphan and re-parented to
913 * the init process.
914 * - Because of the sometimes unexpected semantics of assigning the
915 * controlling tty automagically when a session leader first opens a tty,
916 * we will fork() once more to get rid of the session leadership role.
917 */
918
919 /* We start off by opening the pidfile, so that we can fail straight away
920 * if it already exists. */
921 int fdPidfile = -1;
922 if (pszPidfile != NULL)
923 {
924 /* @note the exclusive create is not guaranteed on all file
925 * systems (e.g. NFSv2) */
926 if ((fdPidfile = open(pszPidfile, O_RDWR | O_CREAT | O_EXCL, 0644)) == -1)
927 return RTErrConvertFromErrno(errno);
928 }
929
930 /* Ignore SIGHUP straight away. */
931 struct sigaction OldSigAct;
932 struct sigaction SigAct;
933 memset(&SigAct, 0, sizeof(SigAct));
934 SigAct.sa_handler = SIG_IGN;
935 int rcSigAct = sigaction(SIGHUP, &SigAct, &OldSigAct);
936
937 /* First fork, to become independent process. */
938 pid_t pid = fork();
939 if (pid == -1)
940 {
941 if (fdPidfile != -1)
942 close(fdPidfile);
943 return RTErrConvertFromErrno(errno);
944 }
945 if (pid != 0)
946 {
947 /* Parent exits, no longer necessary. The child gets reparented
948 * to the init process. */
949 exit(0);
950 }
951
952 /* Create new session, fix up the standard file descriptors and the
953 * current working directory. */
954 /** @todo r=klaus the webservice uses this function and assumes that the
955 * contract id of the daemon is the same as that of the original process.
956 * Whenever this code is changed this must still remain possible. */
957 pid_t newpgid = setsid();
958 int SavedErrno = errno;
959 if (rcSigAct != -1)
960 sigaction(SIGHUP, &OldSigAct, NULL);
961 if (newpgid == -1)
962 {
963 if (fdPidfile != -1)
964 close(fdPidfile);
965 return RTErrConvertFromErrno(SavedErrno);
966 }
967
968 if (!fNoClose)
969 {
970 /* Open stdin(0), stdout(1) and stderr(2) as /dev/null. */
971 int fd = open("/dev/null", O_RDWR);
972 if (fd == -1) /* paranoia */
973 {
974 close(STDIN_FILENO);
975 close(STDOUT_FILENO);
976 close(STDERR_FILENO);
977 fd = open("/dev/null", O_RDWR);
978 }
979 if (fd != -1)
980 {
981 dup2(fd, STDIN_FILENO);
982 dup2(fd, STDOUT_FILENO);
983 dup2(fd, STDERR_FILENO);
984 if (fd > 2)
985 close(fd);
986 }
987 }
988
989 if (!fNoChDir)
990 {
991 int rcIgnored = chdir("/");
992 NOREF(rcIgnored);
993 }
994
995 /* Second fork to lose session leader status. */
996 pid = fork();
997 if (pid == -1)
998 {
999 if (fdPidfile != -1)
1000 close(fdPidfile);
1001 return RTErrConvertFromErrno(errno);
1002 }
1003
1004 if (pid != 0)
1005 {
1006 /* Write the pid file, this is done in the parent, before exiting. */
1007 if (fdPidfile != -1)
1008 {
1009 char szBuf[256];
1010 size_t cbPid = RTStrPrintf(szBuf, sizeof(szBuf), "%d\n", pid);
1011 ssize_t cbIgnored = write(fdPidfile, szBuf, cbPid); NOREF(cbIgnored);
1012 close(fdPidfile);
1013 }
1014 exit(0);
1015 }
1016
1017 if (fdPidfile != -1)
1018 close(fdPidfile);
1019
1020 return VINF_SUCCESS;
1021}
1022
Note: See TracBrowser for help on using the repository browser.

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