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