dey-examples/ble-gatt-server-example/subprocess.c

174 lines
4.5 KiB
C

/*
* Copyright (c) 2017 Digi International Inc.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <sys/wait.h>
#include <unistd.h>
#include "subprocess.h"
/**
* Convert a command string to an array of pointers to null-terminated strings
*
* @param command the command to tokenize
* @return strings array on sucess, NULL on error
*/
static char **tokenize_command(char *command)
{
const char *delim = " ";
char **argv;
char *token, *buf;
int c = 1;
argv = calloc(c, sizeof(char *));
if (!argv)
goto done;
/* 'strtok' changes the original string, so use a copy we can free */
buf = strdup(command);
token = strtok(buf, delim);
argv[c - 1] = token ? strdup(token) : NULL;
while (argv[c - 1]) {
void *tmp;
c++;
tmp = realloc(argv, c * sizeof(char *));
if (!tmp)
free(argv);
argv = tmp;
if (!argv)
break;
token = strtok(NULL, delim);
argv[c - 1] = token ? strdup(token) : NULL;
}
free(buf);
done:
return argv;
}
/**
* Safely fork a child process and execute the given command with PATH lookup.
*
* @param command the command to execute
* @param child_status return child status of executable
* @return 0 on success, -1 on error with errno set
*/
int safe_execute(char *command, int *const child_status)
{
int status = 0, i;
int sub_status = 0;
pid_t pid = 0;
pid_t sub_pid = 0;
struct sigaction const action_ignore = {.sa_handler = SIG_IGN };
char **argv = tokenize_command(command);
if ((NULL == command) || (NULL == argv) || (NULL == child_status)) {
status = EINVAL;
goto done;
}
/* Initialize child_status */
*child_status = -1;
/*
* Fork a child process to do the execvp() call because that call will not
* return if it works. We're doing it this way to try to make this
* function as "nice" to the calling application as possible by not
* inhibiting interrupts as can happen if the system() call is used (plus
* that utility can have weird side-effects if the calling application has
* suid or sgid privileges set).
*/
pid = fork();
if (pid < 0) {
status = errno;
syslog(LOG_ERR, "[%s:%d] could not fork process: %s (%d)\n",
__func__, __LINE__, strerror(errno), errno);
goto done;
}
/*
* If we're now the child process, do the execvp(), replacing our process
* with the called process. SIGINT and SIGTERM are ignored in child process
* as we don't have any good ways of cleaning up the subprocess after a
* signal.
*/
if (0 == pid) {
if (sigaction(SIGINT, &action_ignore, NULL) < 0) {
status = errno;
syslog
(LOG_ERR, "[%s:%d] error ignoring SIGINT: %s (%d)\n\n",
__func__, __LINE__, strerror(errno), errno);
}
if (sigaction(SIGTERM, &action_ignore, NULL) < 0) {
status = errno;
syslog
(LOG_ERR, "[%s:%d] error ignoring SIGTERM: %s (%d)\n\n",
__func__, __LINE__, strerror(errno), errno);
}
if (execvp(argv[0], (char *const *)argv) < 0) {
status = errno;
syslog
(LOG_ERR, "[%s:%d] error executing child process: %s (%d)\n",
__func__, __LINE__, strerror(errno), errno);
}
_exit(127);
}
/*
* Otherwise, if we're the parent, wait for the child process to terminate.
* If waitpid returns due to a signal (EINTR), ignore the error and
* continue to wait.
*/
do {
errno = 0;
sub_pid = waitpid(pid, &sub_status, 0);
if ((sub_pid < 0) && (EINTR != errno)) {
status = errno;
syslog
(LOG_ERR, "[%s:%d] error returned by waitpid: %s (%d)\n",
__func__, __LINE__, strerror(errno), errno);
goto done;
}
if (WIFEXITED(sub_status)) {
*child_status = WEXITSTATUS(sub_status);
}
} while ((errno == EINTR) || (!WIFEXITED(sub_status)));
done:
/* Free resources */
i = 0;
while (argv && argv[i]) {
free(argv[i]);
i++;
}
free(argv);
errno = status;
return (status == 0) ? 0 : -1;
}