redo plugin structure
[vlock.git] / src / script.c
blobf4eb73b8179eb197811bedc379654739904929b2
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 struct script_context
53 /* The pipe file descriptor that is connected to the script's stdin. */
54 int fd;
55 /* The PID of the script. */
56 pid_t pid;
59 /* Get the dependency from the script. No error detection. Aborts on fatal
60 * errors. */
61 static void get_dependency(const char *path, const char *dependency_name,
62 struct list *dependency_list);
63 /* Launch the script creating a new script_context. No error detection aborts
64 * on fatal errors. */
65 static struct script_context *launch_script(const char *path);
68 static void close_script(struct plugin *s);
69 static bool call_script_hook(struct plugin *s, const char *hook_name);
71 struct plugin *open_script(const char *name, char **error)
73 char *path;
74 struct plugin *s = __allocate_plugin(name);
76 if (asprintf(&path, "%s/%s", VLOCK_SCRIPT_DIR, name) < 0) {
77 *error = strdup("filename too long");
78 goto path_error;
81 /* Test for access. This must be done manually because vlock most likely
82 * runs as a setuid executable and would otherwise override restrictions.
84 * Also there is currently no error detection in case exec() fails later.
86 if (access(path, X_OK) < 0) {
87 (void) asprintf(error, "%s: %s", path, strerror(errno));
88 goto access_error;
91 /* Get the dependency information. */
92 for (size_t i = 0; i < nr_dependencies; i++)
93 get_dependency(path, dependency_names[i], s->dependencies[i]);
95 /* Launch the script. */
96 s->context = launch_script(path);
97 s->close = close_script;
98 s->call_hook = call_script_hook;
100 free(path);
102 return s;
104 access_error:
105 free(path);
107 path_error:
108 __destroy_plugin(s);
109 return NULL;
112 static void close_script(struct plugin *s)
114 struct script_context *context = s->context;
116 /* Close the pipe. */
117 (void) close(context->fd);
119 /* Kill the child process. */
120 if (!wait_for_death(context->pid, 0, 500000L))
121 ensure_death(context->pid);
123 free(context);
126 /* Invoke the hook by writing it on a single line to the scripts stdin. */
127 static bool call_script_hook(struct plugin *s, const char *hook_name)
129 struct script_context *context = s->context;
130 char *data;
131 ssize_t data_length;
132 ssize_t length;
133 struct sigaction act, oldact;
135 /* Format the line. */
136 data_length = asprintf(&data, "%s\n", hook_name);
138 if (data_length < 0)
139 fatal_error("memory allocation failed");
141 /* When writing to a pipe when the read end is closed the kernel invariably
142 * sends SIGPIPE. Ignore it. */
143 (void) sigemptyset(&(act.sa_mask));
144 act.sa_flags = SA_RESTART;
145 act.sa_handler = SIG_IGN;
146 (void) sigaction(SIGPIPE, &act, &oldact);
148 /* Send hook name and a newline through the pipe. */
149 length = write(context->fd, data, data_length);
151 /* Restore the previous SIGPIPE handler. */
152 (void) sigaction(SIGPIPE, &oldact, NULL);
154 /* Scripts fail silently. */
155 return (length == data_length);
158 static struct script_context *launch_script(const char *path)
160 struct script_context *script = ensure_malloc(sizeof *script);
161 int pipe_fds[2];
163 /* Open a pipe. */
164 if (pipe(pipe_fds) < 0)
165 fatal_error("pipe() failed");
167 script->fd = pipe_fds[1];
169 script->pid = fork();
171 if (script->pid == 0) {
172 /* Child. */
173 int nullfd = open("/dev/null", O_WRONLY);
175 if (nullfd < 0)
176 _exit(1);
178 /* Redirect stdio. */
179 (void) dup2(pipe_fds[0], STDIN_FILENO);
180 (void) dup2(nullfd, STDOUT_FILENO);
181 (void) dup2(nullfd, STDERR_FILENO);
183 /* Close all other file descriptors. */
184 close_all_fds();
186 // drop privileges
187 setgid(getgid());
188 setuid(getuid());
190 /* Close all other file descriptors. */
191 (void) execl(path, path, "hooks", NULL);
193 _exit(1);
196 /* Close read end of the pipe. */
197 (void) close(pipe_fds[0]);
199 if (script->pid > 0)
200 return script;
202 free(script);
203 fatal_error("fork() failed");
206 static char *read_dependency(const char *path, const char *dependency_name);
207 static void parse_dependency(char *data, struct list *dependency_list);
209 /* Get the dependency from the script. */
210 static void get_dependency(const char *path, const char *dependency_name,
211 struct list *dependency_list)
213 /* Read the dependency data. */
214 char *data = read_dependency(path, dependency_name);
216 if (data != NULL) {
217 /* Parse the dependency data. */
218 parse_dependency(data, dependency_list);
219 free(data);
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 char *error = NULL;
229 int pipe_fds[2];
230 pid_t pid;
231 struct timeval timeout = (struct timeval){1, 0};
232 char *data = ensure_calloc(1, sizeof *data);
233 size_t data_length = 0;
235 /* Open a pipe for the script. */
236 if (pipe(pipe_fds) < 0)
237 fatal_error("pipe() failed");
239 pid = fork();
241 if (pid == 0) {
242 /* Child. */
243 int nullfd = open("/dev/null", O_RDWR);
245 if (nullfd < 0)
246 _exit(1);
248 /* Redirect stdio. */
249 (void) dup2(nullfd, STDIN_FILENO);
250 (void) dup2(pipe_fds[1], STDOUT_FILENO);
251 (void) dup2(nullfd, STDERR_FILENO);
253 /* Close all other file descriptors. */
254 close_all_fds();
256 /* Drop privileges. */
257 setgid(getgid());
258 setuid(getuid());
260 (void) execl(path, path, dependency_name, NULL);
262 _exit(1);
265 /* Close write end of the pipe. */
266 (void) close(pipe_fds[1]);
268 if (pid < 0) {
269 (void) close(pipe_fds[0]);
270 free(data);
271 fatal_error("fork() failed");
274 /* Read the dependency from the child. Reading fails if either the timeout
275 * elapses or more that LINE_MAX bytes are read. */
276 for (;;) {
277 struct timeval t = timeout;
278 struct timeval t1;
279 struct timeval t2;
280 char buffer[LINE_MAX];
281 ssize_t length;
283 fd_set read_fds;
285 FD_ZERO(&read_fds);
286 FD_SET(pipe_fds[0], &read_fds);
288 /* t1 is before select. */
289 (void) gettimeofday(&t1, NULL);
291 if (select(pipe_fds[0]+1, &read_fds, NULL, NULL, &t) != 1) {
292 timeout:
293 asprintf(&error, "timeout while reading dependency '%s' from '%s", dependency_name, path);
294 goto read_error;
297 /* t2 is after select. */
298 (void) gettimeofday(&t2, NULL);
300 /* Get the time that during select. */
301 timersub(&t2, &t1, &t2);
303 /* This is very unlikely. */
304 if (timercmp(&t2, &timeout, >))
305 goto timeout;
307 /* Reduce the timeout. */
308 timersub(&timeout, &t2, &timeout);
310 /* Read dependency data from the script. */
311 length = read(pipe_fds[0], buffer, sizeof buffer - 1);
313 /* Did the script close its stdin or exit? */
314 if (length <= 0)
315 break;
317 if (data_length+length+1 > LINE_MAX) {
318 asprintf(&error, "too much data while reading dependency '%s' from '%s'", dependency_name, path);
319 goto read_error;
322 /* Append the buffer to the data string. */
323 data = ensure_realloc(data, data_length+length);
324 strncpy(data+data_length, buffer, length);
325 data_length += length;
328 /* Terminate the data string. */
329 data[data_length] = '\0';
331 /* Close the read end of the pipe. */
332 (void) close(pipe_fds[0]);
333 /* Kill the script. */
334 if (!wait_for_death(pid, 0, 500000L))
335 ensure_death(pid);
337 return data;
339 read_error:
340 free(data);
341 (void) close(pipe_fds[0]);
342 if (!wait_for_death(pid, 0, 500000L))
343 ensure_death(pid);
344 fprintf(stderr, "%s\n", error);
345 free(error);
346 abort();
349 static void parse_dependency(char *data, struct list *dependency_list)
351 for (char *s = data, *saveptr;; s = NULL) {
352 char *token = strtok_r(s, "\n", &saveptr);
354 if (token == NULL)
355 break;
357 list_append(dependency_list, ensure_not_null(strdup(token), "could not copy string"));