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/mdwopt.h>
58 #include <mLib/quis.h>
59 #include <mLib/report.h>
63 /*----- Magic constants ---------------------------------------------------*/
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 */
69 /*----- Utility functions -------------------------------------------------*/
71 static int getuint(const char *p
, const char *q
)
77 if (!q
) q
= p
+ strlen(p
);
79 i
= strtoul(p
, &qq
, 0);
80 if (errno
|| qq
< q
|| i
> INT_MAX
)
81 die(1, "invalid integer `%s'", p
);
92 /*----- Token management --------------------------------------------------*/
96 char tok
[(COOKIESZ
+ 2)*4/3 + 1];
107 #define ENUM(tok) T_##tok,
114 #define MASK(tok) TF_##tok = 1u << T_##tok,
117 TF_ALL
= (1u << T_LIMIT
) - 1u
120 static struct token toktab
[] = {
121 #define INIT(tok) { #tok },
127 static void inittoks(void)
129 static struct token
*t
, *tt
;
130 unsigned char buf
[COOKIESZ
];
136 if ((fd
= open("/dev/urandom", O_RDONLY
)) < 0)
137 die(2, "open (urandom): %s", strerror(errno
));
139 for (t
= toktab
; t
->label
; t
++) {
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");
145 base64_encode(&bc
, buf
, COOKIESZ
, &d
);
146 base64_encode(&bc
, 0, 0, &d
);
149 for (tt
= toktab
; tt
< t
; tt
++) {
150 if (strcmp(d
.buf
, tt
->tok
) == 0)
154 assert(d
.len
< sizeof(t
->tok
));
155 memcpy(t
->tok
, d
.buf
, d
.len
+ 1);
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 */
167 static void tokmatch_init(struct tokmatch
*tm
)
168 { tm
->tf
= TF_ALL
; tm
->o
= 0; tm
->f
= 0; }
170 static int tokmatch_update(struct tokmatch
*tm
, int ch
)
172 const struct token
*t
;
177 for (t
= toktab
, tf
= 1; t
->label
; t
++, tf
<<= 1) {
178 if ((tm
->tf
& tf
) && !t
->tok
[tm
->o
])
183 for (t
= toktab
, tf
= 1; t
->label
; t
++, tf
<<= 1) {
184 if ((tm
->tf
& tf
) && !t
->tok
[tm
->o
] && !(tm
->f
& TMF_CR
))
191 for (t
= toktab
, tf
= 1; t
->label
; t
++, tf
<<= 1) {
192 if ((tm
->tf
& tf
) && ch
!= t
->tok
[tm
->o
])
201 static int writetok(unsigned i
, int fd
)
203 static const char nl
= '\n';
204 const struct token
*t
= &toktab
[i
];
205 size_t n
= strlen(t
->tok
);
208 if (write(fd
, t
->tok
, n
) < n
||
209 write(fd
, &nl
, 1) < 1)
214 /*----- Data structures ---------------------------------------------------*/
217 struct client
*next
; /* Links in the client chain */
218 int fd
; /* File descriptor for socket */
219 struct tokmatch tm
; /* Token matching context */
222 /*----- Static variables --------------------------------------------------*/
224 static int *fs
; /* File descriptors for targets */
225 static char **fsname
; /* File system names */
226 static size_t nfs
; /* Number of descriptors */
228 /*----- Cleanup -----------------------------------------------------------*/
230 #define EOM ((char *)0)
231 static void emerg(const char *msg
,...)
236 do { const char *m_ = m; if (write(2, m_, strlen(m_))); } while (0)
239 MSG(QUIS
); MSG(": ");
242 msg
= va_arg(ap
, const char *);
243 } while (msg
!= EOM
);
249 static void partial_cleanup(size_t n
)
254 for (i
= 0; i
< nfs
; i
++) {
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
);
270 static void cleanup(void) { partial_cleanup(nfs
); }
272 static int sigcatch
[] = {
273 SIGINT
, SIGQUIT
, SIGTERM
, SIGHUP
, SIGALRM
,
274 SIGILL
, SIGSEGV
, SIGBUS
, SIGFPE
, SIGABRT
277 static void sigmumble(int sig
)
282 emerg(strsignal(sig
), 0);
284 signal(sig
, SIG_DFL
);
285 sigemptyset(&ss
); sigaddset(&ss
, sig
);
286 sigprocmask(SIG_UNBLOCK
, &ss
, 0);
291 /*----- Help functions ----------------------------------------------------*/
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"); }
298 static void help(FILE *fp
)
300 version(fp
); putc('\n', fp
);
303 Freezes a filesystem temporarily, with some measure of safety.\n\
305 The program listens for connections on a TCP port, and prints a line\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\
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\
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\
330 /*----- Main program ------------------------------------------------------*/
332 int main(int argc
, char *argv
[])
335 int loport
= -1, hiport
= -1;
337 struct sockaddr_in sin
;
341 struct timeval now
, when
, delta
;
342 struct client
*clients
= 0, *c
, **cc
;
343 const struct token
*t
;
349 #define f_bogus 0x01u
350 #define f_notreally 0x02u
355 /* --- Partially initialize the socket address --- */
357 sin
.sin_family
= AF_INET
;
358 sin
.sin_addr
.s_addr
= INADDR_ANY
;
361 /* --- Parse the command line --- */
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' },
374 if ((i
= mdwopt(argc
, argv
, "hvua:np:", opts
, 0, 0, 0)) < 0) break;
376 case 'h': help(stdout
); exit(0);
377 case 'v': version(stdout
); exit(0);
378 case 'u': usage(stdout
); exit(0);
380 if ((h
= gethostbyname(optarg
)) == 0) {
381 die(1, "failed to resolve address `%s': %s",
382 optarg
, hstrerror(h_errno
));
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
));
389 case 'n': f
|= f_notreally
; break;
391 if ((p
= strchr(optarg
, '-')) == 0)
392 loport
= hiport
= getuint(optarg
, 0);
394 loport
= getuint(optarg
, p
);
395 hiport
= getuint(p
+ 1, 0);
398 default: f
|= f_bogus
; break;
401 if (f
& f_bogus
) { usage(stderr
); exit(1); }
402 if (optind
>= argc
) { usage(stderr
); exit(1); }
404 /* --- Open the file systems --- */
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
));
414 if (f
& f_notreally
) {
415 for (i
= 0; i
< nfs
; i
++) {
421 /* --- Generate random tokens --- */
425 /* --- Create the listening socket --- */
427 if ((sk
= socket(PF_INET
, SOCK_STREAM
, 0)) < 0)
428 die(2, "socket: %s", strerror(errno
));
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
));
445 if (i
> hiport
) die(2, "bind: all ports in use");
447 if (listen(sk
, 5)) die(2, "listen: %s", strerror(errno
));
449 /* --- Tell the caller how to connect to us, and start the timer --- */
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
);
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);
462 /* --- Collect incoming connections, and check for the cookie --- *
464 * This is the tricky part.
471 for (c
= clients
; c
; c
= c
->next
) {
472 FD_SET(c
->fd
, &fdin
);
473 if (c
->fd
> maxfd
) maxfd
= c
->fd
;
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);
480 if (TV_CMP(&now
, >=, &when
)) die(3, "timeout (accept)");
482 if (FD_ISSET(sk
, &fdin
)) {
484 fd
= accept(sk
, (struct sockaddr
*)&sin
, &sasz
);
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
);
493 else if (errno
!= EAGAIN
)
494 moan("accept: %s", strerror(errno
));
498 for (cc
= &clients
; *cc
;) {
500 if (!FD_ISSET(c
->fd
, &fdin
)) goto next_client
;
501 n
= read(c
->fd
, buf
, sizeof(buf
));
502 if (!n
) goto disconn
;
504 if (errno
== EAGAIN
) goto next_client
;
505 D( moan("read (client; auth): %s", strerror(errno
)); )
508 for (p
= buf
, q
= p
+ n
; p
< q
; p
++) {
509 switch (tokmatch_update(&c
->tm
, *p
)) {
511 case TF_FREEZE
: goto connected
;
513 D( moan("bad token from client"); )
532 close(sk
); sk
= c
->fd
;
534 if (clients
->fd
!= sk
) close(clients
->fd
);
540 /* --- Establish signal handlers --- *
542 * Hopefully this will prevent bad things happening if we have an accident.
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
));
551 /* --- Prevent the OOM killer from clobbering us --- */
553 if ((fd
= open("/proc/self/oom_adj", O_WRONLY
)) < 0 ||
554 write(fd
, "-17\n", 4) < 4 ||
556 die(2, "set oom_adj: %s", strerror(errno
));
558 /* --- Actually freeze the filesystem --- */
560 for (i
= 0; i
< nfs
; i
++) {
562 moan("not really freezing %s", fsname
[i
]);
564 if (ioctl(fs
[i
], FIFREEZE
, 0) < 0) {
566 die(2, "ioctl (freeze %s): %s", fsname
[i
], strerror(errno
));
570 if (writetok(T_FROZEN
, sk
)) {
572 die(2, "write (frozen): %s", strerror(errno
));
575 /* --- Now wait for the other end to detach --- */
578 TV_ADDL(&when
, &now
, TO_KEEPALIVE
, 0);
579 for (p
++; p
< q
; p
++) {
580 switch (tokmatch_update(&tm
, *p
)) {
582 case TF_KEEPALIVE
: tokmatch_init(&tm
); break;
583 case TF_THAW
: goto done
;
584 default: cleanup(); die(3, "unknown token (keepalive)");
590 TV_SUB(&delta
, &when
, &now
);
591 if (select(sk
+ 1, &fdin
, 0, 0, &delta
) < 0) {
593 die(2, "select (keepalive): %s", strerror(errno
));
596 gettimeofday(&now
, 0);
597 if (TV_CMP(&now
, >, &when
)) {
598 cleanup(); die(3, "timeout (keepalive)");
600 if (FD_ISSET(sk
, &fdin
)) {
601 n
= read(sk
, buf
, sizeof(buf
));
602 if (!n
) { cleanup(); die(3, "end-of-file (keepalive)"); }
604 if (errno
== EAGAIN
) ;
607 die(2, "read (client, keepalive): %s", strerror(errno
));
610 for (p
= buf
, q
= p
+ n
; p
< q
; p
++) {
611 switch (tokmatch_update(&tm
, *p
)) {
614 TV_ADDL(&when
, &now
, TO_KEEPALIVE
, 0);
621 die(3, "unknown token (keepalive)");
630 if (writetok(T_THAWED
, sk
))
631 die(2, "write (thaw): %s", strerror(errno
));
636 /*----- That's all, folks -------------------------------------------------*/