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