/* Checkpoint management for tar.
Copyright 2007-2023 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 . */
#include
#include "common.h"
#include "wordsplit.h"
#include
#include
#include "fprintftime.h"
#include
enum checkpoint_opcode
{
cop_dot,
cop_bell,
cop_echo,
cop_ttyout,
cop_sleep,
cop_exec,
cop_totals,
cop_wait
};
struct checkpoint_action
{
struct checkpoint_action *next;
enum checkpoint_opcode opcode;
union
{
time_t time;
char *command;
int signal;
} v;
};
/* Checkpointing counter */
static unsigned checkpoint;
/* List of checkpoint actions */
static struct checkpoint_action *checkpoint_action, *checkpoint_action_tail;
/* State of the checkpoint system */
enum {
CHKP_INIT, /* Needs initialization */
CHKP_COMPILE, /* Actions are being compiled */
CHKP_RUN /* Actions are being run */
};
static int checkpoint_state;
/* Blocked signals */
static sigset_t sigs;
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 == '\'')
&& len > 1 && 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 (checkpoint_state == CHKP_INIT)
{
sigemptyset (&sigs);
checkpoint_state = CHKP_COMPILE;
}
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 if (strncmp (str, "wait=", 5) == 0)
{
act = alloc_action (cop_wait);
act->v.signal = decode_signal (str + 5);
sigaddset (&sigs, act->v.signal);
}
else
FATAL_ERROR ((0, 0, _("%s: unknown checkpoint action"), str));
}
void
checkpoint_finish_compile (void)
{
if (checkpoint_state == CHKP_INIT
&& checkpoint_option
&& !checkpoint_action)
{
/* Provide a historical default */
checkpoint_compile_action ("echo");
}
if (checkpoint_state == CHKP_COMPILE)
{
sigprocmask (SIG_BLOCK, &sigs, NULL);
if (!checkpoint_option)
/* set default checkpoint rate */
checkpoint_option = DEFAULT_CHECKPOINT;
checkpoint_state = CHKP_RUN;
}
}
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 ();
break;
case cop_wait:
{
int n;
sigwait (&sigs, &n);
}
}
}
}
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);
}
}