fix installation
[vlock.git] / src / script.c
blobcd4b68d5f00c6dc9fb90eda601000b7f9680fd03
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 free(path);
92 return false;
95 /* Get the dependency information. */
96 for (size_t i = 0; i < nr_dependencies; i++)
97 if (!get_dependency(path, dependency_names[i], p->dependencies[i]))
98 return false;
100 /* Launch the script. */
101 p->context = launch_script(path);
103 free(path);
105 return true;
108 static void destroy_script(struct plugin *p)
110 struct script_context *context = p->context;
112 if (context != NULL) {
113 /* Close the pipe. */
114 (void) close(context->fd);
116 /* Kill the child process. */
117 if (!wait_for_death(context->pid, 0, 500000L))
118 ensure_death(context->pid);
120 free(context);
124 /* Invoke the hook by writing it on a single line to the scripts stdin. */
125 static bool call_script_hook(struct plugin *s, const char *hook_name)
127 struct script_context *context = s->context;
128 char *data;
129 ssize_t data_length;
130 ssize_t length;
131 struct sigaction act, oldact;
133 /* Format the line. */
134 data_length = asprintf(&data, "%s\n", hook_name);
136 if (data_length < 0)
137 fatal_error("memory allocation failed");
139 /* When writing to a pipe when the read end is closed the kernel invariably
140 * sends SIGPIPE. Ignore it. */
141 (void) sigemptyset(&(act.sa_mask));
142 act.sa_flags = SA_RESTART;
143 act.sa_handler = SIG_IGN;
144 (void) sigaction(SIGPIPE, &act, &oldact);
146 /* Send hook name and a newline through the pipe. */
147 length = write(context->fd, data, data_length);
149 /* Restore the previous SIGPIPE handler. */
150 (void) sigaction(SIGPIPE, &oldact, NULL);
152 /* Scripts fail silently. */
153 return (length == data_length);
156 static struct script_context *launch_script(const char *path)
158 struct script_context *script = ensure_malloc(sizeof *script);
159 int pipe_fds[2];
161 /* Open a pipe. */
162 if (pipe(pipe_fds) < 0)
163 fatal_error("pipe() failed");
165 script->fd = pipe_fds[1];
167 script->pid = fork();
169 if (script->pid == 0) {
170 /* Child. */
171 int nullfd = open("/dev/null", O_WRONLY);
173 if (nullfd < 0)
174 _exit(1);
176 /* Redirect stdio. */
177 (void) dup2(pipe_fds[0], STDIN_FILENO);
178 (void) dup2(nullfd, STDOUT_FILENO);
179 (void) dup2(nullfd, STDERR_FILENO);
181 /* Close all other file descriptors. */
182 close_all_fds();
184 // drop privileges
185 setgid(getgid());
186 setuid(getuid());
188 /* Close all other file descriptors. */
189 (void) execl(path, path, "hooks", NULL);
191 _exit(1);
194 /* Close read end of the pipe. */
195 (void) close(pipe_fds[0]);
197 if (script->pid > 0)
198 return script;
200 free(script);
201 fatal_error("fork() failed");
204 static char *read_dependency(const char *path, const char *dependency_name);
205 static bool parse_dependency(char *data, struct list *dependency_list);
207 /* Get the dependency from the script. */
208 static bool get_dependency(const char *path, const char *dependency_name,
209 struct list *dependency_list)
211 /* Read the dependency data. */
212 char *data;
213 errno = 0;
214 data = read_dependency(path, dependency_name);
217 if (data == NULL) {
218 return errno != 0;
219 } else {
220 /* Parse the dependency data. */
221 bool result = parse_dependency(data, dependency_list);
222 int errsv = errno;
223 free(data);
224 errno = errsv;
225 return result;
229 /* Read the dependency data by starting the script with the name of the
230 * dependency as a single command line argument. The script should then print
231 * the dependencies to its stdout one on per line. */
232 static char *read_dependency(const char *path, const char *dependency_name)
234 char *error = NULL;
235 int pipe_fds[2];
236 pid_t pid;
237 struct timeval timeout = (struct timeval){1, 0};
238 char *data = ensure_calloc(1, sizeof *data);
239 size_t data_length = 0;
241 /* Open a pipe for the script. */
242 if (pipe(pipe_fds) < 0)
243 fatal_error("pipe() failed");
245 pid = fork();
247 if (pid == 0) {
248 /* Child. */
249 int nullfd = open("/dev/null", O_RDWR);
251 if (nullfd < 0)
252 _exit(1);
254 /* Redirect stdio. */
255 (void) dup2(nullfd, STDIN_FILENO);
256 (void) dup2(pipe_fds[1], STDOUT_FILENO);
257 (void) dup2(nullfd, STDERR_FILENO);
259 /* Close all other file descriptors. */
260 close_all_fds();
262 /* Drop privileges. */
263 setgid(getgid());
264 setuid(getuid());
266 (void) execl(path, path, dependency_name, NULL);
268 _exit(1);
271 /* Close write end of the pipe. */
272 (void) close(pipe_fds[1]);
274 if (pid < 0) {
275 (void) close(pipe_fds[0]);
276 free(data);
277 fatal_error("fork() failed");
280 /* Read the dependency from the child. Reading fails if either the timeout
281 * elapses or more that LINE_MAX bytes are read. */
282 for (;;) {
283 struct timeval t = timeout;
284 struct timeval t1;
285 struct timeval t2;
286 char buffer[LINE_MAX];
287 ssize_t length;
289 fd_set read_fds;
291 FD_ZERO(&read_fds);
292 FD_SET(pipe_fds[0], &read_fds);
294 /* t1 is before select. */
295 (void) gettimeofday(&t1, NULL);
297 if (select(pipe_fds[0]+1, &read_fds, NULL, NULL, &t) != 1) {
298 timeout:
299 asprintf(&error, "timeout while reading dependency '%s' from '%s", dependency_name, path);
300 goto read_error;
303 /* t2 is after select. */
304 (void) gettimeofday(&t2, NULL);
306 /* Get the time that during select. */
307 timersub(&t2, &t1, &t2);
309 /* This is very unlikely. */
310 if (timercmp(&t2, &timeout, >))
311 goto timeout;
313 /* Reduce the timeout. */
314 timersub(&timeout, &t2, &timeout);
316 /* Read dependency data from the script. */
317 length = read(pipe_fds[0], buffer, sizeof buffer - 1);
319 /* Did the script close its stdin or exit? */
320 if (length <= 0)
321 break;
323 if (data_length+length+1 > LINE_MAX) {
324 asprintf(&error, "too much data while reading dependency '%s' from '%s'", dependency_name, path);
325 goto read_error;
328 /* Append the buffer to the data string. */
329 data = ensure_realloc(data, data_length+length);
330 strncpy(data+data_length, buffer, length);
331 data_length += length;
334 /* Terminate the data string. */
335 data[data_length] = '\0';
337 /* Close the read end of the pipe. */
338 (void) close(pipe_fds[0]);
339 /* Kill the script. */
340 if (!wait_for_death(pid, 0, 500000L))
341 ensure_death(pid);
343 return data;
345 read_error:
346 free(data);
347 (void) close(pipe_fds[0]);
348 if (!wait_for_death(pid, 0, 500000L))
349 ensure_death(pid);
350 fprintf(stderr, "%s\n", error);
351 free(error);
352 abort();
355 static bool parse_dependency(char *data, struct list *dependency_list)
357 for (char *s = data, *saveptr;; s = NULL) {
358 char *token = strtok_r(s, "\n", &saveptr);
360 if (token == NULL)
361 break;
363 list_append(dependency_list, ensure_not_null(strdup(token), "could not copy string"));
366 return true;