4
0

checkpoint.c 9.6 KB

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