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