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