VirtualBox

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

Last change on this file since 31984 was 30312, checked in by vboxsync, 15 years ago

Runtime/process-posix: Better error message if the binaries format is invalid. Can happen if the user tries to execute guest shell scripts with the missing magic '#!/bin/sh'.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 18.7 KB
Line 
1/* $Id: process-posix.cpp 30312 2010-06-18 13:13:13Z 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 if (errno == ENOEXEC)
397 {
398 /* This can happen when trying to start a shell script without the magic #!/bin/sh */
399 RTAssertMsg2Weak("Cannot execute this binary format!\n");
400 }
401 else
402 RTAssertMsg2Weak("execve returns %d errno=%d\n", rc, errno);
403 RTAssertReleasePanic();
404 exit(127);
405 }
406 if (pid > 0)
407 {
408 if (phProcess)
409 *phProcess = pid;
410 return VINF_SUCCESS;
411 }
412 rc = errno;
413 }
414
415
416 return VERR_NOT_IMPLEMENTED;
417}
418
419
420RTR3DECL(int) RTProcWait(RTPROCESS Process, unsigned fFlags, PRTPROCSTATUS pProcStatus)
421{
422 int rc;
423 do rc = RTProcWaitNoResume(Process, fFlags, pProcStatus);
424 while (rc == VERR_INTERRUPTED);
425 return rc;
426}
427
428RTR3DECL(int) RTProcWaitNoResume(RTPROCESS Process, unsigned fFlags, PRTPROCSTATUS pProcStatus)
429{
430 /*
431 * Validate input.
432 */
433 if (Process <= 0)
434 {
435 AssertMsgFailed(("Invalid Process=%d\n", Process));
436 return VERR_INVALID_PARAMETER;
437 }
438 if (fFlags & ~(RTPROCWAIT_FLAGS_NOBLOCK | RTPROCWAIT_FLAGS_BLOCK))
439 {
440 AssertMsgFailed(("Invalid flags %#x\n", fFlags));
441 return VERR_INVALID_PARAMETER;
442 }
443
444 /*
445 * Performe the wait.
446 */
447 int iStatus = 0;
448 int rc = waitpid(Process, &iStatus, fFlags & RTPROCWAIT_FLAGS_NOBLOCK ? WNOHANG : 0);
449 if (rc > 0)
450 {
451 /*
452 * Fill in the status structure.
453 */
454 if (pProcStatus)
455 {
456 if (WIFEXITED(iStatus))
457 {
458 pProcStatus->enmReason = RTPROCEXITREASON_NORMAL;
459 pProcStatus->iStatus = WEXITSTATUS(iStatus);
460 }
461 else if (WIFSIGNALED(iStatus))
462 {
463 pProcStatus->enmReason = RTPROCEXITREASON_SIGNAL;
464 pProcStatus->iStatus = WTERMSIG(iStatus);
465 }
466 else
467 {
468 Assert(!WIFSTOPPED(iStatus));
469 pProcStatus->enmReason = RTPROCEXITREASON_ABEND;
470 pProcStatus->iStatus = iStatus;
471 }
472 }
473 return VINF_SUCCESS;
474 }
475
476 /*
477 * Child running?
478 */
479 if (!rc)
480 {
481 Assert(fFlags & RTPROCWAIT_FLAGS_NOBLOCK);
482 return VERR_PROCESS_RUNNING;
483 }
484
485 /*
486 * Figure out which error to return.
487 */
488 int iErr = errno;
489 if (iErr == ECHILD)
490 return VERR_PROCESS_NOT_FOUND;
491 return RTErrConvertFromErrno(iErr);
492}
493
494
495RTR3DECL(int) RTProcTerminate(RTPROCESS Process)
496{
497 if (!kill(Process, SIGKILL))
498 return VINF_SUCCESS;
499 return RTErrConvertFromErrno(errno);
500}
501
502
503RTR3DECL(uint64_t) RTProcGetAffinityMask()
504{
505 // @todo
506 return 1;
507}
508
509
510RTR3DECL(int) RTProcDaemonizeUsingFork(bool fNoChDir, bool fNoClose, const char *pszPidfile)
511{
512 /*
513 * Fork the child process in a new session and quit the parent.
514 *
515 * - fork once and create a new session (setsid). This will detach us
516 * from the controlling tty meaning that we won't receive the SIGHUP
517 * (or any other signal) sent to that session.
518 * - The SIGHUP signal is ignored because the session/parent may throw
519 * us one before we get to the setsid.
520 * - When the parent exit(0) we will become an orphan and re-parented to
521 * the init process.
522 * - Because of the sometimes unexpected semantics of assigning the
523 * controlling tty automagically when a session leader first opens a tty,
524 * we will fork() once more to get rid of the session leadership role.
525 */
526
527 /* We start off by opening the pidfile, so that we can fail straight away
528 * if it already exists. */
529 int fdPidfile = -1;
530 if (pszPidfile != NULL)
531 {
532 /* @note the exclusive create is not guaranteed on all file
533 * systems (e.g. NFSv2) */
534 if ((fdPidfile = open(pszPidfile, O_RDWR | O_CREAT | O_EXCL, 0644)) == -1)
535 return RTErrConvertFromErrno(errno);
536 }
537
538 /* Ignore SIGHUP straight away. */
539 struct sigaction OldSigAct;
540 struct sigaction SigAct;
541 memset(&SigAct, 0, sizeof(SigAct));
542 SigAct.sa_handler = SIG_IGN;
543 int rcSigAct = sigaction(SIGHUP, &SigAct, &OldSigAct);
544
545 /* First fork, to become independent process. */
546 pid_t pid = fork();
547 if (pid == -1)
548 return RTErrConvertFromErrno(errno);
549 if (pid != 0)
550 {
551 /* Parent exits, no longer necessary. The child gets reparented
552 * to the init process. */
553 exit(0);
554 }
555
556 /* Create new session, fix up the standard file descriptors and the
557 * current working directory. */
558 pid_t newpgid = setsid();
559 int SavedErrno = errno;
560 if (rcSigAct != -1)
561 sigaction(SIGHUP, &OldSigAct, NULL);
562 if (newpgid == -1)
563 return RTErrConvertFromErrno(SavedErrno);
564
565 if (!fNoClose)
566 {
567 /* Open stdin(0), stdout(1) and stderr(2) as /dev/null. */
568 int fd = open("/dev/null", O_RDWR);
569 if (fd == -1) /* paranoia */
570 {
571 close(STDIN_FILENO);
572 close(STDOUT_FILENO);
573 close(STDERR_FILENO);
574 fd = open("/dev/null", O_RDWR);
575 }
576 if (fd != -1)
577 {
578 dup2(fd, STDIN_FILENO);
579 dup2(fd, STDOUT_FILENO);
580 dup2(fd, STDERR_FILENO);
581 if (fd > 2)
582 close(fd);
583 }
584 }
585
586 if (!fNoChDir)
587 {
588 int rcChdir = chdir("/");
589 }
590
591 /* Second fork to lose session leader status. */
592 pid = fork();
593 if (pid == -1)
594 return RTErrConvertFromErrno(errno);
595
596 if (pid != 0)
597 {
598 /* Write the pid file, this is done in the parent, before exiting. */
599 if (fdPidfile != -1)
600 {
601 char szBuf[256];
602 size_t cbPid = RTStrPrintf(szBuf, sizeof(szBuf), "%d\n", pid);
603 int rcWrite = write(fdPidfile, szBuf, cbPid);
604 close(fdPidfile);
605 }
606 exit(0);
607 }
608
609 return VINF_SUCCESS;
610}
611
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