3 * Freeze a file system under remote control
5 * (c) 2012 Mark Wooding
8 /*----- Licensing notice --------------------------------------------------*
10 * This file is part of the `rsync-backup' program.
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.
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.
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.
27 /*----- Header files ------------------------------------------------------*/
39 #include <sys/types.h>
41 #include <sys/select.h>
44 #include <sys/ioctl.h>
48 #include <sys/socket.h>
49 #include <arpa/inet.h>
50 #include <netinet/in.h>
53 #include <mLib/alloc.h>
54 #include <mLib/dstr.h>
55 #include <mLib/base64.h>
56 #include <mLib/fdflags.h>
57 #include <mLib/macros.h>
58 #include <mLib/mdwopt.h>
59 #include <mLib/quis.h>
60 #include <mLib/report.h>
64 /*----- Magic constants ---------------------------------------------------*/
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 */
70 /*----- Utility functions -------------------------------------------------*/
72 static int getuint(const char *p, const char *q)
78 if (!q) q = p + strlen(p);
80 i = strtoul(p, &qq, 0);
81 if (errno || qq < q || i > INT_MAX)
82 die(1, "invalid integer `%s'", p);
93 /*----- Token management --------------------------------------------------*/
97 char tok[(COOKIESZ + 2)*4/3 + 1];
108 #define ENUM(tok) T_##tok,
115 #define MASK(tok) TF_##tok = 1u << T_##tok,
118 TF_ALL = (1u << T_LIMIT) - 1u
121 static struct token toktab[] = {
122 #define INIT(tok) { #tok },
128 static void inittoks(void)
130 static struct token *t, *tt;
131 unsigned char buf[COOKIESZ];
137 if ((fd = open("/dev/urandom", O_RDONLY)) < 0)
138 die(2, "open (urandom): %s", strerror(errno));
140 for (t = toktab; t->label; t++) {
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");
146 base64_encode(&bc, buf, COOKIESZ, &d);
147 base64_encode(&bc, 0, 0, &d);
150 for (tt = toktab; tt < t; tt++) {
151 if (strcmp(d.buf, tt->tok) == 0)
155 assert(d.len < sizeof(t->tok));
156 memcpy(t->tok, d.buf, d.len + 1);
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 */
168 static void tokmatch_init(struct tokmatch *tm)
169 { tm->tf = TF_ALL; tm->o = 0; tm->f = 0; }
171 static int tokmatch_update(struct tokmatch *tm, int ch)
173 const struct token *t;
178 for (t = toktab, tf = 1; t->label; t++, tf <<= 1) {
179 if ((tm->tf & tf) && !t->tok[tm->o])
184 for (t = toktab, tf = 1; t->label; t++, tf <<= 1) {
185 if ((tm->tf & tf) && !t->tok[tm->o] && !(tm->f & TMF_CR))
192 for (t = toktab, tf = 1; t->label; t++, tf <<= 1) {
193 if ((tm->tf & tf) && ch != t->tok[tm->o])
202 static int writetok(unsigned i, int fd)
204 static const char nl = '\n';
205 const struct token *t = &toktab[i];
206 size_t n = strlen(t->tok);
209 if (write(fd, t->tok, n) < n ||
210 write(fd, &nl, 1) < 1)
215 /*----- Data structures ---------------------------------------------------*/
218 struct client *next; /* Links in the client chain */
219 int fd; /* File descriptor for socket */
220 struct tokmatch tm; /* Token matching context */
223 /*----- Static variables --------------------------------------------------*/
225 static int *fs; /* File descriptors for targets */
226 static char **fsname; /* File system names */
227 static size_t nfs; /* Number of descriptors */
229 /*----- Cleanup -----------------------------------------------------------*/
231 #define EOM ((char *)0)
232 static void EXECL_LIKE(0) emerg(const char *msg, ...)
237 do { const char *m_ = m; if (write(2, m_, strlen(m_))); } while (0)
240 MSG(QUIS); MSG(": ");
243 msg = va_arg(ap, const char *);
244 } while (msg != EOM);
250 static void partial_cleanup(size_t n)
255 for (i = 0; i < n; i++) {
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);
271 static void cleanup(void) { partial_cleanup(nfs); }
273 static int sigcatch[] = {
274 SIGINT, SIGQUIT, SIGTERM, SIGHUP, SIGALRM,
275 SIGILL, SIGSEGV, SIGBUS, SIGFPE, SIGABRT
278 static void NORETURN sigmumble(int sig)
283 emerg(strsignal(sig), EOM);
285 signal(sig, SIG_DFL);
286 sigemptyset(&ss); sigaddset(&ss, sig);
287 sigprocmask(SIG_UNBLOCK, &ss, 0);
292 /*----- Help functions ----------------------------------------------------*/
294 static void version(FILE *fp)
295 { pquis(fp, "$, " PACKAGE " version " VERSION "\n"); }
296 static void usage(FILE *fp)
297 { pquis(fp, "Usage: $ [-n] [-a ADDR] [-p LOPORT[-HIPORT]] FILSYS ...\n"); }
299 static void help(FILE *fp)
301 version(fp); putc('\n', fp);
304 Freezes a filesystem temporarily, with some measure of safety.\n\
306 The program listens for connections on a TCP port, and prints a number of\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\
317 to standard output. You must connect to the <port>, using TCP, and send\n\
318 the FREEZE token followed by a newline within a short period of time. The\n\
319 filesystems will then be frozen, and the FROZEN token written to the\n\
320 connection. In order to keep the file system frozen, you must keep the\n\
321 connection open, and periodically echo the KEEPALIVE token to it. To\n\
322 release the filesystems, write the THAW token to the connection, followed\n\
323 by a newline; the filesystem will be thawed, and the THAWED token echoed\n\
324 back. If the connection closes, or no valid message is received within a\n\
325 set period of time, or the program receives one of a variety of signals or\n\
326 otherwise becomes unhappy, the filesystems are thawed again.\n\
328 The program exits with status zero if everything worked as planned, while\n\
329 nonzero codes indicate failures of various kinds (see the manual); an exit\n\
330 status of 112 means that thawing a filesystem failed.\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\
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\
345 /*----- Main program ------------------------------------------------------*/
347 int main(int argc, char *argv[])
350 int loport = -1, hiport = -1;
352 struct sockaddr_in sin;
356 struct timeval now, when, delta;
357 struct client *clients = 0, *c, **cc;
358 const struct token *t;
364 #define f_bogus 0x01u
365 #define f_notreally 0x02u
370 /* --- Partially initialize the socket address --- */
372 sin.sin_family = AF_INET;
373 sin.sin_addr.s_addr = INADDR_ANY;
376 /* --- Parse the command line --- */
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' },
389 if ((i = mdwopt(argc, argv, "hvua:np:", opts, 0, 0, 0)) < 0) break;
391 case 'h': help(stdout); exit(0);
392 case 'v': version(stdout); exit(0);
393 case 'u': usage(stdout); exit(0);
395 if ((h = gethostbyname(optarg)) == 0) {
396 die(1, "failed to resolve address `%s': %s",
397 optarg, hstrerror(h_errno));
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));
404 case 'n': f |= f_notreally; break;
406 if ((p = strchr(optarg, '-')) == 0)
407 loport = hiport = getuint(optarg, 0);
409 loport = getuint(optarg, p);
410 hiport = getuint(p + 1, 0);
413 default: f |= f_bogus; break;
416 if (f & f_bogus) { usage(stderr); exit(1); }
417 if (optind >= argc) { usage(stderr); exit(1); }
419 /* --- Open the file systems --- */
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));
429 if (f & f_notreally) {
430 for (i = 0; i < nfs; i++) {
436 /* --- Generate random tokens --- */
440 /* --- Create the listening socket --- */
442 if ((sk = socket(PF_INET, SOCK_STREAM, 0)) < 0)
443 die(2, "socket: %s", strerror(errno));
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));
460 if (i > hiport) die(2, "bind: all ports in use");
462 if (listen(sk, 5)) die(2, "listen: %s", strerror(errno));
464 /* --- Tell the caller how to connect to us, and start the timer --- */
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);
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);
477 /* --- Collect incoming connections, and check for the cookie --- *
479 * This is the tricky part.
486 for (c = clients; c; c = c->next) {
487 FD_SET(c->fd, &fdin);
488 if (c->fd > maxfd) maxfd = c->fd;
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);
495 if (TV_CMP(&now, >=, &when)) die(3, "timeout (accept)");
497 if (FD_ISSET(sk, &fdin)) {
499 fd = accept(sk, (struct sockaddr *)&sin, &sasz);
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);
508 else if (errno != EAGAIN)
509 moan("accept: %s", strerror(errno));
513 for (cc = &clients; *cc;) {
515 if (!FD_ISSET(c->fd, &fdin)) goto next_client;
516 n = read(c->fd, buf, sizeof(buf));
517 if (!n) goto disconn;
519 if (errno == EAGAIN) goto next_client;
520 D( moan("read (client; auth): %s", strerror(errno)); )
523 for (p = buf, q = p + n; p < q; p++) {
524 switch (tokmatch_update(&c->tm, *p)) {
526 case TF_FREEZE: goto connected;
528 D( moan("bad token from client"); )
547 close(sk); sk = c->fd;
549 if (clients->fd != sk) close(clients->fd);
555 /* --- Establish signal handlers --- *
557 * Hopefully this will prevent bad things happening if we have an accident.
560 for (i = 0; i < sizeof(sigcatch)/sizeof(sigcatch[0]); i++) {
561 if (signal(sigcatch[i], sigmumble) == SIG_ERR)
562 die(2, "signal (%d): %s", i, strerror(errno));
566 /* --- Prevent the OOM killer from clobbering us --- */
568 if ((fd = open("/proc/self/oom_adj", O_WRONLY)) < 0 ||
569 write(fd, "-17\n", 4) < 4 ||
571 die(2, "set oom_adj: %s", strerror(errno));
573 /* --- Actually freeze the filesystem --- */
575 for (i = 0; i < nfs; i++) {
577 moan("not really freezing %s", fsname[i]);
579 if (ioctl(fs[i], FIFREEZE, 0) < 0) {
581 die(2, "ioctl (freeze %s): %s", fsname[i], strerror(errno));
585 if (writetok(T_FROZEN, sk)) {
587 die(2, "write (frozen): %s", strerror(errno));
590 /* --- Now wait for the other end to detach --- */
593 TV_ADDL(&when, &now, TO_KEEPALIVE, 0);
594 for (p++; p < q; p++) {
595 switch (tokmatch_update(&tm, *p)) {
597 case TF_KEEPALIVE: tokmatch_init(&tm); break;
598 case TF_THAW: goto done;
599 default: cleanup(); die(3, "unknown token (keepalive)");
605 TV_SUB(&delta, &when, &now);
606 if (select(sk + 1, &fdin, 0, 0, &delta) < 0) {
608 die(2, "select (keepalive): %s", strerror(errno));
611 gettimeofday(&now, 0);
612 if (TV_CMP(&now, >, &when)) {
613 cleanup(); die(3, "timeout (keepalive)");
615 if (FD_ISSET(sk, &fdin)) {
616 n = read(sk, buf, sizeof(buf));
617 if (!n) { cleanup(); die(3, "end-of-file (keepalive)"); }
619 if (errno == EAGAIN) ;
622 die(2, "read (client, keepalive): %s", strerror(errno));
625 for (p = buf, q = p + n; p < q; p++) {
626 switch (tokmatch_update(&tm, *p)) {
629 TV_ADDL(&when, &now, TO_KEEPALIVE, 0);
636 die(3, "unknown token (keepalive)");
645 if (writetok(T_THAWED, sk))
646 die(2, "write (thaw): %s", strerror(errno));
651 /*----- That's all, folks -------------------------------------------------*/