diff options
Diffstat (limited to 'src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibDaemonize.cpp')
-rw-r--r-- | src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibDaemonize.cpp | 326 |
1 files changed, 326 insertions, 0 deletions
diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibDaemonize.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibDaemonize.cpp new file mode 100644 index 00000000..2aad8c5c --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibDaemonize.cpp @@ -0,0 +1,326 @@ +/** $Id: VBoxGuestR3LibDaemonize.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, daemonize a process. + */ + +/* + * Copyright (C) 2007-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#if defined(RT_OS_OS2) +# define INCL_BASE +# define INCL_ERRORS +# include <os2.h> + +# include <iprt/alloca.h> +# include <iprt/string.h> + +#elif defined(RT_OS_WINDOWS) +# error "PORTME" + +#else /* the unices */ +# include <sys/types.h> +# include <sys/stat.h> +# include <sys/wait.h> +# include <stdio.h> +# include <fcntl.h> +# include <stdlib.h> +# include <unistd.h> +# include <signal.h> +# include <errno.h> +#endif + +#include <iprt/process.h> +#include <iprt/string.h> +#include <VBox/err.h> +#include <VBox/log.h> + +#include "VBoxGuestR3LibInternal.h" + + +/** + * Daemonize the process for running in the background. + * + * This is supposed to do the same job as the BSD daemon() call. + * + * @returns 0 on success + * + * @param fNoChDir Pass false to change working directory to root. + * @param fNoClose Pass false to redirect standard file streams to /dev/null. + * @param fRespawn Restart the daemonised process after five seconds if it + * terminates abnormally. + * @param pcRespawn Where to store a count of how often we have respawned, + * intended for avoiding error spamming. Optional. + * @param fReturnOnUpdate If True, this function will return control to caller when + * child process will terminate with exit code of VBGLR3EXITCODERELOAD, + * indicating that Guest Additions update has been started and this running + * process will be asked to be restarted by arrival of the next SIGUSR1 + * signal (caller should wait for SIGUSR1). If False, this functions will + * never return, but rather exit() when child process terminates with + * exit code 0. + * @param pfUpdateStarted A flag which passed to caller if fReturnOnUpdate is True (can be NULL). + * @param szPidfile Optional path to parent process' pidfile (can be NULL). + * @param phPidfile Optional path to parent process' pidfile handle (can not be NULL if + * szPidfile was specified). + * + * @todo Use RTProcDaemonize instead of this. + * @todo Implement fRespawn on OS/2. + * @todo Make the respawn interval configurable. But not until someone + * actually needs that. + */ +VBGLR3DECL(int) VbglR3DaemonizeEx(bool fNoChDir, bool fNoClose, bool fRespawn, unsigned *pcRespawn, + bool fReturnOnUpdate, bool *pfUpdateStarted, const char *szPidfile, + RTFILE *phPidfile) +{ +#if defined(RT_OS_OS2) + PPIB pPib; + PTIB pTib; + DosGetInfoBlocks(&pTib, &pPib); + + RT_NOREF(fReturnOnUpdate); + RT_NOREF(pfUpdateStarted); + RT_NOREF(szPidfile); + RT_NOREF(phPidfile); + + AssertRelease(!fRespawn); + /* Get the full path to the executable. */ + char szExe[CCHMAXPATH]; + APIRET rc = DosQueryModuleName(pPib->pib_hmte, sizeof(szExe), szExe); + if (rc) + return RTErrConvertFromOS2(rc); + + /* calc the length of the command line. */ + char *pch = pPib->pib_pchcmd; + size_t cch0 = strlen(pch); + pch += cch0 + 1; + size_t cch1 = strlen(pch); + pch += cch1 + 1; + char *pchArgs; + if (cch1 && *pch) + { + do pch = strchr(pch, '\0') + 1; + while (*pch); + + size_t cchTotal = pch - pPib->pib_pchcmd; + pchArgs = (char *)alloca(cchTotal + sizeof("--daemonized\0\0")); + memcpy(pchArgs, pPib->pib_pchcmd, cchTotal - 1); + memcpy(pchArgs + cchTotal - 1, "--daemonized\0\0", sizeof("--daemonized\0\0")); + } + else + { + size_t cchTotal = pch - pPib->pib_pchcmd + 1; + pchArgs = (char *)alloca(cchTotal + sizeof(" --daemonized ")); + memcpy(pchArgs, pPib->pib_pchcmd, cch0 + 1); + pch = pchArgs + cch0 + 1; + memcpy(pch, " --daemonized ", sizeof(" --daemonized ") - 1); + pch += sizeof(" --daemonized ") - 1; + if (cch1) + memcpy(pch, pPib->pib_pchcmd + cch0 + 1, cch1 + 2); + else + pch[0] = pch[1] = '\0'; + } + + /* spawn a detach process */ + char szObj[128]; + RESULTCODES ResCodes = { 0, 0 }; + szObj[0] = '\0'; + rc = DosExecPgm(szObj, sizeof(szObj), EXEC_BACKGROUND, (PCSZ)pchArgs, NULL, &ResCodes, (PCSZ)szExe); + if (rc) + { + /** @todo Change this to some standard log/print error?? */ + /* VBoxServiceError("DosExecPgm failed with rc=%d and szObj='%s'\n", rc, szObj); */ + return RTErrConvertFromOS2(rc); + } + DosExit(EXIT_PROCESS, 0); + return VERR_GENERAL_FAILURE; + +#elif defined(RT_OS_WINDOWS) +# error "PORTME" + +#else /* the unices */ + /* + * Fork the child process in a new session and quit the parent. + * + * - fork once and create a new session (setsid). This will detach us + * from the controlling tty meaning that we won't receive the SIGHUP + * (or any other signal) sent to that session. + * - The SIGHUP signal is ignored because the session/parent may throw + * us one before we get to the setsid. + * - When the parent exit(0) we will become an orphan and re-parented to + * the init process. + * - Because of the Linux / System V semantics of assigning the controlling + * tty automagically when a session leader first opens a tty, we will + * fork() once more on Linux to get rid of the session leadership role. + */ + + struct sigaction OldSigAct; + struct sigaction SigAct; + RT_ZERO(SigAct); + SigAct.sa_handler = SIG_IGN; + int rcSigAct = sigaction(SIGHUP, &SigAct, &OldSigAct); + + pid_t pid = fork(); + if (pid == -1) + return RTErrConvertFromErrno(errno); + if (pid != 0) + exit(0); + + /* + * The orphaned child becomes is reparented to the init process. + * We create a new session for it (setsid), point the standard + * file descriptors to /dev/null, and change to the root directory. + */ + pid_t newpgid = setsid(); + int SavedErrno = errno; + if (rcSigAct != -1) + sigaction(SIGHUP, &OldSigAct, NULL); + if (newpgid == -1) + return RTErrConvertFromErrno(SavedErrno); + + if (!fNoClose) + { + /* Open stdin(0), stdout(1) and stderr(2) as /dev/null. */ + int fd = open("/dev/null", O_RDWR); + if (fd == -1) /* paranoia */ + { + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(STDERR_FILENO); + fd = open("/dev/null", O_RDWR); + } + if (fd != -1) + { + dup2(fd, STDIN_FILENO); + dup2(fd, STDOUT_FILENO); + dup2(fd, STDERR_FILENO); + if (fd > 2) + close(fd); + } + } + + if (!fNoChDir) + { + int rcShutUpGcc = chdir("/"); + RT_NOREF_PV(rcShutUpGcc); + } + + /* + * Change the umask - this is non-standard daemon() behavior. + */ + umask(027); + +# ifdef RT_OS_LINUX + /* + * And fork again to lose session leader status (non-standard daemon() + * behaviour). + */ + pid = fork(); + if (pid == -1) + return RTErrConvertFromErrno(errno); + if (pid != 0) + exit(0); +# endif /* RT_OS_LINUX */ + + /* Check if another instance is already running. */ + if (szPidfile != NULL) + { + if (phPidfile != NULL) + { + int rc = VbglR3PidfileWait(szPidfile, phPidfile, 5000); + + /* Another instance of process is already running. */ + if (rc == VERR_FILE_LOCK_VIOLATION) + { + LogRel(("cannot aquire pidfile %s, exitting\n", szPidfile)); + exit(1); + } + + /* Unable to lock on pidfile. */ + if (RT_FAILURE(rc)) + exit(1); + } + else + return VERR_INVALID_PARAMETER; + } + + if (fRespawn) + { + /* We implement re-spawning as a third fork(), with the parent process + * monitoring the child and re-starting it after a delay if it exits + * abnormally. */ + unsigned cRespawn = 0; + for (;;) + { + int iStatus, rcWait; + + if (pcRespawn != NULL) + *pcRespawn = cRespawn; + pid = fork(); + if (pid == -1) + return RTErrConvertFromErrno(errno); + if (pid == 0) + return VINF_SUCCESS; + do + rcWait = waitpid(pid, &iStatus, 0); + while (rcWait == -1 && errno == EINTR); + if (rcWait == -1) + exit(1); + if (WIFEXITED(iStatus)) + { + if (WEXITSTATUS(iStatus) == 0) + exit(0); + else if (fReturnOnUpdate && WEXITSTATUS(iStatus) == VBGLR3EXITCODERELOAD) + { + /* Tell caller that update has been started. */ + if (pfUpdateStarted != NULL) + *pfUpdateStarted = true; + + return VINF_SUCCESS; + } + } + sleep(5); + ++cRespawn; + } + } + return VINF_SUCCESS; +#endif +} + +/** + * A wrapper function for VbglR3DaemonizeEx. + */ +VBGLR3DECL(int) VbglR3Daemonize(bool fNoChDir, bool fNoClose, bool fRespawn, unsigned *pcRespawn) +{ + return VbglR3DaemonizeEx(fNoChDir, fNoClose, fRespawn, pcRespawn, false, NULL, NULL, NULL); +} |