/* Checkpoint management for tar. Copyright 2007-2024 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 #include #include #include #include #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; char commandbuf[FLEXIBLE_ARRAY_MEMBER]; }; /* Checkpointing counter */ static intmax_t checkpoint; /* List of checkpoint actions */ static struct checkpoint_action *checkpoint_action, **checkpoint_action_tail = &checkpoint_action; /* State of the checkpoint system */ static enum { CHKP_INIT, /* Needs initialization */ CHKP_COMPILE, /* Actions are being compiled */ CHKP_RUN /* Actions are being run */ } checkpoint_state; /* Blocked signals */ static sigset_t sigs; static struct checkpoint_action * alloc_action (enum checkpoint_opcode opcode, char const *quoted_string) { idx_t quoted_size = quoted_string ? strlen (quoted_string) + 1 : 0; struct checkpoint_action *p = xmalloc (FLEXSIZEOF (struct checkpoint_action, commandbuf, quoted_size)); *checkpoint_action_tail = p; checkpoint_action_tail = &p->next; p->next = NULL; p->opcode = opcode; if (quoted_string) { p->v.command = memcpy (p->commandbuf, quoted_string, quoted_size); unquote_string (p->v.command); } return p; } void checkpoint_compile_action (const char *str) { if (checkpoint_state == CHKP_INIT) { sigemptyset (&sigs); checkpoint_state = CHKP_COMPILE; } if (strcmp (str, ".") == 0 || strcmp (str, "dot") == 0) alloc_action (cop_dot, NULL); else if (strcmp (str, "bell") == 0) alloc_action (cop_bell, NULL); else if (strcmp (str, "echo") == 0) alloc_action (cop_echo, NULL)->v.command = NULL; else if (strncmp (str, "echo=", 5) == 0) alloc_action (cop_echo, str + 5); else if (strncmp (str, "exec=", 5) == 0) alloc_action (cop_exec, str + 5); else if (strncmp (str, "ttyout=", 7) == 0) alloc_action (cop_ttyout, str + 7); else if (strncmp (str, "sleep=", 6) == 0) { char const *arg = str + 6; char *p; alloc_action (cop_sleep, NULL)->v.time = stoint (arg, &p, NULL, 0, TYPE_MAXIMUM (time_t)); if ((p == arg) | *p) paxfatal (0, _("%s: not a valid timeout"), str); } else if (strcmp (str, "totals") == 0) alloc_action (cop_totals, NULL); else if (strncmp (str, "wait=", 5) == 0) { int sig = decode_signal (str + 5); alloc_action (cop_wait, NULL)->v.signal = sig; sigaddset (&sigs, sig); } else paxfatal (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 intmax_t 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) { char *end; intmax_t col = stoint (columns, &end, NULL, 0, INTMAX_MAX); if (! (*end | !col)) return col; } return 80; } static char * getarg (char const *input, char const **endp, char **argbuf, idx_t *arglen) { if (input[0] == '{') { char *p = strchr (input + 1, '}'); if (p) { idx_t n = p - input; if (n > *arglen) *argbuf = xpalloc (*argbuf, arglen, n - *arglen, -1, 1); n--; *endp = p + 1; (*argbuf)[n] = 0; return memcpy (*argbuf, input + 1, n); } } *endp = input; return NULL; } static bool tty_cleanup; static const char *def_format = "%{%Y-%m-%d %H:%M:%S}t: %ds, %{read,wrote}T%*\r"; static idx_t format_checkpoint_string (FILE *fp, idx_t len, const char *input, bool do_write, intmax_t cpn) { const char *opstr = do_write ? gettext ("write") : gettext ("read"); const char *ip; static char *argbuf = NULL; static idx_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ón de escritura", *not* "Escribiendo un punto de comprobación" */ 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ón de lectura", *not* "Leyendo un punto de comprobación" */ 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': len += fprintf (fp, "%jd", cpn); break; case 's': fputs (opstr, fp); len += strlen (opstr); break; case 'd': len += fprintf (fp, "%.0f", compute_duration_ns () / BILLION); break; case 'T': { static char const *const checkpoint_total_format[] = { "R", "W", "D" }; char const *const *fmt = checkpoint_total_format, *fmtbuf[3]; struct wordsplit ws; compute_duration_ns (); if (arg) { ws.ws_delim = ","; if (wordsplit (arg, &ws, (WRDSF_NOVAR | WRDSF_NOCMD | WRDSF_QUOTE | WRDSF_DELIM))) paxerror (0, _("cannot split string '%s': %s"), arg, wordsplit_strerror (&ws)); else if (3 < ws.ws_wordc) paxerror (0, _("too many words in '%s'"), arg); 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 timespec ts = current_timespec (); const char *fmt = arg ? arg : "%c"; struct tm *tm = localtime (&ts.tv_sec); len += (tm ? fprintftime (fp, fmt, tm, 0, ts.tv_nsec) : fprintf (fp, "????""-??""-?? ??:??:??")); } break; case '*': { intmax_t w; if (!arg) w = getwidth (fp); else { char *end; w = stoint (arg, &end, NULL, 0, INTMAX_MAX); if ((end == arg) | *end) w = 80; } 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 = true; } 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_ns (); 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) { intmax_t 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); } }