]> git.ipfire.org Git - thirdparty/util-linux.git/blame - term-utils/scriptreplay.c
taskset: Accept 0 pid for current process
[thirdparty/util-linux.git] / term-utils / scriptreplay.c
CommitLineData
18a706bd 1/*
6f922a34 2 * Copyright (C) 2024, Jonathan Ketchker <jonathan@ketchker.com>
bdd43357 3 * Copyright (C) 2008-2019, Karel Zak <kzak@redhat.com>
18a706bd
KZ
4 * Copyright (C) 2008, James Youngman <jay@gnu.org>
5 *
6 * This file is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This file is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 *
17 * Based on scriptreplay.pl by Joey Hess <joey@kitenet.net>
18 */
19
20#include <stdio.h>
21#include <stdarg.h>
22#include <stdlib.h>
23#include <string.h>
24#include <errno.h>
25#include <time.h>
26#include <limits.h>
27#include <math.h>
28#include <sys/select.h>
29#include <unistd.h>
84adb5e6 30#include <getopt.h>
b639c2a3 31#include <sys/time.h>
edb088ff 32#include <termios.h>
36295381 33#include <fcntl.h>
3727ae82 34#include <stdbool.h>
18a706bd 35
fec06a06 36#include "c.h"
fec06a06 37#include "xalloc.h"
cdd2a8c3 38#include "closestream.h"
18a706bd 39#include "nls.h"
f0b3b904 40#include "strutils.h"
0d955cd8 41#include "optutils.h"
12352c96 42#include "script-playutils.h"
18a706bd 43
5769a938 44static void __attribute__((__noreturn__))
86be6a32 45usage(void)
18a706bd 46{
86be6a32 47 FILE *out = stdout;
db433bf7 48 fputs(USAGE_HEADER, out);
0d955cd8 49 fprintf(out,
6a408bdc 50 _(" %s [options] <timingfile> [<typescript> [<divisor>]]\n"),
14cc9dda
KZ
51 program_invocation_short_name);
52
451dbcfa
BS
53 fputs(USAGE_SEPARATOR, out);
54 fputs(_("Play back terminal typescripts, using timing information.\n"), out);
55
db433bf7 56 fputs(USAGE_OPTIONS, out);
02a51ce1 57 fputs(_(" -t, --timing <file> script timing log file\n"), out);
dc62fffc 58 fputs(_(" -T, --log-timing <file> alias to -t\n"), out);
0d955cd8
KZ
59 fputs(_(" -I, --log-in <file> script stdin log file\n"), out);
60 fputs(_(" -O, --log-out <file> script stdout log file (default)\n"), out);
61 fputs(_(" -B, --log-io <file> script stdin and stdout log file\n"), out);
a33f1fc4 62 fputs(USAGE_SEPARATOR, out);
eb781922 63 fputs(_(" -s, --typescript <file> deprecated alias to -O\n"), out);
0d955cd8
KZ
64
65 fputs(USAGE_SEPARATOR, out);
4a4f4a62 66 fputs(_(" --summary display overview about recorded session and exit\n"), out);
02a51ce1
KZ
67 fputs(_(" -d, --divisor <num> speed up or slow down execution with time divisor\n"), out);
68 fputs(_(" -m, --maxdelay <num> wait at most this many seconds between updates\n"), out);
7c4a374f 69 fputs(_(" -x, --stream <name> stream type (out, in, signal or info)\n"), out);
88b8e9bf 70 fputs(_(" -c, --cr-mode <type> CR char mode (auto, never, always)\n"), out);
6a408bdc
BS
71
72 fputs(USAGE_SEPARATOR, out);
bad4c729 73 fprintf(out, USAGE_HELP_OPTIONS(25));
84adb5e6 74
762e5865
KZ
75 fputs(USAGE_SEPARATOR, out);
76 fputs(_("Key bindings:\n"), out);
d2c323bc 77 fputs(_(" space toggles between pause and play\n"), out);
2b528997
BS
78 fputs(_(" up-arrow increases playback speed with ten percent\n"), out);
79 fputs(_(" down-arrow decreases playback speed with ten percent\n"), out);
762e5865 80
bad4c729 81 fprintf(out, USAGE_MAN_TAIL("scriptreplay(1)"));
86be6a32 82 exit(EXIT_SUCCESS);
18a706bd
KZ
83}
84
85static double
86getnum(const char *s)
87{
f0b3b904 88 const double d = strtod_or_err(s, _("failed to parse number"));
18a706bd 89
f0b3b904 90 if (isnan(d)) {
18a706bd 91 errno = EINVAL;
f0b3b904 92 err(EXIT_FAILURE, "%s: %s", _("failed to parse number"), s);
18a706bd
KZ
93 }
94 return d;
95}
96
97static void
1179bef2 98delay_for(const struct timeval *delay)
18a706bd
KZ
99{
100#ifdef HAVE_NANOSLEEP
101 struct timespec ts, remainder;
b639c2a3
KZ
102 ts.tv_sec = (time_t) delay->tv_sec;
103 ts.tv_nsec = delay->tv_usec * 1000;
18a706bd 104
7b20bb1a
KZ
105 DBG(TIMING, ul_debug("going to sleep for %"PRId64".%06"PRId64,
106 (int64_t) delay->tv_sec, (int64_t) delay->tv_usec));
167a4851 107
18a706bd
KZ
108 while (-1 == nanosleep(&ts, &remainder)) {
109 if (EINTR == errno)
110 ts = remainder;
111 else
112 break;
113 }
114#else
12053b1a
KZ
115 {
116 struct timeval timeout;
117
118 /* On Linux, select() modifies timeout */
119 memcpy(&timeout, delay, sizeof(struct timeval));
120 select(0, NULL, NULL, NULL, &timeout);
121 }
18a706bd
KZ
122#endif
123}
124
cfc264d5
SG
125static void
126appendchr(char *buf, size_t bufsz, int c)
41ce6545
KZ
127{
128 size_t sz;
129
130 if (strchr(buf, c))
131 return; /* already in */
132
133 sz = strlen(buf);
134 if (sz + 1 < bufsz)
135 buf[sz] = c;
136}
137
cfc264d5 138static int
36295381 139setterm(struct termios *backup, int *saved_flag)
edb088ff
SG
140{
141 struct termios tattr;
142
36295381 143 *saved_flag = fcntl(STDIN_FILENO, F_GETFL);
144 if (*saved_flag == -1)
145 err(EXIT_FAILURE, _("unexpected fcntl failure"));
41e7686c 146 fcntl(STDIN_FILENO, F_SETFL, *saved_flag | O_NONBLOCK);
36295381 147
cfc264d5 148 if (tcgetattr(STDOUT_FILENO, backup) != 0) {
535e81eb
SG
149 if (errno != ENOTTY) /* For debugger. */
150 err(EXIT_FAILURE, _("unexpected tcgetattr failure"));
cfc264d5
SG
151 return 0;
152 }
edb088ff
SG
153 tattr = *backup;
154 cfmakeraw(&tattr);
00a09d78 155 tattr.c_lflag |= ISIG;
584e5051 156 tattr.c_iflag |= IXON;
cfc264d5
SG
157 tcsetattr(STDOUT_FILENO, TCSANOW, &tattr);
158 return 1;
edb088ff
SG
159}
160
18a706bd
KZ
161int
162main(int argc, char *argv[])
163{
b639c2a3 164 static const struct timeval mindelay = { .tv_sec = 0, .tv_usec = 100 };
28d7c584 165 static const struct timeval input_delay = { .tv_sec = 0, .tv_usec = 100000 };
166 struct timeval step_delay = { 0, 0 };
b639c2a3
KZ
167 struct timeval maxdelay;
168
cfc264d5 169 int isterm;
36295381 170 int saved_flag;
edb088ff
SG
171 struct termios saved;
172
12352c96 173 struct replay_setup *setup = NULL;
4a4f4a62 174 struct replay_step *step = NULL;
41ce6545 175 char streams[6] = {0}; /* IOSI - in, out, signal,info */
0d955cd8
KZ
176 const char *log_out = NULL,
177 *log_in = NULL,
178 *log_io = NULL,
179 *log_tm = NULL;
b639c2a3
KZ
180 double divi = 1;
181 int diviopt = FALSE, idx;
d7282ef8 182 int ch, rc = 0, crmode = REPLAY_CRMODE_AUTO, summary = 0;
4a4f4a62
KZ
183 enum {
184 OPT_SUMMARY = CHAR_MAX + 1
185 };
84adb5e6
SK
186
187 static const struct option longopts[] = {
88b8e9bf 188 { "cr-mode", required_argument, 0, 'c' },
0da871b5 189 { "timing", required_argument, 0, 't' },
a33f1fc4
KZ
190 { "log-timing", required_argument, 0, 'T' },
191 { "log-in", required_argument, 0, 'I' },
192 { "log-out", required_argument, 0, 'O' },
193 { "log-io", required_argument, 0, 'B' },
0da871b5
SK
194 { "typescript", required_argument, 0, 's' },
195 { "divisor", required_argument, 0, 'd' },
7f1d4836 196 { "maxdelay", required_argument, 0, 'm' },
41ce6545 197 { "stream", required_argument, 0, 'x' },
4a4f4a62 198 { "summary", no_argument, 0, OPT_SUMMARY },
84adb5e6
SK
199 { "version", no_argument, 0, 'V' },
200 { "help", no_argument, 0, 'h' },
201 { NULL, 0, 0, 0 }
202 };
0d955cd8
KZ
203 static const ul_excl_t excl[] = { /* rows and cols in ASCII order */
204 { 'O', 's' },
205 { 0 }
206 };
207 int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
18a706bd
KZ
208 /* Because we use space as a separator, we can't afford to use any
209 * locale which tolerates a space in a number. In any case, script.c
210 * sets the LC_NUMERIC locale to C, anyway.
211 */
212 setlocale(LC_ALL, "");
213 setlocale(LC_NUMERIC, "C");
214
215 bindtextdomain(PACKAGE, LOCALEDIR);
216 textdomain(PACKAGE);
2c308875 217 close_stdout_atexit();
18a706bd 218
12352c96 219 replay_init_debug();
b639c2a3 220 timerclear(&maxdelay);
fec06a06 221
a33f1fc4 222 while ((ch = getopt_long(argc, argv, "B:c:I:O:T:t:s:d:m:x:Vh", longopts, NULL)) != -1) {
0d955cd8
KZ
223
224 err_exclusive_options(ch, longopts, excl, excl_st);
225
84adb5e6 226 switch(ch) {
88b8e9bf
KZ
227 case 'c':
228 if (strcmp("auto", optarg) == 0)
229 crmode = REPLAY_CRMODE_AUTO;
230 else if (strcmp("never", optarg) == 0)
231 crmode = REPLAY_CRMODE_NEVER;
232 else if (strcmp("always", optarg) == 0)
233 crmode = REPLAY_CRMODE_ALWAYS;
234 else
235 errx(EXIT_FAILURE, _("unsupported mode name: '%s'"), optarg);
236 break;
0da871b5 237 case 't':
a33f1fc4 238 case 'T':
0d955cd8 239 log_tm = optarg;
0da871b5 240 break;
0d955cd8 241 case 'O':
0da871b5 242 case 's':
0d955cd8
KZ
243 log_out = optarg;
244 break;
245 case 'I':
246 log_in = optarg;
247 break;
248 case 'B':
249 log_io = optarg;
0da871b5
SK
250 break;
251 case 'd':
252 diviopt = TRUE;
253 divi = getnum(optarg);
254 break;
7f1d4836 255 case 'm':
b639c2a3 256 strtotimeval_or_err(optarg, &maxdelay, _("failed to parse maximal delay argument"));
7f1d4836 257 break;
41ce6545
KZ
258 case 'x':
259 if (strcmp("in", optarg) == 0)
260 appendchr(streams, sizeof(streams), 'I');
261 else if (strcmp("out", optarg) == 0)
262 appendchr(streams, sizeof(streams), 'O');
263 else if (strcmp("signal", optarg) == 0)
264 appendchr(streams, sizeof(streams), 'S');
7c4a374f
KZ
265 else if (strcmp("info", optarg) == 0)
266 appendchr(streams, sizeof(streams), 'H');
41ce6545
KZ
267 else
268 errx(EXIT_FAILURE, _("unsupported stream name: '%s'"), optarg);
269 break;
4a4f4a62
KZ
270 case OPT_SUMMARY:
271 summary = 1;
272 break;
84adb5e6 273 case 'V':
2c308875 274 print_version(EXIT_SUCCESS);
84adb5e6 275 case 'h':
86be6a32 276 usage();
84adb5e6 277 default:
677ec86c 278 errtryhelp(EXIT_FAILURE);
0d955cd8
KZ
279 }
280 }
84adb5e6
SK
281 argc -= optind;
282 argv += optind;
0da871b5 283 idx = 0;
84adb5e6 284
4a4f4a62
KZ
285 if (summary)
286 streams[0] = 'H', streams[1] = '\0';
287
7c4a374f 288 if (!log_tm && idx < argc)
0d955cd8 289 log_tm = argv[idx++];
d51f8ec1 290 if (!log_out && !summary && !log_in && !log_io)
0d955cd8
KZ
291 log_out = idx < argc ? argv[idx++] : "typescript";
292
0da871b5
SK
293 if (!diviopt)
294 divi = idx < argc ? getnum(argv[idx]) : 1;
18a706bd 295
6646f9f8
KZ
296 if (!log_tm)
297 errx(EXIT_FAILURE, _("timing file not specified"));
d51f8ec1 298 if (!(log_out || log_in || log_io) && !summary)
7c4a374f
KZ
299 errx(EXIT_FAILURE, _("data log file not specified"));
300
12352c96
KZ
301 setup = replay_new_setup();
302
303 if (replay_set_timing_file(setup, log_tm) != 0)
0d955cd8
KZ
304 err(EXIT_FAILURE, _("cannot open %s"), log_tm);
305
12352c96 306 if (log_out && replay_associate_log(setup, "O", log_out) != 0)
0d955cd8
KZ
307 err(EXIT_FAILURE, _("cannot open %s"), log_out);
308
12352c96 309 if (log_in && replay_associate_log(setup, "I", log_in) != 0)
0d955cd8 310 err(EXIT_FAILURE, _("cannot open %s"), log_in);
7f1d4836 311
12352c96 312 if (log_io && replay_associate_log(setup, "IO", log_io) != 0)
0d955cd8 313 err(EXIT_FAILURE, _("cannot open %s"), log_io);
18a706bd 314
41ce6545 315 if (!*streams) {
ac407b16 316 /* output is preferred default */
41ce6545
KZ
317 if (log_out || log_io)
318 appendchr(streams, sizeof(streams), 'O');
319 else if (log_in)
320 appendchr(streams, sizeof(streams), 'I');
321 }
322
12352c96 323 replay_set_default_type(setup,
41ce6545 324 *streams && streams[1] == '\0' ? *streams : 'O');
12352c96 325 replay_set_crmode(setup, crmode);
41ce6545 326
b639c2a3
KZ
327 if (divi != 1)
328 replay_set_delay_div(setup, divi);
329 if (timerisset(&maxdelay))
330 replay_set_delay_max(setup, &maxdelay);
331 replay_set_delay_min(setup, &mindelay);
332
36295381 333 isterm = setterm(&saved, &saved_flag);
edb088ff 334
fec06a06 335 do {
7000120b 336 switch (fgetc(stdin)) {
d616c2fb 337 case ' ':
338 replay_toggle_pause(setup);
339 break;
340 case '\033':
341 ch = fgetc(stdin);
342 if (ch == '[') {
c2c16fdb 343 ch = fgetc(stdin);
d616c2fb 344 if (ch == 'A') { /* Up arrow */
2b528997 345 divi *= 1.1;
d616c2fb 346 replay_set_delay_div(setup, divi);
347 } else if (ch == 'B') { /* Down arrow */
2b528997 348 divi *= 0.9;
d616c2fb 349 if (divi < 0.1)
350 divi = 0.1;
351 replay_set_delay_div(setup, divi);
352 } else if (ch == 'C') { /* Right arrow */
353 rc = replay_emit_step_data(setup, step, STDOUT_FILENO);
d7282ef8 354 if (!rc)
d616c2fb 355 rc = replay_get_next_step(setup, streams, &step);
d7282ef8
KZ
356 if (!rc) {
357 struct timeval *delay = replay_step_get_delay(step);
358 if (delay && timerisset(delay))
359 step_delay = *delay;
6cc9d27d 360 }
2da91bd3 361 }
d616c2fb 362 }
363 break;
3727ae82 364 }
c655d42c 365 if (rc)
366 break;
3727ae82 367
0bfd75db 368 if (replay_get_is_paused(setup)) {
28d7c584 369 delay_for(&input_delay);
3727ae82 370 continue;
371 }
363c6592 372
0bfd75db 373 if (timerisset(&step_delay)) {
28d7c584 374 const struct timeval *timeout = (timercmp(&step_delay, &input_delay, <) ? (&step_delay) : (&input_delay));
363c6592 375 delay_for(timeout);
28d7c584 376 timersub(&step_delay, timeout, &step_delay);
377 if (step_delay.tv_sec < 0 || step_delay.tv_usec < 0)
378 timerclear(&step_delay);
363c6592 379 continue;
380 }
381
28d7c584 382 if (!timerisset(&step_delay) && step)
363c6592 383 rc = replay_emit_step_data(setup, step, STDOUT_FILENO);
384 if (rc)
385 break;
d7282ef8 386
12352c96 387 rc = replay_get_next_step(setup, streams, &step);
fec06a06
KZ
388 if (rc)
389 break;
18a706bd 390
4a4f4a62 391 if (!summary) {
b639c2a3 392 struct timeval *delay = replay_step_get_delay(step);
6b4faed1 393
b639c2a3 394 if (delay && timerisset(delay))
28d7c584 395 step_delay = *delay;
4a4f4a62 396 }
fec06a06
KZ
397 } while (rc == 0);
398
0bfd75db 399 if (isterm) {
36295381 400 fcntl(STDIN_FILENO, F_SETFL, &saved_flag);
edb088ff 401 tcsetattr(STDOUT_FILENO, TCSADRAIN, &saved);
36295381 402 }
edb088ff 403
fec06a06 404 if (step && rc < 0)
12352c96 405 err(EXIT_FAILURE, _("%s: log file error"), replay_step_get_filename(step));
fec06a06
KZ
406 else if (rc < 0)
407 err(EXIT_FAILURE, _("%s: line %d: timing file error"),
12352c96
KZ
408 replay_get_timing_file(setup),
409 replay_get_timing_line(setup));
f004a02d 410 printf("\n");
263835e8
KZ
411 replay_free_setup(setup);
412
18a706bd
KZ
413 exit(EXIT_SUCCESS);
414}