]> git.ipfire.org Git - thirdparty/util-linux.git/blob - term-utils/script.c
term-utils: verify writing to streams was successful
[thirdparty/util-linux.git] / term-utils / script.c
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:
15 * This product includes software developed by the University of
16 * California, Berkeley and its contributors.
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
34 /*
35 * 1999-02-22 Arkadiusz Mi¶kiewicz <misiek@pld.ORG.PL>
36 * - added Native Language Support
37 *
38 * 2000-07-30 Per Andreas Buer <per@linpro.no> - added "q"-option
39 */
40
41 /*
42 * script
43 */
44 #include <stdio.h>
45 #include <stdlib.h>
46 #include <paths.h>
47 #include <time.h>
48 #include <sys/stat.h>
49 #include <termios.h>
50 #include <sys/ioctl.h>
51 #include <sys/time.h>
52 #include <signal.h>
53 #include <errno.h>
54 #include <string.h>
55 #include <getopt.h>
56 #include <unistd.h>
57 #include <fcntl.h>
58 #include <limits.h>
59 #include <locale.h>
60 #include <stddef.h>
61
62 #include "closestream.h"
63 #include "nls.h"
64 #include "c.h"
65
66 #if defined(HAVE_LIBUTIL) && defined(HAVE_PTY_H)
67 # include <pty.h>
68 #endif
69
70 #ifdef HAVE_LIBUTEMPTER
71 # include <utempter.h>
72 #endif
73
74 #define DEFAULT_OUTPUT "typescript"
75
76 void finish(int);
77 void done(void);
78 void fail(void);
79 void resize(int);
80 void fixtty(void);
81 void getmaster(void);
82 void getslave(void);
83 void doinput(void);
84 void dooutput(FILE *timingfd);
85 void doshell(void);
86
87 char *shell;
88 FILE *fscript;
89 int master = -1;
90 int slave;
91 pid_t child;
92 pid_t subchild;
93 int childstatus;
94 char *fname;
95
96 struct termios tt;
97 struct winsize win;
98 int lb;
99 int l;
100 #if !HAVE_LIBUTIL || !HAVE_PTY_H
101 char line[] = "/dev/ptyXX";
102 #endif
103 int aflg = 0;
104 char *cflg = NULL;
105 int eflg = 0;
106 int fflg = 0;
107 int qflg = 0;
108 int tflg = 0;
109 int forceflg = 0;
110
111 int die;
112 int resized;
113
114 static void
115 die_if_link(char *fn) {
116 struct stat s;
117
118 if (forceflg)
119 return;
120 if (lstat(fn, &s) == 0 && (S_ISLNK(s.st_mode) || s.st_nlink > 1))
121 errx(EXIT_FAILURE,
122 _("output file `%s' is a link\n"
123 "Use --force if you really want to use it.\n"
124 "Program not started."), fn);
125 }
126
127 static void __attribute__((__noreturn__))
128 usage(FILE *out)
129 {
130 fputs(_("\nUsage:\n"), out);
131 fprintf(out,
132 _(" %s [options] [file]\n"), program_invocation_short_name);
133
134 fputs(_("\nOptions:\n"), out);
135 fputs(_(" -a, --append append the output\n"
136 " -c, --command <command> run command rather than interactive shell\n"
137 " -e, --return return exit code of the child process\n"
138 " -f, --flush run flush after each write\n"
139 " --force use output file even when it is a link\n"
140 " -q, --quiet be quiet\n"
141 " -t, --timing[=<file>] output timing data to stderr (or to FILE)\n"
142 " -V, --version output version information and exit\n"
143 " -h, --help display this help and exit\n\n"), out);
144
145 exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
146 }
147
148 /*
149 * script -t prints time delays as floating point numbers
150 * The example program (scriptreplay) that we provide to handle this
151 * timing output is a perl script, and does not handle numbers in
152 * locale format (not even when "use locale;" is added).
153 * So, since these numbers are not for human consumption, it seems
154 * easiest to set LC_NUMERIC here.
155 */
156
157 int
158 main(int argc, char **argv) {
159 sigset_t block_mask, unblock_mask;
160 struct sigaction sa;
161 extern int optind;
162 int ch;
163 FILE *timingfd = stderr;
164
165 enum { FORCE_OPTION = CHAR_MAX + 1 };
166
167 static const struct option longopts[] = {
168 { "append", no_argument, NULL, 'a' },
169 { "command", required_argument, NULL, 'c' },
170 { "return", no_argument, NULL, 'e' },
171 { "flush", no_argument, NULL, 'f' },
172 { "force", no_argument, NULL, FORCE_OPTION, },
173 { "quiet", no_argument, NULL, 'q' },
174 { "timing", optional_argument, NULL, 't' },
175 { "version", no_argument, NULL, 'V' },
176 { "help", no_argument, NULL, 'h' },
177 { NULL, 0, NULL, 0 }
178 };
179
180 setlocale(LC_ALL, "");
181 setlocale(LC_NUMERIC, "C"); /* see comment above */
182 bindtextdomain(PACKAGE, LOCALEDIR);
183 textdomain(PACKAGE);
184 atexit(close_stdout);
185
186 while ((ch = getopt_long(argc, argv, "ac:efqt::Vh", longopts, NULL)) != -1)
187 switch(ch) {
188 case 'a':
189 aflg = 1;
190 break;
191 case 'c':
192 cflg = optarg;
193 break;
194 case 'e':
195 eflg = 1;
196 break;
197 case 'f':
198 fflg = 1;
199 break;
200 case FORCE_OPTION:
201 forceflg = 1;
202 break;
203 case 'q':
204 qflg = 1;
205 break;
206 case 't':
207 if (optarg)
208 if ((timingfd = fopen(optarg, "w")) == NULL)
209 err(EXIT_FAILURE, _("cannot open timing file %s"), optarg);
210 tflg = 1;
211 break;
212 case 'V':
213 printf(_("%s from %s\n"), program_invocation_short_name,
214 PACKAGE_STRING);
215 exit(EXIT_SUCCESS);
216 break;
217 case 'h':
218 usage(stdout);
219 break;
220 case '?':
221 default:
222 usage(stderr);
223 }
224 argc -= optind;
225 argv += optind;
226
227 if (argc > 0)
228 fname = argv[0];
229 else {
230 fname = DEFAULT_OUTPUT;
231 die_if_link(fname);
232 }
233 if ((fscript = fopen(fname, aflg ? "a" : "w")) == NULL) {
234 warn(_("open failed: %s"), fname);
235 fail();
236 }
237
238 shell = getenv("SHELL");
239 if (shell == NULL)
240 shell = _PATH_BSHELL;
241
242 getmaster();
243 if (!qflg)
244 printf(_("Script started, file is %s\n"), fname);
245 fixtty();
246
247 #ifdef HAVE_LIBUTEMPTER
248 utempter_add_record(master, NULL);
249 #endif
250 /* setup SIGCHLD handler */
251 sigemptyset(&sa.sa_mask);
252 sa.sa_flags = 0;
253 sa.sa_handler = finish;
254 sigaction(SIGCHLD, &sa, NULL);
255
256 /* init mask for SIGCHLD */
257 sigprocmask(SIG_SETMASK, NULL, &block_mask);
258 sigaddset(&block_mask, SIGCHLD);
259
260 sigprocmask(SIG_SETMASK, &block_mask, &unblock_mask);
261 child = fork();
262 sigprocmask(SIG_SETMASK, &unblock_mask, NULL);
263
264 if (child < 0) {
265 warn(_("fork failed"));
266 fail();
267 }
268 if (child == 0) {
269
270 sigprocmask(SIG_SETMASK, &block_mask, NULL);
271 subchild = child = fork();
272 sigprocmask(SIG_SETMASK, &unblock_mask, NULL);
273
274 if (child < 0) {
275 warn(_("fork failed"));
276 fail();
277 }
278 if (child)
279 dooutput(timingfd);
280 else
281 doshell();
282 } else {
283 sa.sa_handler = resize;
284 sigaction(SIGWINCH, &sa, NULL);
285 }
286 doinput();
287
288 if (close_stream(timingfd) != 0)
289 errx(EXIT_FAILURE, _("write error"));
290 return EXIT_SUCCESS;
291 }
292
293 void
294 doinput(void) {
295 ssize_t cc;
296 char ibuf[BUFSIZ];
297
298 if (close_stream(fscript) != 0)
299 errx(EXIT_FAILURE, _("write error"));
300
301 while (die == 0) {
302 if ((cc = read(STDIN_FILENO, ibuf, BUFSIZ)) > 0) {
303 ssize_t wrt = write(master, ibuf, cc);
304 if (wrt < 0) {
305 warn (_("write failed"));
306 fail();
307 }
308 }
309 else if (cc < 0 && errno == EINTR && resized)
310 resized = 0;
311 else
312 break;
313 }
314
315 done();
316 }
317
318 #include <sys/wait.h>
319
320 void
321 finish(int dummy __attribute__ ((__unused__))) {
322 int status;
323 pid_t pid;
324
325 while ((pid = wait3(&status, WNOHANG, 0)) > 0)
326 if (pid == child) {
327 childstatus = status;
328 die = 1;
329 }
330 }
331
332 void
333 resize(int dummy __attribute__ ((__unused__))) {
334 resized = 1;
335 /* transmit window change information to the child */
336 ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&win);
337 ioctl(slave, TIOCSWINSZ, (char *)&win);
338 }
339
340 /*
341 * Stop extremely silly gcc complaint on %c:
342 * warning: `%c' yields only last 2 digits of year in some locales
343 */
344 static void
345 my_strftime(char *buf, size_t len, const char *fmt, const struct tm *tm) {
346 strftime(buf, len, fmt, tm);
347 }
348
349 void
350 dooutput(FILE *timingfd) {
351 ssize_t cc;
352 time_t tvec;
353 char obuf[BUFSIZ];
354 struct timeval tv;
355 double oldtime=time(NULL), newtime;
356 int flgs = 0;
357 ssize_t wrt;
358 ssize_t fwrt;
359
360 close(STDIN_FILENO);
361 #ifdef HAVE_LIBUTIL
362 close(slave);
363 #endif
364 tvec = time((time_t *)NULL);
365 my_strftime(obuf, sizeof obuf, "%c\n", localtime(&tvec));
366 fprintf(fscript, _("Script started on %s"), obuf);
367
368 do {
369 if (die && flgs == 0) {
370 /* ..child is dead, but it doesn't mean that there is
371 * nothing in buffers.
372 */
373 flgs = fcntl(master, F_GETFL, 0);
374 if (fcntl(master, F_SETFL, (flgs | O_NONBLOCK)) == -1)
375 break;
376 }
377 if (tflg)
378 gettimeofday(&tv, NULL);
379
380 errno = 0;
381 cc = read(master, obuf, sizeof (obuf));
382
383 if (die && errno == EINTR && cc <= 0)
384 /* read() has been interrupted by SIGCHLD, try it again
385 * with O_NONBLOCK
386 */
387 continue;
388 if (cc <= 0)
389 break;
390 if (tflg) {
391 newtime = tv.tv_sec + (double) tv.tv_usec / 1000000;
392 fprintf(timingfd, "%f %zd\n", newtime - oldtime, cc);
393 oldtime = newtime;
394 }
395 wrt = write(STDOUT_FILENO, obuf, cc);
396 if (wrt < 0) {
397 warn (_("write failed"));
398 fail();
399 }
400 fwrt = fwrite(obuf, 1, cc, fscript);
401 if (fwrt < cc) {
402 warn (_("cannot write script file"));
403 fail();
404 }
405 if (fflg)
406 fflush(fscript);
407 } while(1);
408
409 if (flgs)
410 fcntl(master, F_SETFL, flgs);
411 if (close_stream(timingfd) != 0)
412 errx(EXIT_FAILURE, _("write error"));
413 done();
414 }
415
416 void
417 doshell(void) {
418 char *shname;
419
420 #if 0
421 int t;
422
423 t = open(_PATH_DEV_TTY, O_RDWR);
424 if (t >= 0) {
425 ioctl(t, TIOCNOTTY, (char *)0);
426 close(t);
427 }
428 #endif
429
430 getslave();
431 close(master);
432 if (close_stream(fscript) != 0)
433 errx(EXIT_FAILURE, _("write error"));
434 dup2(slave, STDIN_FILENO);
435 dup2(slave, STDOUT_FILENO);
436 dup2(slave, STDERR_FILENO);
437 close(slave);
438
439 master = -1;
440
441 shname = strrchr(shell, '/');
442 if (shname)
443 shname++;
444 else
445 shname = shell;
446
447 /*
448 * When invoked from within /etc/csh.login, script spawns a csh shell
449 * that spawns programs that cannot be killed with a SIGTERM. This is
450 * because csh has a documented behaviour wherein it disables all
451 * signals when processing the /etc/csh.* files.
452 *
453 * Let's restore the default behavior.
454 */
455 signal(SIGTERM, SIG_DFL);
456
457 if (cflg)
458 execl(shell, shname, "-c", cflg, NULL);
459 else
460 execl(shell, shname, "-i", NULL);
461
462 warn(_("failed to execute %s"), shell);
463 fail();
464 }
465
466 void
467 fixtty(void) {
468 struct termios rtt;
469
470 rtt = tt;
471 cfmakeraw(&rtt);
472 rtt.c_lflag &= ~ECHO;
473 tcsetattr(STDIN_FILENO, TCSANOW, &rtt);
474 }
475
476 void
477 fail(void) {
478
479 kill(0, SIGTERM);
480 done();
481 }
482
483 void
484 done(void) {
485 time_t tvec;
486
487 if (subchild) {
488 if (!qflg) {
489 char buf[BUFSIZ];
490 tvec = time((time_t *)NULL);
491 my_strftime(buf, sizeof buf, "%c\n", localtime(&tvec));
492 fprintf(fscript, _("\nScript done on %s"), buf);
493 }
494 if (close_stream(fscript) != 0)
495 errx(EXIT_FAILURE, _("write error"));
496 close(master);
497
498 master = -1;
499 } else {
500 tcsetattr(STDIN_FILENO, TCSADRAIN, &tt);
501 if (!qflg)
502 printf(_("Script done, file is %s\n"), fname);
503 #ifdef HAVE_LIBUTEMPTER
504 if (master >= 0)
505 utempter_remove_record(master);
506 #endif
507 }
508
509 if(eflg) {
510 if (WIFSIGNALED(childstatus))
511 exit(WTERMSIG(childstatus) + 0x80);
512 else
513 exit(WEXITSTATUS(childstatus));
514 }
515 exit(EXIT_SUCCESS);
516 }
517
518 void
519 getmaster(void) {
520 #if defined(HAVE_LIBUTIL) && defined(HAVE_PTY_H)
521 tcgetattr(STDIN_FILENO, &tt);
522 ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&win);
523 if (openpty(&master, &slave, NULL, &tt, &win) < 0) {
524 warn(_("openpty failed"));
525 fail();
526 }
527 #else
528 char *pty, *bank, *cp;
529 struct stat stb;
530
531 pty = &line[strlen("/dev/ptyp")];
532 for (bank = "pqrs"; *bank; bank++) {
533 line[strlen("/dev/pty")] = *bank;
534 *pty = '0';
535 if (stat(line, &stb) < 0)
536 break;
537 for (cp = "0123456789abcdef"; *cp; cp++) {
538 *pty = *cp;
539 master = open(line, O_RDWR);
540 if (master >= 0) {
541 char *tp = &line[strlen("/dev/")];
542 int ok;
543
544 /* verify slave side is usable */
545 *tp = 't';
546 ok = access(line, R_OK|W_OK) == 0;
547 *tp = 'p';
548 if (ok) {
549 tcgetattr(STDIN_FILENO, &tt);
550 ioctl(STDIN_FILENO, TIOCGWINSZ,
551 (char *)&win);
552 return;
553 }
554 close(master);
555 master = -1;
556 }
557 }
558 }
559 master = -1;
560 warn(_("out of pty's"));
561 fail();
562 #endif /* not HAVE_LIBUTIL */
563 }
564
565 void
566 getslave(void) {
567 #ifndef HAVE_LIBUTIL
568 line[strlen("/dev/")] = 't';
569 slave = open(line, O_RDWR);
570 if (slave < 0) {
571 warn(_("open failed: %s"), line);
572 fail();
573 }
574 tcsetattr(slave, TCSANOW, &tt);
575 ioctl(slave, TIOCSWINSZ, (char *)&win);
576 #endif
577 setsid();
578 ioctl(slave, TIOCSCTTY, 0);
579 }