]>
Commit | Line | Data |
---|---|---|
6dbe3af9 KZ |
1 | /* |
2 | * Copyright (c) 1980 Regents of the University of California. | |
3 | * All rights reserved. | |
4 | * | |
5 | * Redistribution and use in source and binary forms, with or without | |
6 | * modification, are permitted provided that the following conditions | |
7 | * are met: | |
8 | * 1. Redistributions of source code must retain the above copyright | |
9 | * notice, this list of conditions and the following disclaimer. | |
10 | * 2. Redistributions in binary form must reproduce the above copyright | |
11 | * notice, this list of conditions and the following disclaimer in the | |
12 | * documentation and/or other materials provided with the distribution. | |
13 | * 3. All advertising materials mentioning features or use of this software | |
14 | * must display the following acknowledgement: | |
edc7e420 SK |
15 | * This product includes software developed by the University of |
16 | * California, Berkeley and its contributors. | |
6dbe3af9 KZ |
17 | * 4. Neither the name of the University nor the names of its contributors |
18 | * may be used to endorse or promote products derived from this software | |
19 | * without specific prior written permission. | |
20 | * | |
21 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND | |
22 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
24 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE | |
25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |
27 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |
28 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
29 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |
30 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |
31 | * SUCH DAMAGE. | |
32 | */ | |
33 | ||
66ee8158 | 34 | /* |
b50945d4 | 35 | * 1999-02-22 Arkadiusz Miśkiewicz <misiek@pld.ORG.PL> |
7eda085c | 36 | * - added Native Language Support |
66ee8158 KZ |
37 | * |
38 | * 2000-07-30 Per Andreas Buer <per@linpro.no> - added "q"-option | |
26ed9fb8 CK |
39 | * |
40 | * 2014-05-30 Csaba Kos <csaba.kos@gmail.com> | |
41 | * - fixed a rare deadlock after child termination | |
7eda085c KZ |
42 | */ |
43 | ||
22853e4a KZ |
44 | #include <stdio.h> |
45 | #include <stdlib.h> | |
46 | #include <paths.h> | |
47 | #include <time.h> | |
6dbe3af9 KZ |
48 | #include <sys/stat.h> |
49 | #include <termios.h> | |
50 | #include <sys/ioctl.h> | |
51 | #include <sys/time.h> | |
172b3c27 | 52 | #include <signal.h> |
1f58c445 | 53 | #include <errno.h> |
2b7ff0d9 ST |
54 | #include <string.h> |
55 | #include <getopt.h> | |
56 | #include <unistd.h> | |
8fb810ff | 57 | #include <fcntl.h> |
8fb810ff SK |
58 | #include <limits.h> |
59 | #include <locale.h> | |
60 | #include <stddef.h> | |
3822032d KZ |
61 | #include <sys/wait.h> |
62 | #include <poll.h> | |
cc1a88fb SK |
63 | #include <sys/signalfd.h> |
64 | #include <assert.h> | |
c2f03da9 | 65 | #include <inttypes.h> |
2b7ff0d9 | 66 | |
cdd2a8c3 | 67 | #include "closestream.h" |
7eda085c | 68 | #include "nls.h" |
91239874 | 69 | #include "c.h" |
3822032d | 70 | #include "ttyutils.h" |
c6fca22e | 71 | #include "all-io.h" |
04639805 | 72 | #include "monotonic.h" |
bdef362d | 73 | #include "timeutils.h" |
aefe9893 | 74 | #include "strutils.h" |
596f4202 | 75 | #include "xalloc.h" |
6dbe3af9 | 76 | |
a2b4dec5 KZ |
77 | #include "debug.h" |
78 | ||
2ba641e5 | 79 | static UL_DEBUG_DEFINE_MASK(script); |
a2b4dec5 KZ |
80 | UL_DEBUG_DEFINE_MASKNAMES(script) = UL_DEBUG_EMPTY_MASKNAMES; |
81 | ||
82 | #define SCRIPT_DEBUG_INIT (1 << 1) | |
83 | #define SCRIPT_DEBUG_POLL (1 << 2) | |
84 | #define SCRIPT_DEBUG_SIGNAL (1 << 3) | |
85 | #define SCRIPT_DEBUG_IO (1 << 4) | |
86 | #define SCRIPT_DEBUG_MISC (1 << 5) | |
87 | #define SCRIPT_DEBUG_ALL 0xFFFF | |
88 | ||
89 | #define DBG(m, x) __UL_DBG(script, SCRIPT_DEBUG_, m, x) | |
90 | #define ON_DBG(m, x) __UL_DBG_CALL(script, SCRIPT_DEBUG_, m, x) | |
91 | ||
e11a5e63 SK |
92 | #if defined(HAVE_LIBUTIL) && defined(HAVE_PTY_H) |
93 | # include <pty.h> | |
5c36a0eb KZ |
94 | #endif |
95 | ||
f0bc3fa0 | 96 | #ifdef HAVE_LIBUTEMPTER |
e11a5e63 | 97 | # include <utempter.h> |
f0bc3fa0 KZ |
98 | #endif |
99 | ||
7e5796c9 | 100 | #define DEFAULT_TYPESCRIPT_FILENAME "typescript" |
05644dab | 101 | |
596f4202 KZ |
102 | enum { |
103 | SCRIPT_FMT_RAW = 1, /* raw slave/master data */ | |
104 | SCRIPT_FMT_TIMING_SIMPLE, /* timing info in classic "<time> <delta>" format */ | |
105 | }; | |
106 | ||
107 | struct script_log { | |
108 | FILE *fp; /* file pointer (handler) */ | |
109 | int format; /* SCRIPT_FMT_* */ | |
110 | char *filename; /* on command line specified name */ | |
111 | struct timeval oldtime; /* previous entry log time */ | |
112 | }; | |
113 | ||
114 | struct script_stream { | |
115 | struct timeval oldtime; /* last update */ | |
9f822648 | 116 | struct script_log **logs; /* logs where to write data from stream */ |
596f4202 KZ |
117 | size_t nlogs; /* number of logs */ |
118 | }; | |
119 | ||
edc7e420 SK |
120 | struct script_control { |
121 | char *shell; /* shell to be executed */ | |
3f19b85f | 122 | char *command; /* command to be executed */ |
596f4202 KZ |
123 | uint64_t outsz; /* current output files size */ |
124 | uint64_t maxsz; /* maximum output files size */ | |
125 | ||
edc7e420 SK |
126 | int master; /* pseudoterminal master file descriptor */ |
127 | int slave; /* pseudoterminal slave file descriptor */ | |
596f4202 KZ |
128 | |
129 | struct script_stream out; /* output */ | |
130 | struct script_stream in; /* input */ | |
131 | ||
89a859d4 | 132 | int poll_timeout; /* poll() timeout, used in end of execution */ |
edc7e420 | 133 | pid_t child; /* child pid */ |
edc7e420 | 134 | int childstatus; /* child process exit value */ |
3f19b85f | 135 | struct termios attrs; /* slave terminal runtime attributes */ |
edc7e420 | 136 | struct winsize win; /* terminal window size */ |
9779651e | 137 | #if !HAVE_LIBUTIL || !HAVE_PTY_H |
8d6fdd2f | 138 | char *line; /* terminal line */ |
5c36a0eb | 139 | #endif |
edc7e420 | 140 | unsigned int |
3f19b85f | 141 | append:1, /* append output */ |
7e5796c9 | 142 | rc_wanted:1, /* return child exit value */ |
3f19b85f KZ |
143 | flush:1, /* flush after each write */ |
144 | quiet:1, /* suppress most output */ | |
3f19b85f | 145 | force:1, /* write output to links */ |
edc7e420 | 146 | isterm:1, /* is child process running as terminal */ |
edc7e420 | 147 | die:1; /* terminate program */ |
7e5796c9 | 148 | |
cc1a88fb | 149 | sigset_t sigset; /* catch SIGCHLD and SIGWINCH with signalfd() */ |
d35ffe80 | 150 | sigset_t sigorg; /* original signal mask */ |
cc1a88fb | 151 | int sigfd; /* file descriptor for signalfd() */ |
edc7e420 | 152 | }; |
1f58c445 | 153 | |
596f4202 KZ |
154 | static void restore_tty(struct script_control *ctl, int mode); |
155 | static void __attribute__((__noreturn__)) fail(struct script_control *ctl); | |
156 | ||
a2b4dec5 KZ |
157 | static void script_init_debug(void) |
158 | { | |
a15dca2f | 159 | __UL_INIT_DEBUG_FROM_ENV(script, SCRIPT_DEBUG_, 0, SCRIPT_DEBUG); |
a2b4dec5 KZ |
160 | } |
161 | ||
6a40c65f SK |
162 | /* |
163 | * For tests we want to be able to control time output | |
164 | */ | |
165 | #ifdef TEST_SCRIPT | |
166 | static inline time_t script_time(time_t *t) | |
167 | { | |
168 | const char *str = getenv("SCRIPT_TEST_SECOND_SINCE_EPOCH"); | |
345208a5 | 169 | int64_t sec; |
6a40c65f | 170 | |
c2f03da9 | 171 | if (!str || sscanf(str, "%"SCNi64, &sec) != 1) |
345208a5 ID |
172 | return time(t); |
173 | if (t) | |
174 | *t = (time_t)sec; | |
175 | return (time_t)sec; | |
6a40c65f SK |
176 | } |
177 | #else /* !TEST_SCRIPT */ | |
178 | # define script_time(x) time(x) | |
179 | #endif | |
180 | ||
86be6a32 | 181 | static void __attribute__((__noreturn__)) usage(void) |
3ff52639 | 182 | { |
86be6a32 | 183 | FILE *out = stdout; |
db433bf7 | 184 | fputs(USAGE_HEADER, out); |
edc7e420 | 185 | fprintf(out, _(" %s [options] [file]\n"), program_invocation_short_name); |
87d6050b | 186 | |
451dbcfa BS |
187 | fputs(USAGE_SEPARATOR, out); |
188 | fputs(_("Make a typescript of a terminal session.\n"), out); | |
189 | ||
db433bf7 | 190 | fputs(USAGE_OPTIONS, out); |
3cf274c9 AO |
191 | fputs(_(" -a, --append append the output\n" |
192 | " -c, --command <command> run command rather than interactive shell\n" | |
193 | " -e, --return return exit code of the child process\n" | |
194 | " -f, --flush run flush after each write\n" | |
195 | " --force use output file even when it is a link\n" | |
aefe9893 | 196 | " -o, --output-limit <size> terminate if output files exceed size\n" |
3cf274c9 AO |
197 | " -q, --quiet be quiet\n" |
198 | " -t[<file>], --timing[=<file>] output timing data to stderr or to FILE\n" | |
b3054454 | 199 | ), out); |
3cf274c9 | 200 | printf(USAGE_HELP_OPTIONS(31)); |
3ff52639 | 201 | |
f45f3ec3 | 202 | printf(USAGE_MAN_TAIL("script(1)")); |
86be6a32 | 203 | exit(EXIT_SUCCESS); |
3ff52639 SK |
204 | } |
205 | ||
596f4202 KZ |
206 | static struct script_log *get_log_by_name(struct script_stream *stream, |
207 | const char *name) | |
4d9b788d | 208 | { |
596f4202 | 209 | size_t i; |
4d9b788d | 210 | |
596f4202 | 211 | for (i = 0; i < stream->nlogs; i++) { |
9f822648 KZ |
212 | struct script_log *log = stream->logs[i]; |
213 | if (strcmp(log->filename, name) == 0) | |
596f4202 KZ |
214 | return log; |
215 | } | |
216 | return NULL; | |
217 | } | |
4d9b788d | 218 | |
596f4202 KZ |
219 | static struct script_log *log_associate(struct script_control *ctl, |
220 | struct script_stream *stream, | |
221 | const char *filename, int format) | |
222 | { | |
223 | struct script_log *log; | |
224 | ||
9f822648 KZ |
225 | assert(ctl); |
226 | assert(filename); | |
227 | assert(stream); | |
228 | ||
229 | log = get_log_by_name(stream, filename); | |
230 | if (log) | |
231 | return log; /* already defined */ | |
232 | ||
233 | log = get_log_by_name(stream == &ctl->out ? &ctl->in : &ctl->out, filename); | |
596f4202 KZ |
234 | if (!log) { |
235 | /* create a new log */ | |
9f822648 KZ |
236 | log = xcalloc(1, sizeof(*log)); |
237 | log->filename = xstrdup(filename); | |
596f4202 KZ |
238 | log->format = format; |
239 | } | |
4d9b788d | 240 | |
9f822648 KZ |
241 | /* add log to the stream */ |
242 | stream->logs = xrealloc(stream->logs, | |
243 | (stream->nlogs + 1) * sizeof(log)); | |
244 | stream->logs[stream->nlogs] = log; | |
245 | stream->nlogs++; | |
246 | ||
596f4202 KZ |
247 | return log; |
248 | } | |
4d9b788d | 249 | |
d805688a | 250 | static void log_close(struct script_control *ctl __attribute__((unused)), |
596f4202 KZ |
251 | struct script_log *log, |
252 | const char *msg, | |
253 | int status) | |
254 | { | |
255 | DBG(MISC, ul_debug("closing %s", log->filename)); | |
4d9b788d | 256 | |
596f4202 KZ |
257 | switch (log->format) { |
258 | case SCRIPT_FMT_RAW: | |
259 | { | |
260 | char buf[FORMAT_TIMESTAMP_MAX]; | |
261 | time_t tvec = script_time((time_t *)NULL); | |
4d9b788d | 262 | |
596f4202 KZ |
263 | strtime_iso(&tvec, ISO_TIMESTAMP, buf, sizeof(buf)); |
264 | if (msg) | |
265 | fprintf(log->fp, _("\nScript done on %s [<%s>]\n"), buf, msg); | |
266 | else | |
267 | fprintf(log->fp, _("\nScript done on %s [COMMAND_EXIT_CODE=\"%d\"]\n"), buf, status); | |
596f4202 KZ |
268 | break; |
269 | } | |
270 | case SCRIPT_FMT_TIMING_SIMPLE: | |
271 | break; | |
272 | } | |
273 | ||
274 | if (close_stream(log->fp) != 0) | |
275 | err(EXIT_FAILURE, "write failed: %s", log->filename); | |
4d9b788d | 276 | |
596f4202 | 277 | log->fp = NULL; |
4d9b788d KZ |
278 | } |
279 | ||
596f4202 KZ |
280 | static void log_start(struct script_control *ctl, |
281 | struct script_log *log) | |
6343ee8c | 282 | { |
6343ee8c | 283 | |
596f4202 | 284 | assert(log->fp == NULL); |
6343ee8c | 285 | |
596f4202 | 286 | DBG(MISC, ul_debug("opening %s", log->filename)); |
6343ee8c | 287 | |
596f4202 KZ |
288 | /* open the log */ |
289 | log->fp = fopen(log->filename, | |
290 | ctl->append && log->format == SCRIPT_FMT_RAW ? | |
291 | "a" UL_CLOEXECSTR : | |
292 | "w" UL_CLOEXECSTR); | |
293 | if (!log->fp) { | |
294 | restore_tty(ctl, TCSANOW); | |
295 | warn(_("cannot open %s"), log->filename); | |
296 | fail(ctl); | |
297 | } | |
6343ee8c | 298 | |
596f4202 KZ |
299 | /* write header, etc. */ |
300 | switch (log->format) { | |
301 | case SCRIPT_FMT_RAW: | |
302 | { | |
303 | char buf[FORMAT_TIMESTAMP_MAX]; | |
304 | time_t tvec = script_time((time_t *)NULL); | |
305 | ||
306 | strtime_iso(&tvec, ISO_TIMESTAMP, buf, sizeof(buf)); | |
307 | fprintf(log->fp, _("Script started on %s ["), buf); | |
308 | ||
309 | if (ctl->isterm) { | |
310 | int cols = 0, lines = 0; | |
311 | const char *tty = NULL, *term = NULL; | |
312 | ||
313 | get_terminal_dimension(&cols, &lines); | |
314 | get_terminal_name(&tty, NULL, NULL); | |
315 | get_terminal_type(&term); | |
316 | ||
317 | if (term) | |
318 | fprintf(log->fp, "TERM=\"%s\" ", term); | |
319 | if (tty) | |
320 | fprintf(log->fp, "TTY=\"%s\" ", tty); | |
321 | ||
322 | fprintf(log->fp, "COLUMNS=\"%d\" LINES=\"%d\"", cols, lines); | |
323 | } else | |
324 | fprintf(log->fp, _("<not executed on terminal>")); | |
325 | ||
326 | fputs("]\n", log->fp); | |
327 | break; | |
328 | } | |
329 | case SCRIPT_FMT_TIMING_SIMPLE: | |
330 | gettime_monotonic(&log->oldtime); | |
331 | break; | |
332 | } | |
333 | } | |
334 | ||
335 | static size_t log_write(struct script_control *ctl, | |
336 | struct script_log *log, | |
337 | char *obuf, size_t bytes) | |
338 | { | |
339 | if (!log->fp) | |
340 | return 0; | |
341 | ||
342 | DBG(IO, ul_debug(" writining %s", log->filename)); | |
343 | ||
344 | switch (log->format) { | |
345 | case SCRIPT_FMT_RAW: | |
346 | if (fwrite_all(obuf, 1, bytes, log->fp)) { | |
347 | warn(_("cannot write %s"), log->filename); | |
348 | fail(ctl); | |
349 | } | |
350 | break; | |
351 | case SCRIPT_FMT_TIMING_SIMPLE: | |
352 | { | |
353 | struct timeval now, delta; | |
354 | int sz; | |
355 | ||
356 | DBG(IO, ul_debug(" writing timing info")); | |
357 | ||
358 | gettime_monotonic(&now); | |
359 | timersub(&now, &log->oldtime, &delta); | |
360 | sz = fprintf(log->fp, "%ld.%06ld %zd\n", | |
361 | (long)delta.tv_sec, (long)delta.tv_usec, bytes); | |
362 | log->oldtime = now; | |
363 | bytes = sz > 0 ? sz : 0; | |
364 | break; | |
365 | } | |
366 | default: | |
367 | break; | |
368 | } | |
369 | ||
370 | if (ctl->flush) | |
371 | fflush(log->fp); | |
372 | ||
373 | return bytes; | |
6343ee8c KZ |
374 | } |
375 | ||
596f4202 KZ |
376 | static uint64_t log_stream_activity( |
377 | struct script_control *ctl, | |
378 | struct script_stream *stream, | |
379 | char *buf, size_t bytes) | |
380 | { | |
381 | size_t i; | |
382 | uint64_t outsz = 0; | |
383 | ||
384 | for (i = 0; i < stream->nlogs; i++) | |
9f822648 | 385 | outsz += log_write(ctl, stream->logs[i], buf, bytes); |
596f4202 KZ |
386 | |
387 | return outsz; | |
388 | } | |
389 | ||
390 | ||
391 | static void die_if_link(struct script_control *ctl, const char *filename) | |
93af8d8b SK |
392 | { |
393 | struct stat s; | |
6dbe3af9 | 394 | |
3f19b85f | 395 | if (ctl->force) |
93af8d8b | 396 | return; |
596f4202 | 397 | if (lstat(filename, &s) == 0 && (S_ISLNK(s.st_mode) || s.st_nlink > 1)) |
93af8d8b SK |
398 | errx(EXIT_FAILURE, |
399 | _("output file `%s' is a link\n" | |
400 | "Use --force if you really want to use it.\n" | |
596f4202 | 401 | "Program not started."), filename); |
93af8d8b | 402 | } |
fd4c1f63 | 403 | |
8198052c SK |
404 | static void restore_tty(struct script_control *ctl, int mode) |
405 | { | |
406 | struct termios rtt; | |
407 | ||
408 | if (!ctl->isterm) | |
409 | return; | |
410 | ||
411 | rtt = ctl->attrs; | |
412 | tcsetattr(STDIN_FILENO, mode, &rtt); | |
413 | } | |
414 | ||
7fb65db1 KZ |
415 | static void enable_rawmode_tty(struct script_control *ctl) |
416 | { | |
417 | struct termios rtt; | |
418 | ||
419 | if (!ctl->isterm) | |
420 | return; | |
421 | ||
422 | rtt = ctl->attrs; | |
423 | cfmakeraw(&rtt); | |
424 | rtt.c_lflag &= ~ECHO; | |
425 | tcsetattr(STDIN_FILENO, TCSANOW, &rtt); | |
426 | } | |
427 | ||
596f4202 | 428 | static void __attribute__((__noreturn__)) done_log(struct script_control *ctl, const char *msg) |
93af8d8b | 429 | { |
596f4202 KZ |
430 | int status; |
431 | size_t i; | |
6343ee8c | 432 | |
a2b4dec5 KZ |
433 | DBG(MISC, ul_debug("done!")); |
434 | ||
8198052c SK |
435 | restore_tty(ctl, TCSADRAIN); |
436 | ||
6343ee8c | 437 | if (WIFSIGNALED(ctl->childstatus)) |
596f4202 | 438 | status = WTERMSIG(ctl->childstatus) + 0x80; |
6343ee8c | 439 | else |
596f4202 KZ |
440 | status = WEXITSTATUS(ctl->childstatus); |
441 | ||
442 | ||
443 | DBG(MISC, ul_debug(" status=%d", status)); | |
444 | ||
445 | /* close all output logs */ | |
446 | for (i = 0; i < ctl->out.nlogs; i++) | |
9f822648 | 447 | log_close(ctl, ctl->out.logs[i], msg, status); |
596f4202 KZ |
448 | |
449 | /* close all input logs */ | |
450 | for (i = 0; i < ctl->in.nlogs; i++) | |
9f822648 | 451 | log_close(ctl, ctl->in.logs[i], msg, status); |
93df6a58 | 452 | |
d805688a KZ |
453 | if (!ctl->quiet) |
454 | printf(_("Script done.\n")); | |
455 | ||
f0bc3fa0 | 456 | #ifdef HAVE_LIBUTEMPTER |
6ddf53a5 SK |
457 | if (ctl->master >= 0) |
458 | utempter_remove_record(ctl->master); | |
f0bc3fa0 | 459 | #endif |
6ddf53a5 | 460 | kill(ctl->child, SIGTERM); /* make sure we don't create orphans */ |
596f4202 | 461 | exit(ctl->rc_wanted ? status : EXIT_SUCCESS); |
6343ee8c KZ |
462 | } |
463 | ||
464 | static void __attribute__((__noreturn__)) done(struct script_control *ctl) | |
465 | { | |
466 | done_log(ctl, NULL); | |
93af8d8b | 467 | } |
5c36a0eb | 468 | |
2ddadb5e | 469 | static void __attribute__((__noreturn__)) fail(struct script_control *ctl) |
edc7e420 | 470 | { |
a2b4dec5 | 471 | DBG(MISC, ul_debug("fail!")); |
93af8d8b | 472 | kill(0, SIGTERM); |
edc7e420 | 473 | done(ctl); |
6dbe3af9 KZ |
474 | } |
475 | ||
5860c45e | 476 | static void wait_for_child(struct script_control *ctl, int wait) |
93af8d8b SK |
477 | { |
478 | int status; | |
479 | pid_t pid; | |
93af8d8b SK |
480 | int options = wait ? 0 : WNOHANG; |
481 | ||
a2b4dec5 KZ |
482 | DBG(MISC, ul_debug("waiting for child")); |
483 | ||
b09feab9 | 484 | while ((pid = wait3(&status, options, NULL)) > 0) |
89a859d4 | 485 | if (pid == ctl->child) |
edc7e420 | 486 | ctl->childstatus = status; |
93af8d8b SK |
487 | } |
488 | ||
596f4202 | 489 | /* data from master to stdout */ |
cf470183 | 490 | static void write_output(struct script_control *ctl, char *obuf, |
04639805 | 491 | ssize_t bytes) |
93af8d8b | 492 | { |
a2b4dec5 KZ |
493 | DBG(IO, ul_debug(" writing to output")); |
494 | ||
cf470183 | 495 | if (write_all(STDOUT_FILENO, obuf, bytes)) { |
54c6611d | 496 | DBG(IO, ul_debug(" writing output *failed*")); |
cf470183 SK |
497 | warn(_("write failed")); |
498 | fail(ctl); | |
499 | } | |
54c6611d KZ |
500 | } |
501 | ||
b2bff066 KZ |
502 | static int write_to_shell(struct script_control *ctl, |
503 | char *buf, size_t bufsz) | |
54c6611d | 504 | { |
b2bff066 | 505 | return write_all(ctl->master, buf, bufsz); |
54c6611d KZ |
506 | } |
507 | ||
508 | /* | |
23e35eca RM |
509 | * The script(1) is usually faster than shell, so it's a good idea to wait until |
510 | * the previous message has been already read by shell from slave before we | |
511 | * write to master. This is necessary especially for EOF situation when we can | |
54c6611d KZ |
512 | * send EOF to master before shell is fully initialized, to workaround this |
513 | * problem we wait until slave is empty. For example: | |
514 | * | |
515 | * echo "date" | script | |
b2bff066 KZ |
516 | * |
517 | * Unfortunately, the child (usually shell) can ignore stdin at all, so we | |
518 | * don't wait forever to avoid dead locks... | |
519 | * | |
520 | * Note that script is primarily designed for interactive sessions as it | |
521 | * maintains master+slave tty stuff within the session. Use pipe to write to | |
522 | * script(1) and assume non-interactive (tee-like) behavior is NOT well | |
523 | * supported. | |
54c6611d | 524 | */ |
54c6611d KZ |
525 | static void write_eof_to_shell(struct script_control *ctl) |
526 | { | |
b2bff066 KZ |
527 | unsigned int tries = 0; |
528 | struct pollfd fds[] = { | |
529 | { .fd = ctl->slave, .events = POLLIN } | |
530 | }; | |
54c6611d KZ |
531 | char c = DEF_EOF; |
532 | ||
b2bff066 KZ |
533 | DBG(IO, ul_debug(" waiting for empty slave")); |
534 | while (poll(fds, 1, 10) == 1 && tries < 8) { | |
535 | DBG(IO, ul_debug(" slave is not empty")); | |
536 | xusleep(250000); | |
537 | tries++; | |
538 | } | |
539 | if (tries < 8) | |
540 | DBG(IO, ul_debug(" slave is empty now")); | |
541 | ||
54c6611d KZ |
542 | DBG(IO, ul_debug(" sending EOF to master")); |
543 | write_to_shell(ctl, &c, sizeof(char)); | |
364cda48 KZ |
544 | } |
545 | ||
54c6611d | 546 | static void handle_io(struct script_control *ctl, int fd, int *eof) |
93af8d8b | 547 | { |
6ddf53a5 | 548 | char buf[BUFSIZ]; |
a8896ad5 | 549 | ssize_t bytes; |
a2b4dec5 | 550 | DBG(IO, ul_debug("%d FD active", fd)); |
54c6611d | 551 | *eof = 0; |
a2b4dec5 | 552 | |
da26af4e | 553 | /* read from active FD */ |
a8896ad5 SK |
554 | bytes = read(fd, buf, sizeof(buf)); |
555 | if (bytes < 0) { | |
54c6611d | 556 | if (errno == EAGAIN || errno == EINTR) |
a8896ad5 SK |
557 | return; |
558 | fail(ctl); | |
559 | } | |
da26af4e | 560 | |
54c6611d | 561 | if (bytes == 0) { |
7dbcd80e | 562 | *eof = 1; |
54c6611d KZ |
563 | return; |
564 | } | |
565 | ||
da26af4e KZ |
566 | /* from stdin (user) to command */ |
567 | if (fd == STDIN_FILENO) { | |
2e9418b7 | 568 | DBG(IO, ul_debug(" stdin --> master %zd bytes", bytes)); |
a2b4dec5 | 569 | |
54c6611d | 570 | if (write_to_shell(ctl, buf, bytes)) { |
a8896ad5 SK |
571 | warn(_("write failed")); |
572 | fail(ctl); | |
573 | } | |
574 | /* without sync write_output() will write both input & | |
575 | * shell output that looks like double echoing */ | |
576 | fdatasync(ctl->master); | |
596f4202 | 577 | ctl->outsz += log_stream_activity(ctl, &ctl->in, buf, (size_t) bytes); |
da26af4e | 578 | |
aefe9893 | 579 | /* from command (master) to stdout and log */ |
a2b4dec5 | 580 | } else if (fd == ctl->master) { |
2e9418b7 | 581 | DBG(IO, ul_debug(" master --> stdout %zd bytes", bytes)); |
04639805 | 582 | write_output(ctl, buf, bytes); |
596f4202 KZ |
583 | ctl->outsz += log_stream_activity(ctl, &ctl->out, buf, (size_t) bytes); |
584 | } | |
aefe9893 | 585 | |
596f4202 KZ |
586 | /* check output limit */ |
587 | if (ctl->maxsz != 0 && ctl->outsz >= ctl->maxsz) { | |
588 | if (!ctl->quiet) | |
589 | printf(_("Script terminated, max output files size %"PRIu64" exceeded.\n"), ctl->maxsz); | |
590 | DBG(IO, ul_debug("output size %"PRIu64", exceeded limit %"PRIu64, ctl->outsz, ctl->maxsz)); | |
591 | done_log(ctl, _("max output size exceeded")); | |
a2b4dec5 | 592 | } |
a8896ad5 SK |
593 | } |
594 | ||
595 | static void handle_signal(struct script_control *ctl, int fd) | |
596 | { | |
597 | struct signalfd_siginfo info; | |
598 | ssize_t bytes; | |
599 | ||
a2b4dec5 KZ |
600 | DBG(SIGNAL, ul_debug("signal FD %d active", fd)); |
601 | ||
a8896ad5 | 602 | bytes = read(fd, &info, sizeof(info)); |
d35ffe80 | 603 | if (bytes != sizeof(info)) { |
7dbcd80e | 604 | if (bytes < 0 && (errno == EAGAIN || errno == EINTR)) |
d35ffe80 KZ |
605 | return; |
606 | fail(ctl); | |
607 | } | |
da26af4e | 608 | |
a8896ad5 SK |
609 | switch (info.ssi_signo) { |
610 | case SIGCHLD: | |
27afe501 KZ |
611 | DBG(SIGNAL, ul_debug(" get signal SIGCHLD [ssi_code=%d, ssi_status=%d]", |
612 | info.ssi_code, info.ssi_status)); | |
613 | if (info.ssi_code == CLD_EXITED | |
614 | || info.ssi_code == CLD_KILLED | |
615 | || info.ssi_code == CLD_DUMPED) { | |
2e7a9227 KZ |
616 | wait_for_child(ctl, 0); |
617 | ctl->poll_timeout = 10; | |
27afe501 KZ |
618 | |
619 | /* In case of ssi_code is CLD_TRAPPED, CLD_STOPPED, or CLD_CONTINUED */ | |
2e7a9227 KZ |
620 | } else if (info.ssi_status == SIGSTOP && ctl->child) { |
621 | DBG(SIGNAL, ul_debug(" child stop by SIGSTOP -- stop parent too")); | |
622 | kill(getpid(), SIGSTOP); | |
623 | DBG(SIGNAL, ul_debug(" resume")); | |
624 | kill(ctl->child, SIGCONT); | |
625 | } | |
a8896ad5 SK |
626 | return; |
627 | case SIGWINCH: | |
b2bff066 | 628 | DBG(SIGNAL, ul_debug(" get signal SIGWINCH")); |
a8896ad5 SK |
629 | if (ctl->isterm) { |
630 | ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&ctl->win); | |
631 | ioctl(ctl->slave, TIOCSWINSZ, (char *)&ctl->win); | |
632 | } | |
633 | break; | |
5860c45e KZ |
634 | case SIGTERM: |
635 | /* fallthrough */ | |
636 | case SIGINT: | |
637 | /* fallthrough */ | |
638 | case SIGQUIT: | |
b2bff066 | 639 | DBG(SIGNAL, ul_debug(" get signal SIG{TERM,INT,QUIT}")); |
5860c45e KZ |
640 | fprintf(stderr, _("\nSession terminated.\n")); |
641 | /* Child termination is going to generate SIGCHILD (see above) */ | |
642 | kill(ctl->child, SIGTERM); | |
643 | return; | |
a8896ad5 SK |
644 | default: |
645 | abort(); | |
646 | } | |
27afe501 | 647 | DBG(SIGNAL, ul_debug("signal handle on FD %d done", fd)); |
a8896ad5 SK |
648 | } |
649 | ||
650 | static void do_io(struct script_control *ctl) | |
651 | { | |
d88b739f | 652 | int ret, eof = 0; |
596f4202 | 653 | size_t i; |
da26af4e | 654 | enum { |
54c6611d | 655 | POLLFD_SIGNAL = 0, |
da26af4e | 656 | POLLFD_MASTER, |
d88b739f | 657 | POLLFD_STDIN |
54c6611d | 658 | |
da26af4e KZ |
659 | }; |
660 | struct pollfd pfd[] = { | |
0664d41d SK |
661 | [POLLFD_SIGNAL] = { .fd = ctl->sigfd, .events = POLLIN | POLLERR | POLLHUP }, |
662 | [POLLFD_MASTER] = { .fd = ctl->master, .events = POLLIN | POLLERR | POLLHUP }, | |
663 | [POLLFD_STDIN] = { .fd = STDIN_FILENO, .events = POLLIN | POLLERR | POLLHUP } | |
da26af4e KZ |
664 | }; |
665 | ||
6dbe3af9 | 666 | |
596f4202 KZ |
667 | /* start all output logs */ |
668 | for (i = 0; i < ctl->out.nlogs; i++) | |
9f822648 | 669 | log_start(ctl, ctl->out.logs[i]); |
8198052c | 670 | |
596f4202 KZ |
671 | /* start all input logs */ |
672 | for (i = 0; i < ctl->in.nlogs; i++) | |
9f822648 | 673 | log_start(ctl, ctl->in.logs[i]); |
a5a16c68 | 674 | |
6ddf53a5 | 675 | while (!ctl->die) { |
da26af4e | 676 | size_t i; |
7dbcd80e | 677 | int errsv; |
da26af4e | 678 | |
a2b4dec5 KZ |
679 | DBG(POLL, ul_debug("calling poll()")); |
680 | ||
cf470183 | 681 | /* wait for input or signal */ |
d88b739f | 682 | ret = poll(pfd, ARRAY_SIZE(pfd), ctl->poll_timeout); |
7dbcd80e | 683 | errsv = errno; |
a2b4dec5 KZ |
684 | DBG(POLL, ul_debug("poll() rc=%d", ret)); |
685 | ||
cf470183 | 686 | if (ret < 0) { |
7dbcd80e | 687 | if (errsv == EAGAIN) |
cf470183 SK |
688 | continue; |
689 | warn(_("poll failed")); | |
edc7e420 | 690 | fail(ctl); |
36c1d79b | 691 | } |
a2b4dec5 KZ |
692 | if (ret == 0) { |
693 | DBG(POLL, ul_debug("setting die=1")); | |
89a859d4 | 694 | ctl->die = 1; |
5860c45e | 695 | break; |
a2b4dec5 | 696 | } |
da26af4e | 697 | |
d88b739f | 698 | for (i = 0; i < ARRAY_SIZE(pfd); i++) { |
cf470183 SK |
699 | if (pfd[i].revents == 0) |
700 | continue; | |
a2b4dec5 | 701 | |
2e9418b7 | 702 | DBG(POLL, ul_debug(" active pfd[%s].fd=%d %s %s %s", |
54c6611d | 703 | i == POLLFD_STDIN ? "stdin" : |
2e9418b7 KZ |
704 | i == POLLFD_MASTER ? "master" : |
705 | i == POLLFD_SIGNAL ? "signal" : "???", | |
706 | pfd[i].fd, | |
54c6611d | 707 | pfd[i].revents & POLLIN ? "POLLIN" : "", |
1200cfbf KZ |
708 | pfd[i].revents & POLLHUP ? "POLLHUP" : "", |
709 | pfd[i].revents & POLLERR ? "POLLERR" : "")); | |
da26af4e KZ |
710 | switch (i) { |
711 | case POLLFD_STDIN: | |
712 | case POLLFD_MASTER: | |
54c6611d KZ |
713 | /* data */ |
714 | if (pfd[i].revents & POLLIN) | |
715 | handle_io(ctl, pfd[i].fd, &eof); | |
716 | /* EOF maybe detected by two ways: | |
717 | * A) poll() return POLLHUP event after close() | |
7dbcd80e | 718 | * B) read() returns 0 (no data) */ |
54c6611d KZ |
719 | if ((pfd[i].revents & POLLHUP) || eof) { |
720 | DBG(POLL, ul_debug(" ignore FD")); | |
721 | pfd[i].fd = -1; | |
54c6611d | 722 | if (i == POLLFD_STDIN) { |
54c6611d | 723 | write_eof_to_shell(ctl); |
b2bff066 | 724 | DBG(POLL, ul_debug(" ignore STDIN")); |
54c6611d KZ |
725 | } |
726 | } | |
cf470183 | 727 | continue; |
da26af4e | 728 | case POLLFD_SIGNAL: |
a8896ad5 | 729 | handle_signal(ctl, pfd[i].fd); |
da26af4e | 730 | break; |
cf470183 | 731 | } |
968e632c | 732 | } |
cf470183 | 733 | } |
a2b4dec5 KZ |
734 | |
735 | DBG(POLL, ul_debug("poll() done")); | |
736 | ||
6ddf53a5 | 737 | if (!ctl->die) |
5860c45e | 738 | wait_for_child(ctl, 1); |
6f3c9c34 | 739 | |
6ddf53a5 | 740 | done(ctl); |
6dbe3af9 KZ |
741 | } |
742 | ||
edc7e420 | 743 | static void getslave(struct script_control *ctl) |
93af8d8b SK |
744 | { |
745 | #ifndef HAVE_LIBUTIL | |
edc7e420 | 746 | ctl->line[strlen("/dev/")] = 't'; |
760e5e68 | 747 | ctl->slave = open(ctl->line, O_RDWR | O_CLOEXEC); |
edc7e420 SK |
748 | if (ctl->slave < 0) { |
749 | warn(_("cannot open %s"), ctl->line); | |
750 | fail(ctl); | |
93af8d8b | 751 | } |
edc7e420 | 752 | if (ctl->isterm) { |
3f19b85f | 753 | tcsetattr(ctl->slave, TCSANOW, &ctl->attrs); |
edc7e420 | 754 | ioctl(ctl->slave, TIOCSWINSZ, (char *)&ctl->win); |
93af8d8b SK |
755 | } |
756 | #endif | |
757 | setsid(); | |
edc7e420 | 758 | ioctl(ctl->slave, TIOCSCTTY, 0); |
93af8d8b SK |
759 | } |
760 | ||
54c6611d | 761 | /* don't use DBG() stuff here otherwise it will be in the typescript file */ |
d35ffe80 | 762 | static void __attribute__((__noreturn__)) do_shell(struct script_control *ctl) |
93af8d8b | 763 | { |
df1dddf9 KZ |
764 | char *shname; |
765 | ||
edc7e420 | 766 | getslave(ctl); |
4368e3e6 KZ |
767 | |
768 | /* close things irrelevant for this process */ | |
edc7e420 | 769 | close(ctl->master); |
d35ffe80 | 770 | close(ctl->sigfd); |
4368e3e6 | 771 | |
edc7e420 SK |
772 | dup2(ctl->slave, STDIN_FILENO); |
773 | dup2(ctl->slave, STDOUT_FILENO); | |
774 | dup2(ctl->slave, STDERR_FILENO); | |
775 | close(ctl->slave); | |
df1dddf9 | 776 | |
edc7e420 | 777 | ctl->master = -1; |
f0bc3fa0 | 778 | |
edc7e420 | 779 | shname = strrchr(ctl->shell, '/'); |
df1dddf9 KZ |
780 | if (shname) |
781 | shname++; | |
782 | else | |
edc7e420 | 783 | shname = ctl->shell; |
df1dddf9 | 784 | |
d35ffe80 KZ |
785 | sigprocmask(SIG_SETMASK, &ctl->sigorg, NULL); |
786 | ||
a17f3264 KZ |
787 | /* |
788 | * When invoked from within /etc/csh.login, script spawns a csh shell | |
789 | * that spawns programs that cannot be killed with a SIGTERM. This is | |
ee312c65 | 790 | * because csh has a documented behavior wherein it disables all |
a17f3264 KZ |
791 | * signals when processing the /etc/csh.* files. |
792 | * | |
793 | * Let's restore the default behavior. | |
794 | */ | |
795 | signal(SIGTERM, SIG_DFL); | |
796 | ||
edc7e420 | 797 | if (access(ctl->shell, X_OK) == 0) { |
3f19b85f KZ |
798 | if (ctl->command) |
799 | execl(ctl->shell, shname, "-c", ctl->command, NULL); | |
b4ff2f54 | 800 | else |
edc7e420 | 801 | execl(ctl->shell, shname, "-i", NULL); |
b4ff2f54 | 802 | } else { |
3f19b85f KZ |
803 | if (ctl->command) |
804 | execlp(shname, "-c", ctl->command, NULL); | |
b4ff2f54 SK |
805 | else |
806 | execlp(shname, "-i", NULL); | |
807 | } | |
edc7e420 SK |
808 | warn(_("failed to execute %s"), ctl->shell); |
809 | fail(ctl); | |
6dbe3af9 KZ |
810 | } |
811 | ||
6dbe3af9 | 812 | |
edc7e420 | 813 | static void getmaster(struct script_control *ctl) |
93af8d8b | 814 | { |
e11a5e63 | 815 | #if defined(HAVE_LIBUTIL) && defined(HAVE_PTY_H) |
3822032d KZ |
816 | int rc; |
817 | ||
edc7e420 | 818 | ctl->isterm = isatty(STDIN_FILENO); |
3822032d | 819 | |
edc7e420 | 820 | if (ctl->isterm) { |
3f19b85f | 821 | if (tcgetattr(STDIN_FILENO, &ctl->attrs) != 0) |
3822032d | 822 | err(EXIT_FAILURE, _("failed to get terminal attributes")); |
edc7e420 | 823 | ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&ctl->win); |
3f19b85f | 824 | rc = openpty(&ctl->master, &ctl->slave, NULL, &ctl->attrs, &ctl->win); |
3822032d | 825 | } else |
edc7e420 | 826 | rc = openpty(&ctl->master, &ctl->slave, NULL, NULL, NULL); |
3822032d KZ |
827 | |
828 | if (rc < 0) { | |
360d5005 | 829 | warn(_("openpty failed")); |
edc7e420 | 830 | fail(ctl); |
5c36a0eb KZ |
831 | } |
832 | #else | |
6dbe3af9 | 833 | char *pty, *bank, *cp; |
6dbe3af9 | 834 | |
edc7e420 | 835 | ctl->isterm = isatty(STDIN_FILENO); |
3822032d | 836 | |
edc7e420 | 837 | pty = &ctl->line[strlen("/dev/ptyp")]; |
6dbe3af9 | 838 | for (bank = "pqrs"; *bank; bank++) { |
edc7e420 | 839 | ctl->line[strlen("/dev/pty")] = *bank; |
6dbe3af9 | 840 | *pty = '0'; |
a4aeb5bd | 841 | if (access(ctl->line, F_OK) != 0) |
6dbe3af9 KZ |
842 | break; |
843 | for (cp = "0123456789abcdef"; *cp; cp++) { | |
844 | *pty = *cp; | |
760e5e68 | 845 | ctl->master = open(ctl->line, O_RDWR | O_CLOEXEC); |
edc7e420 SK |
846 | if (ctl->master >= 0) { |
847 | char *tp = &ctl->line[strlen("/dev/")]; | |
6dbe3af9 KZ |
848 | int ok; |
849 | ||
850 | /* verify slave side is usable */ | |
851 | *tp = 't'; | |
edc7e420 | 852 | ok = access(ctl->line, R_OK | W_OK) == 0; |
6dbe3af9 KZ |
853 | *tp = 'p'; |
854 | if (ok) { | |
edc7e420 | 855 | if (ctl->isterm) { |
3f19b85f | 856 | tcgetattr(STDIN_FILENO, &ctl->attrs); |
edc7e420 | 857 | ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&ctl->win); |
3822032d | 858 | } |
6dbe3af9 KZ |
859 | return; |
860 | } | |
edc7e420 SK |
861 | close(ctl->master); |
862 | ctl->master = -1; | |
6dbe3af9 KZ |
863 | } |
864 | } | |
865 | } | |
edc7e420 | 866 | ctl->master = -1; |
360d5005 | 867 | warn(_("out of pty's")); |
edc7e420 SK |
868 | fail(ctl); |
869 | #endif /* not HAVE_LIBUTIL */ | |
54c6611d KZ |
870 | |
871 | DBG(IO, ul_debug("master fd: %d", ctl->master)); | |
6dbe3af9 KZ |
872 | } |
873 | ||
93af8d8b SK |
874 | int main(int argc, char **argv) |
875 | { | |
edc7e420 SK |
876 | struct script_control ctl = { |
877 | #if !HAVE_LIBUTIL || !HAVE_PTY_H | |
878 | .line = "/dev/ptyXX", | |
879 | #endif | |
880 | .master = -1, | |
596f4202 KZ |
881 | .slave = -1, |
882 | ||
aefe9893 | 883 | .poll_timeout = -1 |
edc7e420 | 884 | }; |
93af8d8b | 885 | int ch; |
596f4202 | 886 | const char *typescript = DEFAULT_TYPESCRIPT_FILENAME; |
d805688a | 887 | const char *timingfile = NULL; |
93af8d8b SK |
888 | |
889 | enum { FORCE_OPTION = CHAR_MAX + 1 }; | |
890 | ||
891 | static const struct option longopts[] = { | |
edc7e420 SK |
892 | {"append", no_argument, NULL, 'a'}, |
893 | {"command", required_argument, NULL, 'c'}, | |
894 | {"return", no_argument, NULL, 'e'}, | |
895 | {"flush", no_argument, NULL, 'f'}, | |
896 | {"force", no_argument, NULL, FORCE_OPTION,}, | |
aefe9893 | 897 | {"output-limit", required_argument, NULL, 'o'}, |
edc7e420 SK |
898 | {"quiet", no_argument, NULL, 'q'}, |
899 | {"timing", optional_argument, NULL, 't'}, | |
900 | {"version", no_argument, NULL, 'V'}, | |
901 | {"help", no_argument, NULL, 'h'}, | |
902 | {NULL, 0, NULL, 0} | |
93af8d8b SK |
903 | }; |
904 | ||
905 | setlocale(LC_ALL, ""); | |
b09feab9 SK |
906 | /* |
907 | * script -t prints time delays as floating point numbers. The example | |
908 | * program (scriptreplay) that we provide to handle this timing output | |
909 | * is a perl script, and does not handle numbers in locale format (not | |
910 | * even when "use locale;" is added). So, since these numbers are not | |
911 | * for human consumption, it seems easiest to set LC_NUMERIC here. | |
912 | */ | |
913 | setlocale(LC_NUMERIC, "C"); | |
93af8d8b SK |
914 | bindtextdomain(PACKAGE, LOCALEDIR); |
915 | textdomain(PACKAGE); | |
2c308875 | 916 | close_stdout_atexit(); |
93af8d8b | 917 | |
a2b4dec5 KZ |
918 | script_init_debug(); |
919 | ||
aefe9893 | 920 | while ((ch = getopt_long(argc, argv, "ac:efo:qt::Vh", longopts, NULL)) != -1) |
edc7e420 | 921 | switch (ch) { |
93af8d8b | 922 | case 'a': |
3f19b85f | 923 | ctl.append = 1; |
93af8d8b SK |
924 | break; |
925 | case 'c': | |
3f19b85f | 926 | ctl.command = optarg; |
93af8d8b SK |
927 | break; |
928 | case 'e': | |
7e5796c9 | 929 | ctl.rc_wanted = 1; |
93af8d8b SK |
930 | break; |
931 | case 'f': | |
3f19b85f | 932 | ctl.flush = 1; |
93af8d8b SK |
933 | break; |
934 | case FORCE_OPTION: | |
3f19b85f | 935 | ctl.force = 1; |
93af8d8b | 936 | break; |
aefe9893 FM |
937 | case 'o': |
938 | ctl.maxsz = strtosize_or_err(optarg, _("failed to parse output limit size")); | |
939 | break; | |
93af8d8b | 940 | case 'q': |
3f19b85f | 941 | ctl.quiet = 1; |
93af8d8b SK |
942 | break; |
943 | case 't': | |
4f7521d6 KZ |
944 | if (optarg && *optarg == '=') |
945 | optarg++; | |
596f4202 KZ |
946 | log_associate(&ctl, &ctl.out, |
947 | optarg ? optarg : "/dev/stderr", | |
948 | SCRIPT_FMT_TIMING_SIMPLE); | |
d805688a KZ |
949 | /* used for message only */ |
950 | timingfile = optarg ? optarg : "stderr"; | |
93af8d8b | 951 | break; |
2c308875 | 952 | |
93af8d8b | 953 | case 'V': |
2c308875 | 954 | print_version(EXIT_SUCCESS); |
93af8d8b | 955 | case 'h': |
86be6a32 | 956 | usage(); |
93af8d8b | 957 | default: |
677ec86c | 958 | errtryhelp(EXIT_FAILURE); |
93af8d8b SK |
959 | } |
960 | argc -= optind; | |
961 | argv += optind; | |
962 | ||
963 | if (argc > 0) | |
596f4202 KZ |
964 | typescript = argv[0]; |
965 | else | |
966 | die_if_link(&ctl, DEFAULT_TYPESCRIPT_FILENAME); | |
967 | ||
968 | /* associate stdout with typescript file */ | |
969 | log_associate(&ctl, &ctl.out, typescript, SCRIPT_FMT_RAW); | |
93af8d8b | 970 | |
edc7e420 SK |
971 | ctl.shell = getenv("SHELL"); |
972 | if (ctl.shell == NULL) | |
973 | ctl.shell = _PATH_BSHELL; | |
93af8d8b | 974 | |
edc7e420 | 975 | getmaster(&ctl); |
d805688a KZ |
976 | if (!ctl.quiet) { |
977 | if (!timingfile) | |
978 | printf(_("Script started, log file is '%s'.\n"), typescript); | |
979 | else | |
980 | printf(_("Script started, log file is '%s', timing file is '%s'.\n"), | |
981 | typescript, timingfile); | |
982 | } | |
7fb65db1 | 983 | enable_rawmode_tty(&ctl); |
93af8d8b SK |
984 | |
985 | #ifdef HAVE_LIBUTEMPTER | |
edc7e420 | 986 | utempter_add_record(ctl.master, NULL); |
5c36a0eb | 987 | #endif |
cc1a88fb | 988 | /* setup signal handler */ |
d35ffe80 KZ |
989 | sigemptyset(&ctl.sigset); |
990 | sigaddset(&ctl.sigset, SIGCHLD); | |
991 | sigaddset(&ctl.sigset, SIGWINCH); | |
5860c45e KZ |
992 | sigaddset(&ctl.sigset, SIGTERM); |
993 | sigaddset(&ctl.sigset, SIGINT); | |
994 | sigaddset(&ctl.sigset, SIGQUIT); | |
d35ffe80 KZ |
995 | |
996 | /* block signals used for signalfd() to prevent the signals being | |
997 | * handled according to their default dispositions */ | |
998 | sigprocmask(SIG_BLOCK, &ctl.sigset, &ctl.sigorg); | |
a2b4dec5 | 999 | |
760e5e68 | 1000 | if ((ctl.sigfd = signalfd(-1, &ctl.sigset, SFD_CLOEXEC)) < 0) |
cc1a88fb | 1001 | err(EXIT_FAILURE, _("cannot set signal handler")); |
93af8d8b | 1002 | |
a2b4dec5 KZ |
1003 | DBG(SIGNAL, ul_debug("signal fd=%d", ctl.sigfd)); |
1004 | ||
93af8d8b | 1005 | fflush(stdout); |
edc7e420 | 1006 | ctl.child = fork(); |
93af8d8b | 1007 | |
d35ffe80 KZ |
1008 | switch (ctl.child) { |
1009 | case -1: /* error */ | |
93af8d8b | 1010 | warn(_("fork failed")); |
edc7e420 | 1011 | fail(&ctl); |
d35ffe80 KZ |
1012 | break; |
1013 | case 0: /* child */ | |
1014 | do_shell(&ctl); | |
1015 | break; | |
1016 | default: /* parent */ | |
1017 | do_io(&ctl); | |
1018 | break; | |
93af8d8b | 1019 | } |
d35ffe80 KZ |
1020 | |
1021 | /* should not happen, all used functions are non-return */ | |
b09feab9 | 1022 | return EXIT_FAILURE; |
6dbe3af9 | 1023 | } |