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