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
, 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
);
67 if (tmp_error
!= NULL
) {
68 g_propagate_error(error
, tmp_error
);
76 /* Parse the dependency data into the list. */
77 parse_dependency(data
, dependency_list
);
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
= {
92 .stdin_fd
= REDIRECT_DEV_NULL
,
93 .stdout_fd
= REDIRECT_PIPE
,
94 .stderr_fd
= REDIRECT_DEV_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
);
108 /* Read the dependency from the child. Reading fails if either the timeout
109 * elapses or more that LINE_MAX bytes are read. */
111 struct timeval t
= timeout
;
114 char buffer
[LINE_MAX
];
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) {
127 g_set_error(&tmp_error
,
129 VLOCK_PLUGIN_ERROR_FAILED
,
130 "reading dependency (%s) data from script %s failed: timeout",
132 /* XXX: plugin->name */ path
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
, >))
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? */
157 if (data_length
+length
+1 > LINE_MAX
) {
158 g_set_error(&tmp_error
,
160 VLOCK_PLUGIN_ERROR_FAILED
,
161 "reading dependency (%s) data from script %s failed: too much data",
163 /* XXX: plugin->name */ path
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';
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
);
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(
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. */
216 /* Was the script launched? */
218 /* Did the script die? */
220 /* The pipe file descriptor that is connected to the script's stdin. */
222 /* The PID of the script. */
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
))
269 /* Launch the script creating a new script_context. */
270 static bool vlock_script_launch(VlockScript
*script
, GError
**error
)
272 GError
*tmp_error
= NULL
;
274 const char *argv
[] = { script
->priv
->path
, "hooks", NULL
};
275 struct child_process child
= {
276 .path
= script
->priv
->path
,
278 .stdin_fd
= REDIRECT_PIPE
,
279 .stdout_fd
= REDIRECT_DEV_NULL
,
280 .stderr_fd
= REDIRECT_DEV_NULL
,
284 if (!create_child(&child
, &tmp_error
)) {
285 g_propagate_error(error
, tmp_error
);
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
);
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
);
308 struct sigaction act
;
309 struct sigaction oldact
;
311 if (!self
->priv
->launched
) {
313 self
->priv
->launched
= vlock_script_launch(self
, NULL
);
315 if (!self
->priv
->launched
) {
317 self
->priv
->dead
= true;
322 if (self
->priv
->dead
)
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
);
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
;