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