src/script.c: simplify dependency parsing loop
[vlock.git] / src / script.c
blobf95334fb52c953e74947043d7273561594502ece
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 pipe file descriptor that is connected to the script's stdin. */
64 int fd;
65 /* The PID of the script. */
66 pid_t pid;
69 /* Get the dependency from the script. */
70 static bool get_dependency(const char *path, const char *dependency_name,
71 struct list *dependency_list);
72 /* Launch the script creating a new script_context. No error detection aborts
73 * on fatal errors. */
74 static struct script_context *launch_script(const char *path);
76 bool init_script(struct plugin *p)
78 char *path;
80 if (asprintf(&path, "%s/%s", VLOCK_SCRIPT_DIR, p->name) < 0) {
81 errno = ENOMEM;
82 return false;
85 /* Test for access. This must be done manually because vlock most likely
86 * runs as a setuid executable and would otherwise override restrictions.
88 * Also there is currently no error detection in case exec() fails later.
90 if (access(path, X_OK) < 0) {
91 int errsv = errno;
92 free(path);
93 errno = errsv;
94 return false;
97 /* Get the dependency information. */
98 for (size_t i = 0; i < nr_dependencies; i++)
99 if (!get_dependency(path, dependency_names[i], p->dependencies[i]))
100 return false;
102 /* Launch the script. */
103 p->context = launch_script(path);
105 if (p->context != NULL) {
106 free(path);
107 return true;
108 } else {
109 int errsv = errno;
110 free(path);
111 errno = errsv;
112 return false;
116 static void destroy_script(struct plugin *p)
118 struct script_context *context = p->context;
120 if (context != NULL) {
121 /* Close the pipe. */
122 (void) close(context->fd);
124 /* Kill the child process. */
125 if (!wait_for_death(context->pid, 0, 500000L))
126 ensure_death(context->pid);
128 free(context);
132 /* Invoke the hook by writing it on a single line to the scripts stdin. */
133 static bool call_script_hook(struct plugin *s, const char *hook_name)
135 static const char newline = '\n';
136 struct script_context *context = s->context;
137 ssize_t hook_name_length = strlen(hook_name);
138 ssize_t length;
139 struct sigaction act;
140 struct sigaction oldact;
142 /* When writing to a pipe when the read end is closed the kernel invariably
143 * sends SIGPIPE. Ignore it. */
144 (void) sigemptyset(&(act.sa_mask));
145 act.sa_flags = SA_RESTART;
146 act.sa_handler = SIG_IGN;
147 (void) sigaction(SIGPIPE, &act, &oldact);
149 /* Send hook name and a newline through the pipe. */
150 length = write(context->fd, hook_name, hook_name_length);
152 if (length > 0)
153 length += write(context->fd, &newline, sizeof newline);
155 /* Restore the previous SIGPIPE handler. */
156 (void) sigaction(SIGPIPE, &oldact, NULL);
158 /* Scripts fail silently. */
159 return (length == hook_name_length + 1);
162 static struct script_context *launch_script(const char *path)
164 int fd_flags;
165 struct script_context *script = malloc(sizeof *script);
166 const char *argv[] = { path, "hooks", NULL };
167 struct child_process child = {
168 .path = path,
169 .argv = argv,
170 .stdin_fd = REDIRECT_PIPE,
171 .stdout_fd = REDIRECT_DEV_NULL,
172 .stderr_fd = REDIRECT_DEV_NULL,
173 .function = NULL,
176 if (script == NULL)
177 return NULL;
179 if (!create_child(&child)) {
180 int errsv = errno;
181 free(script);
182 errno = errsv;
183 return NULL;
186 script->fd = child.stdin_fd;
187 script->pid = child.pid;
189 fd_flags = fcntl(script->fd, F_GETFL, &fd_flags);
191 if (fd_flags != -1) {
192 fd_flags |= O_NONBLOCK;
193 (void) fcntl(script->fd, F_SETFL, fd_flags);
196 return script;
199 static char *read_dependency(const char *path, const char *dependency_name);
200 static bool parse_dependency(char *data, struct list *dependency_list);
202 /* Get the dependency from the script. */
203 static bool get_dependency(const char *path, const char *dependency_name,
204 struct list *dependency_list)
206 /* Read the dependency data. */
207 char *data;
208 errno = 0;
209 data = read_dependency(path, dependency_name);
211 if (data == NULL) {
212 return errno != 0;
213 } else {
214 /* Parse the dependency data. */
215 bool result = parse_dependency(data, dependency_list);
216 int errsv = errno;
217 free(data);
218 errno = errsv;
219 return result;
223 /* Read the dependency data by starting the script with the name of the
224 * dependency as a single command line argument. The script should then print
225 * the dependencies to its stdout one on per line. */
226 static char *read_dependency(const char *path, const char *dependency_name)
228 const char *argv[] = { path, dependency_name, NULL };
229 struct child_process child = {
230 .path = path,
231 .argv = argv,
232 .stdin_fd = REDIRECT_DEV_NULL,
233 .stdout_fd = REDIRECT_PIPE,
234 .stderr_fd = REDIRECT_DEV_NULL,
235 .function = NULL,
237 struct timeval timeout = {1, 0};
238 char *data = calloc(1, sizeof *data);
239 size_t data_length = 0;
241 if (data == NULL)
242 return NULL;
244 if (!create_child(&child)) {
245 int errsv = errno;
246 free(data);
247 errno = errsv;
248 return NULL;
251 /* Read the dependency from the child. Reading fails if either the timeout
252 * elapses or more that LINE_MAX bytes are read. */
253 for (;;) {
254 struct timeval t = timeout;
255 struct timeval t1;
256 struct timeval t2;
257 char buffer[LINE_MAX];
258 ssize_t length;
260 fd_set read_fds;
262 FD_ZERO(&read_fds);
263 FD_SET(child.stdout_fd, &read_fds);
265 /* t1 is before select. */
266 (void) gettimeofday(&t1, NULL);
268 if (select(child.stdout_fd+1, &read_fds, NULL, NULL, &t) != 1) {
269 timeout:
270 errno = ETIMEDOUT;
271 goto error;
274 /* t2 is after select. */
275 (void) gettimeofday(&t2, NULL);
277 /* Get the time that during select. */
278 timersub(&t2, &t1, &t2);
280 /* This is very unlikely. */
281 if (timercmp(&t2, &timeout, >))
282 goto timeout;
284 /* Reduce the timeout. */
285 timersub(&timeout, &t2, &timeout);
287 /* Read dependency data from the script. */
288 length = read(child.stdout_fd, buffer, sizeof buffer - 1);
290 /* Did the script close its stdin or exit? */
291 if (length <= 0)
292 break;
294 if (data_length+length+1 > LINE_MAX) {
295 errno = EFBIG;
296 goto error;
299 /* Grow the data string. */
300 data = realloc(data, data_length+length);
302 if (data == NULL)
303 goto error;
305 /* Append the buffer to the data string. */
306 strncpy(data+data_length, buffer, length);
307 data_length += length;
310 /* Terminate the data string. */
311 data[data_length] = '\0';
313 /* Close the read end of the pipe. */
314 (void) close(child.stdout_fd);
315 /* Kill the script. */
316 if (!wait_for_death(child.pid, 0, 500000L))
317 ensure_death(child.pid);
319 return data;
321 error:
323 int errsv = errno;
324 free(data);
325 (void) close(child.stdout_fd);
326 if (!wait_for_death(child.pid, 0, 500000L))
327 ensure_death(child.pid);
328 errno = errsv;
329 return NULL;
333 static bool parse_dependency(char *data, struct list *dependency_list)
335 for (char *saveptr, *token = strtok_r(data, "\n", &saveptr);
336 token != NULL;
337 token = strtok_r(NULL, "\n", &saveptr)) {
338 char *s = strdup(token);
340 if (s == NULL || !list_append(dependency_list, s))
341 return false;
344 return true;