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>
51 static bool init_script(struct plugin
*p
);
52 static void destroy_script(struct plugin
*p
);
53 static bool call_script_hook(struct plugin
*p
, const char *hook_name
);
55 struct plugin_type
*script
= &(struct plugin_type
){
57 .destroy
= destroy_script
,
58 .call_hook
= call_script_hook
,
63 /* The path to the script. */
65 /* Was the script launched? */
67 /* Did the script die? */
69 /* The pipe file descriptor that is connected to the script's stdin. */
71 /* The PID of the script. */
75 /* Get the dependency from the script. */
76 static bool get_dependency(const char *path
, const char *dependency_name
,
77 struct list
*dependency_list
);
78 /* Launch the script creating a new script_context. */
79 static bool launch_script(struct script_context
*script
);
81 bool init_script(struct plugin
*p
)
84 struct script_context
*context
= malloc(sizeof *context
);
89 context
->dead
= false;
90 context
->launched
= false;
92 if (asprintf(&context
->path
, "%s/%s", VLOCK_SCRIPT_DIR
, p
->name
) < 0) {
98 /* Get the dependency information. Whether the script is executable or not
99 * is also detected here. */
100 for (size_t i
= 0; i
< nr_dependencies
; i
++)
101 if (!get_dependency(context
->path
, dependency_names
[i
], p
->dependencies
[i
]))
104 p
->context
= context
;
115 static void destroy_script(struct plugin
*p
)
117 struct script_context
*context
= p
->context
;
119 if (context
!= NULL
) {
122 if (context
->launched
) {
123 /* Close the pipe. */
124 (void) close(context
->fd
);
126 /* Kill the child process. */
127 if (!wait_for_death(context
->pid
, 0, 500000L))
128 ensure_death(context
->pid
);
135 /* Invoke the hook by writing it on a single line to the scripts stdin. */
136 static bool call_script_hook(struct plugin
*s
, const char *hook_name
)
138 static const char newline
= '\n';
139 struct script_context
*context
= s
->context
;
140 ssize_t hook_name_length
= strlen(hook_name
);
142 struct sigaction act
;
143 struct sigaction oldact
;
145 if (!context
->launched
) {
147 context
->launched
= launch_script(context
);
149 if (!context
->launched
)
157 /* When writing to a pipe when the read end is closed the kernel invariably
158 * sends SIGPIPE. Ignore it. */
159 (void) sigemptyset(&(act
.sa_mask
));
160 act
.sa_flags
= SA_RESTART
;
161 act
.sa_handler
= SIG_IGN
;
162 (void) sigaction(SIGPIPE
, &act
, &oldact
);
164 /* Send hook name and a newline through the pipe. */
165 length
= write(context
->fd
, hook_name
, hook_name_length
);
168 length
+= write(context
->fd
, &newline
, sizeof newline
);
170 /* Restore the previous SIGPIPE handler. */
171 (void) sigaction(SIGPIPE
, &oldact
, NULL
);
173 /* If write fails the script is considered dead. */
174 context
->dead
= (length
!= hook_name_length
+ 1);
176 return !context
->dead
;
179 static bool launch_script(struct script_context
*script
)
182 const char *argv
[] = { script
->path
, "hooks", NULL
};
183 struct child_process child
= {
184 .path
= script
->path
,
186 .stdin_fd
= REDIRECT_PIPE
,
187 .stdout_fd
= REDIRECT_DEV_NULL
,
188 .stderr_fd
= REDIRECT_DEV_NULL
,
192 if (!create_child(&child
))
195 script
->fd
= child
.stdin_fd
;
196 script
->pid
= child
.pid
;
198 fd_flags
= fcntl(script
->fd
, F_GETFL
, &fd_flags
);
200 if (fd_flags
!= -1) {
201 fd_flags
|= O_NONBLOCK
;
202 (void) fcntl(script
->fd
, F_SETFL
, fd_flags
);
208 static char *read_dependency(const char *path
, const char *dependency_name
);
209 static bool parse_dependency(char *data
, struct list
*dependency_list
);
211 /* Get the dependency from the script. */
212 static bool get_dependency(const char *path
, const char *dependency_name
,
213 struct list
*dependency_list
)
215 /* Read the dependency data. */
218 data
= read_dependency(path
, dependency_name
);
223 /* Parse the dependency data. */
224 bool result
= parse_dependency(data
, dependency_list
);
232 /* Read the dependency data by starting the script with the name of the
233 * dependency as a single command line argument. The script should then print
234 * the dependencies to its stdout one on per line. */
235 static char *read_dependency(const char *path
, const char *dependency_name
)
237 const char *argv
[] = { path
, dependency_name
, NULL
};
238 struct child_process child
= {
241 .stdin_fd
= REDIRECT_DEV_NULL
,
242 .stdout_fd
= REDIRECT_PIPE
,
243 .stderr_fd
= REDIRECT_DEV_NULL
,
246 struct timeval timeout
= {1, 0};
247 char *data
= calloc(1, sizeof *data
);
248 size_t data_length
= 0;
253 if (!create_child(&child
)) {
260 /* Read the dependency from the child. Reading fails if either the timeout
261 * elapses or more that LINE_MAX bytes are read. */
263 struct timeval t
= timeout
;
266 char buffer
[LINE_MAX
];
272 FD_SET(child
.stdout_fd
, &read_fds
);
274 /* t1 is before select. */
275 (void) gettimeofday(&t1
, NULL
);
277 if (select(child
.stdout_fd
+1, &read_fds
, NULL
, NULL
, &t
) != 1) {
283 /* t2 is after select. */
284 (void) gettimeofday(&t2
, NULL
);
286 /* Get the time that during select. */
287 timersub(&t2
, &t1
, &t2
);
289 /* This is very unlikely. */
290 if (timercmp(&t2
, &timeout
, >))
293 /* Reduce the timeout. */
294 timersub(&timeout
, &t2
, &timeout
);
296 /* Read dependency data from the script. */
297 length
= read(child
.stdout_fd
, buffer
, sizeof buffer
- 1);
299 /* Did the script close its stdin or exit? */
303 if (data_length
+length
+1 > LINE_MAX
) {
308 /* Grow the data string. */
309 data
= realloc(data
, data_length
+length
);
314 /* Append the buffer to the data string. */
315 strncpy(data
+data_length
, buffer
, length
);
316 data_length
+= length
;
319 /* Terminate the data string. */
320 data
[data_length
] = '\0';
322 /* Close the read end of the pipe. */
323 (void) close(child
.stdout_fd
);
324 /* Kill the script. */
325 if (!wait_for_death(child
.pid
, 0, 500000L))
326 ensure_death(child
.pid
);
334 (void) close(child
.stdout_fd
);
335 if (!wait_for_death(child
.pid
, 0, 500000L))
336 ensure_death(child
.pid
);
342 static bool parse_dependency(char *data
, struct list
*dependency_list
)
344 for (char *saveptr
, *token
= strtok_r(data
, " \r\n", &saveptr
);
346 token
= strtok_r(NULL
, " \r\n", &saveptr
)) {
347 char *s
= strdup(token
);
349 if (s
== NULL
|| !list_append(dependency_list
, s
))