]>
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" |
6dbe3af9 | 74 | |
a2b4dec5 KZ |
75 | #include "debug.h" |
76 | ||
2ba641e5 | 77 | static UL_DEBUG_DEFINE_MASK(script); |
a2b4dec5 KZ |
78 | UL_DEBUG_DEFINE_MASKNAMES(script) = UL_DEBUG_EMPTY_MASKNAMES; |
79 | ||
80 | #define SCRIPT_DEBUG_INIT (1 << 1) | |
81 | #define SCRIPT_DEBUG_POLL (1 << 2) | |
82 | #define SCRIPT_DEBUG_SIGNAL (1 << 3) | |
83 | #define SCRIPT_DEBUG_IO (1 << 4) | |
84 | #define SCRIPT_DEBUG_MISC (1 << 5) | |
85 | #define SCRIPT_DEBUG_ALL 0xFFFF | |
86 | ||
87 | #define DBG(m, x) __UL_DBG(script, SCRIPT_DEBUG_, m, x) | |
88 | #define ON_DBG(m, x) __UL_DBG_CALL(script, SCRIPT_DEBUG_, m, x) | |
89 | ||
e11a5e63 SK |
90 | #if defined(HAVE_LIBUTIL) && defined(HAVE_PTY_H) |
91 | # include <pty.h> | |
5c36a0eb KZ |
92 | #endif |
93 | ||
f0bc3fa0 | 94 | #ifdef HAVE_LIBUTEMPTER |
e11a5e63 | 95 | # include <utempter.h> |
f0bc3fa0 KZ |
96 | #endif |
97 | ||
7e5796c9 | 98 | #define DEFAULT_TYPESCRIPT_FILENAME "typescript" |
05644dab | 99 | |
edc7e420 SK |
100 | struct script_control { |
101 | char *shell; /* shell to be executed */ | |
3f19b85f | 102 | char *command; /* command to be executed */ |
edc7e420 SK |
103 | char *fname; /* output file path */ |
104 | FILE *typescriptfp; /* output file pointer */ | |
9580536a | 105 | char *tname; /* timing file path */ |
edc7e420 | 106 | FILE *timingfp; /* timing file pointer */ |
04639805 | 107 | struct timeval oldtime; /* previous write or command start time */ |
edc7e420 SK |
108 | int master; /* pseudoterminal master file descriptor */ |
109 | int slave; /* pseudoterminal slave file descriptor */ | |
89a859d4 | 110 | int poll_timeout; /* poll() timeout, used in end of execution */ |
edc7e420 | 111 | pid_t child; /* child pid */ |
edc7e420 | 112 | int childstatus; /* child process exit value */ |
3f19b85f | 113 | struct termios attrs; /* slave terminal runtime attributes */ |
edc7e420 | 114 | struct winsize win; /* terminal window size */ |
9779651e | 115 | #if !HAVE_LIBUTIL || !HAVE_PTY_H |
8d6fdd2f | 116 | char *line; /* terminal line */ |
5c36a0eb | 117 | #endif |
edc7e420 | 118 | unsigned int |
3f19b85f | 119 | append:1, /* append output */ |
7e5796c9 | 120 | rc_wanted:1, /* return child exit value */ |
3f19b85f KZ |
121 | flush:1, /* flush after each write */ |
122 | quiet:1, /* suppress most output */ | |
123 | timing:1, /* include timing file */ | |
124 | force:1, /* write output to links */ | |
edc7e420 | 125 | isterm:1, /* is child process running as terminal */ |
edc7e420 | 126 | die:1; /* terminate program */ |
7e5796c9 | 127 | |
cc1a88fb | 128 | sigset_t sigset; /* catch SIGCHLD and SIGWINCH with signalfd() */ |
d35ffe80 | 129 | sigset_t sigorg; /* original signal mask */ |
cc1a88fb | 130 | int sigfd; /* file descriptor for signalfd() */ |
edc7e420 | 131 | }; |
1f58c445 | 132 | |
a2b4dec5 KZ |
133 | static void script_init_debug(void) |
134 | { | |
135 | __UL_INIT_DEBUG(script, SCRIPT_DEBUG_, 0, SCRIPT_DEBUG); | |
136 | } | |
137 | ||
6a40c65f SK |
138 | /* |
139 | * For tests we want to be able to control time output | |
140 | */ | |
141 | #ifdef TEST_SCRIPT | |
142 | static inline time_t script_time(time_t *t) | |
143 | { | |
144 | const char *str = getenv("SCRIPT_TEST_SECOND_SINCE_EPOCH"); | |
345208a5 | 145 | int64_t sec; |
6a40c65f | 146 | |
c2f03da9 | 147 | if (!str || sscanf(str, "%"SCNi64, &sec) != 1) |
345208a5 ID |
148 | return time(t); |
149 | if (t) | |
150 | *t = (time_t)sec; | |
151 | return (time_t)sec; | |
6a40c65f SK |
152 | } |
153 | #else /* !TEST_SCRIPT */ | |
154 | # define script_time(x) time(x) | |
155 | #endif | |
156 | ||
86be6a32 | 157 | static void __attribute__((__noreturn__)) usage(void) |
3ff52639 | 158 | { |
86be6a32 | 159 | FILE *out = stdout; |
db433bf7 | 160 | fputs(USAGE_HEADER, out); |
edc7e420 | 161 | fprintf(out, _(" %s [options] [file]\n"), program_invocation_short_name); |
87d6050b | 162 | |
451dbcfa BS |
163 | fputs(USAGE_SEPARATOR, out); |
164 | fputs(_("Make a typescript of a terminal session.\n"), out); | |
165 | ||
db433bf7 | 166 | fputs(USAGE_OPTIONS, out); |
87d6050b KZ |
167 | fputs(_(" -a, --append append the output\n" |
168 | " -c, --command <command> run command rather than interactive shell\n" | |
d2ec3e33 | 169 | " -e, --return return exit code of the child process\n" |
3ff52639 | 170 | " -f, --flush run flush after each write\n" |
51b65a5b | 171 | " --force use output file even when it is a link\n" |
3ff52639 | 172 | " -q, --quiet be quiet\n" |
87d6050b | 173 | " -t, --timing[=<file>] output timing data to stderr (or to FILE)\n" |
b3054454 RM |
174 | ), out); |
175 | print_usage_help_options(25); | |
3ff52639 | 176 | |
a587cc55 | 177 | fprintf(out, USAGE_MAN_TAIL("script(1)")); |
86be6a32 | 178 | exit(EXIT_SUCCESS); |
3ff52639 SK |
179 | } |
180 | ||
edc7e420 | 181 | static void die_if_link(const struct script_control *ctl) |
93af8d8b SK |
182 | { |
183 | struct stat s; | |
6dbe3af9 | 184 | |
3f19b85f | 185 | if (ctl->force) |
93af8d8b | 186 | return; |
edc7e420 | 187 | if (lstat(ctl->fname, &s) == 0 && (S_ISLNK(s.st_mode) || s.st_nlink > 1)) |
93af8d8b SK |
188 | errx(EXIT_FAILURE, |
189 | _("output file `%s' is a link\n" | |
190 | "Use --force if you really want to use it.\n" | |
edc7e420 | 191 | "Program not started."), ctl->fname); |
93af8d8b | 192 | } |
fd4c1f63 | 193 | |
8198052c SK |
194 | static void restore_tty(struct script_control *ctl, int mode) |
195 | { | |
196 | struct termios rtt; | |
197 | ||
198 | if (!ctl->isterm) | |
199 | return; | |
200 | ||
201 | rtt = ctl->attrs; | |
202 | tcsetattr(STDIN_FILENO, mode, &rtt); | |
203 | } | |
204 | ||
7fb65db1 KZ |
205 | static void enable_rawmode_tty(struct script_control *ctl) |
206 | { | |
207 | struct termios rtt; | |
208 | ||
209 | if (!ctl->isterm) | |
210 | return; | |
211 | ||
212 | rtt = ctl->attrs; | |
213 | cfmakeraw(&rtt); | |
214 | rtt.c_lflag &= ~ECHO; | |
215 | tcsetattr(STDIN_FILENO, TCSANOW, &rtt); | |
216 | } | |
217 | ||
edc7e420 | 218 | static void __attribute__((__noreturn__)) done(struct script_control *ctl) |
93af8d8b | 219 | { |
a2b4dec5 KZ |
220 | DBG(MISC, ul_debug("done!")); |
221 | ||
8198052c SK |
222 | restore_tty(ctl, TCSADRAIN); |
223 | ||
53ca0532 | 224 | if (!ctl->quiet && ctl->typescriptfp) |
6ddf53a5 | 225 | printf(_("Script done, file is %s\n"), ctl->fname); |
f0bc3fa0 | 226 | #ifdef HAVE_LIBUTEMPTER |
6ddf53a5 SK |
227 | if (ctl->master >= 0) |
228 | utempter_remove_record(ctl->master); | |
f0bc3fa0 | 229 | #endif |
6ddf53a5 | 230 | kill(ctl->child, SIGTERM); /* make sure we don't create orphans */ |
f1014a4f | 231 | |
53ca0532 KZ |
232 | if (ctl->timingfp && close_stream(ctl->timingfp) != 0) |
233 | err(EXIT_FAILURE, "write failed: %s", ctl->tname); | |
234 | if (ctl->typescriptfp && close_stream(ctl->typescriptfp) != 0) | |
235 | err(EXIT_FAILURE, "write failed: %s", ctl->fname); | |
7e5796c9 KZ |
236 | |
237 | if (ctl->rc_wanted) { | |
edc7e420 SK |
238 | if (WIFSIGNALED(ctl->childstatus)) |
239 | exit(WTERMSIG(ctl->childstatus) + 0x80); | |
4368e3e6 | 240 | else |
edc7e420 | 241 | exit(WEXITSTATUS(ctl->childstatus)); |
1f58c445 | 242 | } |
93af8d8b SK |
243 | exit(EXIT_SUCCESS); |
244 | } | |
5c36a0eb | 245 | |
2ddadb5e | 246 | static void __attribute__((__noreturn__)) fail(struct script_control *ctl) |
edc7e420 | 247 | { |
a2b4dec5 | 248 | DBG(MISC, ul_debug("fail!")); |
93af8d8b | 249 | kill(0, SIGTERM); |
edc7e420 | 250 | done(ctl); |
6dbe3af9 KZ |
251 | } |
252 | ||
5860c45e | 253 | static void wait_for_child(struct script_control *ctl, int wait) |
93af8d8b SK |
254 | { |
255 | int status; | |
256 | pid_t pid; | |
93af8d8b SK |
257 | int options = wait ? 0 : WNOHANG; |
258 | ||
a2b4dec5 KZ |
259 | DBG(MISC, ul_debug("waiting for child")); |
260 | ||
b09feab9 | 261 | while ((pid = wait3(&status, options, NULL)) > 0) |
89a859d4 | 262 | if (pid == ctl->child) |
edc7e420 | 263 | ctl->childstatus = status; |
93af8d8b SK |
264 | } |
265 | ||
cf470183 | 266 | static void write_output(struct script_control *ctl, char *obuf, |
04639805 | 267 | ssize_t bytes) |
93af8d8b | 268 | { |
a2b4dec5 KZ |
269 | DBG(IO, ul_debug(" writing output")); |
270 | ||
3f19b85f | 271 | if (ctl->timing && ctl->timingfp) { |
04639805 | 272 | struct timeval now, delta; |
cf470183 | 273 | |
a2b4dec5 KZ |
274 | DBG(IO, ul_debug(" writing timing info")); |
275 | ||
04639805 SK |
276 | gettime_monotonic(&now); |
277 | timersub(&now, &ctl->oldtime, &delta); | |
7231fb2a RM |
278 | fprintf(ctl->timingfp, "%ld.%06ld %zd\n", |
279 | (long)delta.tv_sec, (long)delta.tv_usec, bytes); | |
3f19b85f | 280 | if (ctl->flush) |
cf470183 | 281 | fflush(ctl->timingfp); |
04639805 | 282 | ctl->oldtime = now; |
cf470183 | 283 | } |
a2b4dec5 KZ |
284 | |
285 | DBG(IO, ul_debug(" writing to script file")); | |
286 | ||
cf470183 SK |
287 | if (fwrite_all(obuf, 1, bytes, ctl->typescriptfp)) { |
288 | warn(_("cannot write script file")); | |
289 | fail(ctl); | |
290 | } | |
3f19b85f | 291 | if (ctl->flush) |
cf470183 | 292 | fflush(ctl->typescriptfp); |
a2b4dec5 KZ |
293 | |
294 | DBG(IO, ul_debug(" writing to output")); | |
295 | ||
cf470183 | 296 | if (write_all(STDOUT_FILENO, obuf, bytes)) { |
54c6611d | 297 | DBG(IO, ul_debug(" writing output *failed*")); |
cf470183 SK |
298 | warn(_("write failed")); |
299 | fail(ctl); | |
300 | } | |
54c6611d KZ |
301 | |
302 | DBG(IO, ul_debug(" writing output *done*")); | |
303 | } | |
304 | ||
b2bff066 KZ |
305 | static int write_to_shell(struct script_control *ctl, |
306 | char *buf, size_t bufsz) | |
54c6611d | 307 | { |
b2bff066 | 308 | return write_all(ctl->master, buf, bufsz); |
54c6611d KZ |
309 | } |
310 | ||
311 | /* | |
23e35eca RM |
312 | * The script(1) is usually faster than shell, so it's a good idea to wait until |
313 | * the previous message has been already read by shell from slave before we | |
314 | * write to master. This is necessary especially for EOF situation when we can | |
54c6611d KZ |
315 | * send EOF to master before shell is fully initialized, to workaround this |
316 | * problem we wait until slave is empty. For example: | |
317 | * | |
318 | * echo "date" | script | |
b2bff066 KZ |
319 | * |
320 | * Unfortunately, the child (usually shell) can ignore stdin at all, so we | |
321 | * don't wait forever to avoid dead locks... | |
322 | * | |
323 | * Note that script is primarily designed for interactive sessions as it | |
324 | * maintains master+slave tty stuff within the session. Use pipe to write to | |
325 | * script(1) and assume non-interactive (tee-like) behavior is NOT well | |
326 | * supported. | |
54c6611d | 327 | */ |
54c6611d KZ |
328 | static void write_eof_to_shell(struct script_control *ctl) |
329 | { | |
b2bff066 KZ |
330 | unsigned int tries = 0; |
331 | struct pollfd fds[] = { | |
332 | { .fd = ctl->slave, .events = POLLIN } | |
333 | }; | |
54c6611d KZ |
334 | char c = DEF_EOF; |
335 | ||
b2bff066 KZ |
336 | DBG(IO, ul_debug(" waiting for empty slave")); |
337 | while (poll(fds, 1, 10) == 1 && tries < 8) { | |
338 | DBG(IO, ul_debug(" slave is not empty")); | |
339 | xusleep(250000); | |
340 | tries++; | |
341 | } | |
342 | if (tries < 8) | |
343 | DBG(IO, ul_debug(" slave is empty now")); | |
344 | ||
54c6611d KZ |
345 | DBG(IO, ul_debug(" sending EOF to master")); |
346 | write_to_shell(ctl, &c, sizeof(char)); | |
364cda48 KZ |
347 | } |
348 | ||
54c6611d | 349 | static void handle_io(struct script_control *ctl, int fd, int *eof) |
93af8d8b | 350 | { |
6ddf53a5 | 351 | char buf[BUFSIZ]; |
a8896ad5 SK |
352 | ssize_t bytes; |
353 | ||
a2b4dec5 | 354 | DBG(IO, ul_debug("%d FD active", fd)); |
54c6611d | 355 | *eof = 0; |
a2b4dec5 | 356 | |
da26af4e | 357 | /* read from active FD */ |
a8896ad5 SK |
358 | bytes = read(fd, buf, sizeof(buf)); |
359 | if (bytes < 0) { | |
54c6611d | 360 | if (errno == EAGAIN || errno == EINTR) |
a8896ad5 SK |
361 | return; |
362 | fail(ctl); | |
363 | } | |
da26af4e | 364 | |
54c6611d | 365 | if (bytes == 0) { |
7dbcd80e | 366 | *eof = 1; |
54c6611d KZ |
367 | return; |
368 | } | |
369 | ||
da26af4e KZ |
370 | /* from stdin (user) to command */ |
371 | if (fd == STDIN_FILENO) { | |
2e9418b7 | 372 | DBG(IO, ul_debug(" stdin --> master %zd bytes", bytes)); |
a2b4dec5 | 373 | |
54c6611d | 374 | if (write_to_shell(ctl, buf, bytes)) { |
a8896ad5 SK |
375 | warn(_("write failed")); |
376 | fail(ctl); | |
377 | } | |
378 | /* without sync write_output() will write both input & | |
379 | * shell output that looks like double echoing */ | |
380 | fdatasync(ctl->master); | |
da26af4e KZ |
381 | |
382 | /* from command (master) to stdout */ | |
a2b4dec5 | 383 | } else if (fd == ctl->master) { |
2e9418b7 | 384 | DBG(IO, ul_debug(" master --> stdout %zd bytes", bytes)); |
04639805 | 385 | write_output(ctl, buf, bytes); |
a2b4dec5 | 386 | } |
a8896ad5 SK |
387 | } |
388 | ||
389 | static void handle_signal(struct script_control *ctl, int fd) | |
390 | { | |
391 | struct signalfd_siginfo info; | |
392 | ssize_t bytes; | |
393 | ||
a2b4dec5 KZ |
394 | DBG(SIGNAL, ul_debug("signal FD %d active", fd)); |
395 | ||
a8896ad5 | 396 | bytes = read(fd, &info, sizeof(info)); |
d35ffe80 | 397 | if (bytes != sizeof(info)) { |
7dbcd80e | 398 | if (bytes < 0 && (errno == EAGAIN || errno == EINTR)) |
d35ffe80 KZ |
399 | return; |
400 | fail(ctl); | |
401 | } | |
da26af4e | 402 | |
a8896ad5 SK |
403 | switch (info.ssi_signo) { |
404 | case SIGCHLD: | |
b2bff066 | 405 | DBG(SIGNAL, ul_debug(" get signal SIGCHLD")); |
5860c45e | 406 | wait_for_child(ctl, 0); |
a8896ad5 SK |
407 | ctl->poll_timeout = 10; |
408 | return; | |
409 | case SIGWINCH: | |
b2bff066 | 410 | DBG(SIGNAL, ul_debug(" get signal SIGWINCH")); |
a8896ad5 SK |
411 | if (ctl->isterm) { |
412 | ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&ctl->win); | |
413 | ioctl(ctl->slave, TIOCSWINSZ, (char *)&ctl->win); | |
414 | } | |
415 | break; | |
5860c45e KZ |
416 | case SIGTERM: |
417 | /* fallthrough */ | |
418 | case SIGINT: | |
419 | /* fallthrough */ | |
420 | case SIGQUIT: | |
b2bff066 | 421 | DBG(SIGNAL, ul_debug(" get signal SIG{TERM,INT,QUIT}")); |
5860c45e KZ |
422 | fprintf(stderr, _("\nSession terminated.\n")); |
423 | /* Child termination is going to generate SIGCHILD (see above) */ | |
424 | kill(ctl->child, SIGTERM); | |
425 | return; | |
a8896ad5 SK |
426 | default: |
427 | abort(); | |
428 | } | |
429 | } | |
430 | ||
431 | static void do_io(struct script_control *ctl) | |
432 | { | |
54c6611d | 433 | int ret, ignore_stdin = 0, eof = 0; |
076ffa48 | 434 | time_t tvec = script_time((time_t *)NULL); |
a8896ad5 | 435 | char buf[128]; |
da26af4e | 436 | enum { |
54c6611d | 437 | POLLFD_SIGNAL = 0, |
da26af4e | 438 | POLLFD_MASTER, |
54c6611d KZ |
439 | POLLFD_STDIN /* optional; keep it last, see ignore_stdin */ |
440 | ||
da26af4e KZ |
441 | }; |
442 | struct pollfd pfd[] = { | |
0664d41d SK |
443 | [POLLFD_SIGNAL] = { .fd = ctl->sigfd, .events = POLLIN | POLLERR | POLLHUP }, |
444 | [POLLFD_MASTER] = { .fd = ctl->master, .events = POLLIN | POLLERR | POLLHUP }, | |
445 | [POLLFD_STDIN] = { .fd = STDIN_FILENO, .events = POLLIN | POLLERR | POLLHUP } | |
da26af4e KZ |
446 | }; |
447 | ||
6dbe3af9 | 448 | |
760e5e68 SK |
449 | if ((ctl->typescriptfp = |
450 | fopen(ctl->fname, ctl->append ? "a" UL_CLOEXECSTR : "w" UL_CLOEXECSTR)) == NULL) { | |
8198052c SK |
451 | |
452 | restore_tty(ctl, TCSANOW); | |
9580536a SK |
453 | warn(_("cannot open %s"), ctl->fname); |
454 | fail(ctl); | |
455 | } | |
3f19b85f | 456 | if (ctl->timing) { |
8198052c SK |
457 | const char *tname = ctl->tname ? ctl->tname : "/dev/stderr"; |
458 | ||
459 | if (!(ctl->timingfp = fopen(tname, "w" UL_CLOEXECSTR))) { | |
460 | restore_tty(ctl, TCSANOW); | |
461 | warn(_("cannot open %s"), tname); | |
462 | fail(ctl); | |
463 | } | |
9580536a | 464 | } |
4368e3e6 | 465 | |
54c6611d | 466 | |
6f3c9c34 KZ |
467 | if (ctl->typescriptfp) { |
468 | strtime_iso(&tvec, ISO_8601_DATE | ISO_8601_TIME | | |
bdef362d RZ |
469 | ISO_8601_TIMEZONE | ISO_8601_SPACE, |
470 | buf, sizeof(buf)); | |
471 | fprintf(ctl->typescriptfp, _("Script started on %s\n"), buf); | |
d0542241 | 472 | } |
04639805 | 473 | gettime_monotonic(&ctl->oldtime); |
a5a16c68 | 474 | |
6ddf53a5 | 475 | while (!ctl->die) { |
da26af4e | 476 | size_t i; |
7dbcd80e | 477 | int errsv; |
da26af4e | 478 | |
a2b4dec5 KZ |
479 | DBG(POLL, ul_debug("calling poll()")); |
480 | ||
cf470183 | 481 | /* wait for input or signal */ |
54c6611d | 482 | ret = poll(pfd, ARRAY_SIZE(pfd) - ignore_stdin, ctl->poll_timeout); |
7dbcd80e | 483 | errsv = errno; |
a2b4dec5 KZ |
484 | DBG(POLL, ul_debug("poll() rc=%d", ret)); |
485 | ||
cf470183 | 486 | if (ret < 0) { |
7dbcd80e | 487 | if (errsv == EAGAIN) |
cf470183 SK |
488 | continue; |
489 | warn(_("poll failed")); | |
edc7e420 | 490 | fail(ctl); |
36c1d79b | 491 | } |
a2b4dec5 KZ |
492 | if (ret == 0) { |
493 | DBG(POLL, ul_debug("setting die=1")); | |
89a859d4 | 494 | ctl->die = 1; |
5860c45e | 495 | break; |
a2b4dec5 | 496 | } |
da26af4e | 497 | |
54c6611d | 498 | for (i = 0; i < ARRAY_SIZE(pfd) - ignore_stdin; i++) { |
cf470183 SK |
499 | if (pfd[i].revents == 0) |
500 | continue; | |
a2b4dec5 | 501 | |
2e9418b7 | 502 | DBG(POLL, ul_debug(" active pfd[%s].fd=%d %s %s %s", |
54c6611d | 503 | i == POLLFD_STDIN ? "stdin" : |
2e9418b7 KZ |
504 | i == POLLFD_MASTER ? "master" : |
505 | i == POLLFD_SIGNAL ? "signal" : "???", | |
506 | pfd[i].fd, | |
54c6611d | 507 | pfd[i].revents & POLLIN ? "POLLIN" : "", |
1200cfbf KZ |
508 | pfd[i].revents & POLLHUP ? "POLLHUP" : "", |
509 | pfd[i].revents & POLLERR ? "POLLERR" : "")); | |
da26af4e KZ |
510 | switch (i) { |
511 | case POLLFD_STDIN: | |
512 | case POLLFD_MASTER: | |
54c6611d KZ |
513 | /* data */ |
514 | if (pfd[i].revents & POLLIN) | |
515 | handle_io(ctl, pfd[i].fd, &eof); | |
516 | /* EOF maybe detected by two ways: | |
517 | * A) poll() return POLLHUP event after close() | |
7dbcd80e | 518 | * B) read() returns 0 (no data) */ |
54c6611d KZ |
519 | if ((pfd[i].revents & POLLHUP) || eof) { |
520 | DBG(POLL, ul_debug(" ignore FD")); | |
521 | pfd[i].fd = -1; | |
522 | /* according to man poll() set FD to -1 can't be used to ignore | |
523 | * STDIN, so let's remove the FD from pool at all */ | |
524 | if (i == POLLFD_STDIN) { | |
525 | ignore_stdin = 1; | |
526 | write_eof_to_shell(ctl); | |
b2bff066 | 527 | DBG(POLL, ul_debug(" ignore STDIN")); |
54c6611d KZ |
528 | } |
529 | } | |
cf470183 | 530 | continue; |
da26af4e | 531 | case POLLFD_SIGNAL: |
a8896ad5 | 532 | handle_signal(ctl, pfd[i].fd); |
da26af4e | 533 | break; |
cf470183 | 534 | } |
968e632c | 535 | } |
cf470183 | 536 | } |
a2b4dec5 KZ |
537 | |
538 | DBG(POLL, ul_debug("poll() done")); | |
539 | ||
6ddf53a5 | 540 | if (!ctl->die) |
5860c45e | 541 | wait_for_child(ctl, 1); |
6f3c9c34 KZ |
542 | |
543 | if (ctl->typescriptfp) { | |
8353549e | 544 | tvec = script_time((time_t *)NULL); |
6f3c9c34 | 545 | strtime_iso(&tvec, ISO_8601_DATE | ISO_8601_TIME | |
bdef362d RZ |
546 | ISO_8601_TIMEZONE | ISO_8601_SPACE, |
547 | buf, sizeof(buf)); | |
548 | fprintf(ctl->typescriptfp, _("\nScript done on %s\n"), buf); | |
6ddf53a5 SK |
549 | } |
550 | done(ctl); | |
6dbe3af9 KZ |
551 | } |
552 | ||
edc7e420 | 553 | static void getslave(struct script_control *ctl) |
93af8d8b SK |
554 | { |
555 | #ifndef HAVE_LIBUTIL | |
edc7e420 | 556 | ctl->line[strlen("/dev/")] = 't'; |
760e5e68 | 557 | ctl->slave = open(ctl->line, O_RDWR | O_CLOEXEC); |
edc7e420 SK |
558 | if (ctl->slave < 0) { |
559 | warn(_("cannot open %s"), ctl->line); | |
560 | fail(ctl); | |
93af8d8b | 561 | } |
edc7e420 | 562 | if (ctl->isterm) { |
3f19b85f | 563 | tcsetattr(ctl->slave, TCSANOW, &ctl->attrs); |
edc7e420 | 564 | ioctl(ctl->slave, TIOCSWINSZ, (char *)&ctl->win); |
93af8d8b SK |
565 | } |
566 | #endif | |
567 | setsid(); | |
edc7e420 | 568 | ioctl(ctl->slave, TIOCSCTTY, 0); |
93af8d8b SK |
569 | } |
570 | ||
54c6611d | 571 | /* don't use DBG() stuff here otherwise it will be in the typescript file */ |
d35ffe80 | 572 | static void __attribute__((__noreturn__)) do_shell(struct script_control *ctl) |
93af8d8b | 573 | { |
df1dddf9 KZ |
574 | char *shname; |
575 | ||
edc7e420 | 576 | getslave(ctl); |
4368e3e6 KZ |
577 | |
578 | /* close things irrelevant for this process */ | |
edc7e420 | 579 | close(ctl->master); |
d35ffe80 | 580 | close(ctl->sigfd); |
4368e3e6 | 581 | |
edc7e420 SK |
582 | dup2(ctl->slave, STDIN_FILENO); |
583 | dup2(ctl->slave, STDOUT_FILENO); | |
584 | dup2(ctl->slave, STDERR_FILENO); | |
585 | close(ctl->slave); | |
df1dddf9 | 586 | |
edc7e420 | 587 | ctl->master = -1; |
f0bc3fa0 | 588 | |
edc7e420 | 589 | shname = strrchr(ctl->shell, '/'); |
df1dddf9 KZ |
590 | if (shname) |
591 | shname++; | |
592 | else | |
edc7e420 | 593 | shname = ctl->shell; |
df1dddf9 | 594 | |
d35ffe80 KZ |
595 | sigprocmask(SIG_SETMASK, &ctl->sigorg, NULL); |
596 | ||
a17f3264 KZ |
597 | /* |
598 | * When invoked from within /etc/csh.login, script spawns a csh shell | |
599 | * that spawns programs that cannot be killed with a SIGTERM. This is | |
ee312c65 | 600 | * because csh has a documented behavior wherein it disables all |
a17f3264 KZ |
601 | * signals when processing the /etc/csh.* files. |
602 | * | |
603 | * Let's restore the default behavior. | |
604 | */ | |
605 | signal(SIGTERM, SIG_DFL); | |
606 | ||
edc7e420 | 607 | if (access(ctl->shell, X_OK) == 0) { |
3f19b85f KZ |
608 | if (ctl->command) |
609 | execl(ctl->shell, shname, "-c", ctl->command, NULL); | |
b4ff2f54 | 610 | else |
edc7e420 | 611 | execl(ctl->shell, shname, "-i", NULL); |
b4ff2f54 | 612 | } else { |
3f19b85f KZ |
613 | if (ctl->command) |
614 | execlp(shname, "-c", ctl->command, NULL); | |
b4ff2f54 SK |
615 | else |
616 | execlp(shname, "-i", NULL); | |
617 | } | |
edc7e420 SK |
618 | warn(_("failed to execute %s"), ctl->shell); |
619 | fail(ctl); | |
6dbe3af9 KZ |
620 | } |
621 | ||
6dbe3af9 | 622 | |
edc7e420 | 623 | static void getmaster(struct script_control *ctl) |
93af8d8b | 624 | { |
e11a5e63 | 625 | #if defined(HAVE_LIBUTIL) && defined(HAVE_PTY_H) |
3822032d KZ |
626 | int rc; |
627 | ||
edc7e420 | 628 | ctl->isterm = isatty(STDIN_FILENO); |
3822032d | 629 | |
edc7e420 | 630 | if (ctl->isterm) { |
3f19b85f | 631 | if (tcgetattr(STDIN_FILENO, &ctl->attrs) != 0) |
3822032d | 632 | err(EXIT_FAILURE, _("failed to get terminal attributes")); |
edc7e420 | 633 | ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&ctl->win); |
3f19b85f | 634 | rc = openpty(&ctl->master, &ctl->slave, NULL, &ctl->attrs, &ctl->win); |
3822032d | 635 | } else |
edc7e420 | 636 | rc = openpty(&ctl->master, &ctl->slave, NULL, NULL, NULL); |
3822032d KZ |
637 | |
638 | if (rc < 0) { | |
360d5005 | 639 | warn(_("openpty failed")); |
edc7e420 | 640 | fail(ctl); |
5c36a0eb KZ |
641 | } |
642 | #else | |
6dbe3af9 | 643 | char *pty, *bank, *cp; |
6dbe3af9 | 644 | |
edc7e420 | 645 | ctl->isterm = isatty(STDIN_FILENO); |
3822032d | 646 | |
edc7e420 | 647 | pty = &ctl->line[strlen("/dev/ptyp")]; |
6dbe3af9 | 648 | for (bank = "pqrs"; *bank; bank++) { |
edc7e420 | 649 | ctl->line[strlen("/dev/pty")] = *bank; |
6dbe3af9 | 650 | *pty = '0'; |
a4aeb5bd | 651 | if (access(ctl->line, F_OK) != 0) |
6dbe3af9 KZ |
652 | break; |
653 | for (cp = "0123456789abcdef"; *cp; cp++) { | |
654 | *pty = *cp; | |
760e5e68 | 655 | ctl->master = open(ctl->line, O_RDWR | O_CLOEXEC); |
edc7e420 SK |
656 | if (ctl->master >= 0) { |
657 | char *tp = &ctl->line[strlen("/dev/")]; | |
6dbe3af9 KZ |
658 | int ok; |
659 | ||
660 | /* verify slave side is usable */ | |
661 | *tp = 't'; | |
edc7e420 | 662 | ok = access(ctl->line, R_OK | W_OK) == 0; |
6dbe3af9 KZ |
663 | *tp = 'p'; |
664 | if (ok) { | |
edc7e420 | 665 | if (ctl->isterm) { |
3f19b85f | 666 | tcgetattr(STDIN_FILENO, &ctl->attrs); |
edc7e420 | 667 | ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&ctl->win); |
3822032d | 668 | } |
6dbe3af9 KZ |
669 | return; |
670 | } | |
edc7e420 SK |
671 | close(ctl->master); |
672 | ctl->master = -1; | |
6dbe3af9 KZ |
673 | } |
674 | } | |
675 | } | |
edc7e420 | 676 | ctl->master = -1; |
360d5005 | 677 | warn(_("out of pty's")); |
edc7e420 SK |
678 | fail(ctl); |
679 | #endif /* not HAVE_LIBUTIL */ | |
54c6611d KZ |
680 | |
681 | DBG(IO, ul_debug("master fd: %d", ctl->master)); | |
6dbe3af9 KZ |
682 | } |
683 | ||
93af8d8b SK |
684 | int main(int argc, char **argv) |
685 | { | |
edc7e420 SK |
686 | struct script_control ctl = { |
687 | #if !HAVE_LIBUTIL || !HAVE_PTY_H | |
688 | .line = "/dev/ptyXX", | |
689 | #endif | |
690 | .master = -1, | |
89a859d4 | 691 | .poll_timeout = -1, |
edc7e420 SK |
692 | 0 |
693 | }; | |
93af8d8b SK |
694 | int ch; |
695 | ||
696 | enum { FORCE_OPTION = CHAR_MAX + 1 }; | |
697 | ||
698 | static const struct option longopts[] = { | |
edc7e420 SK |
699 | {"append", no_argument, NULL, 'a'}, |
700 | {"command", required_argument, NULL, 'c'}, | |
701 | {"return", no_argument, NULL, 'e'}, | |
702 | {"flush", no_argument, NULL, 'f'}, | |
703 | {"force", no_argument, NULL, FORCE_OPTION,}, | |
704 | {"quiet", no_argument, NULL, 'q'}, | |
705 | {"timing", optional_argument, NULL, 't'}, | |
706 | {"version", no_argument, NULL, 'V'}, | |
707 | {"help", no_argument, NULL, 'h'}, | |
708 | {NULL, 0, NULL, 0} | |
93af8d8b SK |
709 | }; |
710 | ||
711 | setlocale(LC_ALL, ""); | |
b09feab9 SK |
712 | /* |
713 | * script -t prints time delays as floating point numbers. The example | |
714 | * program (scriptreplay) that we provide to handle this timing output | |
715 | * is a perl script, and does not handle numbers in locale format (not | |
716 | * even when "use locale;" is added). So, since these numbers are not | |
717 | * for human consumption, it seems easiest to set LC_NUMERIC here. | |
718 | */ | |
719 | setlocale(LC_NUMERIC, "C"); | |
93af8d8b SK |
720 | bindtextdomain(PACKAGE, LOCALEDIR); |
721 | textdomain(PACKAGE); | |
722 | atexit(close_stdout); | |
723 | ||
a2b4dec5 KZ |
724 | script_init_debug(); |
725 | ||
93af8d8b | 726 | while ((ch = getopt_long(argc, argv, "ac:efqt::Vh", longopts, NULL)) != -1) |
edc7e420 | 727 | switch (ch) { |
93af8d8b | 728 | case 'a': |
3f19b85f | 729 | ctl.append = 1; |
93af8d8b SK |
730 | break; |
731 | case 'c': | |
3f19b85f | 732 | ctl.command = optarg; |
93af8d8b SK |
733 | break; |
734 | case 'e': | |
7e5796c9 | 735 | ctl.rc_wanted = 1; |
93af8d8b SK |
736 | break; |
737 | case 'f': | |
3f19b85f | 738 | ctl.flush = 1; |
93af8d8b SK |
739 | break; |
740 | case FORCE_OPTION: | |
3f19b85f | 741 | ctl.force = 1; |
93af8d8b SK |
742 | break; |
743 | case 'q': | |
3f19b85f | 744 | ctl.quiet = 1; |
93af8d8b SK |
745 | break; |
746 | case 't': | |
9580536a SK |
747 | if (optarg) |
748 | ctl.tname = optarg; | |
3f19b85f | 749 | ctl.timing = 1; |
93af8d8b SK |
750 | break; |
751 | case 'V': | |
752 | printf(UTIL_LINUX_VERSION); | |
753 | exit(EXIT_SUCCESS); | |
754 | break; | |
755 | case 'h': | |
86be6a32 | 756 | usage(); |
93af8d8b | 757 | break; |
93af8d8b | 758 | default: |
677ec86c | 759 | errtryhelp(EXIT_FAILURE); |
93af8d8b SK |
760 | } |
761 | argc -= optind; | |
762 | argv += optind; | |
763 | ||
764 | if (argc > 0) | |
edc7e420 | 765 | ctl.fname = argv[0]; |
93af8d8b | 766 | else { |
7e5796c9 | 767 | ctl.fname = DEFAULT_TYPESCRIPT_FILENAME; |
edc7e420 | 768 | die_if_link(&ctl); |
6dbe3af9 | 769 | } |
93af8d8b | 770 | |
edc7e420 SK |
771 | ctl.shell = getenv("SHELL"); |
772 | if (ctl.shell == NULL) | |
773 | ctl.shell = _PATH_BSHELL; | |
93af8d8b | 774 | |
edc7e420 | 775 | getmaster(&ctl); |
3f19b85f | 776 | if (!ctl.quiet) |
edc7e420 | 777 | printf(_("Script started, file is %s\n"), ctl.fname); |
7fb65db1 | 778 | enable_rawmode_tty(&ctl); |
93af8d8b SK |
779 | |
780 | #ifdef HAVE_LIBUTEMPTER | |
edc7e420 | 781 | utempter_add_record(ctl.master, NULL); |
5c36a0eb | 782 | #endif |
cc1a88fb | 783 | /* setup signal handler */ |
d35ffe80 KZ |
784 | sigemptyset(&ctl.sigset); |
785 | sigaddset(&ctl.sigset, SIGCHLD); | |
786 | sigaddset(&ctl.sigset, SIGWINCH); | |
5860c45e KZ |
787 | sigaddset(&ctl.sigset, SIGTERM); |
788 | sigaddset(&ctl.sigset, SIGINT); | |
789 | sigaddset(&ctl.sigset, SIGQUIT); | |
d35ffe80 KZ |
790 | |
791 | /* block signals used for signalfd() to prevent the signals being | |
792 | * handled according to their default dispositions */ | |
793 | sigprocmask(SIG_BLOCK, &ctl.sigset, &ctl.sigorg); | |
a2b4dec5 | 794 | |
760e5e68 | 795 | if ((ctl.sigfd = signalfd(-1, &ctl.sigset, SFD_CLOEXEC)) < 0) |
cc1a88fb | 796 | err(EXIT_FAILURE, _("cannot set signal handler")); |
93af8d8b | 797 | |
a2b4dec5 KZ |
798 | DBG(SIGNAL, ul_debug("signal fd=%d", ctl.sigfd)); |
799 | ||
93af8d8b | 800 | fflush(stdout); |
edc7e420 | 801 | ctl.child = fork(); |
93af8d8b | 802 | |
d35ffe80 KZ |
803 | switch (ctl.child) { |
804 | case -1: /* error */ | |
93af8d8b | 805 | warn(_("fork failed")); |
edc7e420 | 806 | fail(&ctl); |
d35ffe80 KZ |
807 | break; |
808 | case 0: /* child */ | |
809 | do_shell(&ctl); | |
810 | break; | |
811 | default: /* parent */ | |
812 | do_io(&ctl); | |
813 | break; | |
93af8d8b | 814 | } |
d35ffe80 KZ |
815 | |
816 | /* should not happen, all used functions are non-return */ | |
b09feab9 | 817 | return EXIT_FAILURE; |
6dbe3af9 | 818 | } |