badvpn/system/BProcess.c
2016-08-17 21:34:21 +02:00

455 lines
12 KiB
C

/**
* @file BProcess.c
* @author Ambroz Bizjak <ambrop7@gmail.com>
*
* @section LICENSE
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the author nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <stddef.h>
#include <string.h>
#include <inttypes.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <signal.h>
#include <grp.h>
#include <pwd.h>
#include <errno.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <misc/offset.h>
#include <misc/open_standard_streams.h>
#include <base/BLog.h>
#include "BProcess.h"
#include <generated/blog_channel_BProcess.h>
#define INITIAL_NUM_GROUPS 24
static void call_handler (BProcess *o, int normally, uint8_t normally_exit_status)
{
DEBUGERROR(&o->d_err, o->handler(o->user, normally, normally_exit_status))
}
static BProcess * find_process (BProcessManager *o, pid_t pid)
{
for (LinkedList1Node *node = LinkedList1_GetFirst(&o->processes); node; node = LinkedList1Node_Next(node)) {
BProcess *p = UPPER_OBJECT(node, BProcess, list_node);
if (p->pid == pid) {
return p;
}
}
return NULL;
}
static void work_signals (BProcessManager *o)
{
// read exit status with waitpid()
int status;
pid_t pid = waitpid(-1, &status, WNOHANG);
if (pid <= 0) {
return;
}
// schedule next waitpid
BPending_Set(&o->wait_job);
// find process
BProcess *p = find_process(o, pid);
if (!p) {
BLog(BLOG_DEBUG, "unknown child %p");
}
if (WIFEXITED(status)) {
uint8_t exit_status = WEXITSTATUS(status);
BLog(BLOG_INFO, "child %"PRIiMAX" exited with status %"PRIu8, (intmax_t)pid, exit_status);
if (p) {
call_handler(p, 1, exit_status);
return;
}
}
else if (WIFSIGNALED(status)) {
int signo = WTERMSIG(status);
BLog(BLOG_INFO, "child %"PRIiMAX" exited with signal %d", (intmax_t)pid, signo);
if (p) {
call_handler(p, 0, 0);
return;
}
}
else {
BLog(BLOG_ERROR, "unknown wait status type for pid %"PRIiMAX" (%d)", (intmax_t)pid, status);
}
}
static void wait_job_handler (BProcessManager *o)
{
DebugObject_Access(&o->d_obj);
work_signals(o);
return;
}
static void signal_handler (BProcessManager *o, int signo)
{
ASSERT(signo == SIGCHLD)
DebugObject_Access(&o->d_obj);
work_signals(o);
return;
}
int BProcessManager_Init (BProcessManager *o, BReactor *reactor)
{
// init arguments
o->reactor = reactor;
// init signal handling
sigset_t sset;
ASSERT_FORCE(sigemptyset(&sset) == 0)
ASSERT_FORCE(sigaddset(&sset, SIGCHLD) == 0)
if (!BUnixSignal_Init(&o->signal, o->reactor, sset, (BUnixSignal_handler)signal_handler, o)) {
BLog(BLOG_ERROR, "BUnixSignal_Init failed");
goto fail0;
}
// init processes list
LinkedList1_Init(&o->processes);
// init wait job
BPending_Init(&o->wait_job, BReactor_PendingGroup(o->reactor), (BPending_handler)wait_job_handler, o);
DebugObject_Init(&o->d_obj);
return 1;
fail0:
return 0;
}
void BProcessManager_Free (BProcessManager *o)
{
ASSERT(LinkedList1_IsEmpty(&o->processes))
DebugObject_Free(&o->d_obj);
// free wait job
BPending_Free(&o->wait_job);
// free signal handling
BUnixSignal_Free(&o->signal, 1);
}
static int fds_contains (const int *fds, int fd, size_t *pos)
{
for (size_t i = 0; fds[i] >= 0; i++) {
if (fds[i] == fd) {
if (pos) {
*pos = i;
}
return 1;
}
}
return 0;
}
int BProcess_Init2 (BProcess *o, BProcessManager *m, BProcess_handler handler, void *user, const char *file, char *const argv[], struct BProcess_params params)
{
int res = 0;
// init arguments
o->m = m;
o->handler = handler;
o->user = user;
// count fds
size_t num_fds;
for (num_fds = 0; params.fds[num_fds] >= 0; num_fds++);
// We retrieve all the needed info and allocate all needed memory
// before the fork, because doing these things in the child after
// the fork may be unsafe.
// Get the max FD number.
int max_fd = sysconf(_SC_OPEN_MAX);
if (max_fd < 0) {
BLog(BLOG_ERROR, "sysconf(_SC_OPEN_MAX)");
goto fail0;
}
char *pwnam_buf = NULL;
struct passwd pwd;
gid_t groups_static[INITIAL_NUM_GROUPS];
gid_t *groups = groups_static;
int num_groups = INITIAL_NUM_GROUPS;
if (params.username) {
// Get the max getpwnam_r buffer size.
long pwnam_bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
if (pwnam_bufsize < 0) {
pwnam_bufsize = 16384;
}
// Allocate memory for getpwnam_r.
pwnam_buf = malloc(pwnam_bufsize);
if (!pwnam_buf) {
BLog(BLOG_ERROR, "malloc failed");
goto fail0;
}
// Get information about the user.
struct passwd *getpwnam_res;
getpwnam_r(params.username, &pwd, pwnam_buf, pwnam_bufsize, &getpwnam_res);
if (!getpwnam_res) {
BLog(BLOG_ERROR, "getpwnam_r failed");
goto fail1;
}
// Retrieve the group list.
// Start with a small auto-allocated list and only if it turns out
// to be too small resort to malloc.
while (1) {
int groups_ret = getgrouplist(params.username, pwd.pw_gid, groups, &num_groups);
if ((groups_ret < 0) ? (num_groups <= 0) : (num_groups != groups_ret)) {
BLog(BLOG_ERROR, "getgrouplist behaved inconsistently");
goto fail2;
}
if (groups_ret >= 0) {
break;
}
if (groups != groups_static) {
free(groups);
}
groups = malloc(num_groups * sizeof(gid_t));
if (!groups) {
BLog(BLOG_ERROR, "malloc failed");
goto fail2;
}
}
}
// Make a copy of the file descriptors array.
int *fds2 = malloc((num_fds + 1) * sizeof(fds2[0]));
if (!fds2) {
BLog(BLOG_ERROR, "malloc failed");
goto fail2;
}
memcpy(fds2, params.fds, (num_fds + 1) * sizeof(fds2[0]));
// block signals
// needed to prevent parent's signal handlers from being called
// in the child
sigset_t sset_all;
sigfillset(&sset_all);
sigset_t sset_old;
if (pthread_sigmask(SIG_SETMASK, &sset_all, &sset_old) != 0) {
BLog(BLOG_ERROR, "pthread_sigmask failed");
goto fail3;
}
// fork
pid_t pid = fork();
if (pid == 0) {
// this is child
// restore signal dispositions
for (int i = 1; i < NSIG; i++) {
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = SIG_DFL;
sa.sa_flags = 0;
sigaction(i, &sa, NULL);
}
// unblock signals
sigset_t sset_none;
sigemptyset(&sset_none);
if (pthread_sigmask(SIG_SETMASK, &sset_none, NULL) != 0) {
abort();
}
// close file descriptors, except the given fds
for (int i = 0; i < max_fd; i++) {
if (!fds_contains(fds2, i, NULL)) {
close(i);
}
}
// map fds to requested fd numbers
while (*fds2 >= 0) {
// resolve possible conflict
size_t cpos;
if (fds_contains(fds2 + 1, *params.fds_map, &cpos)) {
// dup() the fd to a new number; the old one will be closed
// in the following dup2()
if ((fds2[1 + cpos] = dup(fds2[1 + cpos])) < 0) {
abort();
}
}
if (*fds2 != *params.fds_map) {
// dup fd
if (dup2(*fds2, *params.fds_map) < 0) {
abort();
}
// close original fd
close(*fds2);
}
fds2++;
params.fds_map++;
}
// make sure standard streams are open
open_standard_streams();
// make session leader if requested
if (params.do_setsid) {
setsid();
}
// assume identity of username, if requested
if (params.username) {
if (setgroups(num_groups, groups) < 0) {
abort();
}
if (setgid(pwd.pw_gid) < 0) {
abort();
}
if (setuid(pwd.pw_uid) < 0) {
abort();
}
}
// do the exec
execv(file, argv);
// if we're still here, something went wrong
abort();
}
// restore original signal mask
ASSERT_FORCE(pthread_sigmask(SIG_SETMASK, &sset_old, NULL) == 0)
if (pid < 0) {
BLog(BLOG_ERROR, "fork failed");
goto fail3;
}
// remember pid
o->pid = pid;
// add to processes list
LinkedList1_Append(&o->m->processes, &o->list_node);
DebugObject_Init(&o->d_obj);
DebugError_Init(&o->d_err, BReactor_PendingGroup(m->reactor));
// Returning success, but cleanup first.
res = 1;
fail3:
free(fds2);
fail2:
if (groups != groups_static) {
free(groups);
}
fail1:
free(pwnam_buf);
fail0:
return res;
}
int BProcess_InitWithFds (BProcess *o, BProcessManager *m, BProcess_handler handler, void *user, const char *file, char *const argv[], const char *username, const int *fds, const int *fds_map)
{
struct BProcess_params params;
params.username = username;
params.fds = fds;
params.fds_map = fds_map;
params.do_setsid = 0;
return BProcess_Init2(o, m, handler, user, file, argv, params);
}
int BProcess_Init (BProcess *o, BProcessManager *m, BProcess_handler handler, void *user, const char *file, char *const argv[], const char *username)
{
int fds[] = {-1};
return BProcess_InitWithFds(o, m, handler, user, file, argv, username, fds, NULL);
}
void BProcess_Free (BProcess *o)
{
DebugError_Free(&o->d_err);
DebugObject_Free(&o->d_obj);
// remove from processes list
LinkedList1_Remove(&o->m->processes, &o->list_node);
}
int BProcess_Terminate (BProcess *o)
{
DebugObject_Access(&o->d_obj);
DebugError_AssertNoError(&o->d_err);
ASSERT(o->pid > 0)
if (kill(o->pid, SIGTERM) < 0) {
BLog(BLOG_ERROR, "kill(%"PRIiMAX", SIGTERM) failed", (intmax_t)o->pid);
return 0;
}
return 1;
}
int BProcess_Kill (BProcess *o)
{
DebugObject_Access(&o->d_obj);
DebugError_AssertNoError(&o->d_err);
ASSERT(o->pid > 0)
if (kill(o->pid, SIGKILL) < 0) {
BLog(BLOG_ERROR, "kill(%"PRIiMAX", SIGKILL) failed", (intmax_t)o->pid);
return 0;
}
return 1;
}