]> git.ipfire.org Git - thirdparty/util-linux.git/blob - term-utils/scriptlive.c
scriptlive: add new command to re-execute script(1) typescript
[thirdparty/util-linux.git] / term-utils / scriptlive.c
1 /*
2 * Copyright (C) 2019, Karel Zak <kzak@redhat.com>
3 *
4 * This file is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This file is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 */
14
15 #include <stdio.h>
16 #include <stdarg.h>
17 #include <stdlib.h>
18 #include <string.h>
19 #include <errno.h>
20 #include <time.h>
21 #include <limits.h>
22 #include <math.h>
23 #include <sys/select.h>
24 #include <unistd.h>
25 #include <getopt.h>
26 #include <signal.h>
27 #include <sys/types.h>
28 #include <sys/wait.h>
29 #include <termios.h>
30 #include <unistd.h>
31 #include <paths.h>
32
33 #include "c.h"
34 #include "xalloc.h"
35 #include "closestream.h"
36 #include "nls.h"
37 #include "strutils.h"
38 #include "optutils.h"
39 #include "script-playutils.h"
40 #include "rpmatch.h"
41
42
43 #define SCRIPT_MIN_DELAY 0.0001 /* from original sripreplay.pl */
44
45 static void __attribute__((__noreturn__))
46 usage(void)
47 {
48 FILE *out = stdout;
49 fputs(USAGE_HEADER, out);
50 fprintf(out,
51 _(" %s [options]\n"),
52 program_invocation_short_name);
53 fprintf(out,
54 _(" %s [-t] timingfile [-I|-B] typescript\n"),
55 program_invocation_short_name);
56
57 fputs(USAGE_SEPARATOR, out);
58 fputs(_("Execute terminal typescript.\n"), out);
59
60 fputs(USAGE_OPTIONS, out);
61 fputs(_(" -t, --timing <file> script timing log file\n"), out);
62 fputs(_(" -I, --log-in <file> script stdin log file\n"), out);
63 fputs(_(" -B, --log-io <file> script stdin and stdout log file\n"), out);
64
65 fputs(USAGE_SEPARATOR, out);
66 fputs(_(" -d, --divisor <num> speed up or slow down execution with time divisor\n"), out);
67 fputs(_(" -m, --maxdelay <num> wait at most this many seconds between updates\n"), out);
68 printf(USAGE_HELP_OPTIONS(25));
69
70 printf(USAGE_MAN_TAIL("scriptlive(1)"));
71 exit(EXIT_SUCCESS);
72 }
73
74 static double
75 getnum(const char *s)
76 {
77 const double d = strtod_or_err(s, _("failed to parse number"));
78
79 if (isnan(d)) {
80 errno = EINVAL;
81 err(EXIT_FAILURE, "%s: %s", _("failed to parse number"), s);
82 }
83 return d;
84 }
85
86 static void
87 delay_for(double delay)
88 {
89 #ifdef HAVE_NANOSLEEP
90 struct timespec ts, remainder;
91 ts.tv_sec = (time_t) delay;
92 ts.tv_nsec = (delay - ts.tv_sec) * 1.0e9;
93
94 DBG(TIMING, ul_debug("going to sleep for %fs", delay));
95
96 while (-1 == nanosleep(&ts, &remainder)) {
97 if (EINTR == errno)
98 ts = remainder;
99 else
100 break;
101 }
102 #else
103 struct timeval tv;
104 tv.tv_sec = (long) delay;
105 tv.tv_usec = (delay - tv.tv_sec) * 1.0e6;
106 select(0, NULL, NULL, NULL, &tv);
107 #endif
108 }
109
110 static int start_shell(const char *shell, pid_t *shell_pid, int *shell_fd)
111 {
112 const char *shname;
113 int fds[2];
114
115 assert(shell_pid);
116 assert(shell_fd);
117
118 if (pipe(fds) < 0)
119 err(EXIT_FAILURE, _("pipe failed"));
120
121 *shell_pid = fork();
122
123 if (*shell_pid == -1)
124 err(EXIT_FAILURE, _("fork failed"));
125 if (*shell_pid != 0) {
126 /* parent */
127 *shell_fd = fds[1];
128 close(fds[0]);
129 return -errno;
130 }
131
132 /* child */
133 shname = strrchr(shell, '/');
134 if (shname)
135 shname++;
136 else
137 shname = shell;
138
139 dup2(fds[0], STDIN_FILENO);
140 close(fds[0]);
141 close(fds[1]);
142
143 execl(shell, shname, "-i", NULL);
144 errexec(shell);
145 }
146
147 int
148 main(int argc, char *argv[])
149 {
150 struct replay_setup *setup = NULL;
151 struct replay_step *step = NULL;
152 const char *log_in = NULL,
153 *log_io = NULL,
154 *log_tm = NULL,
155 *shell;
156 double divi = 1, maxdelay = 0;
157 int diviopt = FALSE, maxdelayopt = FALSE, idx;
158 int ch, rc;
159 int shell_fd;
160 pid_t shell_pid;
161 struct termios attrs;
162
163 static const struct option longopts[] = {
164 { "timing", required_argument, 0, 't' },
165 { "log-in", required_argument, 0, 'I'},
166 { "log-io", required_argument, 0, 'B'},
167 { "divisor", required_argument, 0, 'd' },
168 { "maxdelay", required_argument, 0, 'm' },
169 { "version", no_argument, 0, 'V' },
170 { "help", no_argument, 0, 'h' },
171 { NULL, 0, 0, 0 }
172 };
173 static const ul_excl_t excl[] = { /* rows and cols in ASCII order */
174 { 'B', 'I' },
175 { 0 }
176 };
177 int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
178 /* Because we use space as a separator, we can't afford to use any
179 * locale which tolerates a space in a number. In any case, script.c
180 * sets the LC_NUMERIC locale to C, anyway.
181 */
182 setlocale(LC_ALL, "");
183 setlocale(LC_NUMERIC, "C");
184
185 bindtextdomain(PACKAGE, LOCALEDIR);
186 textdomain(PACKAGE);
187 close_stdout_atexit();
188
189 replay_init_debug();
190
191 while ((ch = getopt_long(argc, argv, "B:I:t:d:m:Vh", longopts, NULL)) != -1) {
192
193 err_exclusive_options(ch, longopts, excl, excl_st);
194
195 switch(ch) {
196 case 't':
197 log_tm = optarg;
198 break;
199 case 'I':
200 log_in = optarg;
201 break;
202 case 'B':
203 log_io = optarg;
204 break;
205 case 'd':
206 diviopt = TRUE;
207 divi = getnum(optarg);
208 break;
209 case 'm':
210 maxdelayopt = TRUE;
211 maxdelay = getnum(optarg);
212 break;
213 case 'V':
214 print_version(EXIT_SUCCESS);
215 case 'h':
216 usage();
217 default:
218 errtryhelp(EXIT_FAILURE);
219 }
220 }
221 argc -= optind;
222 argv += optind;
223 idx = 0;
224
225 if (!isatty(STDIN_FILENO))
226 errx(EXIT_FAILURE, _("stdin is not terminal"));
227
228 if (!log_tm && idx < argc)
229 log_tm = argv[idx++];
230 if (!log_in && !log_io && idx < argc)
231 log_in = argv[idx++];
232
233 if (!diviopt)
234 divi = idx < argc ? getnum(argv[idx]) : 1;
235 if (maxdelay < 0)
236 maxdelay = 0;
237
238 if (!log_tm)
239 errx(EXIT_FAILURE, _("timing file not specified"));
240 if (!(log_in || log_io))
241 errx(EXIT_FAILURE, _("stdin typescript file not specified"));
242
243 setup = replay_new_setup();
244
245 if (replay_set_timing_file(setup, log_tm) != 0)
246 err(EXIT_FAILURE, _("cannot open %s"), log_tm);
247
248 if (log_in && replay_associate_log(setup, "I", log_in) != 0)
249 err(EXIT_FAILURE, _("cannot open %s"), log_in);
250
251 if (log_io && replay_associate_log(setup, "IO", log_io) != 0)
252 err(EXIT_FAILURE, _("cannot open %s"), log_io);
253
254 replay_set_default_type(setup, 'I');
255 replay_set_crmode(setup, REPLAY_CRMODE_AUTO);
256
257 shell = getenv("SHELL");
258 if (shell == NULL)
259 shell = _PATH_BSHELL;
260
261 fprintf(stdout, _(">>> scriptlive: Starting your typescript execution by %s. <<<\n"), shell);
262
263 tcgetattr(STDIN_FILENO, &attrs);
264 start_shell(shell, &shell_pid, &shell_fd);
265
266 do {
267 double delay;
268
269 rc = replay_get_next_step(setup, "I", &step);
270 if (rc)
271 break;
272
273 delay = replay_step_get_delay(step);
274 delay /= divi;
275
276 if (maxdelayopt && delay > maxdelay)
277 delay = maxdelay;
278 if (delay > SCRIPT_MIN_DELAY)
279 delay_for(delay);
280
281 rc = replay_emit_step_data(setup, step, shell_fd);
282 } while (rc == 0);
283
284 kill(shell_pid, SIGTERM);
285 waitpid(shell_pid, 0, 0);
286 tcsetattr(STDIN_FILENO, TCSADRAIN, &attrs);
287
288 if (step && rc < 0)
289 err(EXIT_FAILURE, _("%s: log file error"), replay_step_get_filename(step));
290 else if (rc < 0)
291 err(EXIT_FAILURE, _("%s: line %d: timing file error"),
292 replay_get_timing_file(setup),
293 replay_get_timing_line(setup));
294
295
296 fprintf(stdout, _(">>> scriptlive: Done. <<<\n"));
297
298 exit(EXIT_SUCCESS);
299 }