]>
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> | |
2b7ff0d9 | 65 | |
cdd2a8c3 | 66 | #include "closestream.h" |
7eda085c | 67 | #include "nls.h" |
91239874 | 68 | #include "c.h" |
3822032d | 69 | #include "ttyutils.h" |
c6fca22e | 70 | #include "all-io.h" |
04639805 | 71 | #include "monotonic.h" |
6dbe3af9 | 72 | |
e11a5e63 SK |
73 | #if defined(HAVE_LIBUTIL) && defined(HAVE_PTY_H) |
74 | # include <pty.h> | |
5c36a0eb KZ |
75 | #endif |
76 | ||
f0bc3fa0 | 77 | #ifdef HAVE_LIBUTEMPTER |
e11a5e63 | 78 | # include <utempter.h> |
f0bc3fa0 KZ |
79 | #endif |
80 | ||
7e5796c9 | 81 | #define DEFAULT_TYPESCRIPT_FILENAME "typescript" |
05644dab | 82 | |
6ddf53a5 | 83 | enum { POLLFDS = 3 }; |
cf470183 | 84 | |
edc7e420 SK |
85 | struct script_control { |
86 | char *shell; /* shell to be executed */ | |
3f19b85f | 87 | char *command; /* command to be executed */ |
edc7e420 SK |
88 | char *fname; /* output file path */ |
89 | FILE *typescriptfp; /* output file pointer */ | |
9580536a | 90 | char *tname; /* timing file path */ |
edc7e420 | 91 | FILE *timingfp; /* timing file pointer */ |
04639805 | 92 | struct timeval oldtime; /* previous write or command start time */ |
edc7e420 SK |
93 | int master; /* pseudoterminal master file descriptor */ |
94 | int slave; /* pseudoterminal slave file descriptor */ | |
89a859d4 | 95 | int poll_timeout; /* poll() timeout, used in end of execution */ |
edc7e420 | 96 | pid_t child; /* child pid */ |
edc7e420 | 97 | int childstatus; /* child process exit value */ |
3f19b85f | 98 | struct termios attrs; /* slave terminal runtime attributes */ |
edc7e420 | 99 | struct winsize win; /* terminal window size */ |
9779651e | 100 | #if !HAVE_LIBUTIL || !HAVE_PTY_H |
edc7e420 | 101 | char line *; /* terminal line */ |
5c36a0eb | 102 | #endif |
edc7e420 | 103 | unsigned int |
3f19b85f | 104 | append:1, /* append output */ |
7e5796c9 | 105 | rc_wanted:1, /* return child exit value */ |
3f19b85f KZ |
106 | flush:1, /* flush after each write */ |
107 | quiet:1, /* suppress most output */ | |
108 | timing:1, /* include timing file */ | |
109 | force:1, /* write output to links */ | |
edc7e420 | 110 | isterm:1, /* is child process running as terminal */ |
edc7e420 | 111 | die:1; /* terminate program */ |
7e5796c9 | 112 | |
cc1a88fb SK |
113 | sigset_t sigset; /* catch SIGCHLD and SIGWINCH with signalfd() */ |
114 | int sigfd; /* file descriptor for signalfd() */ | |
edc7e420 | 115 | }; |
1f58c445 | 116 | |
6a40c65f SK |
117 | /* |
118 | * For tests we want to be able to control time output | |
119 | */ | |
120 | #ifdef TEST_SCRIPT | |
121 | static inline time_t script_time(time_t *t) | |
122 | { | |
123 | const char *str = getenv("SCRIPT_TEST_SECOND_SINCE_EPOCH"); | |
124 | time_t sec; | |
125 | ||
126 | if (str && sscanf(str, "%ld", &sec) == 1) | |
127 | return sec; | |
128 | return time(t); | |
129 | } | |
130 | #else /* !TEST_SCRIPT */ | |
131 | # define script_time(x) time(x) | |
132 | #endif | |
133 | ||
93af8d8b | 134 | static void __attribute__((__noreturn__)) usage(FILE *out) |
3ff52639 | 135 | { |
db433bf7 | 136 | fputs(USAGE_HEADER, out); |
edc7e420 | 137 | fprintf(out, _(" %s [options] [file]\n"), program_invocation_short_name); |
87d6050b | 138 | |
451dbcfa BS |
139 | fputs(USAGE_SEPARATOR, out); |
140 | fputs(_("Make a typescript of a terminal session.\n"), out); | |
141 | ||
db433bf7 | 142 | fputs(USAGE_OPTIONS, out); |
87d6050b KZ |
143 | fputs(_(" -a, --append append the output\n" |
144 | " -c, --command <command> run command rather than interactive shell\n" | |
d2ec3e33 | 145 | " -e, --return return exit code of the child process\n" |
3ff52639 | 146 | " -f, --flush run flush after each write\n" |
51b65a5b | 147 | " --force use output file even when it is a link\n" |
3ff52639 | 148 | " -q, --quiet be quiet\n" |
87d6050b | 149 | " -t, --timing[=<file>] output timing data to stderr (or to FILE)\n" |
3ff52639 | 150 | " -V, --version output version information and exit\n" |
87d6050b | 151 | " -h, --help display this help and exit\n\n"), out); |
3ff52639 | 152 | |
a587cc55 | 153 | fprintf(out, USAGE_MAN_TAIL("script(1)")); |
3ff52639 SK |
154 | exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS); |
155 | } | |
156 | ||
edc7e420 | 157 | static void die_if_link(const struct script_control *ctl) |
93af8d8b SK |
158 | { |
159 | struct stat s; | |
6dbe3af9 | 160 | |
3f19b85f | 161 | if (ctl->force) |
93af8d8b | 162 | return; |
edc7e420 | 163 | if (lstat(ctl->fname, &s) == 0 && (S_ISLNK(s.st_mode) || s.st_nlink > 1)) |
93af8d8b SK |
164 | errx(EXIT_FAILURE, |
165 | _("output file `%s' is a link\n" | |
166 | "Use --force if you really want to use it.\n" | |
edc7e420 | 167 | "Program not started."), ctl->fname); |
93af8d8b | 168 | } |
fd4c1f63 | 169 | |
edc7e420 | 170 | static void __attribute__((__noreturn__)) done(struct script_control *ctl) |
93af8d8b | 171 | { |
6ddf53a5 | 172 | if (ctl->isterm) |
3f19b85f KZ |
173 | tcsetattr(STDIN_FILENO, TCSADRAIN, &ctl->attrs); |
174 | if (!ctl->quiet) | |
6ddf53a5 | 175 | printf(_("Script done, file is %s\n"), ctl->fname); |
f0bc3fa0 | 176 | #ifdef HAVE_LIBUTEMPTER |
6ddf53a5 SK |
177 | if (ctl->master >= 0) |
178 | utempter_remove_record(ctl->master); | |
f0bc3fa0 | 179 | #endif |
6ddf53a5 | 180 | kill(ctl->child, SIGTERM); /* make sure we don't create orphans */ |
f1014a4f | 181 | |
7e5796c9 KZ |
182 | if (ctl->timingfp) |
183 | fclose(ctl->timingfp); | |
184 | fclose(ctl->typescriptfp); | |
185 | ||
186 | if (ctl->rc_wanted) { | |
edc7e420 SK |
187 | if (WIFSIGNALED(ctl->childstatus)) |
188 | exit(WTERMSIG(ctl->childstatus) + 0x80); | |
4368e3e6 | 189 | else |
edc7e420 | 190 | exit(WEXITSTATUS(ctl->childstatus)); |
1f58c445 | 191 | } |
93af8d8b SK |
192 | exit(EXIT_SUCCESS); |
193 | } | |
5c36a0eb | 194 | |
2ddadb5e | 195 | static void __attribute__((__noreturn__)) fail(struct script_control *ctl) |
edc7e420 | 196 | { |
93af8d8b | 197 | kill(0, SIGTERM); |
edc7e420 | 198 | done(ctl); |
6dbe3af9 KZ |
199 | } |
200 | ||
edc7e420 | 201 | static void finish(struct script_control *ctl, int wait) |
93af8d8b SK |
202 | { |
203 | int status; | |
204 | pid_t pid; | |
93af8d8b SK |
205 | int options = wait ? 0 : WNOHANG; |
206 | ||
b09feab9 | 207 | while ((pid = wait3(&status, options, NULL)) > 0) |
89a859d4 | 208 | if (pid == ctl->child) |
edc7e420 | 209 | ctl->childstatus = status; |
93af8d8b SK |
210 | } |
211 | ||
cf470183 | 212 | static void write_output(struct script_control *ctl, char *obuf, |
04639805 | 213 | ssize_t bytes) |
93af8d8b | 214 | { |
3f19b85f | 215 | if (ctl->timing && ctl->timingfp) { |
04639805 | 216 | struct timeval now, delta; |
cf470183 | 217 | |
04639805 SK |
218 | gettime_monotonic(&now); |
219 | timersub(&now, &ctl->oldtime, &delta); | |
220 | fprintf(ctl->timingfp, "%ld.%06ld %zd\n", delta.tv_sec, delta.tv_usec, bytes); | |
3f19b85f | 221 | if (ctl->flush) |
cf470183 | 222 | fflush(ctl->timingfp); |
04639805 | 223 | ctl->oldtime = now; |
cf470183 SK |
224 | } |
225 | if (fwrite_all(obuf, 1, bytes, ctl->typescriptfp)) { | |
226 | warn(_("cannot write script file")); | |
227 | fail(ctl); | |
228 | } | |
3f19b85f | 229 | if (ctl->flush) |
cf470183 SK |
230 | fflush(ctl->typescriptfp); |
231 | if (write_all(STDOUT_FILENO, obuf, bytes)) { | |
232 | warn(_("write failed")); | |
233 | fail(ctl); | |
234 | } | |
364cda48 KZ |
235 | } |
236 | ||
04639805 | 237 | static void handle_io(struct script_control *ctl, int fd, int i) |
93af8d8b | 238 | { |
6ddf53a5 | 239 | char buf[BUFSIZ]; |
a8896ad5 SK |
240 | ssize_t bytes; |
241 | ||
242 | bytes = read(fd, buf, sizeof(buf)); | |
243 | if (bytes < 0) { | |
244 | if (errno == EAGAIN) | |
245 | return; | |
246 | fail(ctl); | |
247 | } | |
248 | if (i == 0) { | |
249 | if (write_all(ctl->master, buf, bytes)) { | |
250 | warn(_("write failed")); | |
251 | fail(ctl); | |
252 | } | |
253 | /* without sync write_output() will write both input & | |
254 | * shell output that looks like double echoing */ | |
255 | fdatasync(ctl->master); | |
256 | if (!ctl->isterm && feof(stdin)) { | |
257 | char c = DEF_EOF; | |
258 | write_all(ctl->master, &c, sizeof(char)); | |
259 | } | |
260 | } else | |
04639805 | 261 | write_output(ctl, buf, bytes); |
a8896ad5 SK |
262 | } |
263 | ||
264 | static void handle_signal(struct script_control *ctl, int fd) | |
265 | { | |
266 | struct signalfd_siginfo info; | |
267 | ssize_t bytes; | |
268 | ||
269 | bytes = read(fd, &info, sizeof(info)); | |
270 | assert(bytes == sizeof(info)); | |
271 | switch (info.ssi_signo) { | |
272 | case SIGCHLD: | |
273 | finish(ctl, 0); | |
274 | ctl->poll_timeout = 10; | |
275 | return; | |
276 | case SIGWINCH: | |
277 | if (ctl->isterm) { | |
278 | ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&ctl->win); | |
279 | ioctl(ctl->slave, TIOCSWINSZ, (char *)&ctl->win); | |
280 | } | |
281 | break; | |
282 | default: | |
283 | abort(); | |
284 | } | |
285 | } | |
286 | ||
287 | static void do_io(struct script_control *ctl) | |
288 | { | |
cf470183 SK |
289 | struct pollfd pfd[POLLFDS]; |
290 | int ret, i; | |
076ffa48 | 291 | time_t tvec = script_time((time_t *)NULL); |
a8896ad5 | 292 | char buf[128]; |
6dbe3af9 | 293 | |
3f19b85f | 294 | if ((ctl->typescriptfp = fopen(ctl->fname, ctl->append ? "a" : "w")) == NULL) { |
9580536a SK |
295 | warn(_("cannot open %s"), ctl->fname); |
296 | fail(ctl); | |
297 | } | |
3f19b85f | 298 | if (ctl->timing) { |
9580536a SK |
299 | if (!ctl->tname) { |
300 | if (!(ctl->timingfp = fopen("/dev/stderr", "w"))) | |
301 | err(EXIT_FAILURE, _("cannot open %s"), "/dev/stderr"); | |
302 | } else if (!(ctl->timingfp = fopen(ctl->tname, "w"))) | |
303 | err(EXIT_FAILURE, _("cannot open %s"), ctl->tname); | |
304 | } | |
4368e3e6 | 305 | |
6ddf53a5 SK |
306 | pfd[0].fd = STDIN_FILENO; |
307 | pfd[0].events = POLLIN; | |
308 | pfd[1].fd = ctl->master; | |
309 | pfd[1].events = POLLIN; | |
310 | pfd[2].fd = ctl->sigfd; | |
311 | pfd[2].events = POLLIN | POLLERR | POLLHUP; | |
312 | ||
8353549e | 313 | strftime(buf, sizeof buf, "%c\n", localtime(&tvec)); |
076ffa48 | 314 | fprintf(ctl->typescriptfp, _("Script started on %s"), buf); |
04639805 | 315 | gettime_monotonic(&ctl->oldtime); |
a5a16c68 | 316 | |
6ddf53a5 | 317 | while (!ctl->die) { |
cf470183 | 318 | /* wait for input or signal */ |
89a859d4 | 319 | ret = poll(pfd, POLLFDS, ctl->poll_timeout); |
cf470183 SK |
320 | if (ret < 0) { |
321 | if (errno == EAGAIN) | |
322 | continue; | |
323 | warn(_("poll failed")); | |
edc7e420 | 324 | fail(ctl); |
36c1d79b | 325 | } |
89a859d4 SK |
326 | if (ret == 0) |
327 | ctl->die = 1; | |
cf470183 SK |
328 | for (i = 0; i < POLLFDS; i++) { |
329 | if (pfd[i].revents == 0) | |
330 | continue; | |
6ddf53a5 | 331 | if (i < 2) { |
04639805 | 332 | handle_io(ctl, pfd[i].fd, i); |
cf470183 SK |
333 | continue; |
334 | } | |
6ddf53a5 | 335 | if (i == 2) { |
a8896ad5 SK |
336 | handle_signal(ctl, pfd[i].fd); |
337 | if (!ctl->isterm && -1 < ctl->poll_timeout) | |
338 | /* In situation such as 'date' in | |
339 | * $ echo date | ./script | |
340 | * ignore input when shell has exited. */ | |
341 | pfd[0].fd = -1; | |
cf470183 | 342 | } |
968e632c | 343 | } |
cf470183 | 344 | } |
6ddf53a5 SK |
345 | if (!ctl->die) |
346 | finish(ctl, 1); /* wait for children */ | |
3f19b85f | 347 | if (!ctl->quiet && ctl->typescriptfp) { |
8353549e SK |
348 | tvec = script_time((time_t *)NULL); |
349 | strftime(buf, sizeof buf, "%c\n", localtime(&tvec)); | |
6ddf53a5 SK |
350 | fprintf(ctl->typescriptfp, _("\nScript done on %s"), buf); |
351 | } | |
352 | done(ctl); | |
6dbe3af9 KZ |
353 | } |
354 | ||
edc7e420 | 355 | static void getslave(struct script_control *ctl) |
93af8d8b SK |
356 | { |
357 | #ifndef HAVE_LIBUTIL | |
edc7e420 SK |
358 | ctl->line[strlen("/dev/")] = 't'; |
359 | ctl->slave = open(ctl->line, O_RDWR); | |
360 | if (ctl->slave < 0) { | |
361 | warn(_("cannot open %s"), ctl->line); | |
362 | fail(ctl); | |
93af8d8b | 363 | } |
edc7e420 | 364 | if (ctl->isterm) { |
3f19b85f | 365 | tcsetattr(ctl->slave, TCSANOW, &ctl->attrs); |
edc7e420 | 366 | ioctl(ctl->slave, TIOCSWINSZ, (char *)&ctl->win); |
93af8d8b SK |
367 | } |
368 | #endif | |
369 | setsid(); | |
edc7e420 | 370 | ioctl(ctl->slave, TIOCSCTTY, 0); |
93af8d8b SK |
371 | } |
372 | ||
2ddadb5e | 373 | static void __attribute__((__noreturn__)) doshell(struct script_control *ctl) |
93af8d8b | 374 | { |
df1dddf9 KZ |
375 | char *shname; |
376 | ||
edc7e420 | 377 | getslave(ctl); |
4368e3e6 KZ |
378 | |
379 | /* close things irrelevant for this process */ | |
edc7e420 | 380 | close(ctl->master); |
4368e3e6 | 381 | |
edc7e420 SK |
382 | dup2(ctl->slave, STDIN_FILENO); |
383 | dup2(ctl->slave, STDOUT_FILENO); | |
384 | dup2(ctl->slave, STDERR_FILENO); | |
385 | close(ctl->slave); | |
df1dddf9 | 386 | |
edc7e420 | 387 | ctl->master = -1; |
f0bc3fa0 | 388 | |
edc7e420 | 389 | shname = strrchr(ctl->shell, '/'); |
df1dddf9 KZ |
390 | if (shname) |
391 | shname++; | |
392 | else | |
edc7e420 | 393 | shname = ctl->shell; |
df1dddf9 | 394 | |
a17f3264 KZ |
395 | /* |
396 | * When invoked from within /etc/csh.login, script spawns a csh shell | |
397 | * that spawns programs that cannot be killed with a SIGTERM. This is | |
ee312c65 | 398 | * because csh has a documented behavior wherein it disables all |
a17f3264 KZ |
399 | * signals when processing the /etc/csh.* files. |
400 | * | |
401 | * Let's restore the default behavior. | |
402 | */ | |
403 | signal(SIGTERM, SIG_DFL); | |
404 | ||
edc7e420 | 405 | if (access(ctl->shell, X_OK) == 0) { |
3f19b85f KZ |
406 | if (ctl->command) |
407 | execl(ctl->shell, shname, "-c", ctl->command, NULL); | |
b4ff2f54 | 408 | else |
edc7e420 | 409 | execl(ctl->shell, shname, "-i", NULL); |
b4ff2f54 | 410 | } else { |
3f19b85f KZ |
411 | if (ctl->command) |
412 | execlp(shname, "-c", ctl->command, NULL); | |
b4ff2f54 SK |
413 | else |
414 | execlp(shname, "-i", NULL); | |
415 | } | |
edc7e420 SK |
416 | warn(_("failed to execute %s"), ctl->shell); |
417 | fail(ctl); | |
6dbe3af9 KZ |
418 | } |
419 | ||
edc7e420 | 420 | static void fixtty(struct script_control *ctl) |
93af8d8b | 421 | { |
6dbe3af9 KZ |
422 | struct termios rtt; |
423 | ||
edc7e420 | 424 | if (!ctl->isterm) |
3822032d KZ |
425 | return; |
426 | ||
3f19b85f | 427 | rtt = ctl->attrs; |
6dbe3af9 KZ |
428 | cfmakeraw(&rtt); |
429 | rtt.c_lflag &= ~ECHO; | |
05644dab | 430 | tcsetattr(STDIN_FILENO, TCSANOW, &rtt); |
6dbe3af9 KZ |
431 | } |
432 | ||
edc7e420 | 433 | static void getmaster(struct script_control *ctl) |
93af8d8b | 434 | { |
e11a5e63 | 435 | #if defined(HAVE_LIBUTIL) && defined(HAVE_PTY_H) |
3822032d KZ |
436 | int rc; |
437 | ||
edc7e420 | 438 | ctl->isterm = isatty(STDIN_FILENO); |
3822032d | 439 | |
edc7e420 | 440 | if (ctl->isterm) { |
3f19b85f | 441 | if (tcgetattr(STDIN_FILENO, &ctl->attrs) != 0) |
3822032d | 442 | err(EXIT_FAILURE, _("failed to get terminal attributes")); |
edc7e420 | 443 | ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&ctl->win); |
3f19b85f | 444 | rc = openpty(&ctl->master, &ctl->slave, NULL, &ctl->attrs, &ctl->win); |
3822032d | 445 | } else |
edc7e420 | 446 | rc = openpty(&ctl->master, &ctl->slave, NULL, NULL, NULL); |
3822032d KZ |
447 | |
448 | if (rc < 0) { | |
360d5005 | 449 | warn(_("openpty failed")); |
edc7e420 | 450 | fail(ctl); |
5c36a0eb KZ |
451 | } |
452 | #else | |
6dbe3af9 KZ |
453 | char *pty, *bank, *cp; |
454 | struct stat stb; | |
455 | ||
edc7e420 | 456 | ctl->isterm = isatty(STDIN_FILENO); |
3822032d | 457 | |
edc7e420 | 458 | pty = &ctl->line[strlen("/dev/ptyp")]; |
6dbe3af9 | 459 | for (bank = "pqrs"; *bank; bank++) { |
edc7e420 | 460 | ctl->line[strlen("/dev/pty")] = *bank; |
6dbe3af9 | 461 | *pty = '0'; |
edc7e420 | 462 | if (stat(ctl->line, &stb) < 0) |
6dbe3af9 KZ |
463 | break; |
464 | for (cp = "0123456789abcdef"; *cp; cp++) { | |
465 | *pty = *cp; | |
edc7e420 SK |
466 | ctl->master = open(ctl->line, O_RDWR); |
467 | if (ctl->master >= 0) { | |
468 | char *tp = &ctl->line[strlen("/dev/")]; | |
6dbe3af9 KZ |
469 | int ok; |
470 | ||
471 | /* verify slave side is usable */ | |
472 | *tp = 't'; | |
edc7e420 | 473 | ok = access(ctl->line, R_OK | W_OK) == 0; |
6dbe3af9 KZ |
474 | *tp = 'p'; |
475 | if (ok) { | |
edc7e420 | 476 | if (ctl->isterm) { |
3f19b85f | 477 | tcgetattr(STDIN_FILENO, &ctl->attrs); |
edc7e420 | 478 | ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&ctl->win); |
3822032d | 479 | } |
6dbe3af9 KZ |
480 | return; |
481 | } | |
edc7e420 SK |
482 | close(ctl->master); |
483 | ctl->master = -1; | |
6dbe3af9 KZ |
484 | } |
485 | } | |
486 | } | |
edc7e420 | 487 | ctl->master = -1; |
360d5005 | 488 | warn(_("out of pty's")); |
edc7e420 SK |
489 | fail(ctl); |
490 | #endif /* not HAVE_LIBUTIL */ | |
6dbe3af9 KZ |
491 | } |
492 | ||
93af8d8b SK |
493 | int main(int argc, char **argv) |
494 | { | |
edc7e420 SK |
495 | struct script_control ctl = { |
496 | #if !HAVE_LIBUTIL || !HAVE_PTY_H | |
497 | .line = "/dev/ptyXX", | |
498 | #endif | |
499 | .master = -1, | |
89a859d4 | 500 | .poll_timeout = -1, |
edc7e420 SK |
501 | 0 |
502 | }; | |
93af8d8b SK |
503 | int ch; |
504 | ||
505 | enum { FORCE_OPTION = CHAR_MAX + 1 }; | |
506 | ||
507 | static const struct option longopts[] = { | |
edc7e420 SK |
508 | {"append", no_argument, NULL, 'a'}, |
509 | {"command", required_argument, NULL, 'c'}, | |
510 | {"return", no_argument, NULL, 'e'}, | |
511 | {"flush", no_argument, NULL, 'f'}, | |
512 | {"force", no_argument, NULL, FORCE_OPTION,}, | |
513 | {"quiet", no_argument, NULL, 'q'}, | |
514 | {"timing", optional_argument, NULL, 't'}, | |
515 | {"version", no_argument, NULL, 'V'}, | |
516 | {"help", no_argument, NULL, 'h'}, | |
517 | {NULL, 0, NULL, 0} | |
93af8d8b SK |
518 | }; |
519 | ||
520 | setlocale(LC_ALL, ""); | |
b09feab9 SK |
521 | /* |
522 | * script -t prints time delays as floating point numbers. The example | |
523 | * program (scriptreplay) that we provide to handle this timing output | |
524 | * is a perl script, and does not handle numbers in locale format (not | |
525 | * even when "use locale;" is added). So, since these numbers are not | |
526 | * for human consumption, it seems easiest to set LC_NUMERIC here. | |
527 | */ | |
528 | setlocale(LC_NUMERIC, "C"); | |
93af8d8b SK |
529 | bindtextdomain(PACKAGE, LOCALEDIR); |
530 | textdomain(PACKAGE); | |
531 | atexit(close_stdout); | |
532 | ||
533 | while ((ch = getopt_long(argc, argv, "ac:efqt::Vh", longopts, NULL)) != -1) | |
edc7e420 | 534 | switch (ch) { |
93af8d8b | 535 | case 'a': |
3f19b85f | 536 | ctl.append = 1; |
93af8d8b SK |
537 | break; |
538 | case 'c': | |
3f19b85f | 539 | ctl.command = optarg; |
93af8d8b SK |
540 | break; |
541 | case 'e': | |
7e5796c9 | 542 | ctl.rc_wanted = 1; |
93af8d8b SK |
543 | break; |
544 | case 'f': | |
3f19b85f | 545 | ctl.flush = 1; |
93af8d8b SK |
546 | break; |
547 | case FORCE_OPTION: | |
3f19b85f | 548 | ctl.force = 1; |
93af8d8b SK |
549 | break; |
550 | case 'q': | |
3f19b85f | 551 | ctl.quiet = 1; |
93af8d8b SK |
552 | break; |
553 | case 't': | |
9580536a SK |
554 | if (optarg) |
555 | ctl.tname = optarg; | |
3f19b85f | 556 | ctl.timing = 1; |
93af8d8b SK |
557 | break; |
558 | case 'V': | |
559 | printf(UTIL_LINUX_VERSION); | |
560 | exit(EXIT_SUCCESS); | |
561 | break; | |
562 | case 'h': | |
563 | usage(stdout); | |
564 | break; | |
93af8d8b SK |
565 | default: |
566 | usage(stderr); | |
567 | } | |
568 | argc -= optind; | |
569 | argv += optind; | |
570 | ||
571 | if (argc > 0) | |
edc7e420 | 572 | ctl.fname = argv[0]; |
93af8d8b | 573 | else { |
7e5796c9 | 574 | ctl.fname = DEFAULT_TYPESCRIPT_FILENAME; |
edc7e420 | 575 | die_if_link(&ctl); |
6dbe3af9 | 576 | } |
93af8d8b | 577 | |
edc7e420 SK |
578 | ctl.shell = getenv("SHELL"); |
579 | if (ctl.shell == NULL) | |
580 | ctl.shell = _PATH_BSHELL; | |
93af8d8b | 581 | |
edc7e420 | 582 | getmaster(&ctl); |
3f19b85f | 583 | if (!ctl.quiet) |
edc7e420 SK |
584 | printf(_("Script started, file is %s\n"), ctl.fname); |
585 | fixtty(&ctl); | |
93af8d8b SK |
586 | |
587 | #ifdef HAVE_LIBUTEMPTER | |
edc7e420 | 588 | utempter_add_record(ctl.master, NULL); |
5c36a0eb | 589 | #endif |
cc1a88fb SK |
590 | /* setup signal handler */ |
591 | assert(sigemptyset(&ctl.sigset) == 0); | |
592 | assert(sigaddset(&ctl.sigset, SIGCHLD) == 0); | |
593 | assert(sigaddset(&ctl.sigset, SIGWINCH) == 0); | |
594 | assert(sigprocmask(SIG_BLOCK, &ctl.sigset, NULL) == 0); | |
595 | if ((ctl.sigfd = signalfd(-1, &ctl.sigset, 0)) < 0) | |
596 | err(EXIT_FAILURE, _("cannot set signal handler")); | |
93af8d8b SK |
597 | |
598 | fflush(stdout); | |
edc7e420 | 599 | ctl.child = fork(); |
93af8d8b | 600 | |
edc7e420 | 601 | if (ctl.child < 0) { |
93af8d8b | 602 | warn(_("fork failed")); |
edc7e420 | 603 | fail(&ctl); |
93af8d8b | 604 | } |
6ddf53a5 SK |
605 | if (ctl.child == 0) |
606 | doshell(&ctl); | |
607 | do_io(&ctl); | |
b09feab9 SK |
608 | /* should not happen, do_io() calls done() */ |
609 | return EXIT_FAILURE; | |
6dbe3af9 | 610 | } |