VirtualBox

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

Last change on this file since 80569 was 80569, checked in by vboxsync, 5 years ago

Main: bugref:9341: Added VM autostart during boot support for windows host

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 41.9 KB
Line 
1/* $Id: process-creation-posix.cpp 80569 2019-09-03 14:34:21Z vboxsync $ */
2/** @file
3 * IPRT - Process Creation, POSIX.
4 */
5
6/*
7 * Copyright (C) 2006-2019 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* Header Files *
30*********************************************************************************************************************************/
31#define LOG_GROUP RTLOGGROUP_PROCESS
32#include <iprt/cdefs.h>
33#ifdef RT_OS_LINUX
34# define IPRT_WITH_DYNAMIC_CRYPT_R
35#endif
36#if (defined(RT_OS_LINUX) || defined(RT_OS_OS2)) && !defined(_GNU_SOURCE)
37# define _GNU_SOURCE
38#endif
39
40#ifdef RT_OS_OS2
41# define crypt unistd_crypt
42# define setkey unistd_setkey
43# define encrypt unistd_encrypt
44# include <unistd.h>
45# undef crypt
46# undef setkey
47# undef encrypt
48#else
49# include <unistd.h>
50#endif
51#include <stdlib.h>
52#include <errno.h>
53#include <sys/types.h>
54#include <sys/stat.h>
55#include <sys/wait.h>
56#include <fcntl.h>
57#include <signal.h>
58#include <grp.h>
59#include <pwd.h>
60#if defined(RT_OS_LINUX) || defined(RT_OS_OS2) || defined(RT_OS_SOLARIS)
61# include <crypt.h>
62#endif
63#if defined(RT_OS_LINUX) || defined(RT_OS_SOLARIS)
64# include <shadow.h>
65#endif
66
67#if defined(RT_OS_LINUX) || defined(RT_OS_OS2)
68/* While Solaris has posix_spawn() of course we don't want to use it as
69 * we need to have the child in a different process contract, no matter
70 * whether it is started detached or not. */
71# define HAVE_POSIX_SPAWN 1
72#endif
73#if defined(RT_OS_DARWIN) && defined(MAC_OS_X_VERSION_MIN_REQUIRED)
74# if MAC_OS_X_VERSION_MIN_REQUIRED >= 1050
75# define HAVE_POSIX_SPAWN 1
76# endif
77#endif
78#ifdef HAVE_POSIX_SPAWN
79# include <spawn.h>
80#endif
81
82#ifdef RT_OS_DARWIN
83# include <mach-o/dyld.h>
84# include <security/pam_appl.h>
85# include <stdlib.h>
86# include <dlfcn.h>
87# include <iprt/asm.h>
88#endif
89
90#ifdef RT_OS_SOLARIS
91# include <limits.h>
92# include <sys/ctfs.h>
93# include <sys/contract/process.h>
94# include <libcontract.h>
95#endif
96
97#ifndef RT_OS_SOLARIS
98# include <paths.h>
99#else
100# define _PATH_MAILDIR "/var/mail"
101# define _PATH_DEFPATH "/usr/bin:/bin"
102# define _PATH_STDPATH "/sbin:/usr/sbin:/bin:/usr/bin"
103#endif
104
105
106#include <iprt/process.h>
107#include "internal/iprt.h"
108
109#include <iprt/assert.h>
110#include <iprt/env.h>
111#include <iprt/err.h>
112#include <iprt/file.h>
113#ifdef IPRT_WITH_DYNAMIC_CRYPT_R
114# include <iprt/ldr.h>
115#endif
116#include <iprt/log.h>
117#include <iprt/path.h>
118#include <iprt/pipe.h>
119#include <iprt/socket.h>
120#include <iprt/string.h>
121#include <iprt/mem.h>
122#include "internal/process.h"
123
124
125/*********************************************************************************************************************************
126* Structures and Typedefs *
127*********************************************************************************************************************************/
128#ifdef RT_OS_DARWIN
129/** For passing info between rtCheckCredentials and rtPamConv. */
130typedef struct RTPROCPAMARGS
131{
132 const char *pszUser;
133 const char *pszPassword;
134} RTPROCPAMARGS;
135/** Pointer to rtPamConv argument package. */
136typedef RTPROCPAMARGS *PRTPROCPAMARGS;
137#endif
138
139
140#ifdef RT_OS_DARWIN
141/**
142 * Worker for rtCheckCredentials that feeds password and maybe username to PAM.
143 *
144 * @returns PAM status.
145 * @param cMessages Number of messages.
146 * @param papMessages Message vector.
147 * @param ppaResponses Where to put our responses.
148 * @param pvAppData Pointer to RTPROCPAMARGS.
149 */
150static int rtPamConv(int cMessages, const struct pam_message **papMessages, struct pam_response **ppaResponses, void *pvAppData)
151{
152 LogFlow(("rtPamConv: cMessages=%d\n", cMessages));
153 PRTPROCPAMARGS pArgs = (PRTPROCPAMARGS)pvAppData;
154 AssertPtrReturn(pArgs, PAM_CONV_ERR);
155
156 struct pam_response *paResponses = (struct pam_response *)calloc(cMessages, sizeof(paResponses[0]));
157 AssertReturn(paResponses, PAM_CONV_ERR);
158 for (int i = 0; i < cMessages; i++)
159 {
160 LogFlow(("rtPamConv: #%d: msg_style=%d msg=%s\n", i, papMessages[i]->msg_style, papMessages[i]->msg));
161
162 paResponses[i].resp_retcode = 0;
163 if (papMessages[i]->msg_style == PAM_PROMPT_ECHO_OFF)
164 paResponses[i].resp = strdup(pArgs->pszPassword);
165 else if (papMessages[i]->msg_style == PAM_PROMPT_ECHO_ON)
166 paResponses[i].resp = strdup(pArgs->pszUser);
167 else
168 {
169 paResponses[i].resp = NULL;
170 continue;
171 }
172 if (paResponses[i].resp == NULL)
173 {
174 while (i-- > 0)
175 free(paResponses[i].resp);
176 free(paResponses);
177 LogFlow(("rtPamConv: out of memory\n"));
178 return PAM_CONV_ERR;
179 }
180 }
181
182 *ppaResponses = paResponses;
183 return PAM_SUCCESS;
184}
185#endif /* RT_OS_DARWIN */
186
187
188#ifdef IPRT_WITH_DYNAMIC_CRYPT_R
189/** Pointer to crypt_r(). */
190typedef char *(*PFNCRYPTR)(const char *, const char *, struct crypt_data *);
191
192/**
193 * Wrapper for resolving and calling crypt_r dynamcially.
194 *
195 * The reason for this is that fedora 30+ wants to use libxcrypt rather than the
196 * glibc libcrypt. The two libraries has different crypt_data sizes and layout,
197 * so we allocate a 256KB data block to be on the safe size (caller does this).
198 */
199static char *rtProcDynamicCryptR(const char *pszKey, const char *pszSalt, struct crypt_data *pData)
200{
201 static PFNCRYPTR volatile s_pfnCryptR = NULL;
202 PFNCRYPTR pfnCryptR = s_pfnCryptR;
203 if (pfnCryptR)
204 return pfnCryptR(pszKey, pszSalt, pData);
205
206 pfnCryptR = (PFNCRYPTR)(uintptr_t)RTLdrGetSystemSymbolEx("libcrypt.so", "crypt_r", RTLDRLOAD_FLAGS_SO_VER_RANGE(1, 6));
207 if (!pfnCryptR)
208 pfnCryptR = (PFNCRYPTR)(uintptr_t)RTLdrGetSystemSymbolEx("libxcrypt.so", "crypt_r", RTLDRLOAD_FLAGS_SO_VER_RANGE(1, 32));
209 if (pfnCryptR)
210 {
211 s_pfnCryptR = pfnCryptR;
212 return pfnCryptR(pszKey, pszSalt, pData);
213 }
214
215 LogRel(("IPRT/RTProc: Unable to locate crypt_r!\n"));
216 return NULL;
217}
218#endif /* IPRT_WITH_DYNAMIC_CRYPT_R */
219
220
221/**
222 * Check the credentials and return the gid/uid of user.
223 *
224 * @param pszUser username
225 * @param pszPasswd password
226 * @param gid where to store the GID of the user
227 * @param uid where to store the UID of the user
228 * @returns IPRT status code
229 */
230static int rtCheckCredentials(const char *pszUser, const char *pszPasswd, gid_t *pGid, uid_t *pUid)
231{
232#if defined(RT_OS_DARWIN)
233 RTLogPrintf("rtCheckCredentials\n");
234
235 /*
236 * Resolve user to UID and GID.
237 */
238 char szBuf[_4K];
239 struct passwd Pw;
240 struct passwd *pPw;
241 if (getpwnam_r(pszUser, &Pw, szBuf, sizeof(szBuf), &pPw) != 0)
242 return VERR_AUTHENTICATION_FAILURE;
243 if (!pPw)
244 return VERR_AUTHENTICATION_FAILURE;
245
246 *pUid = pPw->pw_uid;
247 *pGid = pPw->pw_gid;
248
249 /*
250 * Use PAM for the authentication.
251 * Note! libpam.2.dylib was introduced with 10.6.x (OpenPAM).
252 */
253 void *hModPam = dlopen("libpam.dylib", RTLD_LAZY | RTLD_GLOBAL);
254 if (hModPam)
255 {
256 int (*pfnPamStart)(const char *, const char *, struct pam_conv *, pam_handle_t **);
257 int (*pfnPamAuthenticate)(pam_handle_t *, int);
258 int (*pfnPamAcctMgmt)(pam_handle_t *, int);
259 int (*pfnPamSetItem)(pam_handle_t *, int, const void *);
260 int (*pfnPamEnd)(pam_handle_t *, int);
261 *(void **)&pfnPamStart = dlsym(hModPam, "pam_start");
262 *(void **)&pfnPamAuthenticate = dlsym(hModPam, "pam_authenticate");
263 *(void **)&pfnPamAcctMgmt = dlsym(hModPam, "pam_acct_mgmt");
264 *(void **)&pfnPamSetItem = dlsym(hModPam, "pam_set_item");
265 *(void **)&pfnPamEnd = dlsym(hModPam, "pam_end");
266 ASMCompilerBarrier();
267 if ( pfnPamStart
268 && pfnPamAuthenticate
269 && pfnPamAcctMgmt
270 && pfnPamSetItem
271 && pfnPamEnd)
272 {
273#define pam_start pfnPamStart
274#define pam_authenticate pfnPamAuthenticate
275#define pam_acct_mgmt pfnPamAcctMgmt
276#define pam_set_item pfnPamSetItem
277#define pam_end pfnPamEnd
278
279 /* Do the PAM stuff.
280 Note! Abusing 'login' here for now... */
281 pam_handle_t *hPam = NULL;
282 RTPROCPAMARGS PamConvArgs = { pszUser, pszPasswd };
283 struct pam_conv PamConversation;
284 RT_ZERO(PamConversation);
285 PamConversation.appdata_ptr = &PamConvArgs;
286 PamConversation.conv = rtPamConv;
287 int rc = pam_start("login", pszUser, &PamConversation, &hPam);
288 if (rc == PAM_SUCCESS)
289 {
290 rc = pam_set_item(hPam, PAM_RUSER, pszUser);
291 if (rc == PAM_SUCCESS)
292 rc = pam_authenticate(hPam, 0);
293 if (rc == PAM_SUCCESS)
294 {
295 rc = pam_acct_mgmt(hPam, 0);
296 if ( rc == PAM_SUCCESS
297 || rc == PAM_AUTHINFO_UNAVAIL /*??*/)
298 {
299 pam_end(hPam, PAM_SUCCESS);
300 dlclose(hModPam);
301 return VINF_SUCCESS;
302 }
303 Log(("rtCheckCredentials: pam_acct_mgmt -> %d\n", rc));
304 }
305 else
306 Log(("rtCheckCredentials: pam_authenticate -> %d\n", rc));
307 pam_end(hPam, rc);
308 }
309 else
310 Log(("rtCheckCredentials: pam_start -> %d\n", rc));
311 }
312 else
313 Log(("rtCheckCredentials: failed to resolve symbols: %p %p %p %p %p\n",
314 pfnPamStart, pfnPamAuthenticate, pfnPamAcctMgmt, pfnPamSetItem, pfnPamEnd));
315 dlclose(hModPam);
316 }
317 else
318 Log(("rtCheckCredentials: Loading libpam.dylib failed\n"));
319 return VERR_AUTHENTICATION_FAILURE;
320
321#else
322 /*
323 * Lookup the user in /etc/passwd first.
324 *
325 * Note! On FreeBSD and OS/2 the root user will open /etc/shadow here, so
326 * the getspnam_r step is not necessary.
327 */
328 struct passwd Pwd;
329 char szBuf[_4K];
330 struct passwd *pPwd = NULL;
331 if (getpwnam_r(pszUser, &Pwd, szBuf, sizeof(szBuf), &pPwd) != 0)
332 return VERR_AUTHENTICATION_FAILURE;
333 if (pPwd == NULL)
334 return VERR_AUTHENTICATION_FAILURE;
335
336# if defined(RT_OS_LINUX) || defined(RT_OS_SOLARIS)
337 /*
338 * Ditto for /etc/shadow and replace pw_passwd from above if we can access it:
339 */
340 struct spwd ShwPwd;
341 char szBuf2[_4K];
342# if defined(RT_OS_LINUX)
343 struct spwd *pShwPwd = NULL;
344 if (getspnam_r(pszUser, &ShwPwd, szBuf2, sizeof(szBuf2), &pShwPwd) != 0)
345 pShwPwd = NULL;
346# else
347 struct spwd *pShwPwd = getspnam_r(pszUser, &ShwPwd, szBuf2, sizeof(szBuf2));
348# endif
349 if (pShwPwd != NULL)
350 pPwd->pw_passwd = pShwPwd->sp_pwdp;
351# endif
352
353 /*
354 * Encrypt the passed in password and see if it matches.
355 */
356# if !defined(RT_OS_LINUX)
357 int rc;
358# else
359 /* Default fCorrect=true if no password specified. In that case, pPwd->pw_passwd
360 must be NULL (no password set for this user). Fail if a password is specified
361 but the user does not have one assigned. */
362 int rc = !pszPasswd || !*pszPasswd ? VINF_SUCCESS : VERR_AUTHENTICATION_FAILURE;
363 if (pPwd->pw_passwd && *pPwd->pw_passwd)
364# endif
365 {
366# if defined(RT_OS_LINUX) || defined(RT_OS_OS2)
367# ifdef IPRT_WITH_DYNAMIC_CRYPT_R
368 size_t const cbCryptData = RT_MAX(sizeof(struct crypt_data) * 2, _256K);
369# else
370 size_t const cbCryptData = sizeof(struct crypt_data);
371# endif
372 struct crypt_data *pCryptData = (struct crypt_data *)RTMemTmpAllocZ(cbCryptData);
373 if (pCryptData)
374 {
375# ifdef IPRT_WITH_DYNAMIC_CRYPT_R
376 char *pszEncPasswd = rtProcDynamicCryptR(pszPasswd, pPwd->pw_passwd, pCryptData);
377# else
378 char *pszEncPasswd = crypt_r(pszPasswd, pPwd->pw_passwd, pCryptData);
379# endif
380 rc = pszEncPasswd && !strcmp(pszEncPasswd, pPwd->pw_passwd) ? VINF_SUCCESS : VERR_AUTHENTICATION_FAILURE;
381 RTMemWipeThoroughly(pCryptData, cbCryptData, 3);
382 RTMemTmpFree(pCryptData);
383 }
384 else
385 rc = VERR_NO_TMP_MEMORY;
386# else
387 char *pszEncPasswd = crypt(pszPasswd, pPwd->pw_passwd);
388 rc = strcmp(pszEncPasswd, pPwd->pw_passwd) == 0 ? VINF_SUCCESS : VERR_AUTHENTICATION_FAILURE;
389# endif
390 }
391
392 /*
393 * Return GID and UID on success. Always wipe stack buffers.
394 */
395 if (RT_SUCCESS(rc))
396 {
397 *pGid = pPwd->pw_gid;
398 *pUid = pPwd->pw_uid;
399 }
400 RTMemWipeThoroughly(szBuf, sizeof(szBuf), 3);
401# if defined(RT_OS_LINUX) || defined(RT_OS_SOLARIS)
402 RTMemWipeThoroughly(szBuf2, sizeof(szBuf2), 3);
403# endif
404 return rc;
405#endif
406}
407
408#ifdef RT_OS_SOLARIS
409
410/** @todo the error reporting of the Solaris process contract code could be
411 * a lot better, but essentially it is not meant to run into errors after
412 * the debugging phase. */
413static int rtSolarisContractPreFork(void)
414{
415 int templateFd = open64(CTFS_ROOT "/process/template", O_RDWR);
416 if (templateFd < 0)
417 return -1;
418
419 /* Set template parameters and event sets. */
420 if (ct_pr_tmpl_set_param(templateFd, CT_PR_PGRPONLY))
421 {
422 close(templateFd);
423 return -1;
424 }
425 if (ct_pr_tmpl_set_fatal(templateFd, CT_PR_EV_HWERR))
426 {
427 close(templateFd);
428 return -1;
429 }
430 if (ct_tmpl_set_critical(templateFd, 0))
431 {
432 close(templateFd);
433 return -1;
434 }
435 if (ct_tmpl_set_informative(templateFd, CT_PR_EV_HWERR))
436 {
437 close(templateFd);
438 return -1;
439 }
440
441 /* Make this the active template for the process. */
442 if (ct_tmpl_activate(templateFd))
443 {
444 close(templateFd);
445 return -1;
446 }
447
448 return templateFd;
449}
450
451static void rtSolarisContractPostForkChild(int templateFd)
452{
453 if (templateFd == -1)
454 return;
455
456 /* Clear the active template. */
457 ct_tmpl_clear(templateFd);
458 close(templateFd);
459}
460
461static void rtSolarisContractPostForkParent(int templateFd, pid_t pid)
462{
463 if (templateFd == -1)
464 return;
465
466 /* Clear the active template. */
467 int cleared = ct_tmpl_clear(templateFd);
468 close(templateFd);
469
470 /* If the clearing failed or the fork failed there's nothing more to do. */
471 if (cleared || pid <= 0)
472 return;
473
474 /* Look up the contract which was created by this thread. */
475 int statFd = open64(CTFS_ROOT "/process/latest", O_RDONLY);
476 if (statFd == -1)
477 return;
478 ct_stathdl_t statHdl;
479 if (ct_status_read(statFd, CTD_COMMON, &statHdl))
480 {
481 close(statFd);
482 return;
483 }
484 ctid_t ctId = ct_status_get_id(statHdl);
485 ct_status_free(statHdl);
486 close(statFd);
487 if (ctId < 0)
488 return;
489
490 /* Abandon this contract we just created. */
491 char ctlPath[PATH_MAX];
492 size_t len = snprintf(ctlPath, sizeof(ctlPath),
493 CTFS_ROOT "/process/%ld/ctl", (long)ctId);
494 if (len >= sizeof(ctlPath))
495 return;
496 int ctlFd = open64(ctlPath, O_WRONLY);
497 if (statFd == -1)
498 return;
499 if (ct_ctl_abandon(ctlFd) < 0)
500 {
501 close(ctlFd);
502 return;
503 }
504 close(ctlFd);
505}
506
507#endif /* RT_OS_SOLARIS */
508
509
510RTR3DECL(int) RTProcCreate(const char *pszExec, const char * const *papszArgs, RTENV Env, unsigned fFlags, PRTPROCESS pProcess)
511{
512 return RTProcCreateEx(pszExec, papszArgs, Env, fFlags,
513 NULL, NULL, NULL, /* standard handles */
514 NULL /*pszAsUser*/, NULL /* pszPassword*/, NULL /*pvExtraData*/,
515 pProcess);
516}
517
518
519/**
520 * Adjust the profile environment after forking the child process and changing
521 * the UID.
522 *
523 * @returns IRPT status code.
524 * @param hEnvToUse The environment we're going to use with execve.
525 * @param fFlags The process creation flags.
526 * @param hEnv The environment passed in by the user.
527 */
528static int rtProcPosixAdjustProfileEnvFromChild(RTENV hEnvToUse, uint32_t fFlags, RTENV hEnv)
529{
530 int rc = VINF_SUCCESS;
531#ifdef RT_OS_DARWIN
532 if ( RT_SUCCESS(rc)
533 && (!(fFlags & RTPROC_FLAGS_ENV_CHANGE_RECORD) || RTEnvExistEx(hEnv, "TMPDIR")) )
534 {
535 char szValue[_4K];
536 size_t cbNeeded = confstr(_CS_DARWIN_USER_TEMP_DIR, szValue, sizeof(szValue));
537 if (cbNeeded > 0 && cbNeeded < sizeof(szValue))
538 {
539 char *pszTmp;
540 rc = RTStrCurrentCPToUtf8(&pszTmp, szValue);
541 if (RT_SUCCESS(rc))
542 {
543 rc = RTEnvSetEx(hEnvToUse, "TMPDIR", pszTmp);
544 RTStrFree(pszTmp);
545 }
546 }
547 else
548 rc = VERR_BUFFER_OVERFLOW;
549 }
550#else
551 RT_NOREF_PV(hEnvToUse); RT_NOREF_PV(fFlags); RT_NOREF_PV(hEnv);
552#endif
553 return rc;
554}
555
556
557/**
558 * Create a very very basic environment for a user.
559 *
560 * @returns IPRT status code.
561 * @param phEnvToUse Where to return the created environment.
562 * @param pszUser The user name for the profile.
563 */
564static int rtProcPosixCreateProfileEnv(PRTENV phEnvToUse, const char *pszUser)
565{
566 struct passwd Pwd;
567 struct passwd *pPwd = NULL;
568 char achBuf[_4K];
569 int rc;
570 errno = 0;
571 if (pszUser)
572 rc = getpwnam_r(pszUser, &Pwd, achBuf, sizeof(achBuf), &pPwd);
573 else
574 rc = getpwuid_r(getuid(), &Pwd, achBuf, sizeof(achBuf), &pPwd);
575 if (rc == 0 && pPwd)
576 {
577 char *pszDir;
578 rc = RTStrCurrentCPToUtf8(&pszDir, pPwd->pw_dir);
579 if (RT_SUCCESS(rc))
580 {
581 char *pszShell;
582 rc = RTStrCurrentCPToUtf8(&pszShell, pPwd->pw_shell);
583 if (RT_SUCCESS(rc))
584 {
585 char *pszUserFree = NULL;
586 if (!pszUser)
587 {
588 rc = RTStrCurrentCPToUtf8(&pszUserFree, pPwd->pw_name);
589 if (RT_SUCCESS(rc))
590 pszUser = pszUserFree;
591 }
592 if (RT_SUCCESS(rc))
593 {
594 rc = RTEnvCreate(phEnvToUse);
595 if (RT_SUCCESS(rc))
596 {
597 RTENV hEnvToUse = *phEnvToUse;
598
599 rc = RTEnvSetEx(hEnvToUse, "HOME", pszDir);
600 if (RT_SUCCESS(rc))
601 rc = RTEnvSetEx(hEnvToUse, "SHELL", pszShell);
602 if (RT_SUCCESS(rc))
603 rc = RTEnvSetEx(hEnvToUse, "USER", pszUser);
604 if (RT_SUCCESS(rc))
605 rc = RTEnvSetEx(hEnvToUse, "LOGNAME", pszUser);
606
607 if (RT_SUCCESS(rc))
608 rc = RTEnvSetEx(hEnvToUse, "PATH", pPwd->pw_uid == 0 ? _PATH_STDPATH : _PATH_DEFPATH);
609
610 if (RT_SUCCESS(rc))
611 {
612 RTStrPrintf(achBuf, sizeof(achBuf), "%s/%s", _PATH_MAILDIR, pszUser);
613 rc = RTEnvSetEx(hEnvToUse, "MAIL", achBuf);
614 }
615
616#ifdef RT_OS_DARWIN
617 if (RT_SUCCESS(rc) && !pszUserFree)
618 {
619 size_t cbNeeded = confstr(_CS_DARWIN_USER_TEMP_DIR, achBuf, sizeof(achBuf));
620 if (cbNeeded > 0 && cbNeeded < sizeof(achBuf))
621 {
622 char *pszTmp;
623 rc = RTStrCurrentCPToUtf8(&pszTmp, achBuf);
624 if (RT_SUCCESS(rc))
625 {
626 rc = RTEnvSetEx(hEnvToUse, "TMPDIR", pszTmp);
627 RTStrFree(pszTmp);
628 }
629 }
630 else
631 rc = VERR_BUFFER_OVERFLOW;
632 }
633#endif
634
635 /** @todo load /etc/environment, /etc/profile.env and ~/.pam_environment? */
636
637 if (RT_FAILURE(rc))
638 RTEnvDestroy(hEnvToUse);
639 }
640 RTStrFree(pszUserFree);
641 }
642 RTStrFree(pszShell);
643 }
644 RTStrFree(pszDir);
645 }
646 }
647 else
648 rc = errno ? RTErrConvertFromErrno(errno) : VERR_ACCESS_DENIED;
649 return rc;
650}
651
652
653/**
654 * RTPathTraverseList callback used by RTProcCreateEx to locate the executable.
655 */
656static DECLCALLBACK(int) rtPathFindExec(char const *pchPath, size_t cchPath, void *pvUser1, void *pvUser2)
657{
658 const char *pszExec = (const char *)pvUser1;
659 char *pszRealExec = (char *)pvUser2;
660 int rc = RTPathJoinEx(pszRealExec, RTPATH_MAX, pchPath, cchPath, pszExec, RTSTR_MAX);
661 if (RT_FAILURE(rc))
662 return rc;
663 if (!access(pszRealExec, X_OK))
664 return VINF_SUCCESS;
665 if ( errno == EACCES
666 || errno == EPERM)
667 return RTErrConvertFromErrno(errno);
668 return VERR_TRY_AGAIN;
669}
670
671/**
672 * Cleans up the environment on the way out.
673 */
674static int rtProcPosixCreateReturn(int rc, RTENV hEnvToUse, RTENV hEnv)
675{
676 if (hEnvToUse != hEnv)
677 RTEnvDestroy(hEnvToUse);
678 return rc;
679}
680
681
682RTR3DECL(int) RTProcCreateEx(const char *pszExec, const char * const *papszArgs, RTENV hEnv, uint32_t fFlags,
683 PCRTHANDLE phStdIn, PCRTHANDLE phStdOut, PCRTHANDLE phStdErr, const char *pszAsUser,
684 const char *pszPassword, void *pvExtraData, PRTPROCESS phProcess)
685{
686 int rc;
687 LogFlow(("RTProcCreateEx: pszExec=%s pszAsUser=%s\n", pszExec, pszAsUser));
688
689 /*
690 * Input validation
691 */
692 AssertPtrReturn(pszExec, VERR_INVALID_POINTER);
693 AssertReturn(*pszExec, VERR_INVALID_PARAMETER);
694 AssertReturn(!(fFlags & ~RTPROC_FLAGS_VALID_MASK), VERR_INVALID_PARAMETER);
695 AssertReturn(!(fFlags & RTPROC_FLAGS_DETACHED) || !phProcess, VERR_INVALID_PARAMETER);
696 AssertReturn(hEnv != NIL_RTENV, VERR_INVALID_PARAMETER);
697 AssertPtrReturn(papszArgs, VERR_INVALID_PARAMETER);
698 AssertPtrNullReturn(pszAsUser, VERR_INVALID_POINTER);
699 AssertReturn(!pszAsUser || *pszAsUser, VERR_INVALID_PARAMETER);
700 AssertReturn(!pszPassword || pszAsUser, VERR_INVALID_PARAMETER);
701 AssertPtrNullReturn(pszPassword, VERR_INVALID_POINTER);
702#if defined(RT_OS_OS2)
703 if (fFlags & RTPROC_FLAGS_DETACHED)
704 return VERR_PROC_DETACH_NOT_SUPPORTED;
705#endif
706 AssertReturn(pvExtraData == NULL || (fFlags & RTPROC_FLAGS_DESIRED_SESSION_ID), VERR_INVALID_PARAMETER);
707
708 /*
709 * Get the file descriptors for the handles we've been passed.
710 */
711 PCRTHANDLE paHandles[3] = { phStdIn, phStdOut, phStdErr };
712 int aStdFds[3] = { -1, -1, -1 };
713 for (int i = 0; i < 3; i++)
714 {
715 if (paHandles[i])
716 {
717 AssertPtrReturn(paHandles[i], VERR_INVALID_POINTER);
718 switch (paHandles[i]->enmType)
719 {
720 case RTHANDLETYPE_FILE:
721 aStdFds[i] = paHandles[i]->u.hFile != NIL_RTFILE
722 ? (int)RTFileToNative(paHandles[i]->u.hFile)
723 : -2 /* close it */;
724 break;
725
726 case RTHANDLETYPE_PIPE:
727 aStdFds[i] = paHandles[i]->u.hPipe != NIL_RTPIPE
728 ? (int)RTPipeToNative(paHandles[i]->u.hPipe)
729 : -2 /* close it */;
730 break;
731
732 case RTHANDLETYPE_SOCKET:
733 aStdFds[i] = paHandles[i]->u.hSocket != NIL_RTSOCKET
734 ? (int)RTSocketToNative(paHandles[i]->u.hSocket)
735 : -2 /* close it */;
736 break;
737
738 default:
739 AssertMsgFailedReturn(("%d: %d\n", i, paHandles[i]->enmType), VERR_INVALID_PARAMETER);
740 }
741 /** @todo check the close-on-execness of these handles? */
742 }
743 }
744
745 for (int i = 0; i < 3; i++)
746 if (aStdFds[i] == i)
747 aStdFds[i] = -1;
748
749 for (int i = 0; i < 3; i++)
750 AssertMsgReturn(aStdFds[i] < 0 || aStdFds[i] > i,
751 ("%i := %i not possible because we're lazy\n", i, aStdFds[i]),
752 VERR_NOT_SUPPORTED);
753
754 /*
755 * Resolve the user id if specified.
756 */
757 uid_t uid = ~(uid_t)0;
758 gid_t gid = ~(gid_t)0;
759 if (pszAsUser)
760 {
761 rc = rtCheckCredentials(pszAsUser, pszPassword, &gid, &uid);
762 if (RT_FAILURE(rc))
763 return rc;
764 }
765
766 /*
767 * Create the child environment if either RTPROC_FLAGS_PROFILE or
768 * RTPROC_FLAGS_ENV_CHANGE_RECORD are in effect.
769 */
770 RTENV hEnvToUse = hEnv;
771 if ( (fFlags & (RTPROC_FLAGS_ENV_CHANGE_RECORD | RTPROC_FLAGS_PROFILE))
772 && ( (fFlags & RTPROC_FLAGS_ENV_CHANGE_RECORD)
773 || hEnv == RTENV_DEFAULT) )
774 {
775 if (fFlags & RTPROC_FLAGS_PROFILE)
776 rc = rtProcPosixCreateProfileEnv(&hEnvToUse, pszAsUser);
777 else
778 rc = RTEnvClone(&hEnvToUse, RTENV_DEFAULT);
779 if (RT_SUCCESS(rc))
780 {
781 if ((fFlags & RTPROC_FLAGS_ENV_CHANGE_RECORD) && hEnv != RTENV_DEFAULT)
782 rc = RTEnvApplyChanges(hEnvToUse, hEnv);
783 if (RT_FAILURE(rc))
784 RTEnvDestroy(hEnvToUse);
785 }
786 if (RT_FAILURE(rc))
787 return rc;
788 }
789
790 /*
791 * Check for execute access to the file.
792 */
793 char szRealExec[RTPATH_MAX];
794 if (access(pszExec, X_OK))
795 {
796 rc = errno;
797 if ( !(fFlags & RTPROC_FLAGS_SEARCH_PATH)
798 || rc != ENOENT
799 || RTPathHavePath(pszExec) )
800 rc = RTErrConvertFromErrno(rc);
801 else
802 {
803 /* search */
804 char *pszPath = RTEnvDupEx(hEnvToUse, "PATH");
805 rc = RTPathTraverseList(pszPath, ':', rtPathFindExec, (void *)pszExec, &szRealExec[0]);
806 RTStrFree(pszPath);
807 if (RT_SUCCESS(rc))
808 pszExec = szRealExec;
809 else
810 rc = rc == VERR_END_OF_STRING ? VERR_FILE_NOT_FOUND : rc;
811 }
812
813 if (RT_FAILURE(rc))
814 return rtProcPosixCreateReturn(rc, hEnvToUse, hEnv);
815 }
816
817 pid_t pid = -1;
818 const char * const *papszEnv = RTEnvGetExecEnvP(hEnvToUse);
819 AssertPtrReturn(papszEnv, rtProcPosixCreateReturn(VERR_INVALID_HANDLE, hEnvToUse, hEnv));
820
821
822 /*
823 * Take care of detaching the process.
824 *
825 * HACK ALERT! Put the process into a new process group with pgid = pid
826 * to make sure it differs from that of the parent process to ensure that
827 * the IPRT waitpid call doesn't race anyone (read XPCOM) doing group wide
828 * waits. setsid() includes the setpgid() functionality.
829 * 2010-10-11 XPCOM no longer waits for anything, but it cannot hurt.
830 */
831#ifndef RT_OS_OS2
832 if (fFlags & RTPROC_FLAGS_DETACHED)
833 {
834# ifdef RT_OS_SOLARIS
835 int templateFd = -1;
836 if (!(fFlags & RTPROC_FLAGS_SAME_CONTRACT))
837 {
838 templateFd = rtSolarisContractPreFork();
839 if (templateFd == -1)
840 return rtProcPosixCreateReturn(VERR_OPEN_FAILED, hEnvToUse, hEnv);
841 }
842# endif /* RT_OS_SOLARIS */
843 pid = fork();
844 if (!pid)
845 {
846# ifdef RT_OS_SOLARIS
847 if (!(fFlags & RTPROC_FLAGS_SAME_CONTRACT))
848 rtSolarisContractPostForkChild(templateFd);
849# endif
850 setsid(); /* see comment above */
851
852 pid = -1;
853 /* Child falls through to the actual spawn code below. */
854 }
855 else
856 {
857# ifdef RT_OS_SOLARIS
858 if (!(fFlags & RTPROC_FLAGS_SAME_CONTRACT))
859 rtSolarisContractPostForkParent(templateFd, pid);
860# endif
861 if (pid > 0)
862 {
863 /* Must wait for the temporary process to avoid a zombie. */
864 int status = 0;
865 pid_t pidChild = 0;
866
867 /* Restart if we get interrupted. */
868 do
869 {
870 pidChild = waitpid(pid, &status, 0);
871 } while ( pidChild == -1
872 && errno == EINTR);
873
874 /* Assume that something wasn't found. No detailed info. */
875 if (status)
876 return rtProcPosixCreateReturn(VERR_PROCESS_NOT_FOUND, hEnvToUse, hEnv);
877 if (phProcess)
878 *phProcess = 0;
879 return rtProcPosixCreateReturn(VINF_SUCCESS, hEnvToUse, hEnv);
880 }
881 return rtProcPosixCreateReturn(RTErrConvertFromErrno(errno), hEnvToUse, hEnv);
882 }
883 }
884#endif
885
886 /*
887 * Spawn the child.
888 *
889 * Any spawn code MUST not execute any atexit functions if it is for a
890 * detached process. It would lead to running the atexit functions which
891 * make only sense for the parent. libORBit e.g. gets confused by multiple
892 * execution. Remember, there was only a fork() so far, and until exec()
893 * is successfully run there is nothing which would prevent doing anything
894 * silly with the (duplicated) file descriptors.
895 */
896#ifdef HAVE_POSIX_SPAWN
897 /** @todo OS/2: implement DETACHED (BACKGROUND stuff), see VbglR3Daemonize. */
898 if ( uid == ~(uid_t)0
899 && gid == ~(gid_t)0)
900 {
901 /* Spawn attributes. */
902 posix_spawnattr_t Attr;
903 rc = posix_spawnattr_init(&Attr);
904 if (!rc)
905 {
906 /* Indicate that process group and signal mask are to be changed,
907 and that the child should use default signal actions. */
908 rc = posix_spawnattr_setflags(&Attr, POSIX_SPAWN_SETPGROUP | POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSIGDEF);
909 Assert(rc == 0);
910
911 /* The child starts in its own process group. */
912 if (!rc)
913 {
914 rc = posix_spawnattr_setpgroup(&Attr, 0 /* pg == child pid */);
915 Assert(rc == 0);
916 }
917
918 /* Unmask all signals. */
919 if (!rc)
920 {
921 sigset_t SigMask;
922 sigemptyset(&SigMask);
923 rc = posix_spawnattr_setsigmask(&Attr, &SigMask); Assert(rc == 0);
924 }
925
926 /* File changes. */
927 posix_spawn_file_actions_t FileActions;
928 posix_spawn_file_actions_t *pFileActions = NULL;
929 if ((aStdFds[0] != -1 || aStdFds[1] != -1 || aStdFds[2] != -1) && !rc)
930 {
931 rc = posix_spawn_file_actions_init(&FileActions);
932 if (!rc)
933 {
934 pFileActions = &FileActions;
935 for (int i = 0; i < 3; i++)
936 {
937 int fd = aStdFds[i];
938 if (fd == -2)
939 rc = posix_spawn_file_actions_addclose(&FileActions, i);
940 else if (fd >= 0 && fd != i)
941 {
942 rc = posix_spawn_file_actions_adddup2(&FileActions, fd, i);
943 if (!rc)
944 {
945 for (int j = i + 1; j < 3; j++)
946 if (aStdFds[j] == fd)
947 {
948 fd = -1;
949 break;
950 }
951 if (fd >= 0)
952 rc = posix_spawn_file_actions_addclose(&FileActions, fd);
953 }
954 }
955 if (rc)
956 break;
957 }
958 }
959 }
960
961 if (!rc)
962 rc = posix_spawn(&pid, pszExec, pFileActions, &Attr, (char * const *)papszArgs,
963 (char * const *)papszEnv);
964
965 /* cleanup */
966 int rc2 = posix_spawnattr_destroy(&Attr); Assert(rc2 == 0); NOREF(rc2);
967 if (pFileActions)
968 {
969 rc2 = posix_spawn_file_actions_destroy(pFileActions);
970 Assert(rc2 == 0);
971 }
972
973 /* return on success.*/
974 if (!rc)
975 {
976 /* For a detached process this happens in the temp process, so
977 * it's not worth doing anything as this process must exit. */
978 if (fFlags & RTPROC_FLAGS_DETACHED)
979 _Exit(0);
980 if (phProcess)
981 *phProcess = pid;
982 return rtProcPosixCreateReturn(VINF_SUCCESS, hEnvToUse, hEnv);
983 }
984 }
985 /* For a detached process this happens in the temp process, so
986 * it's not worth doing anything as this process must exit. */
987 if (fFlags & RTPROC_FLAGS_DETACHED)
988 _Exit(124);
989 }
990 else
991#endif
992 {
993#ifdef RT_OS_SOLARIS
994 int templateFd = -1;
995 if (!(fFlags & RTPROC_FLAGS_SAME_CONTRACT))
996 {
997 templateFd = rtSolarisContractPreFork();
998 if (templateFd == -1)
999 return rtProcPosixCreateReturn(VERR_OPEN_FAILED, hEnvToUse, hEnv);
1000 }
1001#endif /* RT_OS_SOLARIS */
1002 pid = fork();
1003 if (!pid)
1004 {
1005#ifdef RT_OS_SOLARIS
1006 if (!(fFlags & RTPROC_FLAGS_SAME_CONTRACT))
1007 rtSolarisContractPostForkChild(templateFd);
1008#endif /* RT_OS_SOLARIS */
1009 if (!(fFlags & RTPROC_FLAGS_DETACHED))
1010 setpgid(0, 0); /* see comment above */
1011
1012 /*
1013 * Change group and user if requested.
1014 */
1015#if 1 /** @todo This needs more work, see suplib/hardening. */
1016 if (pszAsUser)
1017 {
1018 int ret = initgroups(pszAsUser, gid);
1019 if (ret)
1020 {
1021 if (fFlags & RTPROC_FLAGS_DETACHED)
1022 _Exit(126);
1023 else
1024 exit(126);
1025 }
1026 }
1027 if (gid != ~(gid_t)0)
1028 {
1029 if (setgid(gid))
1030 {
1031 if (fFlags & RTPROC_FLAGS_DETACHED)
1032 _Exit(126);
1033 else
1034 exit(126);
1035 }
1036 }
1037
1038 if (uid != ~(uid_t)0)
1039 {
1040 if (setuid(uid))
1041 {
1042 if (fFlags & RTPROC_FLAGS_DETACHED)
1043 _Exit(126);
1044 else
1045 exit(126);
1046 }
1047 }
1048#endif
1049
1050 /*
1051 * Some final profile environment tweaks, if running as user.
1052 */
1053 if ( (fFlags & RTPROC_FLAGS_PROFILE)
1054 && pszAsUser
1055 && ( (fFlags & RTPROC_FLAGS_ENV_CHANGE_RECORD)
1056 || hEnv == RTENV_DEFAULT) )
1057 {
1058 rc = rtProcPosixAdjustProfileEnvFromChild(hEnvToUse, fFlags, hEnv);
1059 papszEnv = RTEnvGetExecEnvP(hEnvToUse);
1060 if (RT_FAILURE(rc) || !papszEnv)
1061 {
1062 if (fFlags & RTPROC_FLAGS_DETACHED)
1063 _Exit(126);
1064 else
1065 exit(126);
1066 }
1067 }
1068
1069 /*
1070 * Unset the signal mask.
1071 */
1072 sigset_t SigMask;
1073 sigemptyset(&SigMask);
1074 rc = sigprocmask(SIG_SETMASK, &SigMask, NULL);
1075 Assert(rc == 0);
1076
1077 /*
1078 * Apply changes to the standard file descriptor and stuff.
1079 */
1080 for (int i = 0; i < 3; i++)
1081 {
1082 int fd = aStdFds[i];
1083 if (fd == -2)
1084 close(i);
1085 else if (fd >= 0)
1086 {
1087 int rc2 = dup2(fd, i);
1088 if (rc2 != i)
1089 {
1090 if (fFlags & RTPROC_FLAGS_DETACHED)
1091 _Exit(125);
1092 else
1093 exit(125);
1094 }
1095 for (int j = i + 1; j < 3; j++)
1096 if (aStdFds[j] == fd)
1097 {
1098 fd = -1;
1099 break;
1100 }
1101 if (fd >= 0)
1102 close(fd);
1103 }
1104 }
1105
1106 /*
1107 * Finally, execute the requested program.
1108 */
1109 rc = execve(pszExec, (char * const *)papszArgs, (char * const *)papszEnv);
1110 if (errno == ENOEXEC)
1111 {
1112 /* This can happen when trying to start a shell script without the magic #!/bin/sh */
1113 RTAssertMsg2Weak("Cannot execute this binary format!\n");
1114 }
1115 else
1116 RTAssertMsg2Weak("execve returns %d errno=%d\n", rc, errno);
1117 RTAssertReleasePanic();
1118 if (fFlags & RTPROC_FLAGS_DETACHED)
1119 _Exit(127);
1120 else
1121 exit(127);
1122 }
1123#ifdef RT_OS_SOLARIS
1124 if (!(fFlags & RTPROC_FLAGS_SAME_CONTRACT))
1125 rtSolarisContractPostForkParent(templateFd, pid);
1126#endif /* RT_OS_SOLARIS */
1127 if (pid > 0)
1128 {
1129 /* For a detached process this happens in the temp process, so
1130 * it's not worth doing anything as this process must exit. */
1131 if (fFlags & RTPROC_FLAGS_DETACHED)
1132 _Exit(0);
1133 if (phProcess)
1134 *phProcess = pid;
1135 return rtProcPosixCreateReturn(VINF_SUCCESS, hEnvToUse, hEnv);
1136 }
1137 /* For a detached process this happens in the temp process, so
1138 * it's not worth doing anything as this process must exit. */
1139 if (fFlags & RTPROC_FLAGS_DETACHED)
1140 _Exit(124);
1141 return rtProcPosixCreateReturn(RTErrConvertFromErrno(errno), hEnvToUse, hEnv);
1142 }
1143
1144 return rtProcPosixCreateReturn(VERR_NOT_IMPLEMENTED, hEnvToUse, hEnv);
1145}
1146
1147
1148RTR3DECL(int) RTProcDaemonizeUsingFork(bool fNoChDir, bool fNoClose, const char *pszPidfile)
1149{
1150 /*
1151 * Fork the child process in a new session and quit the parent.
1152 *
1153 * - fork once and create a new session (setsid). This will detach us
1154 * from the controlling tty meaning that we won't receive the SIGHUP
1155 * (or any other signal) sent to that session.
1156 * - The SIGHUP signal is ignored because the session/parent may throw
1157 * us one before we get to the setsid.
1158 * - When the parent exit(0) we will become an orphan and re-parented to
1159 * the init process.
1160 * - Because of the sometimes unexpected semantics of assigning the
1161 * controlling tty automagically when a session leader first opens a tty,
1162 * we will fork() once more to get rid of the session leadership role.
1163 */
1164
1165 /* We start off by opening the pidfile, so that we can fail straight away
1166 * if it already exists. */
1167 int fdPidfile = -1;
1168 if (pszPidfile != NULL)
1169 {
1170 /* @note the exclusive create is not guaranteed on all file
1171 * systems (e.g. NFSv2) */
1172 if ((fdPidfile = open(pszPidfile, O_RDWR | O_CREAT | O_EXCL, 0644)) == -1)
1173 return RTErrConvertFromErrno(errno);
1174 }
1175
1176 /* Ignore SIGHUP straight away. */
1177 struct sigaction OldSigAct;
1178 struct sigaction SigAct;
1179 memset(&SigAct, 0, sizeof(SigAct));
1180 SigAct.sa_handler = SIG_IGN;
1181 int rcSigAct = sigaction(SIGHUP, &SigAct, &OldSigAct);
1182
1183 /* First fork, to become independent process. */
1184 pid_t pid = fork();
1185 if (pid == -1)
1186 {
1187 if (fdPidfile != -1)
1188 close(fdPidfile);
1189 return RTErrConvertFromErrno(errno);
1190 }
1191 if (pid != 0)
1192 {
1193 /* Parent exits, no longer necessary. The child gets reparented
1194 * to the init process. */
1195 exit(0);
1196 }
1197
1198 /* Create new session, fix up the standard file descriptors and the
1199 * current working directory. */
1200 /** @todo r=klaus the webservice uses this function and assumes that the
1201 * contract id of the daemon is the same as that of the original process.
1202 * Whenever this code is changed this must still remain possible. */
1203 pid_t newpgid = setsid();
1204 int SavedErrno = errno;
1205 if (rcSigAct != -1)
1206 sigaction(SIGHUP, &OldSigAct, NULL);
1207 if (newpgid == -1)
1208 {
1209 if (fdPidfile != -1)
1210 close(fdPidfile);
1211 return RTErrConvertFromErrno(SavedErrno);
1212 }
1213
1214 if (!fNoClose)
1215 {
1216 /* Open stdin(0), stdout(1) and stderr(2) as /dev/null. */
1217 int fd = open("/dev/null", O_RDWR);
1218 if (fd == -1) /* paranoia */
1219 {
1220 close(STDIN_FILENO);
1221 close(STDOUT_FILENO);
1222 close(STDERR_FILENO);
1223 fd = open("/dev/null", O_RDWR);
1224 }
1225 if (fd != -1)
1226 {
1227 dup2(fd, STDIN_FILENO);
1228 dup2(fd, STDOUT_FILENO);
1229 dup2(fd, STDERR_FILENO);
1230 if (fd > 2)
1231 close(fd);
1232 }
1233 }
1234
1235 if (!fNoChDir)
1236 {
1237 int rcIgnored = chdir("/");
1238 NOREF(rcIgnored);
1239 }
1240
1241 /* Second fork to lose session leader status. */
1242 pid = fork();
1243 if (pid == -1)
1244 {
1245 if (fdPidfile != -1)
1246 close(fdPidfile);
1247 return RTErrConvertFromErrno(errno);
1248 }
1249
1250 if (pid != 0)
1251 {
1252 /* Write the pid file, this is done in the parent, before exiting. */
1253 if (fdPidfile != -1)
1254 {
1255 char szBuf[256];
1256 size_t cbPid = RTStrPrintf(szBuf, sizeof(szBuf), "%d\n", pid);
1257 ssize_t cbIgnored = write(fdPidfile, szBuf, cbPid); NOREF(cbIgnored);
1258 close(fdPidfile);
1259 }
1260 exit(0);
1261 }
1262
1263 if (fdPidfile != -1)
1264 close(fdPidfile);
1265
1266 return VINF_SUCCESS;
1267}
1268
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