]>
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" |
fc58044f | 76 | #include "optutils.h" |
fbed5507 | 77 | #include "signames.h" |
6dbe3af9 | 78 | |
a2b4dec5 KZ |
79 | #include "debug.h" |
80 | ||
2ba641e5 | 81 | static UL_DEBUG_DEFINE_MASK(script); |
a2b4dec5 KZ |
82 | UL_DEBUG_DEFINE_MASKNAMES(script) = UL_DEBUG_EMPTY_MASKNAMES; |
83 | ||
84 | #define SCRIPT_DEBUG_INIT (1 << 1) | |
85 | #define SCRIPT_DEBUG_POLL (1 << 2) | |
86 | #define SCRIPT_DEBUG_SIGNAL (1 << 3) | |
87 | #define SCRIPT_DEBUG_IO (1 << 4) | |
88 | #define SCRIPT_DEBUG_MISC (1 << 5) | |
89 | #define SCRIPT_DEBUG_ALL 0xFFFF | |
90 | ||
91 | #define DBG(m, x) __UL_DBG(script, SCRIPT_DEBUG_, m, x) | |
92 | #define ON_DBG(m, x) __UL_DBG_CALL(script, SCRIPT_DEBUG_, m, x) | |
93 | ||
e11a5e63 SK |
94 | #if defined(HAVE_LIBUTIL) && defined(HAVE_PTY_H) |
95 | # include <pty.h> | |
5c36a0eb KZ |
96 | #endif |
97 | ||
f0bc3fa0 | 98 | #ifdef HAVE_LIBUTEMPTER |
e11a5e63 | 99 | # include <utempter.h> |
f0bc3fa0 KZ |
100 | #endif |
101 | ||
7e5796c9 | 102 | #define DEFAULT_TYPESCRIPT_FILENAME "typescript" |
05644dab | 103 | |
fec06a06 KZ |
104 | /* |
105 | * Script is driven by stream (stdout/stdin) activity. It's possible to | |
106 | * associate arbitrary number of log files with the stream. We have two basic | |
107 | * types of log files: "timing file" (simple or multistream) and "data file" | |
108 | * (raw). | |
109 | * | |
110 | * The same log file maybe be shared between both streams. For exmaple | |
111 | * multi-stream timing file is possible to use for stdin as well as for stdout. | |
112 | */ | |
596f4202 KZ |
113 | enum { |
114 | SCRIPT_FMT_RAW = 1, /* raw slave/master data */ | |
daee4d95 KZ |
115 | SCRIPT_FMT_TIMING_SIMPLE, /* (classic) in format "<delta> <offset>" */ |
116 | SCRIPT_FMT_TIMING_MULTI, /* (advanced) multiple streams in format "<type> <delta> <offset|etc> */ | |
596f4202 KZ |
117 | }; |
118 | ||
119 | struct script_log { | |
120 | FILE *fp; /* file pointer (handler) */ | |
121 | int format; /* SCRIPT_FMT_* */ | |
122 | char *filename; /* on command line specified name */ | |
88025c74 | 123 | struct timeval oldtime; /* previous entry log time (SCRIPT_FMT_TIMING_* only) */ |
c1c2ee0b KZ |
124 | |
125 | unsigned int initialized : 1; | |
596f4202 KZ |
126 | }; |
127 | ||
128 | struct script_stream { | |
9f822648 | 129 | struct script_log **logs; /* logs where to write data from stream */ |
596f4202 | 130 | size_t nlogs; /* number of logs */ |
c1c2ee0b | 131 | char ident; /* stream identifier */ |
596f4202 KZ |
132 | }; |
133 | ||
edc7e420 SK |
134 | struct script_control { |
135 | char *shell; /* shell to be executed */ | |
3f19b85f | 136 | char *command; /* command to be executed */ |
596f4202 KZ |
137 | uint64_t outsz; /* current output files size */ |
138 | uint64_t maxsz; /* maximum output files size */ | |
139 | ||
edc7e420 SK |
140 | int master; /* pseudoterminal master file descriptor */ |
141 | int slave; /* pseudoterminal slave file descriptor */ | |
596f4202 KZ |
142 | |
143 | struct script_stream out; /* output */ | |
144 | struct script_stream in; /* input */ | |
145 | ||
3cecd176 KZ |
146 | struct script_log *siglog; /* log for signal entries */ |
147 | struct script_log *infolog; /* log for info entries */ | |
3cecd176 KZ |
148 | |
149 | const char *ttyname; | |
150 | const char *ttytype; | |
151 | int ttycols; | |
152 | int ttylines; | |
fbed5507 | 153 | |
89a859d4 | 154 | int poll_timeout; /* poll() timeout, used in end of execution */ |
edc7e420 | 155 | pid_t child; /* child pid */ |
edc7e420 | 156 | int childstatus; /* child process exit value */ |
3f19b85f | 157 | struct termios attrs; /* slave terminal runtime attributes */ |
edc7e420 | 158 | struct winsize win; /* terminal window size */ |
9779651e | 159 | #if !HAVE_LIBUTIL || !HAVE_PTY_H |
8d6fdd2f | 160 | char *line; /* terminal line */ |
5c36a0eb | 161 | #endif |
edc7e420 | 162 | unsigned int |
3f19b85f | 163 | append:1, /* append output */ |
7e5796c9 | 164 | rc_wanted:1, /* return child exit value */ |
3f19b85f KZ |
165 | flush:1, /* flush after each write */ |
166 | quiet:1, /* suppress most output */ | |
3f19b85f | 167 | force:1, /* write output to links */ |
edc7e420 | 168 | isterm:1, /* is child process running as terminal */ |
edc7e420 | 169 | die:1; /* terminate program */ |
7e5796c9 | 170 | |
cc1a88fb | 171 | sigset_t sigset; /* catch SIGCHLD and SIGWINCH with signalfd() */ |
d35ffe80 | 172 | sigset_t sigorg; /* original signal mask */ |
cc1a88fb | 173 | int sigfd; /* file descriptor for signalfd() */ |
edc7e420 | 174 | }; |
1f58c445 | 175 | |
596f4202 KZ |
176 | static void restore_tty(struct script_control *ctl, int mode); |
177 | static void __attribute__((__noreturn__)) fail(struct script_control *ctl); | |
3cecd176 | 178 | static uint64_t log_info(struct script_control *ctl, const char *name, const char *msgfmt, ...); |
596f4202 | 179 | |
a2b4dec5 KZ |
180 | static void script_init_debug(void) |
181 | { | |
a15dca2f | 182 | __UL_INIT_DEBUG_FROM_ENV(script, SCRIPT_DEBUG_, 0, SCRIPT_DEBUG); |
a2b4dec5 KZ |
183 | } |
184 | ||
3cecd176 KZ |
185 | static void init_terminal_info(struct script_control *ctl) |
186 | { | |
187 | if (ctl->ttyname || !ctl->isterm) | |
188 | return; /* already initialized */ | |
189 | ||
190 | get_terminal_dimension(&ctl->ttycols, &ctl->ttylines); | |
191 | get_terminal_name(&ctl->ttyname, NULL, NULL); | |
192 | get_terminal_type(&ctl->ttytype); | |
193 | } | |
194 | ||
6a40c65f SK |
195 | /* |
196 | * For tests we want to be able to control time output | |
197 | */ | |
198 | #ifdef TEST_SCRIPT | |
199 | static inline time_t script_time(time_t *t) | |
200 | { | |
201 | const char *str = getenv("SCRIPT_TEST_SECOND_SINCE_EPOCH"); | |
345208a5 | 202 | int64_t sec; |
6a40c65f | 203 | |
c2f03da9 | 204 | if (!str || sscanf(str, "%"SCNi64, &sec) != 1) |
345208a5 ID |
205 | return time(t); |
206 | if (t) | |
207 | *t = (time_t)sec; | |
208 | return (time_t)sec; | |
6a40c65f SK |
209 | } |
210 | #else /* !TEST_SCRIPT */ | |
211 | # define script_time(x) time(x) | |
212 | #endif | |
213 | ||
86be6a32 | 214 | static void __attribute__((__noreturn__)) usage(void) |
3ff52639 | 215 | { |
86be6a32 | 216 | FILE *out = stdout; |
db433bf7 | 217 | fputs(USAGE_HEADER, out); |
edc7e420 | 218 | fprintf(out, _(" %s [options] [file]\n"), program_invocation_short_name); |
87d6050b | 219 | |
451dbcfa BS |
220 | fputs(USAGE_SEPARATOR, out); |
221 | fputs(_("Make a typescript of a terminal session.\n"), out); | |
222 | ||
db433bf7 | 223 | fputs(USAGE_OPTIONS, out); |
70062aad | 224 | fputs(_(" -I, --log-in <file> log stdin to file\n"), out); |
ddbdb792 | 225 | fputs(_(" -O, --log-out <file> log stdout to file (default)\n"), out); |
c1c2ee0b | 226 | fputs(_(" -B, --log-io <file> log stdin and stdout to file\n"), out); |
65dc24ab KZ |
227 | fputs(USAGE_SEPARATOR, out); |
228 | ||
fc58044f | 229 | fputs(_(" -T, --log-timing <file> log timing information to file\n"), out); |
65dc24ab | 230 | fputs(_(" -t[<file>], --timing[=<file>] deprecated alias to -T (default file is stderr)\n"), out); |
daee4d95 | 231 | fputs(_(" -m, --logging-format <name> force to 'classic' or 'advanced' format\n"), out); |
65dc24ab KZ |
232 | fputs(USAGE_SEPARATOR, out); |
233 | ||
234 | fputs(_(" -a, --append append to the log file\n"), out); | |
c64963f8 KZ |
235 | fputs(_(" -c, --command <command> run command rather than interactive shell\n"), out); |
236 | fputs(_(" -e, --return return exit code of the child process\n"), out); | |
237 | fputs(_(" -f, --flush run flush after each write\n"), out); | |
238 | fputs(_(" --force use output file even when it is a link\n"), out); | |
239 | fputs(_(" -o, --output-limit <size> terminate if output files exceed size\n"), out); | |
240 | fputs(_(" -q, --quiet be quiet\n"), out); | |
3ff52639 | 241 | |
c64963f8 KZ |
242 | fputs(USAGE_SEPARATOR, out); |
243 | printf(USAGE_HELP_OPTIONS(31)); | |
f45f3ec3 | 244 | printf(USAGE_MAN_TAIL("script(1)")); |
c64963f8 | 245 | |
86be6a32 | 246 | exit(EXIT_SUCCESS); |
3ff52639 SK |
247 | } |
248 | ||
596f4202 KZ |
249 | static struct script_log *get_log_by_name(struct script_stream *stream, |
250 | const char *name) | |
4d9b788d | 251 | { |
596f4202 | 252 | size_t i; |
4d9b788d | 253 | |
596f4202 | 254 | for (i = 0; i < stream->nlogs; i++) { |
9f822648 KZ |
255 | struct script_log *log = stream->logs[i]; |
256 | if (strcmp(log->filename, name) == 0) | |
596f4202 KZ |
257 | return log; |
258 | } | |
259 | return NULL; | |
260 | } | |
4d9b788d | 261 | |
596f4202 KZ |
262 | static struct script_log *log_associate(struct script_control *ctl, |
263 | struct script_stream *stream, | |
264 | const char *filename, int format) | |
265 | { | |
266 | struct script_log *log; | |
267 | ||
c1c2ee0b KZ |
268 | DBG(MISC, ul_debug("associate %s with stream", filename)); |
269 | ||
9f822648 KZ |
270 | assert(ctl); |
271 | assert(filename); | |
272 | assert(stream); | |
273 | ||
274 | log = get_log_by_name(stream, filename); | |
275 | if (log) | |
276 | return log; /* already defined */ | |
277 | ||
278 | log = get_log_by_name(stream == &ctl->out ? &ctl->in : &ctl->out, filename); | |
596f4202 KZ |
279 | if (!log) { |
280 | /* create a new log */ | |
9f822648 KZ |
281 | log = xcalloc(1, sizeof(*log)); |
282 | log->filename = xstrdup(filename); | |
596f4202 KZ |
283 | log->format = format; |
284 | } | |
4d9b788d | 285 | |
9f822648 KZ |
286 | /* add log to the stream */ |
287 | stream->logs = xrealloc(stream->logs, | |
288 | (stream->nlogs + 1) * sizeof(log)); | |
289 | stream->logs[stream->nlogs] = log; | |
290 | stream->nlogs++; | |
291 | ||
fbed5507 | 292 | /* remember where to write info about signals */ |
3cecd176 KZ |
293 | if (format == SCRIPT_FMT_TIMING_MULTI) { |
294 | if (!ctl->siglog) | |
295 | ctl->siglog = log; | |
296 | if (!ctl->infolog) | |
297 | ctl->infolog = log; | |
3cecd176 | 298 | } |
fbed5507 | 299 | |
596f4202 KZ |
300 | return log; |
301 | } | |
4d9b788d | 302 | |
d805688a | 303 | static void log_close(struct script_control *ctl __attribute__((unused)), |
596f4202 KZ |
304 | struct script_log *log, |
305 | const char *msg, | |
306 | int status) | |
307 | { | |
c1c2ee0b KZ |
308 | if (!log->initialized) |
309 | return; | |
310 | ||
596f4202 | 311 | DBG(MISC, ul_debug("closing %s", log->filename)); |
4d9b788d | 312 | |
596f4202 KZ |
313 | switch (log->format) { |
314 | case SCRIPT_FMT_RAW: | |
315 | { | |
316 | char buf[FORMAT_TIMESTAMP_MAX]; | |
317 | time_t tvec = script_time((time_t *)NULL); | |
4d9b788d | 318 | |
596f4202 KZ |
319 | strtime_iso(&tvec, ISO_TIMESTAMP, buf, sizeof(buf)); |
320 | if (msg) | |
321 | fprintf(log->fp, _("\nScript done on %s [<%s>]\n"), buf, msg); | |
322 | else | |
323 | fprintf(log->fp, _("\nScript done on %s [COMMAND_EXIT_CODE=\"%d\"]\n"), buf, status); | |
596f4202 KZ |
324 | break; |
325 | } | |
326 | case SCRIPT_FMT_TIMING_SIMPLE: | |
327 | break; | |
328 | } | |
329 | ||
330 | if (close_stream(log->fp) != 0) | |
331 | err(EXIT_FAILURE, "write failed: %s", log->filename); | |
4d9b788d | 332 | |
596f4202 | 333 | log->fp = NULL; |
c1c2ee0b | 334 | log->initialized = 0; |
4d9b788d KZ |
335 | } |
336 | ||
596f4202 KZ |
337 | static void log_start(struct script_control *ctl, |
338 | struct script_log *log) | |
6343ee8c | 339 | { |
c1c2ee0b KZ |
340 | if (log->initialized) |
341 | return; | |
6343ee8c | 342 | |
596f4202 | 343 | DBG(MISC, ul_debug("opening %s", log->filename)); |
6343ee8c | 344 | |
c1c2ee0b KZ |
345 | assert(log->fp == NULL); |
346 | ||
596f4202 KZ |
347 | /* open the log */ |
348 | log->fp = fopen(log->filename, | |
349 | ctl->append && log->format == SCRIPT_FMT_RAW ? | |
c1c2ee0b KZ |
350 | "a" UL_CLOEXECSTR : |
351 | "w" UL_CLOEXECSTR); | |
596f4202 KZ |
352 | if (!log->fp) { |
353 | restore_tty(ctl, TCSANOW); | |
354 | warn(_("cannot open %s"), log->filename); | |
355 | fail(ctl); | |
356 | } | |
6343ee8c | 357 | |
596f4202 KZ |
358 | /* write header, etc. */ |
359 | switch (log->format) { | |
360 | case SCRIPT_FMT_RAW: | |
361 | { | |
362 | char buf[FORMAT_TIMESTAMP_MAX]; | |
363 | time_t tvec = script_time((time_t *)NULL); | |
364 | ||
365 | strtime_iso(&tvec, ISO_TIMESTAMP, buf, sizeof(buf)); | |
366 | fprintf(log->fp, _("Script started on %s ["), buf); | |
367 | ||
368 | if (ctl->isterm) { | |
3cecd176 | 369 | init_terminal_info(ctl); |
596f4202 | 370 | |
3cecd176 KZ |
371 | if (ctl->ttytype) |
372 | fprintf(log->fp, "TERM=\"%s\" ", ctl->ttytype); | |
373 | if (ctl->ttyname) | |
374 | fprintf(log->fp, "TTY=\"%s\" ", ctl->ttyname); | |
596f4202 | 375 | |
3cecd176 | 376 | fprintf(log->fp, "COLUMNS=\"%d\" LINES=\"%d\"", ctl->ttycols, ctl->ttylines); |
596f4202 KZ |
377 | } else |
378 | fprintf(log->fp, _("<not executed on terminal>")); | |
379 | ||
380 | fputs("]\n", log->fp); | |
381 | break; | |
382 | } | |
383 | case SCRIPT_FMT_TIMING_SIMPLE: | |
36b1369b | 384 | case SCRIPT_FMT_TIMING_MULTI: |
596f4202 KZ |
385 | gettime_monotonic(&log->oldtime); |
386 | break; | |
387 | } | |
c1c2ee0b KZ |
388 | |
389 | log->initialized = 1; | |
596f4202 KZ |
390 | } |
391 | ||
fd42a45b KZ |
392 | static void start_logging(struct script_control *ctl) |
393 | { | |
394 | size_t i; | |
395 | ||
396 | /* start all output logs */ | |
397 | for (i = 0; i < ctl->out.nlogs; i++) | |
398 | log_start(ctl, ctl->out.logs[i]); | |
399 | ||
400 | /* start all input logs */ | |
401 | for (i = 0; i < ctl->in.nlogs; i++) | |
402 | log_start(ctl, ctl->in.logs[i]); | |
403 | } | |
404 | ||
596f4202 | 405 | static size_t log_write(struct script_control *ctl, |
c1c2ee0b | 406 | struct script_stream *stream, |
596f4202 KZ |
407 | struct script_log *log, |
408 | char *obuf, size_t bytes) | |
409 | { | |
410 | if (!log->fp) | |
411 | return 0; | |
412 | ||
413 | DBG(IO, ul_debug(" writining %s", log->filename)); | |
414 | ||
415 | switch (log->format) { | |
416 | case SCRIPT_FMT_RAW: | |
417 | if (fwrite_all(obuf, 1, bytes, log->fp)) { | |
418 | warn(_("cannot write %s"), log->filename); | |
419 | fail(ctl); | |
420 | } | |
421 | break; | |
422 | case SCRIPT_FMT_TIMING_SIMPLE: | |
423 | { | |
424 | struct timeval now, delta; | |
425 | int sz; | |
426 | ||
427 | DBG(IO, ul_debug(" writing timing info")); | |
428 | ||
429 | gettime_monotonic(&now); | |
430 | timersub(&now, &log->oldtime, &delta); | |
431 | sz = fprintf(log->fp, "%ld.%06ld %zd\n", | |
432 | (long)delta.tv_sec, (long)delta.tv_usec, bytes); | |
433 | log->oldtime = now; | |
434 | bytes = sz > 0 ? sz : 0; | |
435 | break; | |
436 | } | |
c1c2ee0b KZ |
437 | case SCRIPT_FMT_TIMING_MULTI: |
438 | { | |
439 | struct timeval now, delta; | |
440 | int sz; | |
441 | ||
442 | DBG(IO, ul_debug(" writing multi-stream timing info")); | |
443 | ||
444 | gettime_monotonic(&now); | |
445 | timersub(&now, &log->oldtime, &delta); | |
446 | sz = fprintf(log->fp, "%c %ld.%06ld %zd\n", | |
447 | stream->ident, | |
448 | (long)delta.tv_sec, (long)delta.tv_usec, bytes); | |
449 | log->oldtime = now; | |
450 | bytes = sz > 0 ? sz : 0; | |
451 | } | |
596f4202 KZ |
452 | default: |
453 | break; | |
454 | } | |
455 | ||
456 | if (ctl->flush) | |
457 | fflush(log->fp); | |
458 | ||
459 | return bytes; | |
6343ee8c KZ |
460 | } |
461 | ||
596f4202 KZ |
462 | static uint64_t log_stream_activity( |
463 | struct script_control *ctl, | |
464 | struct script_stream *stream, | |
465 | char *buf, size_t bytes) | |
466 | { | |
467 | size_t i; | |
468 | uint64_t outsz = 0; | |
469 | ||
470 | for (i = 0; i < stream->nlogs; i++) | |
c1c2ee0b | 471 | outsz += log_write(ctl, stream, stream->logs[i], buf, bytes); |
596f4202 KZ |
472 | |
473 | return outsz; | |
474 | } | |
475 | ||
fbed5507 KZ |
476 | static uint64_t log_signal(struct script_control *ctl, int signum, char *msgfmt, ...) |
477 | { | |
478 | struct script_log *log; | |
479 | struct timeval now, delta; | |
480 | char msg[BUFSIZ] = {0}; | |
481 | va_list ap; | |
482 | int sz; | |
483 | ||
484 | assert(ctl); | |
485 | ||
486 | log = ctl->siglog; | |
487 | if (!log) | |
488 | return 0; | |
489 | ||
490 | assert(log->format == SCRIPT_FMT_TIMING_MULTI); | |
fbed5507 KZ |
491 | DBG(IO, ul_debug(" writing signal to multi-stream timing")); |
492 | ||
493 | gettime_monotonic(&now); | |
494 | timersub(&now, &log->oldtime, &delta); | |
495 | ||
496 | if (msgfmt) { | |
497 | int rc; | |
498 | va_start(ap, msgfmt); | |
499 | rc = vsnprintf(msg, sizeof(msg), msgfmt, ap); | |
500 | va_end(ap); | |
501 | if (rc < 0) | |
502 | *msg = '\0';; | |
503 | } | |
504 | ||
505 | if (*msg) | |
fd42a45b | 506 | sz = fprintf(log->fp, "S %ld.%06ld SIG%s %s\n", |
fbed5507 KZ |
507 | (long)delta.tv_sec, (long)delta.tv_usec, |
508 | signum_to_signame(signum), msg); | |
509 | else | |
510 | sz = fprintf(log->fp, "S %ld.%06ld SIG%s\n", | |
511 | (long)delta.tv_sec, (long)delta.tv_usec, | |
512 | signum_to_signame(signum)); | |
513 | ||
514 | log->oldtime = now; | |
515 | return sz > 0 ? sz : 0; | |
516 | } | |
596f4202 | 517 | |
3cecd176 KZ |
518 | static uint64_t log_info(struct script_control *ctl, const char *name, const char *msgfmt, ...) |
519 | { | |
520 | struct script_log *log; | |
521 | char msg[BUFSIZ] = {0}; | |
522 | va_list ap; | |
523 | int sz; | |
524 | ||
525 | assert(ctl); | |
526 | ||
527 | log = ctl->infolog; | |
528 | if (!log) | |
529 | return 0; | |
530 | ||
531 | assert(log->format == SCRIPT_FMT_TIMING_MULTI); | |
532 | DBG(IO, ul_debug(" writing info to multi-stream log")); | |
533 | ||
534 | if (msgfmt) { | |
535 | int rc; | |
536 | va_start(ap, msgfmt); | |
537 | rc = vsnprintf(msg, sizeof(msg), msgfmt, ap); | |
538 | va_end(ap); | |
539 | if (rc < 0) | |
540 | *msg = '\0';; | |
541 | } | |
542 | ||
543 | if (*msg) | |
fd42a45b | 544 | sz = fprintf(log->fp, "H 0 %s %s\n", name, msg); |
3cecd176 KZ |
545 | else |
546 | sz = fprintf(log->fp, "H 0 %s\n", name); | |
547 | ||
548 | return sz > 0 ? sz : 0; | |
549 | } | |
550 | ||
596f4202 | 551 | static void die_if_link(struct script_control *ctl, const char *filename) |
93af8d8b SK |
552 | { |
553 | struct stat s; | |
6dbe3af9 | 554 | |
3f19b85f | 555 | if (ctl->force) |
93af8d8b | 556 | return; |
596f4202 | 557 | if (lstat(filename, &s) == 0 && (S_ISLNK(s.st_mode) || s.st_nlink > 1)) |
93af8d8b SK |
558 | errx(EXIT_FAILURE, |
559 | _("output file `%s' is a link\n" | |
560 | "Use --force if you really want to use it.\n" | |
596f4202 | 561 | "Program not started."), filename); |
93af8d8b | 562 | } |
fd4c1f63 | 563 | |
8198052c SK |
564 | static void restore_tty(struct script_control *ctl, int mode) |
565 | { | |
566 | struct termios rtt; | |
567 | ||
568 | if (!ctl->isterm) | |
569 | return; | |
570 | ||
571 | rtt = ctl->attrs; | |
572 | tcsetattr(STDIN_FILENO, mode, &rtt); | |
573 | } | |
574 | ||
7fb65db1 KZ |
575 | static void enable_rawmode_tty(struct script_control *ctl) |
576 | { | |
577 | struct termios rtt; | |
578 | ||
579 | if (!ctl->isterm) | |
580 | return; | |
581 | ||
582 | rtt = ctl->attrs; | |
583 | cfmakeraw(&rtt); | |
584 | rtt.c_lflag &= ~ECHO; | |
585 | tcsetattr(STDIN_FILENO, TCSANOW, &rtt); | |
586 | } | |
587 | ||
596f4202 | 588 | static void __attribute__((__noreturn__)) done_log(struct script_control *ctl, const char *msg) |
93af8d8b | 589 | { |
596f4202 KZ |
590 | int status; |
591 | size_t i; | |
6343ee8c | 592 | |
a2b4dec5 KZ |
593 | DBG(MISC, ul_debug("done!")); |
594 | ||
8198052c SK |
595 | restore_tty(ctl, TCSADRAIN); |
596 | ||
6343ee8c | 597 | if (WIFSIGNALED(ctl->childstatus)) |
596f4202 | 598 | status = WTERMSIG(ctl->childstatus) + 0x80; |
6343ee8c | 599 | else |
596f4202 KZ |
600 | status = WEXITSTATUS(ctl->childstatus); |
601 | ||
602 | ||
603 | DBG(MISC, ul_debug(" status=%d", status)); | |
604 | ||
605 | /* close all output logs */ | |
606 | for (i = 0; i < ctl->out.nlogs; i++) | |
9f822648 | 607 | log_close(ctl, ctl->out.logs[i], msg, status); |
596f4202 KZ |
608 | |
609 | /* close all input logs */ | |
610 | for (i = 0; i < ctl->in.nlogs; i++) | |
9f822648 | 611 | log_close(ctl, ctl->in.logs[i], msg, status); |
93df6a58 | 612 | |
d805688a KZ |
613 | if (!ctl->quiet) |
614 | printf(_("Script done.\n")); | |
615 | ||
f0bc3fa0 | 616 | #ifdef HAVE_LIBUTEMPTER |
6ddf53a5 SK |
617 | if (ctl->master >= 0) |
618 | utempter_remove_record(ctl->master); | |
f0bc3fa0 | 619 | #endif |
6ddf53a5 | 620 | kill(ctl->child, SIGTERM); /* make sure we don't create orphans */ |
596f4202 | 621 | exit(ctl->rc_wanted ? status : EXIT_SUCCESS); |
6343ee8c KZ |
622 | } |
623 | ||
624 | static void __attribute__((__noreturn__)) done(struct script_control *ctl) | |
625 | { | |
626 | done_log(ctl, NULL); | |
93af8d8b | 627 | } |
5c36a0eb | 628 | |
2ddadb5e | 629 | static void __attribute__((__noreturn__)) fail(struct script_control *ctl) |
edc7e420 | 630 | { |
a2b4dec5 | 631 | DBG(MISC, ul_debug("fail!")); |
93af8d8b | 632 | kill(0, SIGTERM); |
edc7e420 | 633 | done(ctl); |
6dbe3af9 KZ |
634 | } |
635 | ||
5860c45e | 636 | static void wait_for_child(struct script_control *ctl, int wait) |
93af8d8b SK |
637 | { |
638 | int status; | |
639 | pid_t pid; | |
93af8d8b SK |
640 | int options = wait ? 0 : WNOHANG; |
641 | ||
a2b4dec5 KZ |
642 | DBG(MISC, ul_debug("waiting for child")); |
643 | ||
b09feab9 | 644 | while ((pid = wait3(&status, options, NULL)) > 0) |
89a859d4 | 645 | if (pid == ctl->child) |
edc7e420 | 646 | ctl->childstatus = status; |
93af8d8b SK |
647 | } |
648 | ||
596f4202 | 649 | /* data from master to stdout */ |
cf470183 | 650 | static void write_output(struct script_control *ctl, char *obuf, |
04639805 | 651 | ssize_t bytes) |
93af8d8b | 652 | { |
a2b4dec5 KZ |
653 | DBG(IO, ul_debug(" writing to output")); |
654 | ||
cf470183 | 655 | if (write_all(STDOUT_FILENO, obuf, bytes)) { |
54c6611d | 656 | DBG(IO, ul_debug(" writing output *failed*")); |
cf470183 SK |
657 | warn(_("write failed")); |
658 | fail(ctl); | |
659 | } | |
54c6611d KZ |
660 | } |
661 | ||
b2bff066 KZ |
662 | static int write_to_shell(struct script_control *ctl, |
663 | char *buf, size_t bufsz) | |
54c6611d | 664 | { |
b2bff066 | 665 | return write_all(ctl->master, buf, bufsz); |
54c6611d KZ |
666 | } |
667 | ||
668 | /* | |
23e35eca RM |
669 | * The script(1) is usually faster than shell, so it's a good idea to wait until |
670 | * the previous message has been already read by shell from slave before we | |
671 | * write to master. This is necessary especially for EOF situation when we can | |
54c6611d KZ |
672 | * send EOF to master before shell is fully initialized, to workaround this |
673 | * problem we wait until slave is empty. For example: | |
674 | * | |
675 | * echo "date" | script | |
b2bff066 KZ |
676 | * |
677 | * Unfortunately, the child (usually shell) can ignore stdin at all, so we | |
678 | * don't wait forever to avoid dead locks... | |
679 | * | |
680 | * Note that script is primarily designed for interactive sessions as it | |
681 | * maintains master+slave tty stuff within the session. Use pipe to write to | |
682 | * script(1) and assume non-interactive (tee-like) behavior is NOT well | |
683 | * supported. | |
54c6611d | 684 | */ |
54c6611d KZ |
685 | static void write_eof_to_shell(struct script_control *ctl) |
686 | { | |
b2bff066 KZ |
687 | unsigned int tries = 0; |
688 | struct pollfd fds[] = { | |
689 | { .fd = ctl->slave, .events = POLLIN } | |
690 | }; | |
54c6611d KZ |
691 | char c = DEF_EOF; |
692 | ||
b2bff066 KZ |
693 | DBG(IO, ul_debug(" waiting for empty slave")); |
694 | while (poll(fds, 1, 10) == 1 && tries < 8) { | |
695 | DBG(IO, ul_debug(" slave is not empty")); | |
696 | xusleep(250000); | |
697 | tries++; | |
698 | } | |
699 | if (tries < 8) | |
700 | DBG(IO, ul_debug(" slave is empty now")); | |
701 | ||
54c6611d KZ |
702 | DBG(IO, ul_debug(" sending EOF to master")); |
703 | write_to_shell(ctl, &c, sizeof(char)); | |
364cda48 KZ |
704 | } |
705 | ||
54c6611d | 706 | static void handle_io(struct script_control *ctl, int fd, int *eof) |
93af8d8b | 707 | { |
6ddf53a5 | 708 | char buf[BUFSIZ]; |
a8896ad5 | 709 | ssize_t bytes; |
a2b4dec5 | 710 | DBG(IO, ul_debug("%d FD active", fd)); |
54c6611d | 711 | *eof = 0; |
a2b4dec5 | 712 | |
da26af4e | 713 | /* read from active FD */ |
a8896ad5 SK |
714 | bytes = read(fd, buf, sizeof(buf)); |
715 | if (bytes < 0) { | |
54c6611d | 716 | if (errno == EAGAIN || errno == EINTR) |
a8896ad5 SK |
717 | return; |
718 | fail(ctl); | |
719 | } | |
da26af4e | 720 | |
54c6611d | 721 | if (bytes == 0) { |
7dbcd80e | 722 | *eof = 1; |
54c6611d KZ |
723 | return; |
724 | } | |
725 | ||
da26af4e KZ |
726 | /* from stdin (user) to command */ |
727 | if (fd == STDIN_FILENO) { | |
2e9418b7 | 728 | DBG(IO, ul_debug(" stdin --> master %zd bytes", bytes)); |
a2b4dec5 | 729 | |
54c6611d | 730 | if (write_to_shell(ctl, buf, bytes)) { |
a8896ad5 SK |
731 | warn(_("write failed")); |
732 | fail(ctl); | |
733 | } | |
734 | /* without sync write_output() will write both input & | |
735 | * shell output that looks like double echoing */ | |
736 | fdatasync(ctl->master); | |
596f4202 | 737 | ctl->outsz += log_stream_activity(ctl, &ctl->in, buf, (size_t) bytes); |
da26af4e | 738 | |
aefe9893 | 739 | /* from command (master) to stdout and log */ |
a2b4dec5 | 740 | } else if (fd == ctl->master) { |
2e9418b7 | 741 | DBG(IO, ul_debug(" master --> stdout %zd bytes", bytes)); |
04639805 | 742 | write_output(ctl, buf, bytes); |
596f4202 KZ |
743 | ctl->outsz += log_stream_activity(ctl, &ctl->out, buf, (size_t) bytes); |
744 | } | |
aefe9893 | 745 | |
596f4202 KZ |
746 | /* check output limit */ |
747 | if (ctl->maxsz != 0 && ctl->outsz >= ctl->maxsz) { | |
748 | if (!ctl->quiet) | |
749 | printf(_("Script terminated, max output files size %"PRIu64" exceeded.\n"), ctl->maxsz); | |
750 | DBG(IO, ul_debug("output size %"PRIu64", exceeded limit %"PRIu64, ctl->outsz, ctl->maxsz)); | |
751 | done_log(ctl, _("max output size exceeded")); | |
a2b4dec5 | 752 | } |
a8896ad5 SK |
753 | } |
754 | ||
755 | static void handle_signal(struct script_control *ctl, int fd) | |
756 | { | |
757 | struct signalfd_siginfo info; | |
758 | ssize_t bytes; | |
759 | ||
a2b4dec5 KZ |
760 | DBG(SIGNAL, ul_debug("signal FD %d active", fd)); |
761 | ||
a8896ad5 | 762 | bytes = read(fd, &info, sizeof(info)); |
d35ffe80 | 763 | if (bytes != sizeof(info)) { |
7dbcd80e | 764 | if (bytes < 0 && (errno == EAGAIN || errno == EINTR)) |
d35ffe80 KZ |
765 | return; |
766 | fail(ctl); | |
767 | } | |
da26af4e | 768 | |
a8896ad5 SK |
769 | switch (info.ssi_signo) { |
770 | case SIGCHLD: | |
27afe501 KZ |
771 | DBG(SIGNAL, ul_debug(" get signal SIGCHLD [ssi_code=%d, ssi_status=%d]", |
772 | info.ssi_code, info.ssi_status)); | |
773 | if (info.ssi_code == CLD_EXITED | |
774 | || info.ssi_code == CLD_KILLED | |
775 | || info.ssi_code == CLD_DUMPED) { | |
2e7a9227 KZ |
776 | wait_for_child(ctl, 0); |
777 | ctl->poll_timeout = 10; | |
27afe501 KZ |
778 | |
779 | /* In case of ssi_code is CLD_TRAPPED, CLD_STOPPED, or CLD_CONTINUED */ | |
2e7a9227 KZ |
780 | } else if (info.ssi_status == SIGSTOP && ctl->child) { |
781 | DBG(SIGNAL, ul_debug(" child stop by SIGSTOP -- stop parent too")); | |
782 | kill(getpid(), SIGSTOP); | |
783 | DBG(SIGNAL, ul_debug(" resume")); | |
784 | kill(ctl->child, SIGCONT); | |
785 | } | |
a8896ad5 SK |
786 | return; |
787 | case SIGWINCH: | |
b2bff066 | 788 | DBG(SIGNAL, ul_debug(" get signal SIGWINCH")); |
a8896ad5 SK |
789 | if (ctl->isterm) { |
790 | ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&ctl->win); | |
791 | ioctl(ctl->slave, TIOCSWINSZ, (char *)&ctl->win); | |
fbed5507 | 792 | log_signal(ctl, info.ssi_signo, |
3cecd176 | 793 | "ROWS=%d COLS=%d", |
fbed5507 KZ |
794 | ctl->win.ws_row, |
795 | ctl->win.ws_col); | |
a8896ad5 SK |
796 | } |
797 | break; | |
5860c45e KZ |
798 | case SIGTERM: |
799 | /* fallthrough */ | |
800 | case SIGINT: | |
801 | /* fallthrough */ | |
802 | case SIGQUIT: | |
fbed5507 | 803 | log_signal(ctl, info.ssi_signo, NULL); |
b2bff066 | 804 | DBG(SIGNAL, ul_debug(" get signal SIG{TERM,INT,QUIT}")); |
5860c45e KZ |
805 | fprintf(stderr, _("\nSession terminated.\n")); |
806 | /* Child termination is going to generate SIGCHILD (see above) */ | |
807 | kill(ctl->child, SIGTERM); | |
808 | return; | |
a8896ad5 SK |
809 | default: |
810 | abort(); | |
811 | } | |
27afe501 | 812 | DBG(SIGNAL, ul_debug("signal handle on FD %d done", fd)); |
a8896ad5 SK |
813 | } |
814 | ||
815 | static void do_io(struct script_control *ctl) | |
816 | { | |
d88b739f | 817 | int ret, eof = 0; |
da26af4e | 818 | enum { |
54c6611d | 819 | POLLFD_SIGNAL = 0, |
da26af4e | 820 | POLLFD_MASTER, |
d88b739f | 821 | POLLFD_STDIN |
54c6611d | 822 | |
da26af4e KZ |
823 | }; |
824 | struct pollfd pfd[] = { | |
0664d41d SK |
825 | [POLLFD_SIGNAL] = { .fd = ctl->sigfd, .events = POLLIN | POLLERR | POLLHUP }, |
826 | [POLLFD_MASTER] = { .fd = ctl->master, .events = POLLIN | POLLERR | POLLHUP }, | |
827 | [POLLFD_STDIN] = { .fd = STDIN_FILENO, .events = POLLIN | POLLERR | POLLHUP } | |
da26af4e KZ |
828 | }; |
829 | ||
6ddf53a5 | 830 | while (!ctl->die) { |
da26af4e | 831 | size_t i; |
7dbcd80e | 832 | int errsv; |
da26af4e | 833 | |
a2b4dec5 KZ |
834 | DBG(POLL, ul_debug("calling poll()")); |
835 | ||
cf470183 | 836 | /* wait for input or signal */ |
d88b739f | 837 | ret = poll(pfd, ARRAY_SIZE(pfd), ctl->poll_timeout); |
7dbcd80e | 838 | errsv = errno; |
a2b4dec5 KZ |
839 | DBG(POLL, ul_debug("poll() rc=%d", ret)); |
840 | ||
cf470183 | 841 | if (ret < 0) { |
7dbcd80e | 842 | if (errsv == EAGAIN) |
cf470183 SK |
843 | continue; |
844 | warn(_("poll failed")); | |
edc7e420 | 845 | fail(ctl); |
36c1d79b | 846 | } |
a2b4dec5 KZ |
847 | if (ret == 0) { |
848 | DBG(POLL, ul_debug("setting die=1")); | |
89a859d4 | 849 | ctl->die = 1; |
5860c45e | 850 | break; |
a2b4dec5 | 851 | } |
da26af4e | 852 | |
d88b739f | 853 | for (i = 0; i < ARRAY_SIZE(pfd); i++) { |
cf470183 SK |
854 | if (pfd[i].revents == 0) |
855 | continue; | |
a2b4dec5 | 856 | |
2e9418b7 | 857 | DBG(POLL, ul_debug(" active pfd[%s].fd=%d %s %s %s", |
54c6611d | 858 | i == POLLFD_STDIN ? "stdin" : |
2e9418b7 KZ |
859 | i == POLLFD_MASTER ? "master" : |
860 | i == POLLFD_SIGNAL ? "signal" : "???", | |
861 | pfd[i].fd, | |
54c6611d | 862 | pfd[i].revents & POLLIN ? "POLLIN" : "", |
1200cfbf KZ |
863 | pfd[i].revents & POLLHUP ? "POLLHUP" : "", |
864 | pfd[i].revents & POLLERR ? "POLLERR" : "")); | |
da26af4e KZ |
865 | switch (i) { |
866 | case POLLFD_STDIN: | |
867 | case POLLFD_MASTER: | |
54c6611d KZ |
868 | /* data */ |
869 | if (pfd[i].revents & POLLIN) | |
870 | handle_io(ctl, pfd[i].fd, &eof); | |
871 | /* EOF maybe detected by two ways: | |
872 | * A) poll() return POLLHUP event after close() | |
7dbcd80e | 873 | * B) read() returns 0 (no data) */ |
54c6611d KZ |
874 | if ((pfd[i].revents & POLLHUP) || eof) { |
875 | DBG(POLL, ul_debug(" ignore FD")); | |
876 | pfd[i].fd = -1; | |
54c6611d | 877 | if (i == POLLFD_STDIN) { |
54c6611d | 878 | write_eof_to_shell(ctl); |
b2bff066 | 879 | DBG(POLL, ul_debug(" ignore STDIN")); |
54c6611d KZ |
880 | } |
881 | } | |
cf470183 | 882 | continue; |
da26af4e | 883 | case POLLFD_SIGNAL: |
a8896ad5 | 884 | handle_signal(ctl, pfd[i].fd); |
da26af4e | 885 | break; |
cf470183 | 886 | } |
968e632c | 887 | } |
cf470183 | 888 | } |
a2b4dec5 KZ |
889 | |
890 | DBG(POLL, ul_debug("poll() done")); | |
891 | ||
6ddf53a5 | 892 | if (!ctl->die) |
5860c45e | 893 | wait_for_child(ctl, 1); |
6f3c9c34 | 894 | |
6ddf53a5 | 895 | done(ctl); |
6dbe3af9 KZ |
896 | } |
897 | ||
edc7e420 | 898 | static void getslave(struct script_control *ctl) |
93af8d8b SK |
899 | { |
900 | #ifndef HAVE_LIBUTIL | |
edc7e420 | 901 | ctl->line[strlen("/dev/")] = 't'; |
760e5e68 | 902 | ctl->slave = open(ctl->line, O_RDWR | O_CLOEXEC); |
edc7e420 SK |
903 | if (ctl->slave < 0) { |
904 | warn(_("cannot open %s"), ctl->line); | |
905 | fail(ctl); | |
93af8d8b | 906 | } |
edc7e420 | 907 | if (ctl->isterm) { |
3f19b85f | 908 | tcsetattr(ctl->slave, TCSANOW, &ctl->attrs); |
edc7e420 | 909 | ioctl(ctl->slave, TIOCSWINSZ, (char *)&ctl->win); |
93af8d8b SK |
910 | } |
911 | #endif | |
912 | setsid(); | |
edc7e420 | 913 | ioctl(ctl->slave, TIOCSCTTY, 0); |
93af8d8b SK |
914 | } |
915 | ||
54c6611d | 916 | /* don't use DBG() stuff here otherwise it will be in the typescript file */ |
d35ffe80 | 917 | static void __attribute__((__noreturn__)) do_shell(struct script_control *ctl) |
93af8d8b | 918 | { |
df1dddf9 KZ |
919 | char *shname; |
920 | ||
edc7e420 | 921 | getslave(ctl); |
4368e3e6 KZ |
922 | |
923 | /* close things irrelevant for this process */ | |
edc7e420 | 924 | close(ctl->master); |
d35ffe80 | 925 | close(ctl->sigfd); |
4368e3e6 | 926 | |
edc7e420 SK |
927 | dup2(ctl->slave, STDIN_FILENO); |
928 | dup2(ctl->slave, STDOUT_FILENO); | |
929 | dup2(ctl->slave, STDERR_FILENO); | |
930 | close(ctl->slave); | |
df1dddf9 | 931 | |
edc7e420 | 932 | ctl->master = -1; |
f0bc3fa0 | 933 | |
edc7e420 | 934 | shname = strrchr(ctl->shell, '/'); |
df1dddf9 KZ |
935 | if (shname) |
936 | shname++; | |
937 | else | |
edc7e420 | 938 | shname = ctl->shell; |
df1dddf9 | 939 | |
d35ffe80 KZ |
940 | sigprocmask(SIG_SETMASK, &ctl->sigorg, NULL); |
941 | ||
a17f3264 KZ |
942 | /* |
943 | * When invoked from within /etc/csh.login, script spawns a csh shell | |
944 | * that spawns programs that cannot be killed with a SIGTERM. This is | |
ee312c65 | 945 | * because csh has a documented behavior wherein it disables all |
a17f3264 KZ |
946 | * signals when processing the /etc/csh.* files. |
947 | * | |
948 | * Let's restore the default behavior. | |
949 | */ | |
950 | signal(SIGTERM, SIG_DFL); | |
951 | ||
edc7e420 | 952 | if (access(ctl->shell, X_OK) == 0) { |
3f19b85f KZ |
953 | if (ctl->command) |
954 | execl(ctl->shell, shname, "-c", ctl->command, NULL); | |
b4ff2f54 | 955 | else |
edc7e420 | 956 | execl(ctl->shell, shname, "-i", NULL); |
b4ff2f54 | 957 | } else { |
3f19b85f KZ |
958 | if (ctl->command) |
959 | execlp(shname, "-c", ctl->command, NULL); | |
b4ff2f54 SK |
960 | else |
961 | execlp(shname, "-i", NULL); | |
962 | } | |
edc7e420 SK |
963 | warn(_("failed to execute %s"), ctl->shell); |
964 | fail(ctl); | |
6dbe3af9 KZ |
965 | } |
966 | ||
6dbe3af9 | 967 | |
edc7e420 | 968 | static void getmaster(struct script_control *ctl) |
93af8d8b | 969 | { |
e11a5e63 | 970 | #if defined(HAVE_LIBUTIL) && defined(HAVE_PTY_H) |
3822032d KZ |
971 | int rc; |
972 | ||
edc7e420 | 973 | ctl->isterm = isatty(STDIN_FILENO); |
3822032d | 974 | |
edc7e420 | 975 | if (ctl->isterm) { |
3f19b85f | 976 | if (tcgetattr(STDIN_FILENO, &ctl->attrs) != 0) |
3822032d | 977 | err(EXIT_FAILURE, _("failed to get terminal attributes")); |
edc7e420 | 978 | ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&ctl->win); |
3f19b85f | 979 | rc = openpty(&ctl->master, &ctl->slave, NULL, &ctl->attrs, &ctl->win); |
3822032d | 980 | } else |
edc7e420 | 981 | rc = openpty(&ctl->master, &ctl->slave, NULL, NULL, NULL); |
3822032d KZ |
982 | |
983 | if (rc < 0) { | |
360d5005 | 984 | warn(_("openpty failed")); |
edc7e420 | 985 | fail(ctl); |
5c36a0eb KZ |
986 | } |
987 | #else | |
6dbe3af9 | 988 | char *pty, *bank, *cp; |
6dbe3af9 | 989 | |
edc7e420 | 990 | ctl->isterm = isatty(STDIN_FILENO); |
3822032d | 991 | |
edc7e420 | 992 | pty = &ctl->line[strlen("/dev/ptyp")]; |
6dbe3af9 | 993 | for (bank = "pqrs"; *bank; bank++) { |
edc7e420 | 994 | ctl->line[strlen("/dev/pty")] = *bank; |
6dbe3af9 | 995 | *pty = '0'; |
a4aeb5bd | 996 | if (access(ctl->line, F_OK) != 0) |
6dbe3af9 KZ |
997 | break; |
998 | for (cp = "0123456789abcdef"; *cp; cp++) { | |
999 | *pty = *cp; | |
760e5e68 | 1000 | ctl->master = open(ctl->line, O_RDWR | O_CLOEXEC); |
edc7e420 SK |
1001 | if (ctl->master >= 0) { |
1002 | char *tp = &ctl->line[strlen("/dev/")]; | |
6dbe3af9 KZ |
1003 | int ok; |
1004 | ||
1005 | /* verify slave side is usable */ | |
1006 | *tp = 't'; | |
edc7e420 | 1007 | ok = access(ctl->line, R_OK | W_OK) == 0; |
6dbe3af9 KZ |
1008 | *tp = 'p'; |
1009 | if (ok) { | |
edc7e420 | 1010 | if (ctl->isterm) { |
3f19b85f | 1011 | tcgetattr(STDIN_FILENO, &ctl->attrs); |
edc7e420 | 1012 | ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&ctl->win); |
3822032d | 1013 | } |
6dbe3af9 KZ |
1014 | return; |
1015 | } | |
edc7e420 SK |
1016 | close(ctl->master); |
1017 | ctl->master = -1; | |
6dbe3af9 KZ |
1018 | } |
1019 | } | |
1020 | } | |
edc7e420 | 1021 | ctl->master = -1; |
360d5005 | 1022 | warn(_("out of pty's")); |
edc7e420 SK |
1023 | fail(ctl); |
1024 | #endif /* not HAVE_LIBUTIL */ | |
54c6611d KZ |
1025 | |
1026 | DBG(IO, ul_debug("master fd: %d", ctl->master)); | |
6dbe3af9 KZ |
1027 | } |
1028 | ||
93af8d8b SK |
1029 | int main(int argc, char **argv) |
1030 | { | |
edc7e420 SK |
1031 | struct script_control ctl = { |
1032 | #if !HAVE_LIBUTIL || !HAVE_PTY_H | |
1033 | .line = "/dev/ptyXX", | |
1034 | #endif | |
1035 | .master = -1, | |
596f4202 KZ |
1036 | .slave = -1, |
1037 | ||
c1c2ee0b KZ |
1038 | .out = { .ident = 'O' }, |
1039 | .in = { .ident = 'I' }, | |
1040 | ||
aefe9893 | 1041 | .poll_timeout = -1 |
edc7e420 | 1042 | }; |
c1c2ee0b | 1043 | int ch, format = 0; |
70062aad | 1044 | const char *outfile = NULL, *infile = NULL; |
d805688a | 1045 | const char *timingfile = NULL; |
93af8d8b SK |
1046 | |
1047 | enum { FORCE_OPTION = CHAR_MAX + 1 }; | |
1048 | ||
1049 | static const struct option longopts[] = { | |
edc7e420 SK |
1050 | {"append", no_argument, NULL, 'a'}, |
1051 | {"command", required_argument, NULL, 'c'}, | |
1052 | {"return", no_argument, NULL, 'e'}, | |
1053 | {"flush", no_argument, NULL, 'f'}, | |
1054 | {"force", no_argument, NULL, FORCE_OPTION,}, | |
70062aad | 1055 | {"log-in", required_argument, NULL, 'I'}, |
ddbdb792 | 1056 | {"log-out", required_argument, NULL, 'O'}, |
c1c2ee0b | 1057 | {"log-io", required_argument, NULL, 'B'}, |
fc58044f | 1058 | {"log-timing", required_argument, NULL, 'T'}, |
daee4d95 | 1059 | {"logging-format", required_argument, NULL, 'm'}, |
aefe9893 | 1060 | {"output-limit", required_argument, NULL, 'o'}, |
edc7e420 SK |
1061 | {"quiet", no_argument, NULL, 'q'}, |
1062 | {"timing", optional_argument, NULL, 't'}, | |
1063 | {"version", no_argument, NULL, 'V'}, | |
1064 | {"help", no_argument, NULL, 'h'}, | |
1065 | {NULL, 0, NULL, 0} | |
93af8d8b | 1066 | }; |
fc58044f KZ |
1067 | static const ul_excl_t excl[] = { /* rows and cols in ASCII order */ |
1068 | { 'T', 't' }, | |
1069 | { 0 } | |
1070 | }; | |
1071 | int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; | |
93af8d8b | 1072 | setlocale(LC_ALL, ""); |
b09feab9 SK |
1073 | /* |
1074 | * script -t prints time delays as floating point numbers. The example | |
1075 | * program (scriptreplay) that we provide to handle this timing output | |
1076 | * is a perl script, and does not handle numbers in locale format (not | |
1077 | * even when "use locale;" is added). So, since these numbers are not | |
1078 | * for human consumption, it seems easiest to set LC_NUMERIC here. | |
1079 | */ | |
1080 | setlocale(LC_NUMERIC, "C"); | |
93af8d8b SK |
1081 | bindtextdomain(PACKAGE, LOCALEDIR); |
1082 | textdomain(PACKAGE); | |
2c308875 | 1083 | close_stdout_atexit(); |
93af8d8b | 1084 | |
a2b4dec5 KZ |
1085 | script_init_debug(); |
1086 | ||
daee4d95 | 1087 | while ((ch = getopt_long(argc, argv, "aB:c:efI:O:o:qm:T:t::Vh", longopts, NULL)) != -1) { |
fc58044f KZ |
1088 | |
1089 | err_exclusive_options(ch, longopts, excl, excl_st); | |
1090 | ||
edc7e420 | 1091 | switch (ch) { |
93af8d8b | 1092 | case 'a': |
3f19b85f | 1093 | ctl.append = 1; |
93af8d8b SK |
1094 | break; |
1095 | case 'c': | |
3f19b85f | 1096 | ctl.command = optarg; |
93af8d8b SK |
1097 | break; |
1098 | case 'e': | |
7e5796c9 | 1099 | ctl.rc_wanted = 1; |
93af8d8b SK |
1100 | break; |
1101 | case 'f': | |
3f19b85f | 1102 | ctl.flush = 1; |
93af8d8b SK |
1103 | break; |
1104 | case FORCE_OPTION: | |
3f19b85f | 1105 | ctl.force = 1; |
93af8d8b | 1106 | break; |
c1c2ee0b KZ |
1107 | case 'B': |
1108 | log_associate(&ctl, &ctl.in, optarg, SCRIPT_FMT_RAW); | |
1109 | log_associate(&ctl, &ctl.out, optarg, SCRIPT_FMT_RAW); | |
1110 | infile = outfile = optarg; | |
1111 | break; | |
70062aad KZ |
1112 | case 'I': |
1113 | log_associate(&ctl, &ctl.in, optarg, SCRIPT_FMT_RAW); | |
1114 | infile = optarg; | |
1115 | break; | |
ddbdb792 | 1116 | case 'O': |
70062aad KZ |
1117 | log_associate(&ctl, &ctl.out, optarg, SCRIPT_FMT_RAW); |
1118 | outfile = optarg; | |
ddbdb792 | 1119 | break; |
aefe9893 FM |
1120 | case 'o': |
1121 | ctl.maxsz = strtosize_or_err(optarg, _("failed to parse output limit size")); | |
1122 | break; | |
93af8d8b | 1123 | case 'q': |
3f19b85f | 1124 | ctl.quiet = 1; |
93af8d8b | 1125 | break; |
daee4d95 KZ |
1126 | case 'm': |
1127 | if (strcasecmp(optarg, "classic") == 0) | |
1128 | format = SCRIPT_FMT_TIMING_SIMPLE; | |
1129 | else if (strcasecmp(optarg, "advanced") == 0) | |
1130 | format = SCRIPT_FMT_TIMING_MULTI; | |
1131 | else | |
1132 | errx(EXIT_FAILURE, _("unssuported logging format: '%s'"), optarg); | |
1133 | break; | |
93af8d8b | 1134 | case 't': |
4f7521d6 KZ |
1135 | if (optarg && *optarg == '=') |
1136 | optarg++; | |
596f4202 KZ |
1137 | log_associate(&ctl, &ctl.out, |
1138 | optarg ? optarg : "/dev/stderr", | |
1139 | SCRIPT_FMT_TIMING_SIMPLE); | |
d805688a KZ |
1140 | /* used for message only */ |
1141 | timingfile = optarg ? optarg : "stderr"; | |
93af8d8b | 1142 | break; |
fc58044f | 1143 | case 'T' : |
fc58044f KZ |
1144 | timingfile = optarg; |
1145 | break; | |
93af8d8b | 1146 | case 'V': |
2c308875 | 1147 | print_version(EXIT_SUCCESS); |
93af8d8b | 1148 | case 'h': |
86be6a32 | 1149 | usage(); |
93af8d8b | 1150 | default: |
677ec86c | 1151 | errtryhelp(EXIT_FAILURE); |
93af8d8b | 1152 | } |
fc58044f | 1153 | } |
93af8d8b SK |
1154 | argc -= optind; |
1155 | argv += optind; | |
1156 | ||
70062aad KZ |
1157 | /* default if no --log-* specified */ |
1158 | if (!outfile && !infile) { | |
ddbdb792 | 1159 | if (argc > 0) |
70062aad KZ |
1160 | outfile = argv[0]; |
1161 | else { | |
ddbdb792 | 1162 | die_if_link(&ctl, DEFAULT_TYPESCRIPT_FILENAME); |
70062aad KZ |
1163 | outfile = DEFAULT_TYPESCRIPT_FILENAME; |
1164 | } | |
596f4202 | 1165 | |
70062aad KZ |
1166 | /* associate stdout with typescript file */ |
1167 | log_associate(&ctl, &ctl.out, outfile, SCRIPT_FMT_RAW); | |
1168 | } | |
93af8d8b | 1169 | |
c1c2ee0b | 1170 | if (timingfile) { |
d628e9cf KZ |
1171 | /* the old SCRIPT_FMT_TIMING_SIMPLE should be used when |
1172 | * recoding output only (just for backward compatibility), | |
1173 | * otherwise switch to new format. */ | |
c1c2ee0b | 1174 | if (!format) |
d628e9cf | 1175 | format = infile || (outfile && infile) ? |
c1c2ee0b KZ |
1176 | SCRIPT_FMT_TIMING_MULTI : |
1177 | SCRIPT_FMT_TIMING_SIMPLE; | |
daee4d95 KZ |
1178 | |
1179 | else if (format == SCRIPT_FMT_TIMING_SIMPLE && outfile && infile) | |
1180 | errx(EXIT_FAILURE, _("log multiple streams is mutually " | |
1181 | "exclusive with 'classic' format")); | |
c1c2ee0b KZ |
1182 | if (outfile) |
1183 | log_associate(&ctl, &ctl.out, timingfile, format); | |
1184 | if (infile) | |
1185 | log_associate(&ctl, &ctl.in, timingfile, format); | |
1186 | } | |
1187 | ||
edc7e420 SK |
1188 | ctl.shell = getenv("SHELL"); |
1189 | if (ctl.shell == NULL) | |
1190 | ctl.shell = _PATH_BSHELL; | |
93af8d8b | 1191 | |
edc7e420 | 1192 | getmaster(&ctl); |
3cecd176 | 1193 | |
d805688a | 1194 | if (!ctl.quiet) { |
70062aad KZ |
1195 | printf(_("Script started")); |
1196 | if (outfile) | |
1197 | printf(_(", output log file is '%s'"), outfile); | |
1198 | if (infile) | |
1199 | printf(_(", input log file is '%s'"), infile); | |
1200 | if (timingfile) | |
1201 | printf(_(", timing file is '%s'"), timingfile); | |
1202 | printf(_(".\n")); | |
d805688a | 1203 | } |
7fb65db1 | 1204 | enable_rawmode_tty(&ctl); |
93af8d8b SK |
1205 | |
1206 | #ifdef HAVE_LIBUTEMPTER | |
edc7e420 | 1207 | utempter_add_record(ctl.master, NULL); |
5c36a0eb | 1208 | #endif |
cc1a88fb | 1209 | /* setup signal handler */ |
d35ffe80 KZ |
1210 | sigemptyset(&ctl.sigset); |
1211 | sigaddset(&ctl.sigset, SIGCHLD); | |
1212 | sigaddset(&ctl.sigset, SIGWINCH); | |
5860c45e KZ |
1213 | sigaddset(&ctl.sigset, SIGTERM); |
1214 | sigaddset(&ctl.sigset, SIGINT); | |
1215 | sigaddset(&ctl.sigset, SIGQUIT); | |
d35ffe80 KZ |
1216 | |
1217 | /* block signals used for signalfd() to prevent the signals being | |
1218 | * handled according to their default dispositions */ | |
1219 | sigprocmask(SIG_BLOCK, &ctl.sigset, &ctl.sigorg); | |
a2b4dec5 | 1220 | |
760e5e68 | 1221 | if ((ctl.sigfd = signalfd(-1, &ctl.sigset, SFD_CLOEXEC)) < 0) |
cc1a88fb | 1222 | err(EXIT_FAILURE, _("cannot set signal handler")); |
93af8d8b | 1223 | |
a2b4dec5 KZ |
1224 | DBG(SIGNAL, ul_debug("signal fd=%d", ctl.sigfd)); |
1225 | ||
93af8d8b | 1226 | fflush(stdout); |
edc7e420 | 1227 | ctl.child = fork(); |
93af8d8b | 1228 | |
d35ffe80 KZ |
1229 | switch (ctl.child) { |
1230 | case -1: /* error */ | |
93af8d8b | 1231 | warn(_("fork failed")); |
edc7e420 | 1232 | fail(&ctl); |
d35ffe80 KZ |
1233 | break; |
1234 | case 0: /* child */ | |
1235 | do_shell(&ctl); | |
1236 | break; | |
1237 | default: /* parent */ | |
fd42a45b KZ |
1238 | start_logging(&ctl); |
1239 | ||
1240 | if (timingfile && format == SCRIPT_FMT_TIMING_MULTI) { | |
1241 | if (ctl.isterm) { | |
1242 | init_terminal_info(&ctl); | |
1243 | log_info(&ctl, "TERM", ctl.ttytype); | |
1244 | log_info(&ctl, "TTY", ctl.ttyname); | |
1245 | log_info(&ctl, "COLUMNS", "%d", ctl.ttycols); | |
1246 | log_info(&ctl, "LINES", "%d", ctl.ttylines); | |
1247 | } | |
1248 | log_info(&ctl, "SHELL", ctl.shell); | |
1249 | log_info(&ctl, "TIMING_LOG", timingfile); | |
1250 | if (outfile) | |
1251 | log_info(&ctl, "OUTPUT_LOG", outfile); | |
1252 | if (infile) | |
1253 | log_info(&ctl, "INPUT_LOG", infile); | |
1254 | } | |
d35ffe80 KZ |
1255 | do_io(&ctl); |
1256 | break; | |
93af8d8b | 1257 | } |
d35ffe80 KZ |
1258 | |
1259 | /* should not happen, all used functions are non-return */ | |
b09feab9 | 1260 | return EXIT_FAILURE; |
6dbe3af9 | 1261 | } |