]>
Commit | Line | Data |
---|---|---|
6dbe3af9 | 1 | /* |
ec10634e KZ |
2 | * Copyright (C) 1980 Regents of the University of California. |
3 | * Copyright (C) 2013-2019 Karel Zak <kzak@redhat.com> | |
4 | * | |
6dbe3af9 KZ |
5 | * All rights reserved. |
6 | * | |
7 | * Redistribution and use in source and binary forms, with or without | |
8 | * modification, are permitted provided that the following conditions | |
9 | * are met: | |
10 | * 1. Redistributions of source code must retain the above copyright | |
11 | * notice, this list of conditions and the following disclaimer. | |
12 | * 2. Redistributions in binary form must reproduce the above copyright | |
13 | * notice, this list of conditions and the following disclaimer in the | |
14 | * documentation and/or other materials provided with the distribution. | |
15 | * 3. All advertising materials mentioning features or use of this software | |
16 | * must display the following acknowledgement: | |
edc7e420 SK |
17 | * This product includes software developed by the University of |
18 | * California, Berkeley and its contributors. | |
6dbe3af9 KZ |
19 | * 4. Neither the name of the University nor the names of its contributors |
20 | * may be used to endorse or promote products derived from this software | |
21 | * without specific prior written permission. | |
22 | * | |
23 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND | |
24 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
25 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
26 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE | |
27 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
28 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |
29 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |
30 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
31 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |
32 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |
33 | * SUCH DAMAGE. | |
34 | */ | |
22853e4a KZ |
35 | #include <stdio.h> |
36 | #include <stdlib.h> | |
37 | #include <paths.h> | |
38 | #include <time.h> | |
6dbe3af9 KZ |
39 | #include <sys/stat.h> |
40 | #include <termios.h> | |
41 | #include <sys/ioctl.h> | |
42 | #include <sys/time.h> | |
172b3c27 | 43 | #include <signal.h> |
1f58c445 | 44 | #include <errno.h> |
2b7ff0d9 ST |
45 | #include <string.h> |
46 | #include <getopt.h> | |
47 | #include <unistd.h> | |
8fb810ff | 48 | #include <fcntl.h> |
8fb810ff SK |
49 | #include <limits.h> |
50 | #include <locale.h> | |
51 | #include <stddef.h> | |
3822032d KZ |
52 | #include <sys/wait.h> |
53 | #include <poll.h> | |
cc1a88fb SK |
54 | #include <sys/signalfd.h> |
55 | #include <assert.h> | |
c2f03da9 | 56 | #include <inttypes.h> |
2b7ff0d9 | 57 | |
cdd2a8c3 | 58 | #include "closestream.h" |
7eda085c | 59 | #include "nls.h" |
91239874 | 60 | #include "c.h" |
3822032d | 61 | #include "ttyutils.h" |
c6fca22e | 62 | #include "all-io.h" |
04639805 | 63 | #include "monotonic.h" |
bdef362d | 64 | #include "timeutils.h" |
aefe9893 | 65 | #include "strutils.h" |
596f4202 | 66 | #include "xalloc.h" |
fc58044f | 67 | #include "optutils.h" |
fbed5507 | 68 | #include "signames.h" |
ec10634e | 69 | #include "pty-session.h" |
a2b4dec5 KZ |
70 | #include "debug.h" |
71 | ||
2ba641e5 | 72 | static UL_DEBUG_DEFINE_MASK(script); |
a2b4dec5 KZ |
73 | UL_DEBUG_DEFINE_MASKNAMES(script) = UL_DEBUG_EMPTY_MASKNAMES; |
74 | ||
75 | #define SCRIPT_DEBUG_INIT (1 << 1) | |
ec10634e KZ |
76 | #define SCRIPT_DEBUG_PTY (1 << 2) |
77 | #define SCRIPT_DEBUG_IO (1 << 3) | |
78 | #define SCRIPT_DEBUG_SIGNAL (1 << 4) | |
a2b4dec5 KZ |
79 | #define SCRIPT_DEBUG_MISC (1 << 5) |
80 | #define SCRIPT_DEBUG_ALL 0xFFFF | |
81 | ||
82 | #define DBG(m, x) __UL_DBG(script, SCRIPT_DEBUG_, m, x) | |
83 | #define ON_DBG(m, x) __UL_DBG_CALL(script, SCRIPT_DEBUG_, m, x) | |
84 | ||
f0bc3fa0 | 85 | #ifdef HAVE_LIBUTEMPTER |
e11a5e63 | 86 | # include <utempter.h> |
f0bc3fa0 KZ |
87 | #endif |
88 | ||
7e5796c9 | 89 | #define DEFAULT_TYPESCRIPT_FILENAME "typescript" |
05644dab | 90 | |
fec06a06 KZ |
91 | /* |
92 | * Script is driven by stream (stdout/stdin) activity. It's possible to | |
93 | * associate arbitrary number of log files with the stream. We have two basic | |
94 | * types of log files: "timing file" (simple or multistream) and "data file" | |
95 | * (raw). | |
96 | * | |
ac407b16 | 97 | * The same log file maybe be shared between both streams. For example |
fec06a06 KZ |
98 | * multi-stream timing file is possible to use for stdin as well as for stdout. |
99 | */ | |
596f4202 KZ |
100 | enum { |
101 | SCRIPT_FMT_RAW = 1, /* raw slave/master data */ | |
daee4d95 KZ |
102 | SCRIPT_FMT_TIMING_SIMPLE, /* (classic) in format "<delta> <offset>" */ |
103 | SCRIPT_FMT_TIMING_MULTI, /* (advanced) multiple streams in format "<type> <delta> <offset|etc> */ | |
596f4202 KZ |
104 | }; |
105 | ||
106 | struct script_log { | |
107 | FILE *fp; /* file pointer (handler) */ | |
108 | int format; /* SCRIPT_FMT_* */ | |
109 | char *filename; /* on command line specified name */ | |
88025c74 | 110 | struct timeval oldtime; /* previous entry log time (SCRIPT_FMT_TIMING_* only) */ |
70c2cd9a | 111 | struct timeval starttime; |
c1c2ee0b KZ |
112 | |
113 | unsigned int initialized : 1; | |
596f4202 KZ |
114 | }; |
115 | ||
116 | struct script_stream { | |
9f822648 | 117 | struct script_log **logs; /* logs where to write data from stream */ |
596f4202 | 118 | size_t nlogs; /* number of logs */ |
c1c2ee0b | 119 | char ident; /* stream identifier */ |
596f4202 KZ |
120 | }; |
121 | ||
edc7e420 | 122 | struct script_control { |
596f4202 KZ |
123 | uint64_t outsz; /* current output files size */ |
124 | uint64_t maxsz; /* maximum output files size */ | |
125 | ||
596f4202 KZ |
126 | struct script_stream out; /* output */ |
127 | struct script_stream in; /* input */ | |
128 | ||
3cecd176 KZ |
129 | struct script_log *siglog; /* log for signal entries */ |
130 | struct script_log *infolog; /* log for info entries */ | |
3cecd176 KZ |
131 | |
132 | const char *ttyname; | |
133 | const char *ttytype; | |
134 | int ttycols; | |
135 | int ttylines; | |
fbed5507 | 136 | |
bdd43357 | 137 | struct ul_pty *pty; /* pseudo-terminal */ |
edc7e420 | 138 | pid_t child; /* child pid */ |
edc7e420 | 139 | int childstatus; /* child process exit value */ |
ec10634e | 140 | |
edc7e420 | 141 | unsigned int |
3f19b85f | 142 | append:1, /* append output */ |
7e5796c9 | 143 | rc_wanted:1, /* return child exit value */ |
3f19b85f KZ |
144 | flush:1, /* flush after each write */ |
145 | quiet:1, /* suppress most output */ | |
3f19b85f | 146 | force:1, /* write output to links */ |
ec10634e | 147 | isterm:1; /* is child process running as terminal */ |
edc7e420 | 148 | }; |
1f58c445 | 149 | |
ec10634e | 150 | static ssize_t log_info(struct script_control *ctl, const char *name, const char *msgfmt, ...); |
596f4202 | 151 | |
a2b4dec5 KZ |
152 | static void script_init_debug(void) |
153 | { | |
a15dca2f | 154 | __UL_INIT_DEBUG_FROM_ENV(script, SCRIPT_DEBUG_, 0, SCRIPT_DEBUG); |
a2b4dec5 KZ |
155 | } |
156 | ||
3cecd176 KZ |
157 | static void init_terminal_info(struct script_control *ctl) |
158 | { | |
159 | if (ctl->ttyname || !ctl->isterm) | |
160 | return; /* already initialized */ | |
161 | ||
162 | get_terminal_dimension(&ctl->ttycols, &ctl->ttylines); | |
163 | get_terminal_name(&ctl->ttyname, NULL, NULL); | |
164 | get_terminal_type(&ctl->ttytype); | |
165 | } | |
166 | ||
6a40c65f SK |
167 | /* |
168 | * For tests we want to be able to control time output | |
169 | */ | |
170 | #ifdef TEST_SCRIPT | |
171 | static inline time_t script_time(time_t *t) | |
172 | { | |
173 | const char *str = getenv("SCRIPT_TEST_SECOND_SINCE_EPOCH"); | |
345208a5 | 174 | int64_t sec; |
6a40c65f | 175 | |
c2f03da9 | 176 | if (!str || sscanf(str, "%"SCNi64, &sec) != 1) |
345208a5 ID |
177 | return time(t); |
178 | if (t) | |
179 | *t = (time_t)sec; | |
180 | return (time_t)sec; | |
6a40c65f SK |
181 | } |
182 | #else /* !TEST_SCRIPT */ | |
183 | # define script_time(x) time(x) | |
184 | #endif | |
185 | ||
86be6a32 | 186 | static void __attribute__((__noreturn__)) usage(void) |
3ff52639 | 187 | { |
86be6a32 | 188 | FILE *out = stdout; |
db433bf7 | 189 | fputs(USAGE_HEADER, out); |
edc7e420 | 190 | fprintf(out, _(" %s [options] [file]\n"), program_invocation_short_name); |
87d6050b | 191 | |
451dbcfa BS |
192 | fputs(USAGE_SEPARATOR, out); |
193 | fputs(_("Make a typescript of a terminal session.\n"), out); | |
194 | ||
db433bf7 | 195 | fputs(USAGE_OPTIONS, out); |
70062aad | 196 | fputs(_(" -I, --log-in <file> log stdin to file\n"), out); |
ddbdb792 | 197 | fputs(_(" -O, --log-out <file> log stdout to file (default)\n"), out); |
c1c2ee0b | 198 | fputs(_(" -B, --log-io <file> log stdin and stdout to file\n"), out); |
65dc24ab KZ |
199 | fputs(USAGE_SEPARATOR, out); |
200 | ||
fc58044f | 201 | fputs(_(" -T, --log-timing <file> log timing information to file\n"), out); |
65dc24ab | 202 | fputs(_(" -t[<file>], --timing[=<file>] deprecated alias to -T (default file is stderr)\n"), out); |
daee4d95 | 203 | fputs(_(" -m, --logging-format <name> force to 'classic' or 'advanced' format\n"), out); |
65dc24ab KZ |
204 | fputs(USAGE_SEPARATOR, out); |
205 | ||
206 | fputs(_(" -a, --append append to the log file\n"), out); | |
c64963f8 KZ |
207 | fputs(_(" -c, --command <command> run command rather than interactive shell\n"), out); |
208 | fputs(_(" -e, --return return exit code of the child process\n"), out); | |
209 | fputs(_(" -f, --flush run flush after each write\n"), out); | |
210 | fputs(_(" --force use output file even when it is a link\n"), out); | |
1eee1acb | 211 | fputs(_(" -E, --echo <when> echo input (auto, always or never)\n"), out); |
c64963f8 KZ |
212 | fputs(_(" -o, --output-limit <size> terminate if output files exceed size\n"), out); |
213 | fputs(_(" -q, --quiet be quiet\n"), out); | |
3ff52639 | 214 | |
c64963f8 KZ |
215 | fputs(USAGE_SEPARATOR, out); |
216 | printf(USAGE_HELP_OPTIONS(31)); | |
f45f3ec3 | 217 | printf(USAGE_MAN_TAIL("script(1)")); |
c64963f8 | 218 | |
86be6a32 | 219 | exit(EXIT_SUCCESS); |
3ff52639 SK |
220 | } |
221 | ||
596f4202 KZ |
222 | static struct script_log *get_log_by_name(struct script_stream *stream, |
223 | const char *name) | |
4d9b788d | 224 | { |
596f4202 | 225 | size_t i; |
4d9b788d | 226 | |
596f4202 | 227 | for (i = 0; i < stream->nlogs; i++) { |
9f822648 KZ |
228 | struct script_log *log = stream->logs[i]; |
229 | if (strcmp(log->filename, name) == 0) | |
596f4202 KZ |
230 | return log; |
231 | } | |
232 | return NULL; | |
233 | } | |
4d9b788d | 234 | |
596f4202 KZ |
235 | static struct script_log *log_associate(struct script_control *ctl, |
236 | struct script_stream *stream, | |
237 | const char *filename, int format) | |
238 | { | |
239 | struct script_log *log; | |
240 | ||
c1c2ee0b KZ |
241 | DBG(MISC, ul_debug("associate %s with stream", filename)); |
242 | ||
9f822648 KZ |
243 | assert(ctl); |
244 | assert(filename); | |
245 | assert(stream); | |
246 | ||
247 | log = get_log_by_name(stream, filename); | |
248 | if (log) | |
249 | return log; /* already defined */ | |
250 | ||
251 | log = get_log_by_name(stream == &ctl->out ? &ctl->in : &ctl->out, filename); | |
596f4202 KZ |
252 | if (!log) { |
253 | /* create a new log */ | |
9f822648 KZ |
254 | log = xcalloc(1, sizeof(*log)); |
255 | log->filename = xstrdup(filename); | |
596f4202 KZ |
256 | log->format = format; |
257 | } | |
4d9b788d | 258 | |
9f822648 KZ |
259 | /* add log to the stream */ |
260 | stream->logs = xrealloc(stream->logs, | |
261 | (stream->nlogs + 1) * sizeof(log)); | |
262 | stream->logs[stream->nlogs] = log; | |
263 | stream->nlogs++; | |
264 | ||
fbed5507 | 265 | /* remember where to write info about signals */ |
3cecd176 KZ |
266 | if (format == SCRIPT_FMT_TIMING_MULTI) { |
267 | if (!ctl->siglog) | |
268 | ctl->siglog = log; | |
269 | if (!ctl->infolog) | |
270 | ctl->infolog = log; | |
3cecd176 | 271 | } |
fbed5507 | 272 | |
596f4202 KZ |
273 | return log; |
274 | } | |
4d9b788d | 275 | |
ec10634e | 276 | static int log_close(struct script_control *ctl, |
596f4202 KZ |
277 | struct script_log *log, |
278 | const char *msg, | |
279 | int status) | |
280 | { | |
ec10634e KZ |
281 | int rc = 0; |
282 | ||
9e9b3d65 | 283 | if (!log || !log->initialized) |
ec10634e | 284 | return 0; |
c1c2ee0b | 285 | |
596f4202 | 286 | DBG(MISC, ul_debug("closing %s", log->filename)); |
4d9b788d | 287 | |
596f4202 KZ |
288 | switch (log->format) { |
289 | case SCRIPT_FMT_RAW: | |
290 | { | |
291 | char buf[FORMAT_TIMESTAMP_MAX]; | |
292 | time_t tvec = script_time((time_t *)NULL); | |
4d9b788d | 293 | |
596f4202 KZ |
294 | strtime_iso(&tvec, ISO_TIMESTAMP, buf, sizeof(buf)); |
295 | if (msg) | |
296 | fprintf(log->fp, _("\nScript done on %s [<%s>]\n"), buf, msg); | |
297 | else | |
298 | fprintf(log->fp, _("\nScript done on %s [COMMAND_EXIT_CODE=\"%d\"]\n"), buf, status); | |
596f4202 KZ |
299 | break; |
300 | } | |
70c2cd9a KZ |
301 | case SCRIPT_FMT_TIMING_MULTI: |
302 | { | |
303 | struct timeval now, delta; | |
304 | ||
305 | gettime_monotonic(&now); | |
306 | timersub(&now, &log->starttime, &delta); | |
307 | ||
308 | log_info(ctl, "DURATION", "%ld.%06ld", | |
309 | (long)delta.tv_sec, (long)delta.tv_usec); | |
310 | log_info(ctl, "EXIT_CODE", "%d", status); | |
311 | break; | |
312 | } | |
596f4202 KZ |
313 | case SCRIPT_FMT_TIMING_SIMPLE: |
314 | break; | |
315 | } | |
316 | ||
ec10634e KZ |
317 | if (close_stream(log->fp) != 0) { |
318 | warn(_("write failed: %s"), log->filename); | |
319 | rc = -errno; | |
320 | } | |
4d9b788d | 321 | |
9e9b3d65 KZ |
322 | free(log->filename); |
323 | memset(log, 0, sizeof(*log)); | |
ec10634e KZ |
324 | |
325 | return rc; | |
4d9b788d KZ |
326 | } |
327 | ||
7727be1a KZ |
328 | static int log_flush(struct script_control *ctl __attribute__((__unused__)), struct script_log *log) |
329 | { | |
330 | ||
331 | if (!log || !log->initialized) | |
332 | return 0; | |
333 | ||
334 | DBG(MISC, ul_debug("flushing %s", log->filename)); | |
335 | ||
336 | fflush(log->fp); | |
337 | return 0; | |
338 | } | |
339 | ||
9e9b3d65 KZ |
340 | static void log_free(struct script_control *ctl, struct script_log *log) |
341 | { | |
342 | size_t i; | |
343 | ||
344 | if (!log) | |
345 | return; | |
346 | ||
347 | /* the same log is possible to reference from more places, remove all | |
348 | * (TODO: maybe use include/list.h to make it more elegant) | |
349 | */ | |
350 | if (ctl->siglog == log) | |
351 | ctl->siglog = NULL; | |
352 | else if (ctl->infolog == log) | |
353 | ctl->infolog = NULL; | |
354 | ||
355 | for (i = 0; i < ctl->out.nlogs; i++) { | |
356 | if (ctl->out.logs[i] == log) | |
357 | ctl->out.logs[i] = NULL; | |
358 | } | |
359 | for (i = 0; i < ctl->in.nlogs; i++) { | |
360 | if (ctl->in.logs[i] == log) | |
361 | ctl->in.logs[i] = NULL; | |
362 | } | |
363 | free(log); | |
364 | } | |
365 | ||
ec10634e | 366 | static int log_start(struct script_control *ctl, |
596f4202 | 367 | struct script_log *log) |
6343ee8c | 368 | { |
c1c2ee0b | 369 | if (log->initialized) |
ec10634e | 370 | return 0; |
6343ee8c | 371 | |
596f4202 | 372 | DBG(MISC, ul_debug("opening %s", log->filename)); |
6343ee8c | 373 | |
c1c2ee0b KZ |
374 | assert(log->fp == NULL); |
375 | ||
596f4202 KZ |
376 | /* open the log */ |
377 | log->fp = fopen(log->filename, | |
378 | ctl->append && log->format == SCRIPT_FMT_RAW ? | |
c1c2ee0b KZ |
379 | "a" UL_CLOEXECSTR : |
380 | "w" UL_CLOEXECSTR); | |
596f4202 | 381 | if (!log->fp) { |
596f4202 | 382 | warn(_("cannot open %s"), log->filename); |
ec10634e | 383 | return -errno; |
596f4202 | 384 | } |
6343ee8c | 385 | |
596f4202 KZ |
386 | /* write header, etc. */ |
387 | switch (log->format) { | |
388 | case SCRIPT_FMT_RAW: | |
389 | { | |
390 | char buf[FORMAT_TIMESTAMP_MAX]; | |
391 | time_t tvec = script_time((time_t *)NULL); | |
392 | ||
393 | strtime_iso(&tvec, ISO_TIMESTAMP, buf, sizeof(buf)); | |
394 | fprintf(log->fp, _("Script started on %s ["), buf); | |
395 | ||
396 | if (ctl->isterm) { | |
3cecd176 | 397 | init_terminal_info(ctl); |
596f4202 | 398 | |
3cecd176 KZ |
399 | if (ctl->ttytype) |
400 | fprintf(log->fp, "TERM=\"%s\" ", ctl->ttytype); | |
401 | if (ctl->ttyname) | |
402 | fprintf(log->fp, "TTY=\"%s\" ", ctl->ttyname); | |
596f4202 | 403 | |
3cecd176 | 404 | fprintf(log->fp, "COLUMNS=\"%d\" LINES=\"%d\"", ctl->ttycols, ctl->ttylines); |
596f4202 KZ |
405 | } else |
406 | fprintf(log->fp, _("<not executed on terminal>")); | |
407 | ||
408 | fputs("]\n", log->fp); | |
409 | break; | |
410 | } | |
411 | case SCRIPT_FMT_TIMING_SIMPLE: | |
36b1369b | 412 | case SCRIPT_FMT_TIMING_MULTI: |
596f4202 | 413 | gettime_monotonic(&log->oldtime); |
70c2cd9a | 414 | gettime_monotonic(&log->starttime); |
596f4202 KZ |
415 | break; |
416 | } | |
c1c2ee0b KZ |
417 | |
418 | log->initialized = 1; | |
ec10634e | 419 | return 0; |
596f4202 KZ |
420 | } |
421 | ||
9e9b3d65 | 422 | static int logging_start(struct script_control *ctl) |
fd42a45b KZ |
423 | { |
424 | size_t i; | |
425 | ||
426 | /* start all output logs */ | |
ec10634e KZ |
427 | for (i = 0; i < ctl->out.nlogs; i++) { |
428 | int rc = log_start(ctl, ctl->out.logs[i]); | |
429 | if (rc) | |
430 | return rc; | |
431 | } | |
fd42a45b KZ |
432 | |
433 | /* start all input logs */ | |
ec10634e KZ |
434 | for (i = 0; i < ctl->in.nlogs; i++) { |
435 | int rc = log_start(ctl, ctl->in.logs[i]); | |
436 | if (rc) | |
437 | return rc; | |
438 | } | |
439 | return 0; | |
fd42a45b KZ |
440 | } |
441 | ||
ec10634e | 442 | static ssize_t log_write(struct script_control *ctl, |
c1c2ee0b | 443 | struct script_stream *stream, |
596f4202 KZ |
444 | struct script_log *log, |
445 | char *obuf, size_t bytes) | |
446 | { | |
ec10634e KZ |
447 | int rc; |
448 | ssize_t ssz = 0; | |
449 | struct timeval now, delta; | |
450 | ||
596f4202 KZ |
451 | if (!log->fp) |
452 | return 0; | |
453 | ||
ec10634e | 454 | DBG(IO, ul_debug(" writing [file=%s]", log->filename)); |
596f4202 KZ |
455 | |
456 | switch (log->format) { | |
457 | case SCRIPT_FMT_RAW: | |
ec10634e KZ |
458 | DBG(IO, ul_debug(" log raw data")); |
459 | rc = fwrite_all(obuf, 1, bytes, log->fp); | |
460 | if (rc) { | |
596f4202 | 461 | warn(_("cannot write %s"), log->filename); |
ec10634e | 462 | return rc; |
596f4202 | 463 | } |
ec10634e | 464 | ssz = bytes; |
596f4202 | 465 | break; |
596f4202 | 466 | |
ec10634e KZ |
467 | case SCRIPT_FMT_TIMING_SIMPLE: |
468 | DBG(IO, ul_debug(" log timing info")); | |
596f4202 KZ |
469 | |
470 | gettime_monotonic(&now); | |
471 | timersub(&now, &log->oldtime, &delta); | |
ec10634e | 472 | ssz = fprintf(log->fp, "%ld.%06ld %zd\n", |
596f4202 | 473 | (long)delta.tv_sec, (long)delta.tv_usec, bytes); |
ec10634e KZ |
474 | if (ssz < 0) |
475 | return -errno; | |
476 | ||
596f4202 | 477 | log->oldtime = now; |
596f4202 | 478 | break; |
c1c2ee0b | 479 | |
ec10634e KZ |
480 | case SCRIPT_FMT_TIMING_MULTI: |
481 | DBG(IO, ul_debug(" log multi-stream timing info")); | |
c1c2ee0b KZ |
482 | |
483 | gettime_monotonic(&now); | |
484 | timersub(&now, &log->oldtime, &delta); | |
ec10634e | 485 | ssz = fprintf(log->fp, "%c %ld.%06ld %zd\n", |
c1c2ee0b KZ |
486 | stream->ident, |
487 | (long)delta.tv_sec, (long)delta.tv_usec, bytes); | |
ec10634e KZ |
488 | if (ssz < 0) |
489 | return -errno; | |
490 | ||
c1c2ee0b | 491 | log->oldtime = now; |
ec10634e | 492 | break; |
596f4202 KZ |
493 | default: |
494 | break; | |
495 | } | |
496 | ||
497 | if (ctl->flush) | |
498 | fflush(log->fp); | |
ec10634e | 499 | return ssz; |
6343ee8c KZ |
500 | } |
501 | ||
ec10634e | 502 | static ssize_t log_stream_activity( |
596f4202 KZ |
503 | struct script_control *ctl, |
504 | struct script_stream *stream, | |
505 | char *buf, size_t bytes) | |
506 | { | |
507 | size_t i; | |
ec10634e | 508 | ssize_t outsz = 0; |
596f4202 | 509 | |
ec10634e KZ |
510 | for (i = 0; i < stream->nlogs; i++) { |
511 | ssize_t ssz = log_write(ctl, stream, stream->logs[i], buf, bytes); | |
512 | ||
513 | if (ssz < 0) | |
514 | return ssz; | |
515 | outsz += ssz; | |
516 | } | |
596f4202 KZ |
517 | |
518 | return outsz; | |
519 | } | |
520 | ||
ec10634e | 521 | static ssize_t log_signal(struct script_control *ctl, int signum, char *msgfmt, ...) |
fbed5507 KZ |
522 | { |
523 | struct script_log *log; | |
524 | struct timeval now, delta; | |
525 | char msg[BUFSIZ] = {0}; | |
526 | va_list ap; | |
ec10634e | 527 | ssize_t sz; |
fbed5507 KZ |
528 | |
529 | assert(ctl); | |
530 | ||
531 | log = ctl->siglog; | |
532 | if (!log) | |
533 | return 0; | |
534 | ||
535 | assert(log->format == SCRIPT_FMT_TIMING_MULTI); | |
fbed5507 KZ |
536 | DBG(IO, ul_debug(" writing signal to multi-stream timing")); |
537 | ||
538 | gettime_monotonic(&now); | |
539 | timersub(&now, &log->oldtime, &delta); | |
540 | ||
541 | if (msgfmt) { | |
542 | int rc; | |
543 | va_start(ap, msgfmt); | |
544 | rc = vsnprintf(msg, sizeof(msg), msgfmt, ap); | |
545 | va_end(ap); | |
546 | if (rc < 0) | |
547 | *msg = '\0';; | |
548 | } | |
549 | ||
550 | if (*msg) | |
fd42a45b | 551 | sz = fprintf(log->fp, "S %ld.%06ld SIG%s %s\n", |
fbed5507 KZ |
552 | (long)delta.tv_sec, (long)delta.tv_usec, |
553 | signum_to_signame(signum), msg); | |
554 | else | |
555 | sz = fprintf(log->fp, "S %ld.%06ld SIG%s\n", | |
556 | (long)delta.tv_sec, (long)delta.tv_usec, | |
557 | signum_to_signame(signum)); | |
558 | ||
559 | log->oldtime = now; | |
ec10634e | 560 | return sz; |
fbed5507 | 561 | } |
596f4202 | 562 | |
ec10634e | 563 | static ssize_t log_info(struct script_control *ctl, const char *name, const char *msgfmt, ...) |
3cecd176 KZ |
564 | { |
565 | struct script_log *log; | |
566 | char msg[BUFSIZ] = {0}; | |
567 | va_list ap; | |
ec10634e | 568 | ssize_t sz; |
3cecd176 KZ |
569 | |
570 | assert(ctl); | |
571 | ||
572 | log = ctl->infolog; | |
573 | if (!log) | |
574 | return 0; | |
575 | ||
576 | assert(log->format == SCRIPT_FMT_TIMING_MULTI); | |
577 | DBG(IO, ul_debug(" writing info to multi-stream log")); | |
578 | ||
579 | if (msgfmt) { | |
580 | int rc; | |
581 | va_start(ap, msgfmt); | |
582 | rc = vsnprintf(msg, sizeof(msg), msgfmt, ap); | |
583 | va_end(ap); | |
584 | if (rc < 0) | |
585 | *msg = '\0';; | |
586 | } | |
587 | ||
588 | if (*msg) | |
a1cc831b | 589 | sz = fprintf(log->fp, "H %f %s %s\n", 0.0, name, msg); |
3cecd176 | 590 | else |
a1cc831b | 591 | sz = fprintf(log->fp, "H %f %s\n", 0.0, name); |
3cecd176 | 592 | |
ec10634e | 593 | return sz; |
8198052c SK |
594 | } |
595 | ||
7fb65db1 | 596 | |
9e9b3d65 | 597 | static void logging_done(struct script_control *ctl, const char *msg) |
93af8d8b | 598 | { |
596f4202 KZ |
599 | int status; |
600 | size_t i; | |
6343ee8c | 601 | |
ec10634e | 602 | DBG(MISC, ul_debug("stop logging")); |
8198052c | 603 | |
6343ee8c | 604 | if (WIFSIGNALED(ctl->childstatus)) |
596f4202 | 605 | status = WTERMSIG(ctl->childstatus) + 0x80; |
6343ee8c | 606 | else |
596f4202 KZ |
607 | status = WEXITSTATUS(ctl->childstatus); |
608 | ||
596f4202 KZ |
609 | DBG(MISC, ul_debug(" status=%d", status)); |
610 | ||
611 | /* close all output logs */ | |
9e9b3d65 KZ |
612 | for (i = 0; i < ctl->out.nlogs; i++) { |
613 | struct script_log *log = ctl->out.logs[i]; | |
614 | log_close(ctl, log, msg, status); | |
615 | log_free(ctl, log); | |
616 | } | |
617 | free(ctl->out.logs); | |
618 | ctl->out.logs = NULL; | |
619 | ctl->out.nlogs = 0; | |
596f4202 KZ |
620 | |
621 | /* close all input logs */ | |
9e9b3d65 KZ |
622 | for (i = 0; i < ctl->in.nlogs; i++) { |
623 | struct script_log *log = ctl->in.logs[i]; | |
624 | log_close(ctl, log, msg, status); | |
625 | log_free(ctl, log); | |
626 | } | |
627 | free(ctl->in.logs); | |
628 | ctl->in.logs = NULL; | |
629 | ctl->in.nlogs = 0; | |
93af8d8b | 630 | } |
5c36a0eb | 631 | |
bdd43357 KZ |
632 | static void callback_child_die( |
633 | void *data, | |
634 | pid_t child __attribute__((__unused__)), | |
635 | int status) | |
93af8d8b | 636 | { |
ec10634e | 637 | struct script_control *ctl = (struct script_control *) data; |
93af8d8b | 638 | |
bdd43357 KZ |
639 | ctl->child = (pid_t) -1; |
640 | ctl->childstatus = status; | |
54c6611d KZ |
641 | } |
642 | ||
bdd43357 KZ |
643 | static void callback_child_sigstop( |
644 | void *data __attribute__((__unused__)), | |
645 | pid_t child) | |
54c6611d | 646 | { |
ec10634e KZ |
647 | DBG(SIGNAL, ul_debug(" child stop by SIGSTOP -- stop parent too")); |
648 | kill(getpid(), SIGSTOP); | |
649 | DBG(SIGNAL, ul_debug(" resume")); | |
bdd43357 | 650 | kill(child, SIGCONT); |
54c6611d KZ |
651 | } |
652 | ||
ec10634e | 653 | static int callback_log_stream_activity(void *data, int fd, char *buf, size_t bufsz) |
54c6611d | 654 | { |
ec10634e KZ |
655 | struct script_control *ctl = (struct script_control *) data; |
656 | ssize_t ssz = 0; | |
54c6611d | 657 | |
ec10634e | 658 | DBG(IO, ul_debug("stream activity callback")); |
b2bff066 | 659 | |
ec10634e KZ |
660 | /* from stdin (user) to command */ |
661 | if (fd == STDIN_FILENO) | |
662 | ssz = log_stream_activity(ctl, &ctl->in, buf, (size_t) bufsz); | |
364cda48 | 663 | |
ec10634e KZ |
664 | /* from command (master) to stdout and log */ |
665 | else if (fd == ul_pty_get_childfd(ctl->pty)) | |
666 | ssz = log_stream_activity(ctl, &ctl->out, buf, (size_t) bufsz); | |
da26af4e | 667 | |
ec10634e KZ |
668 | if (ssz < 0) |
669 | return (int) ssz; | |
54c6611d | 670 | |
ec10634e KZ |
671 | DBG(IO, ul_debug(" append %ld bytes [summary=%zu, max=%zu]", ssz, |
672 | ctl->outsz, ctl->maxsz)); | |
a2b4dec5 | 673 | |
ec10634e | 674 | ctl->outsz += ssz; |
da26af4e | 675 | |
aefe9893 | 676 | |
596f4202 KZ |
677 | /* check output limit */ |
678 | if (ctl->maxsz != 0 && ctl->outsz >= ctl->maxsz) { | |
679 | if (!ctl->quiet) | |
680 | printf(_("Script terminated, max output files size %"PRIu64" exceeded.\n"), ctl->maxsz); | |
681 | DBG(IO, ul_debug("output size %"PRIu64", exceeded limit %"PRIu64, ctl->outsz, ctl->maxsz)); | |
9e9b3d65 | 682 | logging_done(ctl, _("max output size exceeded")); |
ec10634e | 683 | return 1; |
a2b4dec5 | 684 | } |
ec10634e | 685 | return 0; |
a8896ad5 SK |
686 | } |
687 | ||
ec10634e | 688 | static int callback_log_signal(void *data, struct signalfd_siginfo *info, void *sigdata) |
a8896ad5 | 689 | { |
ec10634e KZ |
690 | struct script_control *ctl = (struct script_control *) data; |
691 | ssize_t ssz = 0; | |
a8896ad5 | 692 | |
ec10634e | 693 | switch (info->ssi_signo) { |
a8896ad5 | 694 | case SIGWINCH: |
ec10634e KZ |
695 | { |
696 | struct winsize *win = (struct winsize *) sigdata; | |
697 | ssz = log_signal(ctl, info->ssi_signo, "ROWS=%d COLS=%d", | |
698 | win->ws_row, win->ws_col); | |
a8896ad5 | 699 | break; |
ec10634e | 700 | } |
5860c45e KZ |
701 | case SIGTERM: |
702 | /* fallthrough */ | |
703 | case SIGINT: | |
704 | /* fallthrough */ | |
705 | case SIGQUIT: | |
ec10634e KZ |
706 | ssz = log_signal(ctl, info->ssi_signo, NULL); |
707 | break; | |
a8896ad5 | 708 | default: |
ec10634e KZ |
709 | /* no log */ |
710 | break; | |
93af8d8b | 711 | } |
a17f3264 | 712 | |
ec10634e | 713 | return ssz < 0 ? ssz : 0; |
6dbe3af9 KZ |
714 | } |
715 | ||
7727be1a KZ |
716 | static int callback_flush_logs(void *data) |
717 | { | |
718 | struct script_control *ctl = (struct script_control *) data; | |
719 | size_t i; | |
720 | ||
721 | for (i = 0; i < ctl->out.nlogs; i++) { | |
722 | int rc = log_flush(ctl, ctl->out.logs[i]); | |
723 | if (rc) | |
724 | return rc; | |
725 | } | |
726 | ||
727 | for (i = 0; i < ctl->in.nlogs; i++) { | |
728 | int rc = log_flush(ctl, ctl->in.logs[i]); | |
729 | if (rc) | |
730 | return rc; | |
731 | } | |
732 | return 0; | |
733 | } | |
734 | ||
ec10634e | 735 | static void die_if_link(struct script_control *ctl, const char *filename) |
93af8d8b | 736 | { |
ec10634e | 737 | struct stat s; |
54c6611d | 738 | |
ec10634e KZ |
739 | if (ctl->force) |
740 | return; | |
741 | if (lstat(filename, &s) == 0 && (S_ISLNK(s.st_mode) || s.st_nlink > 1)) | |
742 | errx(EXIT_FAILURE, | |
743 | _("output file `%s' is a link\n" | |
744 | "Use --force if you really want to use it.\n" | |
745 | "Program not started."), filename); | |
6dbe3af9 KZ |
746 | } |
747 | ||
93af8d8b SK |
748 | int main(int argc, char **argv) |
749 | { | |
edc7e420 | 750 | struct script_control ctl = { |
c1c2ee0b KZ |
751 | .out = { .ident = 'O' }, |
752 | .in = { .ident = 'I' }, | |
edc7e420 | 753 | }; |
ec10634e | 754 | struct ul_pty_callbacks *cb; |
1eee1acb | 755 | int ch, format = 0, caught_signal = 0, rc = 0, echo = 0; |
70062aad | 756 | const char *outfile = NULL, *infile = NULL; |
ec10634e | 757 | const char *timingfile = NULL, *shell = NULL, *command = NULL; |
93af8d8b SK |
758 | |
759 | enum { FORCE_OPTION = CHAR_MAX + 1 }; | |
760 | ||
761 | static const struct option longopts[] = { | |
edc7e420 SK |
762 | {"append", no_argument, NULL, 'a'}, |
763 | {"command", required_argument, NULL, 'c'}, | |
1eee1acb | 764 | {"echo", required_argument, NULL, 'E'}, |
edc7e420 SK |
765 | {"return", no_argument, NULL, 'e'}, |
766 | {"flush", no_argument, NULL, 'f'}, | |
767 | {"force", no_argument, NULL, FORCE_OPTION,}, | |
70062aad | 768 | {"log-in", required_argument, NULL, 'I'}, |
ddbdb792 | 769 | {"log-out", required_argument, NULL, 'O'}, |
c1c2ee0b | 770 | {"log-io", required_argument, NULL, 'B'}, |
fc58044f | 771 | {"log-timing", required_argument, NULL, 'T'}, |
daee4d95 | 772 | {"logging-format", required_argument, NULL, 'm'}, |
aefe9893 | 773 | {"output-limit", required_argument, NULL, 'o'}, |
edc7e420 SK |
774 | {"quiet", no_argument, NULL, 'q'}, |
775 | {"timing", optional_argument, NULL, 't'}, | |
776 | {"version", no_argument, NULL, 'V'}, | |
777 | {"help", no_argument, NULL, 'h'}, | |
778 | {NULL, 0, NULL, 0} | |
93af8d8b | 779 | }; |
fc58044f KZ |
780 | static const ul_excl_t excl[] = { /* rows and cols in ASCII order */ |
781 | { 'T', 't' }, | |
782 | { 0 } | |
783 | }; | |
784 | int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; | |
93af8d8b | 785 | setlocale(LC_ALL, ""); |
b09feab9 SK |
786 | /* |
787 | * script -t prints time delays as floating point numbers. The example | |
788 | * program (scriptreplay) that we provide to handle this timing output | |
789 | * is a perl script, and does not handle numbers in locale format (not | |
790 | * even when "use locale;" is added). So, since these numbers are not | |
791 | * for human consumption, it seems easiest to set LC_NUMERIC here. | |
792 | */ | |
793 | setlocale(LC_NUMERIC, "C"); | |
93af8d8b SK |
794 | bindtextdomain(PACKAGE, LOCALEDIR); |
795 | textdomain(PACKAGE); | |
2c308875 | 796 | close_stdout_atexit(); |
93af8d8b | 797 | |
a2b4dec5 | 798 | script_init_debug(); |
ec10634e | 799 | ON_DBG(PTY, ul_pty_init_debug(0xFFFF)); |
a2b4dec5 | 800 | |
1eee1acb KZ |
801 | /* The default is to keep ECHO flag when stdin is not terminal. We need |
802 | * it to make stdin (in case of "echo foo | script") log-able and | |
ac407b16 | 803 | * visible on terminal, and for backward compatibility. |
1eee1acb KZ |
804 | */ |
805 | ctl.isterm = isatty(STDIN_FILENO); | |
806 | echo = ctl.isterm ? 0 : 1; | |
807 | ||
808 | while ((ch = getopt_long(argc, argv, "aB:c:eE:fI:O:o:qm:T:t::Vh", longopts, NULL)) != -1) { | |
fc58044f KZ |
809 | |
810 | err_exclusive_options(ch, longopts, excl, excl_st); | |
811 | ||
edc7e420 | 812 | switch (ch) { |
93af8d8b | 813 | case 'a': |
3f19b85f | 814 | ctl.append = 1; |
93af8d8b SK |
815 | break; |
816 | case 'c': | |
ec10634e | 817 | command = optarg; |
93af8d8b | 818 | break; |
1eee1acb KZ |
819 | case 'E': |
820 | if (strcmp(optarg, "auto") == 0) | |
821 | ; /* keep default */ | |
822 | else if (strcmp(optarg, "never") == 0) | |
823 | echo = 0; | |
824 | else if (strcmp(optarg, "always") == 0) | |
825 | echo = 1; | |
826 | else | |
827 | errx(EXIT_FAILURE, _("unssuported echo mode: '%s'"), optarg); | |
828 | break; | |
93af8d8b | 829 | case 'e': |
7e5796c9 | 830 | ctl.rc_wanted = 1; |
93af8d8b SK |
831 | break; |
832 | case 'f': | |
3f19b85f | 833 | ctl.flush = 1; |
93af8d8b SK |
834 | break; |
835 | case FORCE_OPTION: | |
3f19b85f | 836 | ctl.force = 1; |
93af8d8b | 837 | break; |
c1c2ee0b KZ |
838 | case 'B': |
839 | log_associate(&ctl, &ctl.in, optarg, SCRIPT_FMT_RAW); | |
840 | log_associate(&ctl, &ctl.out, optarg, SCRIPT_FMT_RAW); | |
841 | infile = outfile = optarg; | |
842 | break; | |
70062aad KZ |
843 | case 'I': |
844 | log_associate(&ctl, &ctl.in, optarg, SCRIPT_FMT_RAW); | |
845 | infile = optarg; | |
846 | break; | |
ddbdb792 | 847 | case 'O': |
70062aad KZ |
848 | log_associate(&ctl, &ctl.out, optarg, SCRIPT_FMT_RAW); |
849 | outfile = optarg; | |
ddbdb792 | 850 | break; |
aefe9893 FM |
851 | case 'o': |
852 | ctl.maxsz = strtosize_or_err(optarg, _("failed to parse output limit size")); | |
853 | break; | |
93af8d8b | 854 | case 'q': |
3f19b85f | 855 | ctl.quiet = 1; |
93af8d8b | 856 | break; |
daee4d95 KZ |
857 | case 'm': |
858 | if (strcasecmp(optarg, "classic") == 0) | |
859 | format = SCRIPT_FMT_TIMING_SIMPLE; | |
860 | else if (strcasecmp(optarg, "advanced") == 0) | |
861 | format = SCRIPT_FMT_TIMING_MULTI; | |
862 | else | |
863 | errx(EXIT_FAILURE, _("unssuported logging format: '%s'"), optarg); | |
864 | break; | |
93af8d8b | 865 | case 't': |
4f7521d6 KZ |
866 | if (optarg && *optarg == '=') |
867 | optarg++; | |
596f4202 KZ |
868 | log_associate(&ctl, &ctl.out, |
869 | optarg ? optarg : "/dev/stderr", | |
870 | SCRIPT_FMT_TIMING_SIMPLE); | |
d805688a KZ |
871 | /* used for message only */ |
872 | timingfile = optarg ? optarg : "stderr"; | |
93af8d8b | 873 | break; |
fc58044f | 874 | case 'T' : |
fc58044f KZ |
875 | timingfile = optarg; |
876 | break; | |
93af8d8b | 877 | case 'V': |
2c308875 | 878 | print_version(EXIT_SUCCESS); |
93af8d8b | 879 | case 'h': |
86be6a32 | 880 | usage(); |
93af8d8b | 881 | default: |
677ec86c | 882 | errtryhelp(EXIT_FAILURE); |
93af8d8b | 883 | } |
fc58044f | 884 | } |
93af8d8b SK |
885 | argc -= optind; |
886 | argv += optind; | |
887 | ||
70062aad KZ |
888 | /* default if no --log-* specified */ |
889 | if (!outfile && !infile) { | |
ddbdb792 | 890 | if (argc > 0) |
70062aad KZ |
891 | outfile = argv[0]; |
892 | else { | |
ddbdb792 | 893 | die_if_link(&ctl, DEFAULT_TYPESCRIPT_FILENAME); |
70062aad KZ |
894 | outfile = DEFAULT_TYPESCRIPT_FILENAME; |
895 | } | |
596f4202 | 896 | |
70062aad KZ |
897 | /* associate stdout with typescript file */ |
898 | log_associate(&ctl, &ctl.out, outfile, SCRIPT_FMT_RAW); | |
899 | } | |
93af8d8b | 900 | |
c1c2ee0b | 901 | if (timingfile) { |
d628e9cf KZ |
902 | /* the old SCRIPT_FMT_TIMING_SIMPLE should be used when |
903 | * recoding output only (just for backward compatibility), | |
904 | * otherwise switch to new format. */ | |
c1c2ee0b | 905 | if (!format) |
d628e9cf | 906 | format = infile || (outfile && infile) ? |
c1c2ee0b KZ |
907 | SCRIPT_FMT_TIMING_MULTI : |
908 | SCRIPT_FMT_TIMING_SIMPLE; | |
daee4d95 KZ |
909 | |
910 | else if (format == SCRIPT_FMT_TIMING_SIMPLE && outfile && infile) | |
911 | errx(EXIT_FAILURE, _("log multiple streams is mutually " | |
912 | "exclusive with 'classic' format")); | |
c1c2ee0b KZ |
913 | if (outfile) |
914 | log_associate(&ctl, &ctl.out, timingfile, format); | |
915 | if (infile) | |
916 | log_associate(&ctl, &ctl.in, timingfile, format); | |
917 | } | |
918 | ||
ec10634e KZ |
919 | shell = getenv("SHELL"); |
920 | if (!shell) | |
921 | shell = _PATH_BSHELL; | |
93af8d8b | 922 | |
ec10634e KZ |
923 | ctl.pty = ul_new_pty(ctl.isterm); |
924 | if (!ctl.pty) | |
925 | err(EXIT_FAILURE, "failed to allocate PTY handler"); | |
926 | ||
1eee1acb | 927 | ul_pty_slave_echo(ctl.pty, echo); |
4169bcb7 | 928 | |
ec10634e KZ |
929 | ul_pty_set_callback_data(ctl.pty, (void *) &ctl); |
930 | cb = ul_pty_get_callbacks(ctl.pty); | |
bdd43357 | 931 | cb->child_die = callback_child_die; |
ec10634e KZ |
932 | cb->child_sigstop = callback_child_sigstop; |
933 | cb->log_stream_activity = callback_log_stream_activity; | |
934 | cb->log_signal = callback_log_signal; | |
7727be1a | 935 | cb->flush_logs = callback_flush_logs; |
3cecd176 | 936 | |
d805688a | 937 | if (!ctl.quiet) { |
70062aad KZ |
938 | printf(_("Script started")); |
939 | if (outfile) | |
940 | printf(_(", output log file is '%s'"), outfile); | |
941 | if (infile) | |
942 | printf(_(", input log file is '%s'"), infile); | |
943 | if (timingfile) | |
944 | printf(_(", timing file is '%s'"), timingfile); | |
945 | printf(_(".\n")); | |
d805688a | 946 | } |
ec10634e | 947 | |
b1154c4e KZ |
948 | #ifdef HAVE_LIBUTEMPTER |
949 | utempter_add_record(ul_pty_get_childfd(ctl.pty), NULL); | |
950 | #endif | |
951 | ||
ec10634e KZ |
952 | if (ul_pty_setup(ctl.pty)) |
953 | err(EXIT_FAILURE, _("failed to create pseudo-terminal")); | |
954 | ||
955 | fflush(stdout); | |
956 | ||
957 | /* | |
958 | * We have terminal, do not use err() from now, use "goto done" | |
959 | */ | |
93af8d8b | 960 | |
ec10634e KZ |
961 | switch ((int) (ctl.child = fork())) { |
962 | case -1: /* error */ | |
963 | warn(_("cannot create child process")); | |
964 | rc = -errno; | |
965 | goto done; | |
a2b4dec5 | 966 | |
ec10634e KZ |
967 | case 0: /* child */ |
968 | { | |
969 | const char *shname; | |
93af8d8b | 970 | |
ec10634e | 971 | ul_pty_init_slave(ctl.pty); |
a2b4dec5 | 972 | |
ec10634e | 973 | signal(SIGTERM, SIG_DFL); /* because /etc/csh.login */ |
93af8d8b | 974 | |
ec10634e KZ |
975 | shname = strrchr(shell, '/'); |
976 | shname = shname ? shname + 1 : shell; | |
977 | ||
4169bcb7 KZ |
978 | if (access(shell, X_OK) == 0) { |
979 | if (command) | |
980 | execl(shell, shname, "-c", command, NULL); | |
981 | else | |
982 | execl(shell, shname, "-i", NULL); | |
983 | } else { | |
984 | if (command) | |
985 | execlp(shname, "-c", command, NULL); | |
986 | else | |
987 | execlp(shname, "-i", NULL); | |
988 | } | |
989 | ||
ec10634e | 990 | err(EXIT_FAILURE, "failed to execute %s", shell); |
d35ffe80 | 991 | break; |
ec10634e KZ |
992 | } |
993 | default: | |
d35ffe80 | 994 | break; |
ec10634e KZ |
995 | } |
996 | ||
997 | /* parent */ | |
998 | ul_pty_set_child(ctl.pty, ctl.child); | |
999 | ||
9e9b3d65 | 1000 | rc = logging_start(&ctl); |
ec10634e KZ |
1001 | if (rc) |
1002 | goto done; | |
1003 | ||
bdd43357 | 1004 | /* add extra info to advanced timing file */ |
ec10634e KZ |
1005 | if (timingfile && format == SCRIPT_FMT_TIMING_MULTI) { |
1006 | char buf[FORMAT_TIMESTAMP_MAX]; | |
1007 | time_t tvec = script_time((time_t *)NULL); | |
1008 | ||
1009 | strtime_iso(&tvec, ISO_TIMESTAMP, buf, sizeof(buf)); | |
1010 | log_info(&ctl, "START_TIME", buf); | |
1011 | ||
1012 | if (ctl.isterm) { | |
1013 | init_terminal_info(&ctl); | |
1014 | log_info(&ctl, "TERM", ctl.ttytype); | |
1015 | log_info(&ctl, "TTY", ctl.ttyname); | |
1016 | log_info(&ctl, "COLUMNS", "%d", ctl.ttycols); | |
1017 | log_info(&ctl, "LINES", "%d", ctl.ttylines); | |
fd42a45b | 1018 | } |
ec10634e KZ |
1019 | log_info(&ctl, "SHELL", shell); |
1020 | if (command) | |
1021 | log_info(&ctl, "COMMAND", command); | |
1022 | log_info(&ctl, "TIMING_LOG", timingfile); | |
1023 | if (outfile) | |
1024 | log_info(&ctl, "OUTPUT_LOG", outfile); | |
1025 | if (infile) | |
1026 | log_info(&ctl, "INPUT_LOG", infile); | |
1027 | } | |
1028 | ||
1029 | /* this is the main loop */ | |
1030 | rc = ul_pty_proxy_master(ctl.pty); | |
1031 | ||
1032 | /* all done; cleanup and kill */ | |
1033 | caught_signal = ul_pty_get_delivered_signal(ctl.pty); | |
1034 | ||
1035 | if (!caught_signal && ctl.child != (pid_t)-1) | |
bdd43357 | 1036 | ul_pty_wait_for_child(ctl.pty); /* final wait */ |
ec10634e KZ |
1037 | |
1038 | if (caught_signal && ctl.child != (pid_t)-1) { | |
1039 | fprintf(stderr, "\nSession terminated, killing shell..."); | |
1040 | kill(ctl.child, SIGTERM); | |
1041 | sleep(2); | |
1042 | kill(ctl.child, SIGKILL); | |
1043 | fprintf(stderr, " ...killed.\n"); | |
1044 | } | |
1045 | ||
1046 | done: | |
1047 | ul_pty_cleanup(ctl.pty); | |
9e9b3d65 | 1048 | logging_done(&ctl, NULL); |
ec10634e KZ |
1049 | |
1050 | if (!ctl.quiet) | |
1051 | printf(_("Script done.\n")); | |
1052 | ||
1053 | #ifdef HAVE_LIBUTEMPTER | |
1054 | if (ul_pty_get_childfd(ctl.pty) >= 0) | |
1055 | utempter_remove_record(ul_pty_get_childfd(ctl.pty)); | |
1056 | #endif | |
1057 | ul_free_pty(ctl.pty); | |
1058 | ||
1059 | /* default exit code */ | |
1060 | rc = rc ? EXIT_FAILURE : EXIT_SUCCESS; | |
1061 | ||
1062 | /* exit code based on child status */ | |
1063 | if (ctl.rc_wanted && rc == EXIT_SUCCESS) { | |
1064 | if (WIFSIGNALED(ctl.childstatus)) | |
1065 | rc = WTERMSIG(ctl.childstatus) + 0x80; | |
1066 | else | |
1067 | rc = WEXITSTATUS(ctl.childstatus); | |
93af8d8b | 1068 | } |
d35ffe80 | 1069 | |
ec10634e KZ |
1070 | DBG(MISC, ul_debug("done [rc=%d]", rc)); |
1071 | return rc; | |
6dbe3af9 | 1072 | } |