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