1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
|
#include <fcntl.h>
#include <stdio.h>
#include <stdbool.h>
#include <errno.h>
#include <string.h>
#include <sys/wait.h>
#include "vis-lua.h"
#include "vis-subprocess.h"
#include "util.h"
/* Pool of information about currently running subprocesses */
static Process *process_pool;
/**
* Adds new empty process information structure to the process pool and
* returns it
* @return a new Process instance
*/
static Process *new_process_in_pool(void) {
Process *newprocess = malloc(sizeof(Process));
if (!newprocess) {
return NULL;
}
newprocess->next = process_pool;
process_pool = newprocess;
return newprocess;
}
/**
* Removes the subprocess information from the pool, sets invalidator to NULL
* and frees resources.
* @param a reference to the process to be removed
* @return the next process in the pool
*/
static Process *destroy_process(Process *target) {
if (target->outfd != -1) {
close(target->outfd);
}
if (target->errfd != -1) {
close(target->errfd);
}
if (target->inpfd != -1) {
close(target->inpfd);
}
/* marking stream as closed for lua */
if (target->invalidator) {
*(target->invalidator) = NULL;
}
Process *next = target->next;
free(target->name);
free(target);
return next;
}
/**
* Starts new subprocess by passing the `command` to the shell and
* returns the subprocess information structure, containing file descriptors
* of the process.
* Also stores the subprocess information to the internal pool to track
* its status and responses.
* @param name a string that contains a unique name for the subprocess.
* This name will be passed to the PROCESS_RESPONSE event handler
* to distinguish running subprocesses.
* @param command a command to be executed to spawn a process
* @param invalidator a pointer to the pointer which shows that the subprocess
* is invalid when set to NULL. When the subprocess dies, it is set to NULL.
* If a caller sets the pointer to NULL the subprocess will be killed on the
* next main loop iteration.
*/
Process *vis_process_communicate(Vis *vis, const char *name,
const char *command, Invalidator **invalidator) {
int pin[2], pout[2], perr[2];
pid_t pid = (pid_t)-1;
if (pipe(perr) == -1) {
goto closeerr;
}
if (pipe(pout) == -1) {
goto closeouterr;
}
if (pipe(pin) == -1) {
goto closeall;
}
pid = fork();
if (pid == -1) {
vis_info_show(vis, "fork failed: %s", strerror(errno));
} else if (pid == 0) { /* child process */
sigset_t sigterm_mask;
sigemptyset(&sigterm_mask);
sigaddset(&sigterm_mask, SIGTERM);
if (sigprocmask(SIG_UNBLOCK, &sigterm_mask, NULL) == -1) {
fprintf(stderr, "failed to reset signal mask");
exit(EXIT_FAILURE);
}
dup2(pin[0], STDIN_FILENO);
dup2(pout[1], STDOUT_FILENO);
dup2(perr[1], STDERR_FILENO);
} else { /* main process */
Process *new = new_process_in_pool();
if (!new) {
vis_info_show(vis, "Cannot create process: %s", strerror(errno));
goto closeall;
}
new->name = strdup(name);
if (!new->name) {
vis_info_show(vis, "Cannot copy process name: %s", strerror(errno));
/* pop top element (which is `new`) from the pool */
process_pool = destroy_process(process_pool);
goto closeall;
}
new->outfd = pout[0];
new->errfd = perr[0];
new->inpfd = pin[1];
new->pid = pid;
new->invalidator = invalidator;
close(pin[0]);
close(pout[1]);
close(perr[1]);
return new;
}
closeall:
close(pin[0]);
close(pin[1]);
closeouterr:
close(pout[0]);
close(pout[1]);
closeerr:
close(perr[0]);
close(perr[1]);
if (pid == 0) { /* start command in child process */
execlp(vis->shell, vis->shell, "-c", command, (char*)NULL);
fprintf(stderr, "exec failed: %s(%d)\n", strerror(errno), errno);
exit(1);
} else {
vis_info_show(vis, "process creation failed: %s", strerror(errno));
}
return NULL;
}
/**
* Adds file descriptors of currently running subprocesses to the `readfds`
* to track their readiness and returns maximum file descriptor value
* to pass it to the `pselect` call
* @param readfds the structure for `pselect` call to fill
* @return maximum file descriptor number in the readfds structure
*/
int vis_process_before_tick(fd_set *readfds) {
int maxfd = 0;
for (Process **pointer = &process_pool; *pointer; pointer = &((*pointer)->next)) {
Process *current = *pointer;
if (current->outfd != -1) {
FD_SET(current->outfd, readfds);
maxfd = maxfd < current->outfd ? current->outfd : maxfd;
}
if (current->errfd != -1) {
FD_SET(current->errfd, readfds);
maxfd = maxfd < current->errfd ? current->errfd : maxfd;
}
}
return maxfd;
}
/**
* Reads data from the given subprocess file descriptor `fd` and fires
* the PROCESS_RESPONSE event in Lua with given subprocess `name`,
* `rtype` and the read data as arguments.
* @param fd the file descriptor to read data from
* @param name a name of the subprocess
* @param rtype a type of file descriptor where the new data is found
*/
static void read_and_fire(Vis* vis, int fd, const char *name, ResponseType rtype) {
static char buffer[PIPE_BUF];
size_t obtained = read(fd, &buffer, PIPE_BUF-1);
if (obtained > 0) {
vis_lua_process_response(vis, name, buffer, obtained, rtype);
}
}
/**
* Checks if `readfds` contains file descriptors of subprocesses from
* the pool. If so, it reads their data and fires corresponding events.
* Also checks if each subprocess from the pool is dead or needs to be
* killed then raises an event or kills it if necessary.
* @param readfds the structure for `pselect` call with file descriptors
*/
void vis_process_tick(Vis *vis, fd_set *readfds) {
for (Process **pointer = &process_pool; *pointer; ) {
Process *current = *pointer;
if (current->outfd != -1 && FD_ISSET(current->outfd, readfds)) {
read_and_fire(vis, current->outfd, current->name, STDOUT);
}
if (current->errfd != -1 && FD_ISSET(current->errfd, readfds)) {
read_and_fire(vis, current->errfd, current->name, STDERR);
}
int status;
pid_t wpid = waitpid(current->pid, &status, WNOHANG);
if (wpid == -1) {
vis_message_show(vis, strerror(errno));
} else if (wpid == current->pid) {
goto just_destroy;
} else if (!*(current->invalidator)) {
goto kill_and_destroy;
}
pointer = ¤t->next;
continue;
kill_and_destroy:
kill(current->pid, SIGTERM);
waitpid(current->pid, &status, 0);
just_destroy:
if (WIFSIGNALED(status)) {
vis_lua_process_response(vis, current->name, NULL, WTERMSIG(status), SIGNAL);
} else {
vis_lua_process_response(vis, current->name, NULL, WEXITSTATUS(status), EXIT);
}
/* update our iteration pointer */
*pointer = destroy_process(current);
}
}
|