VirtualBox

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

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

Runtime/r3/posix: support executing as different user (Linux only for now)

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