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>
53 /* The pipe file descriptor that is connected to the script's stdin. */
55 /* The PID of the script. */
59 /* Get the dependency from the script. No error detection. Aborts on fatal
61 static void get_dependency(const char *path
, const char *dependency_name
,
62 struct list
*dependency_list
);
63 /* Launch the script creating a new script_context. No error detection aborts
65 static struct script_context
*launch_script(const char *path
);
68 static void close_script(struct plugin
*s
);
69 static bool call_script_hook(struct plugin
*s
, const char *hook_name
);
71 struct plugin
*open_script(const char *name
, char **error
)
74 struct plugin
*s
= __allocate_plugin(name
);
76 if (asprintf(&path
, "%s/%s", VLOCK_SCRIPT_DIR
, name
) < 0) {
77 *error
= strdup("filename too long");
81 /* Test for access. This must be done manually because vlock most likely
82 * runs as a setuid executable and would otherwise override restrictions.
84 * Also there is currently no error detection in case exec() fails later.
86 if (access(path
, X_OK
) < 0) {
87 (void) asprintf(error
, "%s: %s", path
, strerror(errno
));
91 /* Get the dependency information. */
92 for (size_t i
= 0; i
< nr_dependencies
; i
++)
93 get_dependency(path
, dependency_names
[i
], s
->dependencies
[i
]);
95 /* Launch the script. */
96 s
->context
= launch_script(path
);
97 s
->close
= close_script
;
98 s
->call_hook
= call_script_hook
;
112 static void close_script(struct plugin
*s
)
114 struct script_context
*context
= s
->context
;
116 /* Close the pipe. */
117 (void) close(context
->fd
);
119 /* Kill the child process. */
120 if (!wait_for_death(context
->pid
, 0, 500000L))
121 ensure_death(context
->pid
);
126 /* Invoke the hook by writing it on a single line to the scripts stdin. */
127 static bool call_script_hook(struct plugin
*s
, const char *hook_name
)
129 struct script_context
*context
= s
->context
;
133 struct sigaction act
, oldact
;
135 /* Format the line. */
136 data_length
= asprintf(&data
, "%s\n", hook_name
);
139 fatal_error("memory allocation failed");
141 /* When writing to a pipe when the read end is closed the kernel invariably
142 * sends SIGPIPE. Ignore it. */
143 (void) sigemptyset(&(act
.sa_mask
));
144 act
.sa_flags
= SA_RESTART
;
145 act
.sa_handler
= SIG_IGN
;
146 (void) sigaction(SIGPIPE
, &act
, &oldact
);
148 /* Send hook name and a newline through the pipe. */
149 length
= write(context
->fd
, data
, data_length
);
151 /* Restore the previous SIGPIPE handler. */
152 (void) sigaction(SIGPIPE
, &oldact
, NULL
);
154 /* Scripts fail silently. */
155 return (length
== data_length
);
158 static struct script_context
*launch_script(const char *path
)
160 struct script_context
*script
= ensure_malloc(sizeof *script
);
164 if (pipe(pipe_fds
) < 0)
165 fatal_error("pipe() failed");
167 script
->fd
= pipe_fds
[1];
169 script
->pid
= fork();
171 if (script
->pid
== 0) {
173 int nullfd
= open("/dev/null", O_WRONLY
);
178 /* Redirect stdio. */
179 (void) dup2(pipe_fds
[0], STDIN_FILENO
);
180 (void) dup2(nullfd
, STDOUT_FILENO
);
181 (void) dup2(nullfd
, STDERR_FILENO
);
183 /* Close all other file descriptors. */
190 /* Close all other file descriptors. */
191 (void) execl(path
, path
, "hooks", NULL
);
196 /* Close read end of the pipe. */
197 (void) close(pipe_fds
[0]);
203 fatal_error("fork() failed");
206 static char *read_dependency(const char *path
, const char *dependency_name
);
207 static void parse_dependency(char *data
, struct list
*dependency_list
);
209 /* Get the dependency from the script. */
210 static void get_dependency(const char *path
, const char *dependency_name
,
211 struct list
*dependency_list
)
213 /* Read the dependency data. */
214 char *data
= read_dependency(path
, dependency_name
);
217 /* Parse the dependency data. */
218 parse_dependency(data
, dependency_list
);
223 /* Read the dependency data by starting the script with the name of the
224 * dependency as a single command line argument. The script should then print
225 * the dependencies to its stdout one on per line. */
226 static char *read_dependency(const char *path
, const char *dependency_name
)
231 struct timeval timeout
= (struct timeval
){1, 0};
232 char *data
= ensure_calloc(1, sizeof *data
);
233 size_t data_length
= 0;
235 /* Open a pipe for the script. */
236 if (pipe(pipe_fds
) < 0)
237 fatal_error("pipe() failed");
243 int nullfd
= open("/dev/null", O_RDWR
);
248 /* Redirect stdio. */
249 (void) dup2(nullfd
, STDIN_FILENO
);
250 (void) dup2(pipe_fds
[1], STDOUT_FILENO
);
251 (void) dup2(nullfd
, STDERR_FILENO
);
253 /* Close all other file descriptors. */
256 /* Drop privileges. */
260 (void) execl(path
, path
, dependency_name
, NULL
);
265 /* Close write end of the pipe. */
266 (void) close(pipe_fds
[1]);
269 (void) close(pipe_fds
[0]);
271 fatal_error("fork() failed");
274 /* Read the dependency from the child. Reading fails if either the timeout
275 * elapses or more that LINE_MAX bytes are read. */
277 struct timeval t
= timeout
;
280 char buffer
[LINE_MAX
];
286 FD_SET(pipe_fds
[0], &read_fds
);
288 /* t1 is before select. */
289 (void) gettimeofday(&t1
, NULL
);
291 if (select(pipe_fds
[0]+1, &read_fds
, NULL
, NULL
, &t
) != 1) {
293 asprintf(&error
, "timeout while reading dependency '%s' from '%s", dependency_name
, path
);
297 /* t2 is after select. */
298 (void) gettimeofday(&t2
, NULL
);
300 /* Get the time that during select. */
301 timersub(&t2
, &t1
, &t2
);
303 /* This is very unlikely. */
304 if (timercmp(&t2
, &timeout
, >))
307 /* Reduce the timeout. */
308 timersub(&timeout
, &t2
, &timeout
);
310 /* Read dependency data from the script. */
311 length
= read(pipe_fds
[0], buffer
, sizeof buffer
- 1);
313 /* Did the script close its stdin or exit? */
317 if (data_length
+length
+1 > LINE_MAX
) {
318 asprintf(&error
, "too much data while reading dependency '%s' from '%s'", dependency_name
, path
);
322 /* Append the buffer to the data string. */
323 data
= ensure_realloc(data
, data_length
+length
);
324 strncpy(data
+data_length
, buffer
, length
);
325 data_length
+= length
;
328 /* Terminate the data string. */
329 data
[data_length
] = '\0';
331 /* Close the read end of the pipe. */
332 (void) close(pipe_fds
[0]);
333 /* Kill the script. */
334 if (!wait_for_death(pid
, 0, 500000L))
341 (void) close(pipe_fds
[0]);
342 if (!wait_for_death(pid
, 0, 500000L))
344 fprintf(stderr
, "%s\n", error
);
349 static void parse_dependency(char *data
, struct list
*dependency_list
)
351 for (char *s
= data
, *saveptr
;; s
= NULL
) {
352 char *token
= strtok_r(s
, "\n", &saveptr
);
357 list_append(dependency_list
, ensure_not_null(strdup(token
), "could not copy string"));