summaryrefslogtreecommitdiff
path: root/src/checkpoint.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/checkpoint.c')
-rw-r--r--src/checkpoint.c438
1 files changed, 438 insertions, 0 deletions
diff --git a/src/checkpoint.c b/src/checkpoint.c
new file mode 100644
index 0000000..1a253c2
--- /dev/null
+++ b/src/checkpoint.c
@@ -0,0 +1,438 @@
+/* Checkpoint management for tar.
+
+ Copyright 2007, 2013-2014, 2016 Free Software Foundation, Inc.
+
+ This file is part of GNU tar.
+
+ GNU tar is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ GNU tar is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <system.h>
+#include "common.h"
+#include "wordsplit.h"
+#include <sys/ioctl.h>
+#include <termios.h>
+#include "fprintftime.h"
+
+enum checkpoint_opcode
+ {
+ cop_dot,
+ cop_bell,
+ cop_echo,
+ cop_ttyout,
+ cop_sleep,
+ cop_exec,
+ cop_totals
+ };
+
+struct checkpoint_action
+{
+ struct checkpoint_action *next;
+ enum checkpoint_opcode opcode;
+ union
+ {
+ time_t time;
+ char *command;
+ } v;
+};
+
+/* Checkpointing counter */
+static unsigned checkpoint;
+
+/* List of checkpoint actions */
+static struct checkpoint_action *checkpoint_action, *checkpoint_action_tail;
+
+static struct checkpoint_action *
+alloc_action (enum checkpoint_opcode opcode)
+{
+ struct checkpoint_action *p = xzalloc (sizeof *p);
+ if (checkpoint_action_tail)
+ checkpoint_action_tail->next = p;
+ else
+ checkpoint_action = p;
+ checkpoint_action_tail = p;
+ p->opcode = opcode;
+ return p;
+}
+
+static char *
+copy_string_unquote (const char *str)
+{
+ char *output = xstrdup (str);
+ size_t len = strlen (output);
+ if ((*output == '"' || *output == '\'')
+ && output[len-1] == *output)
+ {
+ memmove (output, output+1, len-2);
+ output[len-2] = 0;
+ }
+ unquote_string (output);
+ return output;
+}
+
+void
+checkpoint_compile_action (const char *str)
+{
+ struct checkpoint_action *act;
+
+ if (strcmp (str, ".") == 0 || strcmp (str, "dot") == 0)
+ alloc_action (cop_dot);
+ else if (strcmp (str, "bell") == 0)
+ alloc_action (cop_bell);
+ else if (strcmp (str, "echo") == 0)
+ alloc_action (cop_echo);
+ else if (strncmp (str, "echo=", 5) == 0)
+ {
+ act = alloc_action (cop_echo);
+ act->v.command = copy_string_unquote (str + 5);
+ }
+ else if (strncmp (str, "exec=", 5) == 0)
+ {
+ act = alloc_action (cop_exec);
+ act->v.command = copy_string_unquote (str + 5);
+ }
+ else if (strncmp (str, "ttyout=", 7) == 0)
+ {
+ act = alloc_action (cop_ttyout);
+ act->v.command = copy_string_unquote (str + 7);
+ }
+ else if (strncmp (str, "sleep=", 6) == 0)
+ {
+ char *p;
+ time_t n = strtoul (str+6, &p, 10);
+ if (*p)
+ FATAL_ERROR ((0, 0, _("%s: not a valid timeout"), str));
+ act = alloc_action (cop_sleep);
+ act->v.time = n;
+ }
+ else if (strcmp (str, "totals") == 0)
+ alloc_action (cop_totals);
+ else
+ FATAL_ERROR ((0, 0, _("%s: unknown checkpoint action"), str));
+}
+
+void
+checkpoint_finish_compile (void)
+{
+ if (checkpoint_option)
+ {
+ if (!checkpoint_action)
+ /* Provide a historical default */
+ checkpoint_compile_action ("echo");
+ }
+ else if (checkpoint_action)
+ /* Otherwise, set default checkpoint rate */
+ checkpoint_option = DEFAULT_CHECKPOINT;
+}
+
+static const char *checkpoint_total_format[] = {
+ "R",
+ "W",
+ "D"
+};
+
+static long
+getwidth (FILE *fp)
+{
+ char const *columns;
+
+#ifdef TIOCGWINSZ
+ struct winsize ws;
+ if (ioctl (fileno (fp), TIOCGWINSZ, &ws) == 0 && 0 < ws.ws_col)
+ return ws.ws_col;
+#endif
+
+ columns = getenv ("COLUMNS");
+ if (columns)
+ {
+ long int col = strtol (columns, NULL, 10);
+ if (0 < col)
+ return col;
+ }
+
+ return 80;
+}
+
+static char *
+getarg (const char *input, const char ** endp, char **argbuf, size_t *arglen)
+{
+ if (input[0] == '{')
+ {
+ char *p = strchr (input + 1, '}');
+ if (p)
+ {
+ size_t n = p - input;
+ if (n > *arglen)
+ {
+ *arglen = n;
+ *argbuf = xrealloc (*argbuf, *arglen);
+ }
+ n--;
+ memcpy (*argbuf, input + 1, n);
+ (*argbuf)[n] = 0;
+ *endp = p + 1;
+ return *argbuf;
+ }
+ }
+
+ *endp = input;
+ return NULL;
+}
+
+static int tty_cleanup;
+
+static const char *def_format =
+ "%{%Y-%m-%d %H:%M:%S}t: %ds, %{read,wrote}T%*\r";
+
+static int
+format_checkpoint_string (FILE *fp, size_t len,
+ const char *input, bool do_write,
+ unsigned cpn)
+{
+ const char *opstr = do_write ? gettext ("write") : gettext ("read");
+ char uintbuf[UINTMAX_STRSIZE_BOUND];
+ char *cps = STRINGIFY_BIGINT (cpn, uintbuf);
+ const char *ip;
+
+ static char *argbuf = NULL;
+ static size_t arglen = 0;
+ char *arg = NULL;
+
+ if (!input)
+ {
+ if (do_write)
+ /* TRANSLATORS: This is a "checkpoint of write operation",
+ *not* "Writing a checkpoint".
+ E.g. in Spanish "Punto de comprobaci@'on de escritura",
+ *not* "Escribiendo un punto de comprobaci@'on" */
+ input = gettext ("Write checkpoint %u");
+ else
+ /* TRANSLATORS: This is a "checkpoint of read operation",
+ *not* "Reading a checkpoint".
+ E.g. in Spanish "Punto de comprobaci@'on de lectura",
+ *not* "Leyendo un punto de comprobaci@'on" */
+ input = gettext ("Read checkpoint %u");
+ }
+
+ for (ip = input; *ip; ip++)
+ {
+ if (*ip == '%')
+ {
+ if (*++ip == '{')
+ {
+ arg = getarg (ip, &ip, &argbuf, &arglen);
+ if (!arg)
+ {
+ fputc ('%', fp);
+ fputc (*ip, fp);
+ len += 2;
+ continue;
+ }
+ }
+ switch (*ip)
+ {
+ case 'c':
+ len += format_checkpoint_string (fp, len, def_format, do_write,
+ cpn);
+ break;
+
+ case 'u':
+ fputs (cps, fp);
+ len += strlen (cps);
+ break;
+
+ case 's':
+ fputs (opstr, fp);
+ len += strlen (opstr);
+ break;
+
+ case 'd':
+ len += fprintf (fp, "%.0f", compute_duration ());
+ break;
+
+ case 'T':
+ {
+ const char **fmt = checkpoint_total_format, *fmtbuf[3];
+ struct wordsplit ws;
+ compute_duration ();
+
+ if (arg)
+ {
+ ws.ws_delim = ",";
+ if (wordsplit (arg, &ws, WRDSF_NOVAR | WRDSF_NOCMD |
+ WRDSF_QUOTE | WRDSF_DELIM))
+ ERROR ((0, 0, _("cannot split string '%s': %s"),
+ arg, wordsplit_strerror (&ws)));
+ else
+ {
+ int i;
+
+ for (i = 0; i < ws.ws_wordc; i++)
+ fmtbuf[i] = ws.ws_wordv[i];
+ for (; i < 3; i++)
+ fmtbuf[i] = NULL;
+ fmt = fmtbuf;
+ }
+ }
+ len += format_total_stats (fp, fmt, ',', 0);
+ if (arg)
+ wordsplit_free (&ws);
+ }
+ break;
+
+ case 't':
+ {
+ struct timeval tv;
+ struct tm *tm;
+ const char *fmt = arg ? arg : "%c";
+
+ gettimeofday (&tv, NULL);
+ tm = localtime (&tv.tv_sec);
+ len += fprintftime (fp, fmt, tm, 0, tv.tv_usec * 1000);
+ }
+ break;
+
+ case '*':
+ {
+ long w = arg ? strtol (arg, NULL, 10) : getwidth (fp);
+ for (; w > len; len++)
+ fputc (' ', fp);
+ }
+ break;
+
+ default:
+ fputc ('%', fp);
+ fputc (*ip, fp);
+ len += 2;
+ break;
+ }
+ arg = NULL;
+ }
+ else
+ {
+ fputc (*ip, fp);
+ if (*ip == '\r')
+ {
+ len = 0;
+ tty_cleanup = 1;
+ }
+ else
+ len++;
+ }
+ }
+ fflush (fp);
+ return len;
+}
+
+static FILE *tty = NULL;
+
+static void
+run_checkpoint_actions (bool do_write)
+{
+ struct checkpoint_action *p;
+
+ for (p = checkpoint_action; p; p = p->next)
+ {
+ switch (p->opcode)
+ {
+ case cop_dot:
+ fputc ('.', stdlis);
+ fflush (stdlis);
+ break;
+
+ case cop_bell:
+ if (!tty)
+ tty = fopen ("/dev/tty", "w");
+ if (tty)
+ {
+ fputc ('\a', tty);
+ fflush (tty);
+ }
+ break;
+
+ case cop_echo:
+ {
+ int n = fprintf (stderr, "%s: ", program_name);
+ format_checkpoint_string (stderr, n, p->v.command, do_write,
+ checkpoint);
+ fputc ('\n', stderr);
+ }
+ break;
+
+ case cop_ttyout:
+ if (!tty)
+ tty = fopen ("/dev/tty", "w");
+ if (tty)
+ format_checkpoint_string (tty, 0, p->v.command, do_write,
+ checkpoint);
+ break;
+
+ case cop_sleep:
+ sleep (p->v.time);
+ break;
+
+ case cop_exec:
+ sys_exec_checkpoint_script (p->v.command,
+ archive_name_cursor[0],
+ checkpoint);
+ break;
+
+ case cop_totals:
+ compute_duration ();
+ print_total_stats ();
+ }
+ }
+}
+
+void
+checkpoint_flush_actions (void)
+{
+ struct checkpoint_action *p;
+
+ for (p = checkpoint_action; p; p = p->next)
+ {
+ switch (p->opcode)
+ {
+ case cop_ttyout:
+ if (tty && tty_cleanup)
+ {
+ long w = getwidth (tty);
+ while (w--)
+ fputc (' ', tty);
+ fputc ('\r', tty);
+ fflush (tty);
+ }
+ break;
+ default:
+ /* nothing */;
+ }
+ }
+}
+
+void
+checkpoint_run (bool do_write)
+{
+ if (checkpoint_option && !(++checkpoint % checkpoint_option))
+ run_checkpoint_actions (do_write);
+}
+
+void
+checkpoint_finish (void)
+{
+ if (checkpoint_option)
+ {
+ checkpoint_flush_actions ();
+ if (tty)
+ fclose (tty);
+ }
+}