-
Notifications
You must be signed in to change notification settings - Fork 261
/
vis-subprocess.c
218 lines (210 loc) · 6.51 KB
/
vis-subprocess.c
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);
}
}