Release 1.1.1.
[rsync-backup] / rfreezefs.c
CommitLineData
f6b4ffdc
MW
1/* -*-c-*-
2 *
3 * Freeze a file system under remote control
4 *
5 * (c) 2012 Mark Wooding
6 */
7
8/*----- Licensing notice --------------------------------------------------*
9 *
10 * This file is part of the `rsync-backup' program.
11 *
12 * rsync-backup is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
16 *
17 * rsync-backup is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License along
23 * with rsync-backup; if not, write to the Free Software Foundation,
24 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25 */
26
27/*----- Header files ------------------------------------------------------*/
28
29#include <assert.h>
30#include <errno.h>
31#include <limits.h>
32#include <signal.h>
33#include <stdarg.h>
34#include <stdio.h>
35#include <string.h>
36#include <stdlib.h>
37#include <time.h>
38
39#include <sys/types.h>
40#include <sys/time.h>
41#include <sys/select.h>
42#include <unistd.h>
43#include <fcntl.h>
44#include <sys/ioctl.h>
45
46#include <linux/fs.h>
47
48#include <sys/socket.h>
49#include <arpa/inet.h>
50#include <netinet/in.h>
51#include <netdb.h>
52
53#include <mLib/alloc.h>
54#include <mLib/dstr.h>
55#include <mLib/base64.h>
56#include <mLib/fdflags.h>
ff6237ea 57#include <mLib/macros.h>
f6b4ffdc
MW
58#include <mLib/mdwopt.h>
59#include <mLib/quis.h>
60#include <mLib/report.h>
61#include <mLib/sub.h>
62#include <mLib/tv.h>
63
64/*----- Magic constants ---------------------------------------------------*/
65
66#define COOKIESZ 16 /* Size of authentication cookie */
67#define TO_CONNECT 30 /* Timeout for incoming connection */
68#define TO_KEEPALIVE 60 /* Timeout between keepalives */
69
70/*----- Utility functions -------------------------------------------------*/
71
72static int getuint(const char *p, const char *q)
73{
74 unsigned long i;
75 int e = errno;
76 char *qq;
77
78 if (!q) q = p + strlen(p);
79 errno = 0;
80 i = strtoul(p, &qq, 0);
81 if (errno || qq < q || i > INT_MAX)
82 die(1, "invalid integer `%s'", p);
83 errno = e;
84 return ((int)i);
85}
86
87#ifdef DEBUG
88# define D(x) x
89#else
90# define D(x)
91#endif
92
93/*----- Token management --------------------------------------------------*/
94
95struct token {
96 const char *label;
97 char tok[(COOKIESZ + 2)*4/3 + 1];
98};
99
100#define TOKENS(_) \
101 _(FREEZE) \
102 _(FROZEN) \
103 _(KEEPALIVE) \
104 _(THAW) \
105 _(THAWED)
106
107enum {
108#define ENUM(tok) T_##tok,
109 TOKENS(ENUM)
110#undef ENUM
111 T_LIMIT
112};
113
114enum {
115#define MASK(tok) TF_##tok = 1u << T_##tok,
116 TOKENS(MASK)
117#undef ENUM
118 TF_ALL = (1u << T_LIMIT) - 1u
119};
120
121static struct token toktab[] = {
122#define INIT(tok) { #tok },
123 TOKENS(INIT)
124#undef INIT
125 { 0 }
126};
127
128static void inittoks(void)
129{
130 static struct token *t, *tt;
131 unsigned char buf[COOKIESZ];
132 int fd;
133 ssize_t n;
134 base64_ctx bc;
135 dstr d = DSTR_INIT;
136
137 if ((fd = open("/dev/urandom", O_RDONLY)) < 0)
138 die(2, "open (urandom): %s", strerror(errno));
139
140 for (t = toktab; t->label; t++) {
141 again:
142 n = read(fd, buf, COOKIESZ);
143 if (n < 0) die(2, "read (urandom): %s", strerror(errno));
144 else if (n < COOKIESZ) die(2, "read (urandom): short read");
145 base64_init(&bc);
146 base64_encode(&bc, buf, COOKIESZ, &d);
147 base64_encode(&bc, 0, 0, &d);
148 dstr_putz(&d);
149
150 for (tt = toktab; tt < t; tt++) {
151 if (strcmp(d.buf, tt->tok) == 0)
152 goto again;
153 }
154
155 assert(d.len < sizeof(t->tok));
156 memcpy(t->tok, d.buf, d.len + 1);
157 dstr_reset(&d);
158 }
159}
160
161struct tokmatch {
162 unsigned tf; /* Possible token matches */
163 size_t o; /* Offset into token string */
164 unsigned f; /* Flags */
165#define TMF_CR 1u /* Seen trailing carriage-return */
166};
167
168static void tokmatch_init(struct tokmatch *tm)
169 { tm->tf = TF_ALL; tm->o = 0; tm->f = 0; }
170
171static int tokmatch_update(struct tokmatch *tm, int ch)
172{
173 const struct token *t;
174 unsigned tf;
175
176 switch (ch) {
177 case '\n':
178 for (t = toktab, tf = 1; t->label; t++, tf <<= 1) {
179 if ((tm->tf & tf) && !t->tok[tm->o])
180 return (tf);
181 }
182 return (-1);
183 case '\r':
184 for (t = toktab, tf = 1; t->label; t++, tf <<= 1) {
185 if ((tm->tf & tf) && !t->tok[tm->o] && !(tm->f & TMF_CR))
186 tm->f |= TMF_CR;
187 else
188 tm->tf &= ~tf;
189 }
190 break;
191 default:
192 for (t = toktab, tf = 1; t->label; t++, tf <<= 1) {
193 if ((tm->tf & tf) && ch != t->tok[tm->o])
194 tm->tf &= ~tf;
195 }
196 tm->o++;
197 break;
198 }
199 return (0);
200}
201
202static int writetok(unsigned i, int fd)
203{
204 static const char nl = '\n';
205 const struct token *t = &toktab[i];
206 size_t n = strlen(t->tok);
207
208 errno = EIO;
209 if (write(fd, t->tok, n) < n ||
210 write(fd, &nl, 1) < 1)
211 return (-1);
212 return (0);
213}
214
215/*----- Data structures ---------------------------------------------------*/
216
217struct client {
218 struct client *next; /* Links in the client chain */
219 int fd; /* File descriptor for socket */
220 struct tokmatch tm; /* Token matching context */
221};
222
223/*----- Static variables --------------------------------------------------*/
224
225static int *fs; /* File descriptors for targets */
226static char **fsname; /* File system names */
227static size_t nfs; /* Number of descriptors */
228
229/*----- Cleanup -----------------------------------------------------------*/
230
231#define EOM ((char *)0)
b7ac8b5e 232static void EXECL_LIKE(0) emerg(const char *msg, ...)
f6b4ffdc
MW
233{
234 va_list ap;
235
236#define MSG(m) \
237 do { const char *m_ = m; if (write(2, m_, strlen(m_))); } while (0)
238
239 va_start(ap, msg);
240 MSG(QUIS); MSG(": ");
241 do {
242 MSG(msg);
243 msg = va_arg(ap, const char *);
244 } while (msg != EOM);
245 MSG("\n");
246
247#undef MSG
248}
249
250static void partial_cleanup(size_t n)
251{
252 int i;
253 int bad = 0;
254
e6368a71 255 for (i = 0; i < n; i++) {
f6b4ffdc
MW
256 if (fs[i] == -1)
257 emerg("not really thawing ", fsname[i], EOM);
258 else if (fs[i] != -2) {
259 if (ioctl(fs[i], FITHAW, 0)) {
260 emerg("VERY BAD! failed to thaw ",
261 fsname[i], ": ", strerror(errno), EOM);
262 bad = 1;
263 }
264 close(fs[i]);
265 }
266 fs[i] = -2;
267 }
268 if (bad) _exit(112);
269}
270
271static void cleanup(void) { partial_cleanup(nfs); }
272
273static int sigcatch[] = {
274 SIGINT, SIGQUIT, SIGTERM, SIGHUP, SIGALRM,
275 SIGILL, SIGSEGV, SIGBUS, SIGFPE, SIGABRT
276};
277
ff6237ea 278static void NORETURN sigmumble(int sig)
f6b4ffdc
MW
279{
280 sigset_t ss;
281
282 cleanup();
ff6237ea 283 emerg(strsignal(sig), EOM);
f6b4ffdc
MW
284
285 signal(sig, SIG_DFL);
286 sigemptyset(&ss); sigaddset(&ss, sig);
287 sigprocmask(SIG_UNBLOCK, &ss, 0);
288 raise(sig);
289 _exit(4);
290}
291
292/*----- Help functions ----------------------------------------------------*/
293
294static void version(FILE *fp)
295 { pquis(fp, "$, " PACKAGE " version " VERSION "\n"); }
296static void usage(FILE *fp)
297 { pquis(fp, "Usage: $ [-n] [-a ADDR] [-p LOPORT[-HIPORT]] FILSYS ...\n"); }
298
299static void help(FILE *fp)
300{
301 version(fp); putc('\n', fp);
302 usage(fp);
303 fputs("\n\
304Freezes a filesystem temporarily, with some measure of safety.\n\
305\n\
6952bdd6
MW
306The program listens for connections on a TCP port, and prints a number of\n\
307lines:\n\
f6b4ffdc 308\n\
6952bdd6
MW
309 PORT <port>\n\
310 TOKEN FREEZE <token>\n\
311 TOKEN FROZEN <token>\n\
312 TOKEN KEEPALIVE <token>\n\
313 TOKEN THAW <token>\n\
314 TOKEN THAWED <token>\n\
315 READY\n\
f6b4ffdc 316\n\
6952bdd6
MW
317to standard output. You must connect to the <port>, using TCP, and send\n\
318the FREEZE token followed by a newline within a short period of time. The\n\
319filesystems will then be frozen, and the FROZEN token written to the\n\
320connection. In order to keep the file system frozen, you must keep the\n\
321connection open, and periodically echo the KEEPALIVE token to it. To\n\
322release the filesystems, write the THAW token to the connection, followed\n\
323by a newline; the filesystem will be thawed, and the THAWED token echoed\n\
324back. If the connection closes, or no valid message is received within a\n\
325set period of time, or the program receives one of a variety of signals or\n\
326otherwise becomes unhappy, the filesystems are thawed again.\n\
327\n\
328The program exits with status zero if everything worked as planned, while\n\
329nonzero codes indicate failures of various kinds (see the manual); an exit\n\
330status of 112 means that thawing a filesystem failed.\n\
f6b4ffdc
MW
331\n\
332Options:\n\
333\n\
334-h, --help Print this help text.\n\
335-v, --version Print the program version number.\n\
336-u, --usage Print a short usage message.\n\
337\n\
338-a, --address=ADDR Listen only on ADDR.\n\
339-n, --not-really Don't really freeze or thaw filesystems.\n\
340-p, --port-range=LO[-HI] Select a port number between LO and HI.\n\
341 If HI is omitted, choose only LO.\n\
342", fp);
343}
344
345/*----- Main program ------------------------------------------------------*/
346
347int main(int argc, char *argv[])
348{
349 char buf[256];
350 int loport = -1, hiport = -1;
351 int sk, fd, maxfd;
352 struct sockaddr_in sin;
353 socklen_t sasz;
354 struct hostent *h;
355 const char *p, *q;
356 struct timeval now, when, delta;
357 struct client *clients = 0, *c, **cc;
358 const struct token *t;
359 struct tokmatch tm;
360 fd_set fdin;
361 int i;
362 ssize_t n;
363 unsigned f = 0;
364#define f_bogus 0x01u
365#define f_notreally 0x02u
366
367 ego(argv[0]);
368 sub_init();
369
370 /* --- Partially initialize the socket address --- */
371
372 sin.sin_family = AF_INET;
373 sin.sin_addr.s_addr = INADDR_ANY;
374 sin.sin_port = 0;
375
376 /* --- Parse the command line --- */
377
378 for (;;) {
379 static struct option opts[] = {
380 { "help", 0, 0, 'h' },
381 { "version", 0, 0, 'v' },
382 { "usage", 0, 0, 'u' },
383 { "address", OPTF_ARGREQ, 0, 'a' },
384 { "not-really", 0, 0, 'n' },
385 { "port-range", OPTF_ARGREQ, 0, 'p' },
386 { 0, 0, 0, 0 }
387 };
388
389 if ((i = mdwopt(argc, argv, "hvua:np:", opts, 0, 0, 0)) < 0) break;
390 switch (i) {
391 case 'h': help(stdout); exit(0);
392 case 'v': version(stdout); exit(0);
393 case 'u': usage(stdout); exit(0);
394 case 'a':
395 if ((h = gethostbyname(optarg)) == 0) {
396 die(1, "failed to resolve address `%s': %s",
397 optarg, hstrerror(h_errno));
398 }
399 if (h->h_addrtype != AF_INET)
400 die(1, "unexpected address type resolving `%s'", optarg);
401 assert(h->h_length == sizeof(sin.sin_addr));
402 memcpy(&sin.sin_addr, h->h_addr, sizeof(sin.sin_addr));
403 break;
404 case 'n': f |= f_notreally; break;
405 case 'p':
406 if ((p = strchr(optarg, '-')) == 0)
407 loport = hiport = getuint(optarg, 0);
408 else {
409 loport = getuint(optarg, p);
410 hiport = getuint(p + 1, 0);
411 }
412 break;
413 default: f |= f_bogus; break;
414 }
415 }
416 if (f & f_bogus) { usage(stderr); exit(1); }
417 if (optind >= argc) { usage(stderr); exit(1); }
418
419 /* --- Open the file systems --- */
420
421 nfs = argc - optind;
422 fsname = &argv[optind];
423 fs = xmalloc(nfs*sizeof(*fs));
424 for (i = 0; i < nfs; i++) {
425 if ((fs[i] = open(fsname[i], O_RDONLY)) < 0)
426 die(2, "open (%s): %s", fsname[i], strerror(errno));
427 }
428
429 if (f & f_notreally) {
430 for (i = 0; i < nfs; i++) {
431 close(fs[i]);
432 fs[i] = -1;
433 }
434 }
435
436 /* --- Generate random tokens --- */
437
438 inittoks();
439
440 /* --- Create the listening socket --- */
441
442 if ((sk = socket(PF_INET, SOCK_STREAM, 0)) < 0)
443 die(2, "socket: %s", strerror(errno));
444 i = 1;
445 if (setsockopt(sk, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)))
446 die(2, "setsockopt (reuseaddr): %s", strerror(errno));
447 if (fdflags(sk, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC))
448 die(2, "fdflags: %s", strerror(errno));
449 if (loport < 0 || loport == hiport) {
450 if (loport >= 0) sin.sin_port = htons(loport);
451 if (bind(sk, (struct sockaddr *)&sin, sizeof(sin)))
452 die(2, "bind: %s", strerror(errno));
453 } else if (hiport != loport) {
454 for (i = loport; i <= hiport; i++) {
455 sin.sin_port = htons(i);
456 if (bind(sk, (struct sockaddr *)&sin, sizeof(sin)) >= 0) break;
457 else if (errno != EADDRINUSE)
458 die(2, "bind: %s", strerror(errno));
459 }
460 if (i > hiport) die(2, "bind: all ports in use");
461 }
462 if (listen(sk, 5)) die(2, "listen: %s", strerror(errno));
463
464 /* --- Tell the caller how to connect to us, and start the timer --- */
465
466 sasz = sizeof(sin);
467 if (getsockname(sk, (struct sockaddr *)&sin, &sasz))
468 die(2, "getsockname (listen): %s", strerror(errno));
469 printf("PORT %d\n", ntohs(sin.sin_port));
470 for (t = toktab; t->label; t++)
471 printf("TOKEN %s %s\n", t->label, t->tok);
472 printf("READY\n");
473 if (fflush(stdout) || ferror(stdout))
474 die(2, "write (stdout, rubric): %s", strerror(errno));
475 gettimeofday(&now, 0); TV_ADDL(&when, &now, TO_CONNECT, 0);
476
477 /* --- Collect incoming connections, and check for the cookie --- *
478 *
479 * This is the tricky part.
480 */
481
482 for (;;) {
483 FD_ZERO(&fdin);
484 FD_SET(sk, &fdin);
485 maxfd = sk;
486 for (c = clients; c; c = c->next) {
487 FD_SET(c->fd, &fdin);
488 if (c->fd > maxfd) maxfd = c->fd;
489 }
490 TV_SUB(&delta, &when, &now);
491 if (select(maxfd + 1, &fdin, 0, 0, &delta) < 0)
492 die(2, "select (accept): %s", strerror(errno));
493 gettimeofday(&now, 0);
494
495 if (TV_CMP(&now, >=, &when)) die(3, "timeout (accept)");
496
497 if (FD_ISSET(sk, &fdin)) {
498 sasz = sizeof(sin);
499 fd = accept(sk, (struct sockaddr *)&sin, &sasz);
500 if (fd >= 0) {
501 if (fdflags(fd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC) < 0)
502 die(2, "fdflags: %s", strerror(errno));
503 c = CREATE(struct client);
504 c->next = clients; c->fd = fd; tokmatch_init(&c->tm);
505 clients = c;
506 }
507#ifdef DEBUG
508 else if (errno != EAGAIN)
509 moan("accept: %s", strerror(errno));
510#endif
511 }
512
513 for (cc = &clients; *cc;) {
514 c = *cc;
515 if (!FD_ISSET(c->fd, &fdin)) goto next_client;
516 n = read(c->fd, buf, sizeof(buf));
517 if (!n) goto disconn;
518 else if (n < 0) {
519 if (errno == EAGAIN) goto next_client;
520 D( moan("read (client; auth): %s", strerror(errno)); )
521 goto disconn;
522 } else {
523 for (p = buf, q = p + n; p < q; p++) {
524 switch (tokmatch_update(&c->tm, *p)) {
525 case 0: break;
526 case TF_FREEZE: goto connected;
527 default:
528 D( moan("bad token from client"); )
529 goto disconn;
530 }
531 }
532 }
533
534 next_client:
535 cc = &c->next;
536 continue;
537
538 disconn:
539 close(c->fd);
540 *cc = c->next;
541 DESTROY(c);
542 continue;
543 }
544 }
545
546connected:
547 close(sk); sk = c->fd;
548 while (clients) {
549 if (clients->fd != sk) close(clients->fd);
550 c = clients->next;
551 DESTROY(clients);
552 clients = c;
553 }
554
555 /* --- Establish signal handlers --- *
556 *
557 * Hopefully this will prevent bad things happening if we have an accident.
558 */
559
aaf1bb2b 560 for (i = 0; i < N(sigcatch); i++) {
f6b4ffdc
MW
561 if (signal(sigcatch[i], sigmumble) == SIG_ERR)
562 die(2, "signal (%d): %s", i, strerror(errno));
563 }
564 atexit(cleanup);
565
566 /* --- Prevent the OOM killer from clobbering us --- */
567
568 if ((fd = open("/proc/self/oom_adj", O_WRONLY)) < 0 ||
569 write(fd, "-17\n", 4) < 4 ||
570 close(fd))
571 die(2, "set oom_adj: %s", strerror(errno));
572
573 /* --- Actually freeze the filesystem --- */
574
575 for (i = 0; i < nfs; i++) {
576 if (fs[i] == -1)
577 moan("not really freezing %s", fsname[i]);
578 else {
579 if (ioctl(fs[i], FIFREEZE, 0) < 0) {
580 partial_cleanup(i);
581 die(2, "ioctl (freeze %s): %s", fsname[i], strerror(errno));
582 }
583 }
584 }
585 if (writetok(T_FROZEN, sk)) {
586 cleanup();
587 die(2, "write (frozen): %s", strerror(errno));
588 }
589
590 /* --- Now wait for the other end to detach --- */
591
592 tokmatch_init(&tm);
593 TV_ADDL(&when, &now, TO_KEEPALIVE, 0);
594 for (p++; p < q; p++) {
595 switch (tokmatch_update(&tm, *p)) {
596 case 0: break;
597 case TF_KEEPALIVE: tokmatch_init(&tm); break;
598 case TF_THAW: goto done;
599 default: cleanup(); die(3, "unknown token (keepalive)");
600 }
601 }
602 for (;;) {
603 FD_ZERO(&fdin);
604 FD_SET(sk, &fdin);
605 TV_SUB(&delta, &when, &now);
606 if (select(sk + 1, &fdin, 0, 0, &delta) < 0) {
607 cleanup();
608 die(2, "select (keepalive): %s", strerror(errno));
609 }
610
611 gettimeofday(&now, 0);
612 if (TV_CMP(&now, >, &when)) {
613 cleanup(); die(3, "timeout (keepalive)");
614 }
615 if (FD_ISSET(sk, &fdin)) {
616 n = read(sk, buf, sizeof(buf));
617 if (!n) { cleanup(); die(3, "end-of-file (keepalive)"); }
618 else if (n < 0) {
619 if (errno == EAGAIN) ;
620 else {
621 cleanup();
622 die(2, "read (client, keepalive): %s", strerror(errno));
623 }
624 } else {
625 for (p = buf, q = p + n; p < q; p++) {
626 switch (tokmatch_update(&tm, *p)) {
627 case 0: break;
628 case TF_KEEPALIVE:
629 TV_ADDL(&when, &now, TO_KEEPALIVE, 0);
630 tokmatch_init(&tm);
631 break;
632 case TF_THAW:
633 goto done;
634 default:
635 cleanup();
636 die(3, "unknown token (keepalive)");
637 }
638 }
639 }
640 }
641 }
642
643done:
644 cleanup();
645 if (writetok(T_THAWED, sk))
646 die(2, "write (thaw): %s", strerror(errno));
647 close(sk);
648 return (0);
649}
650
651/*----- That's all, folks -------------------------------------------------*/