src/script.c: only close script file descriptor and kill child if really started
[vlock.git] / src / script.c
blob01c101407f3a9c593eade90b96e69ba9a356fe82
1 /* script.c -- script routines for vlock, the VT locking program for linux
3 * This program is copyright (C) 2007 Frank Benkstein, and is free
4 * software which is freely distributable under the terms of the
5 * GNU General Public License version 2, included as the file COPYING in this
6 * distribution. It is NOT public domain software, and any
7 * redistribution not permitted by the GNU General Public License is
8 * expressly forbidden without prior written permission from
9 * the author.
13 /* Scripts are executables that are run as unprivileged child processes of
14 * vlock. They communicate with vlock through stdin and stdout.
16 * When dependencies are retrieved they are launched once for each dependency
17 * and should print the names of the plugins they depend on on stdout one per
18 * line. The dependency requested is given as a single command line argument.
20 * In hook mode the script is called once with "hooks" as a single command line
21 * argument. It should not exit until its stdin closes. The hook that should
22 * be executed is written to its stdin on a single line.
24 * Currently there is no way for a script to communicate errors or even success
25 * to vlock. If it exits it will linger as a zombie until the plugin is
26 * destroyed.
29 #if !defined(__FreeBSD__) && !defined(_GNU_SOURCE)
30 #define _GNU_SOURCE
31 #endif
33 #include <stdio.h>
34 #include <string.h>
35 #include <stdlib.h>
36 #include <unistd.h>
37 #include <limits.h>
38 #include <sys/select.h>
39 #include <fcntl.h>
40 #include <signal.h>
41 #include <errno.h>
42 #include <sys/time.h>
43 #include <time.h>
45 #include "list.h"
46 #include "process.h"
47 #include "util.h"
49 #include "plugin.h"
51 static bool init_script(struct plugin *p);
52 static void destroy_script(struct plugin *p);
53 static bool call_script_hook(struct plugin *p, const char *hook_name);
55 struct plugin_type *script = &(struct plugin_type){
56 .init = init_script,
57 .destroy = destroy_script,
58 .call_hook = call_script_hook,
61 struct script_context
63 /* The path to the script. */
64 char *path;
65 /* Was the script launched? */
66 bool launched;
67 /* Did the script die? */
68 bool dead;
69 /* The pipe file descriptor that is connected to the script's stdin. */
70 int fd;
71 /* The PID of the script. */
72 pid_t pid;
75 /* Get the dependency from the script. */
76 static bool get_dependency(const char *path, const char *dependency_name,
77 struct list *dependency_list);
78 /* Launch the script creating a new script_context. */
79 static bool launch_script(struct script_context *script);
81 bool init_script(struct plugin *p)
83 int errsv;
84 struct script_context *context = malloc(sizeof *context);
86 if (context == NULL)
87 return false;
89 context->dead = false;
90 context->launched = false;
92 if (asprintf(&context->path, "%s/%s", VLOCK_SCRIPT_DIR, p->name) < 0) {
93 free(context);
94 errno = ENOMEM;
95 return false;
98 /* Get the dependency information. Whether the script is executable or not
99 * is also detected here. */
100 for (size_t i = 0; i < nr_dependencies; i++)
101 if (!get_dependency(context->path, dependency_names[i], p->dependencies[i]))
102 goto error;
104 p->context = context;
105 return true;
107 error:
108 errsv = errno;
109 free(context->path);
110 free(context);
111 errno = errsv;
112 return false;
115 static void destroy_script(struct plugin *p)
117 struct script_context *context = p->context;
119 if (context != NULL) {
120 free(context->path);
122 if (context->launched) {
123 /* Close the pipe. */
124 (void) close(context->fd);
126 /* Kill the child process. */
127 if (!wait_for_death(context->pid, 0, 500000L))
128 ensure_death(context->pid);
131 free(context);
135 /* Invoke the hook by writing it on a single line to the scripts stdin. */
136 static bool call_script_hook(struct plugin *s, const char *hook_name)
138 static const char newline = '\n';
139 struct script_context *context = s->context;
140 ssize_t hook_name_length = strlen(hook_name);
141 ssize_t length;
142 struct sigaction act;
143 struct sigaction oldact;
145 if (!context->launched) {
146 /* Launch script. */
147 context->launched = launch_script(context);
149 if (!context->launched)
150 return false;
153 if (context->dead)
154 /* Nothing to do. */
155 return false;
157 /* When writing to a pipe when the read end is closed the kernel invariably
158 * sends SIGPIPE. Ignore it. */
159 (void) sigemptyset(&(act.sa_mask));
160 act.sa_flags = SA_RESTART;
161 act.sa_handler = SIG_IGN;
162 (void) sigaction(SIGPIPE, &act, &oldact);
164 /* Send hook name and a newline through the pipe. */
165 length = write(context->fd, hook_name, hook_name_length);
167 if (length > 0)
168 length += write(context->fd, &newline, sizeof newline);
170 /* Restore the previous SIGPIPE handler. */
171 (void) sigaction(SIGPIPE, &oldact, NULL);
173 /* If write fails the script is considered dead. */
174 context->dead = (length != hook_name_length + 1);
176 return !context->dead;
179 static bool launch_script(struct script_context *script)
181 int fd_flags;
182 const char *argv[] = { script->path, "hooks", NULL };
183 struct child_process child = {
184 .path = script->path,
185 .argv = argv,
186 .stdin_fd = REDIRECT_PIPE,
187 .stdout_fd = REDIRECT_DEV_NULL,
188 .stderr_fd = REDIRECT_DEV_NULL,
189 .function = NULL,
192 if (!create_child(&child))
193 return false;
195 script->fd = child.stdin_fd;
196 script->pid = child.pid;
198 fd_flags = fcntl(script->fd, F_GETFL, &fd_flags);
200 if (fd_flags != -1) {
201 fd_flags |= O_NONBLOCK;
202 (void) fcntl(script->fd, F_SETFL, fd_flags);
205 return true;
208 static char *read_dependency(const char *path, const char *dependency_name);
209 static bool parse_dependency(char *data, struct list *dependency_list);
211 /* Get the dependency from the script. */
212 static bool get_dependency(const char *path, const char *dependency_name,
213 struct list *dependency_list)
215 /* Read the dependency data. */
216 char *data;
217 errno = 0;
218 data = read_dependency(path, dependency_name);
220 if (data == NULL) {
221 return errno == 0;
222 } else {
223 /* Parse the dependency data. */
224 bool result = parse_dependency(data, dependency_list);
225 int errsv = errno;
226 free(data);
227 errno = errsv;
228 return result;
232 /* Read the dependency data by starting the script with the name of the
233 * dependency as a single command line argument. The script should then print
234 * the dependencies to its stdout one on per line. */
235 static char *read_dependency(const char *path, const char *dependency_name)
237 const char *argv[] = { path, dependency_name, NULL };
238 struct child_process child = {
239 .path = path,
240 .argv = argv,
241 .stdin_fd = REDIRECT_DEV_NULL,
242 .stdout_fd = REDIRECT_PIPE,
243 .stderr_fd = REDIRECT_DEV_NULL,
244 .function = NULL,
246 struct timeval timeout = {1, 0};
247 char *data = calloc(1, sizeof *data);
248 size_t data_length = 0;
250 if (data == NULL)
251 return NULL;
253 if (!create_child(&child)) {
254 int errsv = errno;
255 free(data);
256 errno = errsv;
257 return NULL;
260 /* Read the dependency from the child. Reading fails if either the timeout
261 * elapses or more that LINE_MAX bytes are read. */
262 for (;;) {
263 struct timeval t = timeout;
264 struct timeval t1;
265 struct timeval t2;
266 char buffer[LINE_MAX];
267 ssize_t length;
269 fd_set read_fds;
271 FD_ZERO(&read_fds);
272 FD_SET(child.stdout_fd, &read_fds);
274 /* t1 is before select. */
275 (void) gettimeofday(&t1, NULL);
277 if (select(child.stdout_fd+1, &read_fds, NULL, NULL, &t) != 1) {
278 timeout:
279 errno = ETIMEDOUT;
280 goto error;
283 /* t2 is after select. */
284 (void) gettimeofday(&t2, NULL);
286 /* Get the time that during select. */
287 timersub(&t2, &t1, &t2);
289 /* This is very unlikely. */
290 if (timercmp(&t2, &timeout, >))
291 goto timeout;
293 /* Reduce the timeout. */
294 timersub(&timeout, &t2, &timeout);
296 /* Read dependency data from the script. */
297 length = read(child.stdout_fd, buffer, sizeof buffer - 1);
299 /* Did the script close its stdin or exit? */
300 if (length <= 0)
301 break;
303 if (data_length+length+1 > LINE_MAX) {
304 errno = EFBIG;
305 goto error;
308 /* Grow the data string. */
309 data = realloc(data, data_length+length);
311 if (data == NULL)
312 goto error;
314 /* Append the buffer to the data string. */
315 strncpy(data+data_length, buffer, length);
316 data_length += length;
319 /* Terminate the data string. */
320 data[data_length] = '\0';
322 /* Close the read end of the pipe. */
323 (void) close(child.stdout_fd);
324 /* Kill the script. */
325 if (!wait_for_death(child.pid, 0, 500000L))
326 ensure_death(child.pid);
328 return data;
330 error:
332 int errsv = errno;
333 free(data);
334 (void) close(child.stdout_fd);
335 if (!wait_for_death(child.pid, 0, 500000L))
336 ensure_death(child.pid);
337 errno = errsv;
338 return NULL;
342 static bool parse_dependency(char *data, struct list *dependency_list)
344 for (char *saveptr, *token = strtok_r(data, " \r\n", &saveptr);
345 token != NULL;
346 token = strtok_r(NULL, " \r\n", &saveptr)) {
347 char *s = strdup(token);
349 if (s == NULL || !list_append(dependency_list, s))
350 return false;
353 return true;