chiark / gitweb /
rsync-backup.8: Put the variables in alphabetical order.
[rsync-backup] / rfreezefs.c
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>
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
71 static 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
94 struct 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
106 enum {
107 #define ENUM(tok) T_##tok,
108   TOKENS(ENUM)
109 #undef ENUM
110   T_LIMIT
111 };
112
113 enum {
114 #define MASK(tok) TF_##tok = 1u << T_##tok,
115   TOKENS(MASK)
116 #undef ENUM
117   TF_ALL = (1u << T_LIMIT) - 1u
118 };
119
120 static struct token toktab[] = {
121 #define INIT(tok) { #tok },
122   TOKENS(INIT)
123 #undef INIT
124   { 0 }
125 };
126
127 static 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
160 struct 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
167 static void tokmatch_init(struct tokmatch *tm)
168   { tm->tf = TF_ALL; tm->o = 0; tm->f = 0; }
169
170 static 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
201 static 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
216 struct 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
224 static int *fs;                         /* File descriptors for targets */
225 static char **fsname;                   /* File system names */
226 static size_t nfs;                      /* Number of descriptors */
227
228 /*----- Cleanup -----------------------------------------------------------*/
229
230 #define EOM ((char *)0)
231 static 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
249 static 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
270 static void cleanup(void) { partial_cleanup(nfs); }
271
272 static int sigcatch[] = {
273   SIGINT, SIGQUIT, SIGTERM, SIGHUP, SIGALRM,
274   SIGILL, SIGSEGV, SIGBUS, SIGFPE, SIGABRT
275 };
276
277 static 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
293 static void version(FILE *fp)
294   { pquis(fp, "$, " PACKAGE " version " VERSION "\n"); }
295 static void usage(FILE *fp)
296   { pquis(fp, "Usage: $ [-n] [-a ADDR] [-p LOPORT[-HIPORT]] FILSYS ...\n"); }
297
298 static void help(FILE *fp)
299 {
300   version(fp); putc('\n', fp);
301   usage(fp);
302   fputs("\n\
303 Freezes a filesystem temporarily, with some measure of safety.\n\
304 \n\
305 The program listens for connections on a TCP port, and prints a line\n\
306 \n\
307         PORT COOKIE\n\
308 \n\
309 to standard output.  You must connect to this PORT and send the COOKIE\n\
310 followed by a newline within a short period of time.  The filesystems\n\
311 will then be frozen, and `OK' written to the connection.  In order to\n\
312 keep the file system frozen, you must keep the connection open, and\n\
313 feed data into it.  If the connection closes, or no data is received\n\
314 within a set period of time, or the program receives one of a variety\n\
315 of signals or otherwise becomes unhappy, the filesystems are thawed again.\n\
316 \n\
317 Options:\n\
318 \n\
319 -h, --help                      Print this help text.\n\
320 -v, --version                   Print the program version number.\n\
321 -u, --usage                     Print a short usage message.\n\
322 \n\
323 -a, --address=ADDR              Listen only on ADDR.\n\
324 -n, --not-really                Don't really freeze or thaw filesystems.\n\
325 -p, --port-range=LO[-HI]        Select a port number between LO and HI.\n\
326                                   If HI is omitted, choose only LO.\n\
327 ", fp);
328 }
329
330 /*----- Main program ------------------------------------------------------*/
331
332 int main(int argc, char *argv[])
333 {
334   char buf[256];
335   int loport = -1, hiport = -1;
336   int sk, fd, maxfd;
337   struct sockaddr_in sin;
338   socklen_t sasz;
339   struct hostent *h;
340   const char *p, *q;
341   struct timeval now, when, delta;
342   struct client *clients = 0, *c, **cc;
343   const struct token *t;
344   struct tokmatch tm;
345   fd_set fdin;
346   int i;
347   ssize_t n;
348   unsigned f = 0;
349 #define f_bogus 0x01u
350 #define f_notreally 0x02u
351
352   ego(argv[0]);
353   sub_init();
354
355   /* --- Partially initialize the socket address --- */
356
357   sin.sin_family = AF_INET;
358   sin.sin_addr.s_addr = INADDR_ANY;
359   sin.sin_port = 0;
360
361   /* --- Parse the command line --- */
362
363   for (;;) {
364     static struct option opts[] = {
365       { "help",         0,              0,      'h' },
366       { "version",      0,              0,      'v' },
367       { "usage",        0,              0,      'u' },
368       { "address",      OPTF_ARGREQ,    0,      'a' },
369       { "not-really",   0,              0,      'n' },
370       { "port-range",   OPTF_ARGREQ,    0,      'p' },
371       { 0,              0,              0,      0 }
372     };
373
374     if ((i = mdwopt(argc, argv, "hvua:np:", opts, 0, 0, 0)) < 0) break;
375     switch (i) {
376       case 'h': help(stdout); exit(0);
377       case 'v': version(stdout); exit(0);
378       case 'u': usage(stdout); exit(0);
379       case 'a':
380         if ((h = gethostbyname(optarg)) == 0) {
381           die(1, "failed to resolve address `%s': %s",
382               optarg, hstrerror(h_errno));
383         }
384         if (h->h_addrtype != AF_INET)
385           die(1, "unexpected address type resolving `%s'", optarg);
386         assert(h->h_length == sizeof(sin.sin_addr));
387         memcpy(&sin.sin_addr, h->h_addr, sizeof(sin.sin_addr));
388         break;
389       case 'n': f |= f_notreally; break;
390       case 'p':
391         if ((p = strchr(optarg, '-')) == 0)
392           loport = hiport = getuint(optarg, 0);
393         else {
394           loport = getuint(optarg, p);
395           hiport = getuint(p + 1, 0);
396         }
397         break;
398       default: f |= f_bogus; break;
399     }
400   }
401   if (f & f_bogus) { usage(stderr); exit(1); }
402   if (optind >= argc) { usage(stderr); exit(1); }
403
404   /* --- Open the file systems --- */
405
406   nfs = argc - optind;
407   fsname = &argv[optind];
408   fs = xmalloc(nfs*sizeof(*fs));
409   for (i = 0; i < nfs; i++) {
410     if ((fs[i] = open(fsname[i], O_RDONLY)) < 0)
411       die(2, "open (%s): %s", fsname[i], strerror(errno));
412   }
413
414   if (f & f_notreally) {
415     for (i = 0; i < nfs; i++) {
416       close(fs[i]);
417       fs[i] = -1;
418     }
419   }
420
421   /* --- Generate random tokens --- */
422
423   inittoks();
424
425   /* --- Create the listening socket --- */
426
427   if ((sk = socket(PF_INET, SOCK_STREAM, 0)) < 0)
428     die(2, "socket: %s", strerror(errno));
429   i = 1;
430   if (setsockopt(sk, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)))
431     die(2, "setsockopt (reuseaddr): %s", strerror(errno));
432   if (fdflags(sk, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC))
433     die(2, "fdflags: %s", strerror(errno));
434   if (loport < 0 || loport == hiport) {
435     if (loport >= 0) sin.sin_port = htons(loport);
436     if (bind(sk, (struct sockaddr *)&sin, sizeof(sin)))
437       die(2, "bind: %s", strerror(errno));
438   } else if (hiport != loport) {
439     for (i = loport; i <= hiport; i++) {
440       sin.sin_port = htons(i);
441       if (bind(sk, (struct sockaddr *)&sin, sizeof(sin)) >= 0) break;
442       else if (errno != EADDRINUSE)
443         die(2, "bind: %s", strerror(errno));
444     }
445     if (i > hiport) die(2, "bind: all ports in use");
446   }
447   if (listen(sk, 5)) die(2, "listen: %s", strerror(errno));
448
449   /* --- Tell the caller how to connect to us, and start the timer --- */
450
451   sasz = sizeof(sin);
452   if (getsockname(sk, (struct sockaddr *)&sin, &sasz))
453     die(2, "getsockname (listen): %s", strerror(errno));
454   printf("PORT %d\n", ntohs(sin.sin_port));
455   for (t = toktab; t->label; t++)
456     printf("TOKEN %s %s\n", t->label, t->tok);
457   printf("READY\n");
458   if (fflush(stdout) || ferror(stdout))
459     die(2, "write (stdout, rubric): %s", strerror(errno));
460   gettimeofday(&now, 0); TV_ADDL(&when, &now, TO_CONNECT, 0);
461
462   /* --- Collect incoming connections, and check for the cookie --- *
463    *
464    * This is the tricky part.
465    */
466
467   for (;;) {
468     FD_ZERO(&fdin);
469     FD_SET(sk, &fdin);
470     maxfd = sk;
471     for (c = clients; c; c = c->next) {
472       FD_SET(c->fd, &fdin);
473       if (c->fd > maxfd) maxfd = c->fd;
474     }
475     TV_SUB(&delta, &when, &now);
476     if (select(maxfd + 1, &fdin, 0, 0, &delta) < 0)
477       die(2, "select (accept): %s", strerror(errno));
478     gettimeofday(&now, 0);
479
480     if (TV_CMP(&now, >=, &when)) die(3, "timeout (accept)");
481
482     if (FD_ISSET(sk, &fdin)) {
483       sasz = sizeof(sin);
484       fd = accept(sk, (struct sockaddr *)&sin, &sasz);
485       if (fd >= 0) {
486         if (fdflags(fd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC) < 0)
487           die(2, "fdflags: %s", strerror(errno));
488         c = CREATE(struct client);
489         c->next = clients; c->fd = fd; tokmatch_init(&c->tm);
490         clients = c;
491       }
492 #ifdef DEBUG
493       else if (errno != EAGAIN)
494         moan("accept: %s", strerror(errno));
495 #endif
496     }
497
498     for (cc = &clients; *cc;) {
499       c = *cc;
500       if (!FD_ISSET(c->fd, &fdin)) goto next_client;
501       n = read(c->fd, buf, sizeof(buf));
502       if (!n) goto disconn;
503       else if (n < 0) {
504         if (errno == EAGAIN) goto next_client;
505         D( moan("read (client; auth): %s", strerror(errno)); )
506         goto disconn;
507       } else {
508         for (p = buf, q = p + n; p < q; p++) {
509           switch (tokmatch_update(&c->tm, *p)) {
510             case 0: break;
511             case TF_FREEZE: goto connected;
512             default:
513               D( moan("bad token from client"); )
514               goto disconn;
515           }
516         }
517       }
518
519     next_client:
520       cc = &c->next;
521       continue;
522
523     disconn:
524       close(c->fd);
525       *cc = c->next;
526       DESTROY(c);
527       continue;
528     }
529   }
530
531 connected:
532   close(sk); sk = c->fd;
533   while (clients) {
534     if (clients->fd != sk) close(clients->fd);
535     c = clients->next;
536     DESTROY(clients);
537     clients = c;
538   }
539
540   /* --- Establish signal handlers --- *
541    *
542    * Hopefully this will prevent bad things happening if we have an accident.
543    */
544
545   for (i = 0; i < sizeof(sigcatch)/sizeof(sigcatch[0]); i++) {
546     if (signal(sigcatch[i], sigmumble) == SIG_ERR)
547       die(2, "signal (%d): %s", i, strerror(errno));
548   }
549   atexit(cleanup);
550
551   /* --- Prevent the OOM killer from clobbering us --- */
552
553   if ((fd = open("/proc/self/oom_adj", O_WRONLY)) < 0 ||
554       write(fd, "-17\n", 4) < 4 ||
555       close(fd))
556     die(2, "set oom_adj: %s", strerror(errno));
557
558   /* --- Actually freeze the filesystem --- */
559
560   for (i = 0; i < nfs; i++) {
561     if (fs[i] == -1)
562       moan("not really freezing %s", fsname[i]);
563     else {
564       if (ioctl(fs[i], FIFREEZE, 0) < 0) {
565         partial_cleanup(i);
566         die(2, "ioctl (freeze %s): %s", fsname[i], strerror(errno));
567       }
568     }
569   }
570   if (writetok(T_FROZEN, sk)) {
571     cleanup();
572     die(2, "write (frozen): %s", strerror(errno));
573   }
574
575   /* --- Now wait for the other end to detach --- */
576
577   tokmatch_init(&tm);
578   TV_ADDL(&when, &now, TO_KEEPALIVE, 0);
579   for (p++; p < q; p++) {
580     switch (tokmatch_update(&tm, *p)) {
581       case 0: break;
582       case TF_KEEPALIVE: tokmatch_init(&tm); break;
583       case TF_THAW: goto done;
584       default: cleanup(); die(3, "unknown token (keepalive)");
585     }
586   }
587   for (;;) {
588     FD_ZERO(&fdin);
589     FD_SET(sk, &fdin);
590     TV_SUB(&delta, &when, &now);
591     if (select(sk + 1, &fdin, 0, 0, &delta) < 0) {
592       cleanup();
593       die(2, "select (keepalive): %s", strerror(errno));
594     }
595
596     gettimeofday(&now, 0);
597     if (TV_CMP(&now, >, &when)) {
598       cleanup(); die(3, "timeout (keepalive)");
599     }
600     if (FD_ISSET(sk, &fdin)) {
601       n = read(sk, buf, sizeof(buf));
602       if (!n) { cleanup(); die(3, "end-of-file (keepalive)"); }
603       else if (n < 0) {
604         if (errno == EAGAIN) ;
605         else {
606           cleanup();
607           die(2, "read (client, keepalive): %s", strerror(errno));
608         }
609       } else {
610         for (p = buf, q = p + n; p < q; p++) {
611           switch (tokmatch_update(&tm, *p)) {
612             case 0: break;
613             case TF_KEEPALIVE:
614               TV_ADDL(&when, &now, TO_KEEPALIVE, 0);
615               tokmatch_init(&tm);
616               break;
617             case TF_THAW:
618               goto done;
619             default:
620               cleanup();
621               die(3, "unknown token (keepalive)");
622           }
623         }
624       }
625     }
626   }
627
628 done:
629   cleanup();
630   if (writetok(T_THAWED, sk))
631     die(2, "write (thaw): %s", strerror(errno));
632   close(sk);
633   return (0);
634 }
635
636 /*----- That's all, folks -------------------------------------------------*/