re-mark 1.29b-2 as not yet uploaded (merge madness!)
[debian/tar] / src / checkpoint.c
1 /* Checkpoint management for tar.
2
3    Copyright 2007, 2013-2014, 2016 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
27 enum checkpoint_opcode
28   {
29     cop_dot,
30     cop_bell,
31     cop_echo,
32     cop_ttyout,
33     cop_sleep,
34     cop_exec,
35     cop_totals
36   };
37
38 struct checkpoint_action
39 {
40   struct checkpoint_action *next;
41   enum checkpoint_opcode opcode;
42   union
43   {
44     time_t time;
45     char *command;
46   } v;
47 };
48
49 /* Checkpointing counter */
50 static unsigned checkpoint;
51
52 /* List of checkpoint actions */
53 static struct checkpoint_action *checkpoint_action, *checkpoint_action_tail;
54
55 static struct checkpoint_action *
56 alloc_action (enum checkpoint_opcode opcode)
57 {
58   struct checkpoint_action *p = xzalloc (sizeof *p);
59   if (checkpoint_action_tail)
60     checkpoint_action_tail->next = p;
61   else
62     checkpoint_action = p;
63   checkpoint_action_tail = p;
64   p->opcode = opcode;
65   return p;
66 }
67
68 static char *
69 copy_string_unquote (const char *str)
70 {
71   char *output = xstrdup (str);
72   size_t len = strlen (output);
73   if ((*output == '"' || *output == '\'')
74       && output[len-1] == *output)
75     {
76       memmove (output, output+1, len-2);
77       output[len-2] = 0;
78     }
79   unquote_string (output);
80   return output;
81 }
82
83 void
84 checkpoint_compile_action (const char *str)
85 {
86   struct checkpoint_action *act;
87
88   if (strcmp (str, ".") == 0 || strcmp (str, "dot") == 0)
89     alloc_action (cop_dot);
90   else if (strcmp (str, "bell") == 0)
91     alloc_action (cop_bell);
92   else if (strcmp (str, "echo") == 0)
93     alloc_action (cop_echo);
94   else if (strncmp (str, "echo=", 5) == 0)
95     {
96       act = alloc_action (cop_echo);
97       act->v.command = copy_string_unquote (str + 5);
98     }
99   else if (strncmp (str, "exec=", 5) == 0)
100     {
101       act = alloc_action (cop_exec);
102       act->v.command = copy_string_unquote (str + 5);
103     }
104   else if (strncmp (str, "ttyout=", 7) == 0)
105     {
106       act = alloc_action (cop_ttyout);
107       act->v.command = copy_string_unquote (str + 7);
108     }
109   else if (strncmp (str, "sleep=", 6) == 0)
110     {
111       char *p;
112       time_t n = strtoul (str+6, &p, 10);
113       if (*p)
114         FATAL_ERROR ((0, 0, _("%s: not a valid timeout"), str));
115       act = alloc_action (cop_sleep);
116       act->v.time = n;
117     }
118   else if (strcmp (str, "totals") == 0)
119     alloc_action (cop_totals);
120   else
121     FATAL_ERROR ((0, 0, _("%s: unknown checkpoint action"), str));
122 }
123
124 void
125 checkpoint_finish_compile (void)
126 {
127   if (checkpoint_option)
128     {
129       if (!checkpoint_action)
130         /* Provide a historical default */
131         checkpoint_compile_action ("echo");
132     }
133   else if (checkpoint_action)
134     /* Otherwise, set default checkpoint rate */
135     checkpoint_option = DEFAULT_CHECKPOINT;
136 }
137
138 static const char *checkpoint_total_format[] = {
139   "R",
140   "W",
141   "D"
142 };
143
144 static long
145 getwidth (FILE *fp)
146 {
147   char const *columns;
148
149 #ifdef TIOCGWINSZ
150   struct winsize ws;
151   if (ioctl (fileno (fp), TIOCGWINSZ, &ws) == 0 && 0 < ws.ws_col)
152     return ws.ws_col;
153 #endif
154
155   columns = getenv ("COLUMNS");
156   if (columns)
157     {
158       long int col = strtol (columns, NULL, 10);
159       if (0 < col)
160         return col;
161     }
162
163   return 80;
164 }
165
166 static char *
167 getarg (const char *input, const char ** endp, char **argbuf, size_t *arglen)
168 {
169   if (input[0] == '{')
170     {
171       char *p = strchr (input + 1, '}');
172       if (p)
173         {
174           size_t n = p - input;
175           if (n > *arglen)
176             {
177               *arglen = n;
178               *argbuf = xrealloc (*argbuf, *arglen);
179             }
180           n--;
181           memcpy (*argbuf, input + 1, n);
182           (*argbuf)[n] = 0;
183           *endp = p + 1;
184           return *argbuf;
185         }
186     }
187
188   *endp = input;
189   return NULL;
190 }
191
192 static int tty_cleanup;
193
194 static const char *def_format =
195   "%{%Y-%m-%d %H:%M:%S}t: %ds, %{read,wrote}T%*\r";
196
197 static int
198 format_checkpoint_string (FILE *fp, size_t len,
199                           const char *input, bool do_write,
200                           unsigned cpn)
201 {
202   const char *opstr = do_write ? gettext ("write") : gettext ("read");
203   char uintbuf[UINTMAX_STRSIZE_BOUND];
204   char *cps = STRINGIFY_BIGINT (cpn, uintbuf);
205   const char *ip;
206
207   static char *argbuf = NULL;
208   static size_t arglen = 0;
209   char *arg = NULL;
210
211   if (!input)
212     {
213       if (do_write)
214         /* TRANSLATORS: This is a "checkpoint of write operation",
215          *not* "Writing a checkpoint".
216          E.g. in Spanish "Punto de comprobaci@'on de escritura",
217          *not* "Escribiendo un punto de comprobaci@'on" */
218         input = gettext ("Write checkpoint %u");
219       else
220         /* TRANSLATORS: This is a "checkpoint of read operation",
221          *not* "Reading a checkpoint".
222          E.g. in Spanish "Punto de comprobaci@'on de lectura",
223          *not* "Leyendo un punto de comprobaci@'on" */
224         input = gettext ("Read checkpoint %u");
225     }
226
227   for (ip = input; *ip; ip++)
228     {
229       if (*ip == '%')
230         {
231           if (*++ip == '{')
232             {
233               arg = getarg (ip, &ip, &argbuf, &arglen);
234               if (!arg)
235                 {
236                   fputc ('%', fp);
237                   fputc (*ip, fp);
238                   len += 2;
239                   continue;
240                 }
241             }
242           switch (*ip)
243             {
244             case 'c':
245               len += format_checkpoint_string (fp, len, def_format, do_write,
246                                                cpn);
247               break;
248
249             case 'u':
250               fputs (cps, fp);
251               len += strlen (cps);
252               break;
253
254             case 's':
255               fputs (opstr, fp);
256               len += strlen (opstr);
257               break;
258
259             case 'd':
260               len += fprintf (fp, "%.0f", compute_duration ());
261               break;
262
263             case 'T':
264               {
265                 const char **fmt = checkpoint_total_format, *fmtbuf[3];
266                 struct wordsplit ws;
267                 compute_duration ();
268
269                 if (arg)
270                   {
271                     ws.ws_delim = ",";
272                     if (wordsplit (arg, &ws, WRDSF_NOVAR | WRDSF_NOCMD |
273                                            WRDSF_QUOTE | WRDSF_DELIM))
274                       ERROR ((0, 0, _("cannot split string '%s': %s"),
275                               arg, wordsplit_strerror (&ws)));
276                     else
277                       {
278                         int i;
279
280                         for (i = 0; i < ws.ws_wordc; i++)
281                           fmtbuf[i] = ws.ws_wordv[i];
282                         for (; i < 3; i++)
283                           fmtbuf[i] = NULL;
284                         fmt = fmtbuf;
285                       }
286                   }
287                 len += format_total_stats (fp, fmt, ',', 0);
288                 if (arg)
289                   wordsplit_free (&ws);
290               }
291               break;
292
293             case 't':
294               {
295                 struct timeval tv;
296                 struct tm *tm;
297                 const char *fmt = arg ? arg : "%c";
298
299                 gettimeofday (&tv, NULL);
300                 tm = localtime (&tv.tv_sec);
301                 len += fprintftime (fp, fmt, tm, 0, tv.tv_usec * 1000);
302               }
303               break;
304
305             case '*':
306               {
307                 long w = arg ? strtol (arg, NULL, 10) : getwidth (fp);
308                 for (; w > len; len++)
309                   fputc (' ', fp);
310               }
311               break;
312
313             default:
314               fputc ('%', fp);
315               fputc (*ip, fp);
316               len += 2;
317               break;
318             }
319           arg = NULL;
320         }
321       else
322         {
323           fputc (*ip, fp);
324           if (*ip == '\r')
325             {
326               len = 0;
327               tty_cleanup = 1;
328             }
329           else
330             len++;
331         }
332     }
333   fflush (fp);
334   return len;
335 }
336
337 static FILE *tty = NULL;
338
339 static void
340 run_checkpoint_actions (bool do_write)
341 {
342   struct checkpoint_action *p;
343
344   for (p = checkpoint_action; p; p = p->next)
345     {
346       switch (p->opcode)
347         {
348         case cop_dot:
349           fputc ('.', stdlis);
350           fflush (stdlis);
351           break;
352
353         case cop_bell:
354           if (!tty)
355             tty = fopen ("/dev/tty", "w");
356           if (tty)
357             {
358               fputc ('\a', tty);
359               fflush (tty);
360             }
361           break;
362
363         case cop_echo:
364           {
365             int n = fprintf (stderr, "%s: ", program_name);
366             format_checkpoint_string (stderr, n, p->v.command, do_write,
367                                       checkpoint);
368             fputc ('\n', stderr);
369           }
370           break;
371
372         case cop_ttyout:
373           if (!tty)
374             tty = fopen ("/dev/tty", "w");
375           if (tty)
376             format_checkpoint_string (tty, 0, p->v.command, do_write,
377                                       checkpoint);
378           break;
379
380         case cop_sleep:
381           sleep (p->v.time);
382           break;
383
384         case cop_exec:
385           sys_exec_checkpoint_script (p->v.command,
386                                       archive_name_cursor[0],
387                                       checkpoint);
388           break;
389
390         case cop_totals:
391           compute_duration ();
392           print_total_stats ();
393         }
394     }
395 }
396
397 void
398 checkpoint_flush_actions (void)
399 {
400   struct checkpoint_action *p;
401
402   for (p = checkpoint_action; p; p = p->next)
403     {
404       switch (p->opcode)
405         {
406         case cop_ttyout:
407           if (tty && tty_cleanup)
408             {
409               long w = getwidth (tty);
410               while (w--)
411                 fputc (' ', tty);
412               fputc ('\r', tty);
413               fflush (tty);
414             }
415           break;
416         default:
417           /* nothing */;
418         }
419     }
420 }
421
422 void
423 checkpoint_run (bool do_write)
424 {
425   if (checkpoint_option && !(++checkpoint % checkpoint_option))
426     run_checkpoint_actions (do_write);
427 }
428
429 void
430 checkpoint_finish (void)
431 {
432   if (checkpoint_option)
433     {
434       checkpoint_flush_actions ();
435       if (tty)
436         fclose (tty);
437     }
438 }