1 /* Checkpoint management for tar.
2
3 Copyright 2007-2021 Free Software Foundation, Inc.
4
5 This file is part of GNU tar.
6
7 GNU tar is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 3 of the License, or
10 (at your option) any later version.
11
12 GNU tar is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program. If not, see <http://www.gnu.org/licenses/>. */
19
20 #include <system.h>
21 #include "common.h"
22 #include "wordsplit.h"
23 #include <sys/ioctl.h>
24 #include <termios.h>
25 #include "fprintftime.h"
26 #include <signal.h>
27
28 enum checkpoint_opcode
29 {
30 cop_dot,
31 cop_bell,
32 cop_echo,
33 cop_ttyout,
34 cop_sleep,
35 cop_exec,
36 cop_totals,
37 cop_wait
38 };
39
40 struct checkpoint_action
41 {
42 struct checkpoint_action *next;
43 enum checkpoint_opcode opcode;
44 union
45 {
46 time_t time;
47 char *command;
48 int signal;
49 } v;
50 };
51
52 /* Checkpointing counter */
53 static unsigned checkpoint;
54
55 /* List of checkpoint actions */
56 static struct checkpoint_action *checkpoint_action, *checkpoint_action_tail;
57
58 /* State of the checkpoint system */
59 enum {
60 CHKP_INIT, /* Needs initialization */
61 CHKP_COMPILE, /* Actions are being compiled */
62 CHKP_RUN /* Actions are being run */
63 };
64 static int checkpoint_state;
65 /* Blocked signals */
66 static sigset_t sigs;
67
68 static struct checkpoint_action *
alloc_action(enum checkpoint_opcode opcode)69 alloc_action (enum checkpoint_opcode opcode)
70 {
71 struct checkpoint_action *p = xzalloc (sizeof *p);
72 if (checkpoint_action_tail)
73 checkpoint_action_tail->next = p;
74 else
75 checkpoint_action = p;
76 checkpoint_action_tail = p;
77 p->opcode = opcode;
78 return p;
79 }
80
81 static char *
copy_string_unquote(const char * str)82 copy_string_unquote (const char *str)
83 {
84 char *output = xstrdup (str);
85 size_t len = strlen (output);
86 if ((*output == '"' || *output == '\'')
87 && output[len-1] == *output)
88 {
89 memmove (output, output+1, len-2);
90 output[len-2] = 0;
91 }
92 unquote_string (output);
93 return output;
94 }
95
96 void
checkpoint_compile_action(const char * str)97 checkpoint_compile_action (const char *str)
98 {
99 struct checkpoint_action *act;
100
101 if (checkpoint_state == CHKP_INIT)
102 {
103 sigemptyset (&sigs);
104 checkpoint_state = CHKP_COMPILE;
105 }
106
107 if (strcmp (str, ".") == 0 || strcmp (str, "dot") == 0)
108 alloc_action (cop_dot);
109 else if (strcmp (str, "bell") == 0)
110 alloc_action (cop_bell);
111 else if (strcmp (str, "echo") == 0)
112 alloc_action (cop_echo);
113 else if (strncmp (str, "echo=", 5) == 0)
114 {
115 act = alloc_action (cop_echo);
116 act->v.command = copy_string_unquote (str + 5);
117 }
118 else if (strncmp (str, "exec=", 5) == 0)
119 {
120 act = alloc_action (cop_exec);
121 act->v.command = copy_string_unquote (str + 5);
122 }
123 else if (strncmp (str, "ttyout=", 7) == 0)
124 {
125 act = alloc_action (cop_ttyout);
126 act->v.command = copy_string_unquote (str + 7);
127 }
128 else if (strncmp (str, "sleep=", 6) == 0)
129 {
130 char *p;
131 time_t n = strtoul (str+6, &p, 10);
132 if (*p)
133 FATAL_ERROR ((0, 0, _("%s: not a valid timeout"), str));
134 act = alloc_action (cop_sleep);
135 act->v.time = n;
136 }
137 else if (strcmp (str, "totals") == 0)
138 alloc_action (cop_totals);
139 else if (strncmp (str, "wait=", 5) == 0)
140 {
141 act = alloc_action (cop_wait);
142 act->v.signal = decode_signal (str + 5);
143 sigaddset (&sigs, act->v.signal);
144 }
145 else
146 FATAL_ERROR ((0, 0, _("%s: unknown checkpoint action"), str));
147 }
148
149 void
checkpoint_finish_compile(void)150 checkpoint_finish_compile (void)
151 {
152 if (checkpoint_state == CHKP_INIT
153 && checkpoint_option
154 && !checkpoint_action)
155 {
156 /* Provide a historical default */
157 checkpoint_compile_action ("echo");
158 }
159
160 if (checkpoint_state == CHKP_COMPILE)
161 {
162 sigprocmask (SIG_BLOCK, &sigs, NULL);
163
164 if (!checkpoint_option)
165 /* set default checkpoint rate */
166 checkpoint_option = DEFAULT_CHECKPOINT;
167
168 checkpoint_state = CHKP_RUN;
169 }
170 }
171
172 static const char *checkpoint_total_format[] = {
173 "R",
174 "W",
175 "D"
176 };
177
178 static long
getwidth(FILE * fp)179 getwidth (FILE *fp)
180 {
181 char const *columns;
182
183 #ifdef TIOCGWINSZ
184 struct winsize ws;
185 if (ioctl (fileno (fp), TIOCGWINSZ, &ws) == 0 && 0 < ws.ws_col)
186 return ws.ws_col;
187 #endif
188
189 columns = getenv ("COLUMNS");
190 if (columns)
191 {
192 long int col = strtol (columns, NULL, 10);
193 if (0 < col)
194 return col;
195 }
196
197 return 80;
198 }
199
200 static char *
getarg(const char * input,const char ** endp,char ** argbuf,size_t * arglen)201 getarg (const char *input, const char ** endp, char **argbuf, size_t *arglen)
202 {
203 if (input[0] == '{')
204 {
205 char *p = strchr (input + 1, '}');
206 if (p)
207 {
208 size_t n = p - input;
209 if (n > *arglen)
210 {
211 *arglen = n;
212 *argbuf = xrealloc (*argbuf, *arglen);
213 }
214 n--;
215 memcpy (*argbuf, input + 1, n);
216 (*argbuf)[n] = 0;
217 *endp = p + 1;
218 return *argbuf;
219 }
220 }
221
222 *endp = input;
223 return NULL;
224 }
225
226 static int tty_cleanup;
227
228 static const char *def_format =
229 "%{%Y-%m-%d %H:%M:%S}t: %ds, %{read,wrote}T%*\r";
230
231 static int
format_checkpoint_string(FILE * fp,size_t len,const char * input,bool do_write,unsigned cpn)232 format_checkpoint_string (FILE *fp, size_t len,
233 const char *input, bool do_write,
234 unsigned cpn)
235 {
236 const char *opstr = do_write ? gettext ("write") : gettext ("read");
237 char uintbuf[UINTMAX_STRSIZE_BOUND];
238 char *cps = STRINGIFY_BIGINT (cpn, uintbuf);
239 const char *ip;
240
241 static char *argbuf = NULL;
242 static size_t arglen = 0;
243 char *arg = NULL;
244
245 if (!input)
246 {
247 if (do_write)
248 /* TRANSLATORS: This is a "checkpoint of write operation",
249 *not* "Writing a checkpoint".
250 E.g. in Spanish "Punto de comprobaci@'on de escritura",
251 *not* "Escribiendo un punto de comprobaci@'on" */
252 input = gettext ("Write checkpoint %u");
253 else
254 /* TRANSLATORS: This is a "checkpoint of read operation",
255 *not* "Reading a checkpoint".
256 E.g. in Spanish "Punto de comprobaci@'on de lectura",
257 *not* "Leyendo un punto de comprobaci@'on" */
258 input = gettext ("Read checkpoint %u");
259 }
260
261 for (ip = input; *ip; ip++)
262 {
263 if (*ip == '%')
264 {
265 if (*++ip == '{')
266 {
267 arg = getarg (ip, &ip, &argbuf, &arglen);
268 if (!arg)
269 {
270 fputc ('%', fp);
271 fputc (*ip, fp);
272 len += 2;
273 continue;
274 }
275 }
276 switch (*ip)
277 {
278 case 'c':
279 len += format_checkpoint_string (fp, len, def_format, do_write,
280 cpn);
281 break;
282
283 case 'u':
284 fputs (cps, fp);
285 len += strlen (cps);
286 break;
287
288 case 's':
289 fputs (opstr, fp);
290 len += strlen (opstr);
291 break;
292
293 case 'd':
294 len += fprintf (fp, "%.0f", compute_duration ());
295 break;
296
297 case 'T':
298 {
299 const char **fmt = checkpoint_total_format, *fmtbuf[3];
300 struct wordsplit ws;
301 compute_duration ();
302
303 if (arg)
304 {
305 ws.ws_delim = ",";
306 if (wordsplit (arg, &ws, WRDSF_NOVAR | WRDSF_NOCMD |
307 WRDSF_QUOTE | WRDSF_DELIM))
308 ERROR ((0, 0, _("cannot split string '%s': %s"),
309 arg, wordsplit_strerror (&ws)));
310 else
311 {
312 int i;
313
314 for (i = 0; i < ws.ws_wordc; i++)
315 fmtbuf[i] = ws.ws_wordv[i];
316 for (; i < 3; i++)
317 fmtbuf[i] = NULL;
318 fmt = fmtbuf;
319 }
320 }
321 len += format_total_stats (fp, fmt, ',', 0);
322 if (arg)
323 wordsplit_free (&ws);
324 }
325 break;
326
327 case 't':
328 {
329 struct timeval tv;
330 struct tm *tm;
331 const char *fmt = arg ? arg : "%c";
332
333 gettimeofday (&tv, NULL);
334 tm = localtime (&tv.tv_sec);
335 len += fprintftime (fp, fmt, tm, 0, tv.tv_usec * 1000);
336 }
337 break;
338
339 case '*':
340 {
341 long w = arg ? strtol (arg, NULL, 10) : getwidth (fp);
342 for (; w > len; len++)
343 fputc (' ', fp);
344 }
345 break;
346
347 default:
348 fputc ('%', fp);
349 fputc (*ip, fp);
350 len += 2;
351 break;
352 }
353 arg = NULL;
354 }
355 else
356 {
357 fputc (*ip, fp);
358 if (*ip == '\r')
359 {
360 len = 0;
361 tty_cleanup = 1;
362 }
363 else
364 len++;
365 }
366 }
367 fflush (fp);
368 return len;
369 }
370
371 static FILE *tty = NULL;
372
373 static void
run_checkpoint_actions(bool do_write)374 run_checkpoint_actions (bool do_write)
375 {
376 struct checkpoint_action *p;
377
378 for (p = checkpoint_action; p; p = p->next)
379 {
380 switch (p->opcode)
381 {
382 case cop_dot:
383 fputc ('.', stdlis);
384 fflush (stdlis);
385 break;
386
387 case cop_bell:
388 if (!tty)
389 tty = fopen ("/dev/tty", "w");
390 if (tty)
391 {
392 fputc ('\a', tty);
393 fflush (tty);
394 }
395 break;
396
397 case cop_echo:
398 {
399 int n = fprintf (stderr, "%s: ", program_name);
400 format_checkpoint_string (stderr, n, p->v.command, do_write,
401 checkpoint);
402 fputc ('\n', stderr);
403 }
404 break;
405
406 case cop_ttyout:
407 if (!tty)
408 tty = fopen ("/dev/tty", "w");
409 if (tty)
410 format_checkpoint_string (tty, 0, p->v.command, do_write,
411 checkpoint);
412 break;
413
414 case cop_sleep:
415 sleep (p->v.time);
416 break;
417
418 case cop_exec:
419 sys_exec_checkpoint_script (p->v.command,
420 archive_name_cursor[0],
421 checkpoint);
422 break;
423
424 case cop_totals:
425 compute_duration ();
426 print_total_stats ();
427 break;
428
429 case cop_wait:
430 {
431 int n;
432 sigwait (&sigs, &n);
433 }
434 }
435 }
436 }
437
438 void
checkpoint_flush_actions(void)439 checkpoint_flush_actions (void)
440 {
441 struct checkpoint_action *p;
442
443 for (p = checkpoint_action; p; p = p->next)
444 {
445 switch (p->opcode)
446 {
447 case cop_ttyout:
448 if (tty && tty_cleanup)
449 {
450 long w = getwidth (tty);
451 while (w--)
452 fputc (' ', tty);
453 fputc ('\r', tty);
454 fflush (tty);
455 }
456 break;
457 default:
458 /* nothing */;
459 }
460 }
461 }
462
463 void
checkpoint_run(bool do_write)464 checkpoint_run (bool do_write)
465 {
466 if (checkpoint_option && !(++checkpoint % checkpoint_option))
467 run_checkpoint_actions (do_write);
468 }
469
470 void
checkpoint_finish(void)471 checkpoint_finish (void)
472 {
473 if (checkpoint_option)
474 {
475 checkpoint_flush_actions ();
476 if (tty)
477 fclose (tty);
478 }
479 }
480