src/util.c: shut up gcc warnings
[vlock.git] / src / script.c
blob698891dc742bd93cec41e1e55cd6c3ddee7e9368
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 <glib.h>
46 #include <glib-object.h>
48 #include "process.h"
49 #include "util.h"
51 #include "plugin.h"
52 #include "script.h"
54 static char *read_dependency(const char *path,
55 const char *dependency_name,
56 GError **error);
57 static void parse_dependency(char *data, GList **dependency_list);
59 /* Get the dependency from the script. */
60 static bool get_dependency(const char *path, const char *dependency_name,
61 GList **dependency_list, GError **error)
63 GError *tmp_error = NULL;
65 /* Read the dependency data. */
66 char *data = read_dependency(path, dependency_name, &tmp_error);
68 if (data == NULL) {
69 if (tmp_error != NULL) {
70 g_propagate_error(error, tmp_error);
71 return false;
72 } else
73 /* No data. */
74 return true;
77 /* Parse the dependency data into the list. */
78 parse_dependency(data, dependency_list);
80 g_free(data);
82 return true;
85 /* Read the dependency data by starting the script with the name of the
86 * dependency as a single command line argument. The script should then print
87 * the dependencies to its stdout one on per line. */
88 static char *read_dependency(const char *path,
89 const char *dependency_name,
90 GError **error)
92 GError *tmp_error = NULL;
93 const char *argv[] = { path, dependency_name, NULL };
94 struct child_process child = {
95 .path = path,
96 .argv = argv,
97 .stdin_fd = REDIRECT_DEV_NULL,
98 .stdout_fd = REDIRECT_PIPE,
99 .stderr_fd = REDIRECT_DEV_NULL,
100 .function = NULL,
102 /* Timeout is one second. */
103 struct timeval timeout = {1, 0};
104 char *data = g_malloc(sizeof *data);
105 size_t data_length = 0;
107 if (!create_child(&child, &tmp_error)) {
108 g_assert(tmp_error != NULL);
109 g_propagate_error(error, tmp_error);
110 return NULL;
113 /* Read the dependency from the child. Reading fails if either the timeout
114 * elapses or more that LINE_MAX bytes are read. */
115 for (;;) {
116 struct timeval t = timeout;
117 struct timeval t1;
118 struct timeval t2;
119 char buffer[LINE_MAX];
120 ssize_t length;
122 fd_set read_fds;
124 FD_ZERO(&read_fds);
125 FD_SET(child.stdout_fd, &read_fds);
127 /* t1 is before select. */
128 (void) gettimeofday(&t1, NULL);
130 if (select(child.stdout_fd+1, &read_fds, NULL, NULL, &t) != 1) {
131 timeout:
132 g_set_error(&tmp_error,
133 VLOCK_PLUGIN_ERROR,
134 VLOCK_PLUGIN_ERROR_FAILED,
135 "reading dependency (%s) data from script %s failed: timeout",
136 dependency_name,
137 /* XXX: plugin->name */ path
139 goto error;
142 /* t2 is after select. */
143 (void) gettimeofday(&t2, NULL);
145 /* Get the time that during select. */
146 timersub(&t2, &t1, &t2);
148 /* This is very unlikely. */
149 if (timercmp(&t2, &timeout, >))
150 goto timeout;
152 /* Reduce the timeout. */
153 timersub(&timeout, &t2, &timeout);
155 /* Read dependency data from the script. */
156 length = read(child.stdout_fd, buffer, sizeof buffer - 1);
158 /* Did the script close its stdin or exit? */
159 if (length <= 0)
160 break;
162 if (data_length+length+1 > LINE_MAX) {
163 g_set_error(
164 &tmp_error,
165 VLOCK_PLUGIN_ERROR,
166 VLOCK_PLUGIN_ERROR_FAILED,
167 "reading dependency (%s) data from script %s failed: too much data",
168 dependency_name,
169 /* XXX: plugin->name */ path
171 goto error;
174 /* Grow the data string. */
175 data = g_realloc(data, data_length+length);
177 /* Append the buffer to the data string. */
178 strncpy(data+data_length, buffer, length);
179 data_length += length;
182 /* Terminate the data string. */
183 data[data_length] = '\0';
185 error:
186 /* Close the read end of the pipe. */
187 (void) close(child.stdout_fd);
188 /* Kill the script. */
189 if (!wait_for_death(child.pid, 0, 500000L))
190 ensure_death(child.pid);
192 if (tmp_error != NULL) {
193 g_propagate_error(error, tmp_error);
194 g_free(data);
195 data = NULL;
198 return data;
201 static void parse_dependency(char *data, GList **dependency_list)
203 char **dependency_items = g_strsplit_set(g_strstrip(data), " \r\n", -1);
205 for (size_t i = 0; dependency_items[i] != NULL; i++)
206 *dependency_list = g_list_append(
207 *dependency_list,
208 g_strdup(dependency_items[i])
211 g_strfreev(dependency_items);
214 G_DEFINE_TYPE(VlockScript, vlock_script, TYPE_VLOCK_PLUGIN)
216 #define VLOCK_SCRIPT_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj),\
217 TYPE_VLOCK_SCRIPT,\
218 VlockScriptPrivate))
220 struct _VlockScriptPrivate
222 /* The path to the script. */
223 char *path;
224 /* Was the script launched? */
225 bool launched;
226 /* Did the script die? */
227 bool dead;
228 /* The pipe file descriptor that is connected to the script's stdin. */
229 int fd;
230 /* The PID of the script. */
231 pid_t pid;
234 /* Initialize plugin to default values. */
235 static void vlock_script_init(VlockScript *self)
237 self->priv = VLOCK_SCRIPT_GET_PRIVATE(self);
239 self->priv->dead = false;
240 self->priv->launched = false;
241 self->priv->path = NULL;
244 static void vlock_script_finalize(GObject *object)
246 VlockScript *self = VLOCK_SCRIPT(object);
248 g_free(self->priv->path);
250 if (self->priv->launched) {
251 /* Close the pipe. */
252 (void) close(self->priv->fd);
254 /* Kill the child process. */
255 if (!wait_for_death(self->priv->pid, 0, 500000L))
256 ensure_death(self->priv->pid);
259 G_OBJECT_CLASS(vlock_script_parent_class)->finalize(object);
262 static bool vlock_script_open(VlockPlugin *plugin, GError **error)
264 GError *tmp_error = NULL;
265 VlockScript *self = VLOCK_SCRIPT(plugin);
267 self->priv->path = g_strdup_printf("%s/%s", VLOCK_SCRIPT_DIR, plugin->name);
269 /* Get the dependency information. Whether the script is executable or not
270 * is also detected here. */
271 for (size_t i = 0; i < nr_dependencies; i++)
272 if (!get_dependency(self->priv->path, dependency_names[i],
273 &plugin->dependencies[i], &tmp_error)) {
274 if (g_error_matches(tmp_error,
275 VLOCK_PROCESS_ERROR,
276 VLOCK_PROCESS_ERROR_NOT_FOUND) && i == 0) {
277 g_set_error(error, VLOCK_PLUGIN_ERROR, VLOCK_PLUGIN_ERROR_NOT_FOUND,
278 "%s", tmp_error->message);
279 g_clear_error(&tmp_error);
280 } else
281 g_propagate_error(error, tmp_error);
283 return false;
286 return true;
289 /* Launch the script creating a new script_context. */
290 static bool vlock_script_launch(VlockScript *script, GError **error)
292 GError *tmp_error = NULL;
293 int fd_flags;
294 const char *argv[] = { script->priv->path, "hooks", NULL };
295 struct child_process child = {
296 .path = script->priv->path,
297 .argv = argv,
298 .stdin_fd = REDIRECT_PIPE,
299 .stdout_fd = REDIRECT_DEV_NULL,
300 .stderr_fd = REDIRECT_DEV_NULL,
301 .function = NULL,
304 if (!create_child(&child, &tmp_error)) {
305 g_propagate_error(error, tmp_error);
306 return false;
309 script->priv->fd = child.stdin_fd;
310 script->priv->pid = child.pid;
312 fd_flags = fcntl(script->priv->fd, F_GETFL, &fd_flags);
314 if (fd_flags != -1) {
315 fd_flags |= O_NONBLOCK;
316 (void) fcntl(script->priv->fd, F_SETFL, fd_flags);
319 return true;
322 static bool vlock_script_call_hook(VlockPlugin *plugin, const gchar *hook_name)
324 VlockScript *self = VLOCK_SCRIPT(plugin);
325 static const char newline = '\n';
326 ssize_t hook_name_length = strlen(hook_name);
327 ssize_t length;
328 struct sigaction act;
329 struct sigaction oldact;
331 if (!self->priv->launched) {
332 /* Launch script. */
333 self->priv->launched = vlock_script_launch(self, NULL);
335 if (!self->priv->launched) {
336 /* Do not retry. */
337 self->priv->dead = true;
338 return false;
342 if (self->priv->dead)
343 /* Nothing to do. */
344 return false;
346 /* When writing to a pipe when the read end is closed the kernel invariably
347 * sends SIGPIPE. Ignore it. */
348 (void) sigemptyset(&(act.sa_mask));
349 act.sa_flags = SA_RESTART;
350 act.sa_handler = SIG_IGN;
351 (void) sigaction(SIGPIPE, &act, &oldact);
353 /* Send hook name and a newline through the pipe. */
354 length = write(self->priv->fd, hook_name, hook_name_length);
356 if (length > 0)
357 length += write(self->priv->fd, &newline, sizeof newline);
359 /* Restore the previous SIGPIPE handler. */
360 (void) sigaction(SIGPIPE, &oldact, NULL);
362 /* If write fails the script is considered dead. */
363 self->priv->dead = (length != hook_name_length + 1);
365 return !self->priv->dead;
368 /* Initialize script class. */
369 static void vlock_script_class_init(VlockScriptClass *klass)
371 GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
372 VlockPluginClass *plugin_class = VLOCK_PLUGIN_CLASS(klass);
374 g_type_class_add_private(klass, sizeof(VlockScriptPrivate));
376 /* Virtual methods. */
377 gobject_class->finalize = vlock_script_finalize;
379 plugin_class->open = vlock_script_open;
380 plugin_class->call_hook = vlock_script_call_hook;