diff options
Diffstat (limited to 'open-vm-tools/vgauth/service/servicePosix.c')
-rw-r--r-- | open-vm-tools/vgauth/service/servicePosix.c | 563 |
1 files changed, 563 insertions, 0 deletions
diff --git a/open-vm-tools/vgauth/service/servicePosix.c b/open-vm-tools/vgauth/service/servicePosix.c new file mode 100644 index 00000000..c6e4de32 --- /dev/null +++ b/open-vm-tools/vgauth/service/servicePosix.c @@ -0,0 +1,563 @@ +/********************************************************* + * Copyright (C) 2011-2015 VMware, Inc. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation version 2.1 and no later version. + * + * 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 Lesser GNU General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + *********************************************************/ + +#define _GNU_SOURCE +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <sys/file.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <signal.h> +#include <sys/time.h> +#include <sys/resource.h> + +#if defined(sun) +#include <sys/systeminfo.h> +#endif + +#if defined(__FreeBSD__) || defined(__APPLE__) +# include <sys/sysctl.h> +#endif + +/* + * XXX we can probably remove a lot of these; carried over from hostinfoPosix.c + * til we're building on Mac and can check. + */ +#if defined(__APPLE__) +#include <assert.h> +#include <CoreServices/CoreServices.h> +#include <mach-o/dyld.h> +#include <mach/host_info.h> +#include <mach/mach_host.h> +#include <mach/mach_init.h> +#include <mach/mach.h> +#include <mach/mach_time.h> +#include <sys/mman.h> +#elif defined(__FreeBSD__) +#if !defined(RLIMIT_AS) +# if defined(RLIMIT_VMEM) +# define RLIMIT_AS RLIMIT_VMEM +# else +# define RLIMIT_AS RLIMIT_RSS +# endif +#endif +#else +#if !defined(sun) && (!defined(USING_AUTOCONF) || (defined(HAVE_SYS_IO_H) && defined(HAVE_SYS_SYSINFO_H))) +#include <sys/io.h> +#include <sys/sysinfo.h> +#ifndef HAVE_SYSINFO +#define HAVE_SYSINFO 1 +#endif +#endif +#endif + +#if defined(__APPLE__) || defined(__FreeBSD__) +#include <paths.h> +#endif + +#include "serviceInt.h" +#include "service.h" +#include "VGAuthBasicDefs.h" + + +#if !defined(_PATH_DEVNULL) +#define _PATH_DEVNULL "/dev/null" +#endif + + +/* + ****************************************************************************** + * ServiceResetProcessState -- */ /** + * + * Based on bora/lib/misc/hostinfoPosix.c:Hostinfo_ResetProcessState() + * Clean up signal handlers and file descriptors before an exec(). + * + * @param[in] keepFds Array of fds to leave open. + * @param[in] numKeepFds Number of fds to leave open. + * + ****************************************************************************** + */ + +static void +ServiceResetProcessState(int *keepFds, + size_t numKeepFds) +{ + int s, fd; + struct sigaction sa; + struct rlimit rlim; + + /* + * Disable itimers before resetting the signal handlers. + * Otherwise, the process may still receive timer signals: + * SIGALRM, SIGVTARLM, or SIGPROF. + */ + + struct itimerval it; + it.it_value.tv_sec = it.it_value.tv_usec = 0; + it.it_interval.tv_sec = it.it_interval.tv_usec = 0; + setitimer(ITIMER_REAL, &it, NULL); + setitimer(ITIMER_VIRTUAL, &it, NULL); + setitimer(ITIMER_PROF, &it, NULL); + + for (s = 1; s <= NSIG; s++) { + sa.sa_handler = SIG_DFL; + sigfillset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + sigaction(s, &sa, NULL); + } + + for (fd = (int) sysconf(_SC_OPEN_MAX) - 1; fd > STDERR_FILENO; fd--) { + size_t i; + + for (i = 0; i < numKeepFds; i++) { + if (fd == keepFds[i]) { + break; + } + } + if (i == numKeepFds) { + (void) close(fd); + } + } + if (getrlimit(RLIMIT_AS, &rlim) == 0) { + rlim.rlim_cur = rlim.rlim_max; + setrlimit(RLIMIT_AS, &rlim); + } +} + + +/* + ****************************************************************************** + * ServiceSuicide -- */ /** + * + * @brief Service kill + * + * Reads the pid from pidPath and kills the process ID'ed there. + * Useful for shutdown scripts. + * + * @param[in] pidPath NUL-terminated UTF-8 path to read PID. + * + * @return FALSE if the process could not be killed. + ****************************************************************************** + */ + +gboolean +ServiceSuicide(const char *pidPath) +{ + FILE *pidPathFp = NULL; + char pidBuf[32]; + int pid; + int ret; + int errCode; + gboolean bRet = FALSE; + + pidPathFp = g_fopen(pidPath, "r"); + if (NULL == pidPathFp) { + Warning("%s: failed to open pid file '%s', error %u\n", + __FUNCTION__, pidPath, errno); + return FALSE; + } + + if (fgets(pidBuf, sizeof pidBuf, pidPathFp)) { + pid = atoi(pidBuf); + if (pid <= 0) { + Warning("%s bad pid %d read; skipping\n", __FUNCTION__, + pid); + goto done; + } + Debug("%s: killing service at pid %d\n", __FUNCTION__, pid); + ret = kill(pid, SIGTERM); + errCode = errno; + if (0 != ret) { + if (ESRCH == errCode) { + Debug("%s: pid %d not found, returning success\n", + __FUNCTION__, pid); + bRet = TRUE; + } else { + Warning("%s: kill(%d) failed, error %u\n", + __FUNCTION__, pid, errCode); + } + } else { + bRet = TRUE; + } + } + +done: + if (pidPathFp) { + fclose(pidPathFp); + } + return bRet; +} + + + +/* + ****************************************************************************** + * ServiceDaemonize -- */ /** + * + * @brief Cross-platform daemon(3)-like wrapper. + * + * Stripped down from bora/lib/misc/hostinfoPosix.c:Hostinfo_Daemonize() + * + * The current process is restarted with the given arguments. + * The process state is reset (see Hostinfo_ResetProcessState). + * A new session is created (so the process has no controlling terminal). + * + * Restarts the current process as a daemon, given the path to the + * process. This means: + * + * * You're detached from your parent. (Your parent doesn't + * need to wait for you to exit.) + * * Your process no longer has a controlling terminal or + * process group. + * * Your stdin/stdout/stderr fds are redirected to /dev/null. All + * other descriptors, except for the ones that are passed in the + * parameter keepFds, are closed. + * * Your signal handlers are reset to SIG_DFL in the daemonized + * process, and all the signals are unblocked. + * * Your main() function is called with the specified NULL-terminated + * argument list. + * + * (Don't forget that the first string in args is argv[0] -- the + * name of the process). + * + * + * All stdio file descriptors of the daemon process are redirected to /dev/null. + * + * If pidPath is non-NULL, then upon success, writes the PID + * (as a US-ASCII string followed by a newline) of the daemon + * process to that path. + * + * If 'flags' contains SERVICE_DAEMONIZE_LOCKPID and pidPath is + * non-NULL, then an exclusive flock(2) is taken on pidPath to prevent + * multiple instances of the service from running. + * + * @param[in] path NUL-terminated UTF-8 path to exec + * @param[in] args NUL-terminated UTF-8 argv list + * @param[in] flags Any flags. + * @param[in] pidPath NUL-terminated UTF-8 path to write PID. + * + * + * @return FALSE if the process could not be daemonized. errno contains + * the error on failure. + * Doesn't return if the process was daemonized. + * + ****************************************************************************** + */ + +gboolean +ServiceDaemonize(const char *path, + char * const *args, + ServiceDaemonizeFlags flags, + const char *pidPath) +{ + /* + * We use the double-fork method to make a background process whose + * parent is init instead of the original process. + * + * We do this instead of calling daemon(), because daemon() is + * deprecated on Mac OS 10.5 hosts, and calling it causes a compiler + * warning. + * + * We must exec() after forking, because Mac OS library frameworks + * depend on internal Mach ports, which are not correctly propagated + * across fork calls. exec'ing reinitializes the frameworks, which + * causes them to reopen their Mach ports. + */ + + int pidPathFd = -1; + int childPid; + int pipeFds[2] = { -1, -1 }; + int err = EINVAL; + sigset_t sig; + int fd; + int saveFds[2]; + int numSavedFds = 0; + + ASSERT_ON_COMPILE(sizeof (errno) <= sizeof err); + ASSERT(args); + ASSERT(path); + + if (pidPath) { + pidPathFd = g_open(pidPath, O_WRONLY | O_CREAT, 0644); + if (pidPathFd == -1) { + err = errno; + Warning("%s: Couldn't open PID path [%s], error %u.\n", + __FUNCTION__, pidPath, err); + errno = err; + return FALSE; + } + + /* + * Lock this file to take a mutex on daemonizing this process. The child + * will keep this file descriptor open for as long as it is running. + * + * flock(2) is a BSD extension (also supported on Linux) which creates a + * lock that is inherited by the child after fork(2). fcntl(2) locks do + * not have this property. Solaris only supports fcntl(2) locks. + */ +#ifndef sun + if ((flags & SERVICE_DAEMONIZE_LOCKPID) && + flock(pidPathFd, LOCK_EX | LOCK_NB) == -1) { + err = errno; + Warning("%s: Lock held on PID path [%s], error %u, not daemonizing.\n", + __FUNCTION__, pidPath, err); + errno = err; + close(pidPathFd); + return FALSE; + } +#endif + saveFds[numSavedFds++] = pidPathFd; + } + + if (pipe(pipeFds) == -1) { + err = errno; + Warning("%s: Couldn't create pipe, error %u.\n", __FUNCTION__, err); + pipeFds[0] = pipeFds[1] = -1; + goto cleanup; + } + + saveFds[numSavedFds++] = pipeFds[1]; + + if (fcntl(pipeFds[1], F_SETFD, FD_CLOEXEC) == -1) { + err = errno; + Warning("%s: Couldn't set close-on-exec for fd %d, error %u.\n", + __FUNCTION__, pipeFds[1], err); + goto cleanup; + } + + childPid = fork(); + + switch (childPid) { + case -1: + err = errno; + Warning("%s: Couldn't fork first child, error %u.\n", __FUNCTION__, + err); + goto cleanup; + case 0: + /* We're the first child. Continue on. */ + break; + default: + { + /* We're the original process. Check if the first child exited. */ + int status; + + close(pipeFds[1]); + waitpid(childPid, &status, 0); + if (WIFEXITED(status) && WEXITSTATUS(status) != EXIT_SUCCESS) { + Warning("%s: Child %d exited with error %d.\n", + __FUNCTION__, childPid, WEXITSTATUS(status)); + goto cleanup; + } else if (WIFSIGNALED(status)) { + Warning("%s: Child %d exited with signal %d.\n", + __FUNCTION__, childPid, WTERMSIG(status)); + goto cleanup; + } + + /* + * Check if the second child exec'ed successfully. If it had + * an error, it will write an int errno to this pipe before + * exiting. Otherwise, its end of the pipe will be closed on + * exec and this call will fail as expected. + * The assumption is that we don't get a partial read. In case, + * it did happen, we can detect it by the number of bytes read. + */ + + while (TRUE) { + int res = read(pipeFds[0], &err, sizeof err); + + if (res > 0) { + Warning("%s: Child could not exec %s, read %d, error %u.\n", + __FUNCTION__, path, res, err); + goto cleanup; + } else if ((res == -1) && (errno == EINTR)) { + continue; + } + break; + } + + err = 0; + goto cleanup; + } + } + /* + * Reset the signal mask to unblock all + * signals. fork() clears pending signals. + */ + + ServiceResetProcessState(saveFds, numSavedFds); + sigfillset(&sig); + sigprocmask(SIG_UNBLOCK, &sig, NULL); + + if (setsid() == -1) { + Warning("%s: Couldn't create new session, error %d.\n", + __FUNCTION__, errno); + + _exit(EXIT_FAILURE); + } + + switch (fork()) { + case -1: + { + Warning("%s: Couldn't fork, error %d.\n", + __FUNCTION__, errno); + + return FALSE; + } + case 0: + // We're the second child. Continue on. + break; + default: + /* + * We're the first child. We don't need to exist any more. + * + * Exiting here causes the child to be reparented to the + * init process, so the original process doesn't need to wait + * for the child we forked off. + */ + + _exit(EXIT_SUCCESS); + } + + if (chdir("/") == -1) { + int err = errno; + + Warning("%s: Couldn't chdir to /, error %u.\n", __FUNCTION__, err); + + _exit(EXIT_FAILURE); + } + + (void) umask(0); + + fd = open(_PATH_DEVNULL, O_RDONLY); + if (fd != -1) { + dup2(fd, STDIN_FILENO); + close(fd); + } + + fd = open(_PATH_DEVNULL, O_WRONLY); + if (fd != -1) { + dup2(fd, STDOUT_FILENO); + dup2(fd, STDERR_FILENO); + close(fd); + } + + if (pidPath) { + int64_t pid; + char pidString[32]; + int pidStringLen; + + ASSERT_ON_COMPILE(sizeof (pid_t) <= sizeof pid); + ASSERT(pidPathFd >= 0); + + pid = getpid(); + pidStringLen = g_snprintf(pidString, sizeof pidString, + "%"FMT64"d\n", pid); + if (pidStringLen <= 0) { + err = EINVAL; + + if (write(pipeFds[1], &err, sizeof err) == -1) { + Warning("%s: Couldn't write to parent pipe: %u, original " + "error: %u.\n", __FUNCTION__, errno, err); + } + _exit(EXIT_FAILURE); + } + + if (ftruncate(pidPathFd, 0) == -1) { + err = errno; + Warning("%s: Couldn't truncate path [%s], error %d.\n", + __FUNCTION__, pidPath, err); + + if (write(pipeFds[1], &err, sizeof err) == -1) { + Warning("%s: Couldn't write to parent pipe: %u, original " + "error: %u.\n", __FUNCTION__, errno, err); + } + _exit(EXIT_FAILURE); + } + + if (write(pidPathFd, pidString, pidStringLen) != pidStringLen) { + err = errno; + Warning("%s: Couldn't write PID to path [%s], error %d.\n", + __FUNCTION__, pidPath, err); + + if (write(pipeFds[1], &err, sizeof err) == -1) { + Warning("%s: Couldn't write to parent pipe: %u, original " + "error: %u.\n", __FUNCTION__, errno, err); + } + _exit(EXIT_FAILURE); + } + + if (fsync(pidPathFd) == -1) { + err = errno; + Warning("%s: Couldn't flush PID to path [%s], error %d.\n", + __FUNCTION__, pidPath, err); + + if (write(pipeFds[1], &err, sizeof err) == -1) { + Warning("%s: Couldn't write to parent pipe: %u, original " + "error: %u.\n", __FUNCTION__, errno, err); + } + _exit(EXIT_FAILURE); + } + } + + /* + * XXX + * The original code translated the path and argv into the default + * locale -- we may need to do that again. + */ + if (execv(path, args) == -1) { + err = errno; + Warning("%s: Couldn't exec %s, error %d.\n", __FUNCTION__, path, err); + + /* Let the original process know we failed to exec. */ + if (write(pipeFds[1], &err, sizeof err) == -1) { + Warning("%s: Couldn't write to parent pipe: %u, " + "original error: %u.\n", __FUNCTION__, errno, err); + } + _exit(EXIT_FAILURE); + } + + // NOT_REACHED + + cleanup: + if (pipeFds[0] != -1) { + close(pipeFds[0]); + } + if (pipeFds[1] != -1) { + close(pipeFds[1]); + } + + if (err != 0 && pidPath) { + /* + * Unlink pidPath on error before closing pidPathFd to avoid racing + * with another process attempting to daemonize and unlinking the + * file it created instead. + */ + g_unlink(pidPath); + errno = err; + } + + if (pidPath) { + close(pidPathFd); + } + return err == 0; +} + |