src/script.{c,h}: convert scripts to GObject
[vlock.git] / src / script.c
blob68a85022700a6b83b5a9758db20f4d3ac02c8164
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, const char *dependency_name, GError **error);
55 static void parse_dependency(char *data, GList **dependency_list);
57 /* Get the dependency from the script. */
58 static bool get_dependency(const char *path, const char *dependency_name,
59 GList **dependency_list, GError **error)
61 GError *tmp_error = NULL;
63 /* Read the dependency data. */
64 char *data = read_dependency(path, dependency_name, &tmp_error);
66 if (data == NULL) {
67 if (tmp_error != NULL) {
68 g_propagate_error(error, tmp_error);
69 return false;
70 } else {
71 /* No data. */
72 return true;
76 /* Parse the dependency data into the list. */
77 parse_dependency(data, dependency_list);
79 return true;
82 /* Read the dependency data by starting the script with the name of the
83 * dependency as a single command line argument. The script should then print
84 * the dependencies to its stdout one on per line. */
85 static char *read_dependency(const char *path, const char *dependency_name, GError **error)
87 GError *tmp_error = NULL;
88 const char *argv[] = { path, dependency_name, NULL };
89 struct child_process child = {
90 .path = path,
91 .argv = argv,
92 .stdin_fd = REDIRECT_DEV_NULL,
93 .stdout_fd = REDIRECT_PIPE,
94 .stderr_fd = REDIRECT_DEV_NULL,
95 .function = NULL,
97 /* Timeout is one second. */
98 struct timeval timeout = {1, 0};
99 char *data = g_malloc(sizeof *data);
100 size_t data_length = 0;
102 if (!create_child(&child, &tmp_error)) {
103 g_assert(tmp_error != NULL);
104 g_propagate_error(error, tmp_error);
105 return NULL;
108 /* Read the dependency from the child. Reading fails if either the timeout
109 * elapses or more that LINE_MAX bytes are read. */
110 for (;;) {
111 struct timeval t = timeout;
112 struct timeval t1;
113 struct timeval t2;
114 char buffer[LINE_MAX];
115 ssize_t length;
117 fd_set read_fds;
119 FD_ZERO(&read_fds);
120 FD_SET(child.stdout_fd, &read_fds);
122 /* t1 is before select. */
123 (void) gettimeofday(&t1, NULL);
125 if (select(child.stdout_fd+1, &read_fds, NULL, NULL, &t) != 1) {
126 timeout:
127 g_set_error(&tmp_error,
128 VLOCK_PLUGIN_ERROR,
129 VLOCK_PLUGIN_ERROR_FAILED,
130 "reading dependency (%s) data from script %s failed: timeout",
131 dependency_name,
132 /* XXX: plugin->name */ path
134 goto error;
137 /* t2 is after select. */
138 (void) gettimeofday(&t2, NULL);
140 /* Get the time that during select. */
141 timersub(&t2, &t1, &t2);
143 /* This is very unlikely. */
144 if (timercmp(&t2, &timeout, >))
145 goto timeout;
147 /* Reduce the timeout. */
148 timersub(&timeout, &t2, &timeout);
150 /* Read dependency data from the script. */
151 length = read(child.stdout_fd, buffer, sizeof buffer - 1);
153 /* Did the script close its stdin or exit? */
154 if (length <= 0)
155 break;
157 if (data_length+length+1 > LINE_MAX) {
158 g_set_error(&tmp_error,
159 VLOCK_PLUGIN_ERROR,
160 VLOCK_PLUGIN_ERROR_FAILED,
161 "reading dependency (%s) data from script %s failed: too much data",
162 dependency_name,
163 /* XXX: plugin->name */ path
165 goto error;
168 /* Grow the data string. */
169 data = g_realloc(data, data_length+length);
171 /* Append the buffer to the data string. */
172 strncpy(data+data_length, buffer, length);
173 data_length += length;
176 /* Terminate the data string. */
177 data[data_length] = '\0';
179 error:
180 /* Close the read end of the pipe. */
181 (void) close(child.stdout_fd);
182 /* Kill the script. */
183 if (!wait_for_death(child.pid, 0, 500000L))
184 ensure_death(child.pid);
186 if (tmp_error != NULL) {
187 g_propagate_error(error, tmp_error);
188 g_free(data);
189 data = NULL;
192 return data;
195 static void parse_dependency(char *data, GList **dependency_list)
197 char **dependency_items = g_strsplit_set(data, " \r\n", -1);
199 for (size_t i = 0; dependency_items[i] != NULL; i++)
200 *dependency_list = g_list_append(
201 *dependency_list,
202 g_strdup(dependency_items[i])
205 g_strfreev(dependency_items);
208 G_DEFINE_TYPE(VlockScript, vlock_script, TYPE_VLOCK_PLUGIN)
210 #define VLOCK_SCRIPT_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), TYPE_VLOCK_SCRIPT, VlockScriptPrivate))
212 struct _VlockScriptPrivate
214 /* The path to the script. */
215 char *path;
216 /* Was the script launched? */
217 bool launched;
218 /* Did the script die? */
219 bool dead;
220 /* The pipe file descriptor that is connected to the script's stdin. */
221 int fd;
222 /* The PID of the script. */
223 pid_t pid;
226 /* Initialize plugin to default values. */
227 static void vlock_script_init(VlockScript *self)
229 VlockPlugin *plugin = VLOCK_PLUGIN(self);
231 self->priv = VLOCK_SCRIPT_GET_PRIVATE(self);
233 self->priv->dead = false;
234 self->priv->launched = false;
235 self->priv->path = g_strdup_printf("%s/%s", VLOCK_SCRIPT_DIR, plugin->name);
238 static void vlock_script_finalize(GObject *object)
240 VlockScript *self = VLOCK_SCRIPT(object);
242 g_free(self->priv->path);
244 if (self->priv->launched) {
245 /* Close the pipe. */
246 (void) close(self->priv->fd);
248 /* Kill the child process. */
249 if (!wait_for_death(self->priv->pid, 0, 500000L))
250 ensure_death(self->priv->pid);
253 G_OBJECT_CLASS(vlock_script_parent_class)->finalize(object);
256 static bool vlock_script_open(VlockPlugin *plugin, GError **error)
258 VlockScript *self = VLOCK_SCRIPT(plugin);
260 /* Get the dependency information. Whether the script is executable or not
261 * is also detected here. */
262 for (size_t i = 0; i < nr_dependencies; i++)
263 if (!get_dependency(self->priv->path, dependency_names[i], &plugin->dependencies[i], error))
264 return false;
266 return true;
269 /* Launch the script creating a new script_context. */
270 static bool vlock_script_launch(VlockScript *script, GError **error)
272 GError *tmp_error = NULL;
273 int fd_flags;
274 const char *argv[] = { script->priv->path, "hooks", NULL };
275 struct child_process child = {
276 .path = script->priv->path,
277 .argv = argv,
278 .stdin_fd = REDIRECT_PIPE,
279 .stdout_fd = REDIRECT_DEV_NULL,
280 .stderr_fd = REDIRECT_DEV_NULL,
281 .function = NULL,
284 if (!create_child(&child, &tmp_error)) {
285 g_propagate_error(error, tmp_error);
286 return false;
289 script->priv->fd = child.stdin_fd;
290 script->priv->pid = child.pid;
292 fd_flags = fcntl(script->priv->fd, F_GETFL, &fd_flags);
294 if (fd_flags != -1) {
295 fd_flags |= O_NONBLOCK;
296 (void) fcntl(script->priv->fd, F_SETFL, fd_flags);
299 return true;
302 static bool vlock_script_call_hook(VlockPlugin *plugin, const gchar *hook_name)
304 VlockScript *self = VLOCK_SCRIPT(plugin);
305 static const char newline = '\n';
306 ssize_t hook_name_length = strlen(hook_name);
307 ssize_t length;
308 struct sigaction act;
309 struct sigaction oldact;
311 if (!self->priv->launched) {
312 /* Launch script. */
313 self->priv->launched = vlock_script_launch(self, NULL);
315 if (!self->priv->launched) {
316 /* Do not retry. */
317 self->priv->dead = true;
318 return false;
322 if (self->priv->dead)
323 /* Nothing to do. */
324 return false;
326 /* When writing to a pipe when the read end is closed the kernel invariably
327 * sends SIGPIPE. Ignore it. */
328 (void) sigemptyset(&(act.sa_mask));
329 act.sa_flags = SA_RESTART;
330 act.sa_handler = SIG_IGN;
331 (void) sigaction(SIGPIPE, &act, &oldact);
333 /* Send hook name and a newline through the pipe. */
334 length = write(self->priv->fd, hook_name, hook_name_length);
336 if (length > 0)
337 length += write(self->priv->fd, &newline, sizeof newline);
339 /* Restore the previous SIGPIPE handler. */
340 (void) sigaction(SIGPIPE, &oldact, NULL);
342 /* If write fails the script is considered dead. */
343 self->priv->dead = (length != hook_name_length + 1);
345 return !self->priv->dead;
349 /* Initialize script class. */
350 static void vlock_script_class_init(VlockScriptClass *klass)
352 GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
353 VlockPluginClass *plugin_class = VLOCK_PLUGIN_CLASS(klass);
355 g_type_class_add_private(klass, sizeof(VlockScriptPrivate));
357 /* Virtual methods. */
358 gobject_class->finalize = vlock_script_finalize;
360 plugin_class->open = vlock_script_open;
361 plugin_class->call_hook = vlock_script_call_hook;