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
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
29 #if !defined(__FreeBSD__) && !defined(_GNU_SOURCE)
38 #include <sys/select.h>
46 #include <glib-object.h>
54 static char *read_dependency(const char *path
,
55 const char *dependency_name
,
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
);
69 if (tmp_error
!= NULL
) {
70 g_propagate_error(error
, tmp_error
);
77 /* Parse the dependency data into the list. */
78 parse_dependency(data
, dependency_list
);
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
,
92 GError
*tmp_error
= NULL
;
93 const char *argv
[] = { path
, dependency_name
, NULL
};
94 struct child_process child
= {
97 .stdin_fd
= REDIRECT_DEV_NULL
,
98 .stdout_fd
= REDIRECT_PIPE
,
99 .stderr_fd
= REDIRECT_DEV_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
);
113 /* Read the dependency from the child. Reading fails if either the timeout
114 * elapses or more that LINE_MAX bytes are read. */
116 struct timeval t
= timeout
;
119 char buffer
[LINE_MAX
];
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) {
132 g_set_error(&tmp_error
,
134 VLOCK_PLUGIN_ERROR_FAILED
,
135 "reading dependency (%s) data from script %s failed: timeout",
137 /* XXX: plugin->name */ path
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
, >))
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? */
162 if (data_length
+length
+1 > LINE_MAX
) {
166 VLOCK_PLUGIN_ERROR_FAILED
,
167 "reading dependency (%s) data from script %s failed: too much data",
169 /* XXX: plugin->name */ path
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';
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
);
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(
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),\
220 struct _VlockScriptPrivate
222 /* The path to the script. */
224 /* Was the script launched? */
226 /* Did the script die? */
228 /* The pipe file descriptor that is connected to the script's stdin. */
230 /* The PID of the script. */
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
,
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
);
281 g_propagate_error(error
, tmp_error
);
289 /* Launch the script creating a new script_context. */
290 static bool vlock_script_launch(VlockScript
*script
, GError
**error
)
292 GError
*tmp_error
= NULL
;
294 const char *argv
[] = { script
->priv
->path
, "hooks", NULL
};
295 struct child_process child
= {
296 .path
= script
->priv
->path
,
298 .stdin_fd
= REDIRECT_PIPE
,
299 .stdout_fd
= REDIRECT_DEV_NULL
,
300 .stderr_fd
= REDIRECT_DEV_NULL
,
304 if (!create_child(&child
, &tmp_error
)) {
305 g_propagate_error(error
, tmp_error
);
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
);
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
);
328 struct sigaction act
;
329 struct sigaction oldact
;
331 if (!self
->priv
->launched
) {
333 self
->priv
->launched
= vlock_script_launch(self
, NULL
);
335 if (!self
->priv
->launched
) {
337 self
->priv
->dead
= true;
342 if (self
->priv
->dead
)
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
);
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
;