3 * Freeze a file system under remote control
5 * (c) 2011 Mark Wooding
8 /*----- Licensing notice --------------------------------------------------*
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.
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.
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.
25 /*----- Header files ------------------------------------------------------*/
37 #include <sys/types.h>
39 #include <sys/select.h>
42 #include <sys/ioctl.h>
46 #include <sys/socket.h>
47 #include <arpa/inet.h>
48 #include <netinet/in.h>
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>
61 /*----- Magic constants ---------------------------------------------------*/
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 */
67 /*----- Utility functions -------------------------------------------------*/
69 static int getuint(const char *p
, const char *q
)
75 if (!q
) q
= p
+ strlen(p
);
77 i
= strtoul(p
, &qq
, 0);
78 if (errno
|| qq
< q
|| i
> INT_MAX
)
79 die(1, "invalid integer `%s'", p
);
90 /*----- Token management --------------------------------------------------*/
94 char tok
[(COOKIESZ
+ 2)*4/3 + 1];
105 #define ENUM(tok) T_##tok,
112 #define MASK(tok) TF_##tok = 1u << T_##tok,
115 TF_ALL
= (1u << T_LIMIT
) - 1u
118 static struct token toktab
[] = {
119 #define INIT(tok) { #tok },
125 static void inittoks(void)
127 static struct token
*t
, *tt
;
128 unsigned char buf
[COOKIESZ
];
134 if ((fd
= open("/dev/urandom", O_RDONLY
)) < 0)
135 die(2, "open (urandom): %s", strerror(errno
));
137 for (t
= toktab
; t
->label
; t
++) {
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");
143 base64_encode(&bc
, buf
, COOKIESZ
, &d
);
144 base64_encode(&bc
, 0, 0, &d
);
147 for (tt
= toktab
; tt
< t
; tt
++) {
148 if (strcmp(d
.buf
, tt
->tok
) == 0)
152 assert(d
.len
< sizeof(t
->tok
));
153 memcpy(t
->tok
, d
.buf
, d
.len
+ 1);
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 */
165 static void tokmatch_init(struct tokmatch
*tm
)
166 { tm
->tf
= TF_ALL
; tm
->o
= 0; tm
->f
= 0; }
168 static int tokmatch_update(struct tokmatch
*tm
, int ch
)
170 const struct token
*t
;
175 for (t
= toktab
, tf
= 1; t
->label
; t
++, tf
<<= 1) {
176 if ((tm
->tf
& tf
) && !t
->tok
[tm
->o
])
181 for (t
= toktab
, tf
= 1; t
->label
; t
++, tf
<<= 1) {
182 if ((tm
->tf
& tf
) && !t
->tok
[tm
->o
] && !(tm
->f
& TMF_CR
))
189 for (t
= toktab
, tf
= 1; t
->label
; t
++, tf
<<= 1) {
190 if ((tm
->tf
& tf
) && ch
!= t
->tok
[tm
->o
])
199 static int writetok(unsigned i
, int fd
)
201 static const char nl
= '\n';
202 const struct token
*t
= &toktab
[i
];
203 size_t n
= strlen(t
->tok
);
206 if (write(fd
, t
->tok
, n
) < n
||
207 write(fd
, &nl
, 1) < 1)
212 /*----- Data structures ---------------------------------------------------*/
215 struct client
*next
; /* Links in the client chain */
216 int fd
; /* File descriptor for socket */
217 struct tokmatch tm
; /* Token matching context */
220 /*----- Static variables --------------------------------------------------*/
222 static int *fs
; /* File descriptors for targets */
223 static char **fsname
; /* File system names */
224 static size_t nfs
; /* Number of descriptors */
226 /*----- Cleanup -----------------------------------------------------------*/
228 #define EOM ((char *)0)
229 static void emerg(const char *msg
,...)
234 do { const char *m_ = m; if (write(2, m_, strlen(m_))); } while (0)
237 MSG(QUIS
); MSG(": ");
240 msg
= va_arg(ap
, const char *);
241 } while (msg
!= EOM
);
247 static void partial_cleanup(size_t n
)
252 for (i
= 0; i
< nfs
; i
++) {
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
);
268 static void cleanup(void) { partial_cleanup(nfs
); }
270 static int sigcatch
[] = {
271 SIGINT
, SIGQUIT
, SIGTERM
, SIGHUP
, SIGALRM
,
272 SIGILL
, SIGSEGV
, SIGBUS
, SIGFPE
, SIGABRT
275 static void sigmumble(int sig
)
280 emerg(strsignal(sig
), 0);
282 signal(sig
, SIG_DFL
);
283 sigemptyset(&ss
); sigaddset(&ss
, sig
);
284 sigprocmask(SIG_UNBLOCK
, &ss
, 0);
289 /*----- Help functions ----------------------------------------------------*/
291 static void version(FILE *fp
) { pquis(fp
, "$, version " VERSION
"\n"); }
292 static void usage(FILE *fp
)
293 { pquis(fp
, "Usage: $ [-n] [-a ADDR] [-p LOPORT[-HIPORT]] FILSYS ...\n"); }
295 static void help(FILE *fp
)
297 version(fp
); putc('\n', fp
);
300 Freezes a filesystem temporarily, with some measure of safety.\n\
302 The program listens for connections on a TCP port, and prints a line\n\
306 to standard output. You must connect to this PORT and send the COOKIE\n\
307 followed by a newline within a short period of time. The filesystems\n\
308 will then be frozen, and `OK' written to the connection. In order to\n\
309 keep the file system frozen, you must keep the connection open, and\n\
310 feed data into it. If the connection closes, or no data is received\n\
311 within a set period of time, or the program receives one of a variety\n\
312 of signals or otherwise becomes unhappy, the filesystems are thawed again.\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\
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\
327 /*----- Main program ------------------------------------------------------*/
329 int main(int argc
, char *argv
[])
332 int loport
= -1, hiport
= -1;
334 struct sockaddr_in sin
;
338 struct timeval now
, when
, delta
;
339 struct client
*clients
= 0, *c
, **cc
;
340 const struct token
*t
;
346 #define f_bogus 0x01u
347 #define f_notreally 0x02u
352 /* --- Partially initialize the socket address --- */
354 sin
.sin_family
= AF_INET
;
355 sin
.sin_addr
.s_addr
= INADDR_ANY
;
358 /* --- Parse the command line --- */
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' },
371 if ((i
= mdwopt(argc
, argv
, "hvua:np:", opts
, 0, 0, 0)) < 0) break;
373 case 'h': help(stdout
); exit(0);
374 case 'v': version(stdout
); exit(0);
375 case 'u': usage(stdout
); exit(0);
377 if ((h
= gethostbyname(optarg
)) == 0) {
378 die(1, "failed to resolve address `%s': %s",
379 optarg
, hstrerror(h_errno
));
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
));
386 case 'n': f
|= f_notreally
; break;
388 if ((p
= strchr(optarg
, '-')) == 0)
389 loport
= hiport
= getuint(optarg
, 0);
391 loport
= getuint(optarg
, p
);
392 hiport
= getuint(p
+ 1, 0);
395 default: f
|= f_bogus
; break;
398 if (f
& f_bogus
) { usage(stderr
); exit(1); }
399 if (optind
>= argc
) { usage(stderr
); exit(1); }
401 /* --- Open the file systems --- */
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
));
411 if (f
& f_notreally
) {
412 for (i
= 0; i
< nfs
; i
++) {
418 /* --- Generate random tokens --- */
422 /* --- Create the listening socket --- */
424 if ((sk
= socket(PF_INET
, SOCK_STREAM
, 0)) < 0)
425 die(2, "socket: %s", strerror(errno
));
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
));
442 if (i
> hiport
) die(2, "bind: all ports in use");
444 if (listen(sk
, 5)) die(2, "listen: %s", strerror(errno
));
446 /* --- Tell the caller how to connect to us, and start the timer --- */
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
);
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);
459 /* --- Collect incoming connections, and check for the cookie --- *
461 * This is the tricky part.
468 for (c
= clients
; c
; c
= c
->next
) {
469 FD_SET(c
->fd
, &fdin
);
470 if (c
->fd
> maxfd
) maxfd
= c
->fd
;
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);
477 if (TV_CMP(&now
, >=, &when
)) die(3, "timeout (accept)");
479 if (FD_ISSET(sk
, &fdin
)) {
481 fd
= accept(sk
, (struct sockaddr
*)&sin
, &sasz
);
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
);
490 else if (errno
!= EAGAIN
)
491 moan("accept: %s", strerror(errno
));
495 for (cc
= &clients
; *cc
;) {
497 if (!FD_ISSET(c
->fd
, &fdin
)) goto next_client
;
498 n
= read(c
->fd
, buf
, sizeof(buf
));
499 if (!n
) goto disconn
;
501 if (errno
== EAGAIN
) goto next_client
;
502 D( moan("read (client; auth): %s", strerror(errno
)); )
505 for (p
= buf
, q
= p
+ n
; p
< q
; p
++) {
506 switch (tokmatch_update(&c
->tm
, *p
)) {
508 case TF_FREEZE
: goto connected
;
510 D( moan("bad token from client"); )
531 if (clients
->fd
!= sk
) close(clients
->fd
);
537 /* --- Establish signal handlers --- *
539 * Hopefully this will prevent bad things happening if we have an accident.
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
));
548 /* --- Prevent the OOM killer from clobbering us --- */
550 if ((fd
= open("/proc/self/oom_adj", O_WRONLY
)) < 0 ||
551 write(fd
, "-17\n", 4) < 4 ||
553 die(2, "set oom_adj: %s", strerror(errno
));
555 /* --- Actually freeze the filesystem --- */
557 for (i
= 0; i
< nfs
; i
++) {
559 moan("not really freezing %s", fsname
[i
]);
561 if (ioctl(fs
[i
], FIFREEZE
, 0) < 0) {
563 die(2, "ioctl (freeze %s): %s", fsname
[i
], strerror(errno
));
567 if (writetok(T_FROZEN
, sk
)) {
569 die(2, "write (frozen): %s", strerror(errno
));
572 /* --- Now wait for the other end to detach --- */
575 TV_ADDL(&when
, &now
, TO_KEEPALIVE
, 0);
576 for (p
++; p
< q
; p
++) {
577 switch (tokmatch_update(&tm
, *p
)) {
579 case TF_KEEPALIVE
: tokmatch_init(&tm
); break;
580 case TF_THAW
: goto done
;
581 default: cleanup(); die(3, "unknown token (keepalive)");
587 TV_SUB(&delta
, &when
, &now
);
588 if (select(sk
+ 1, &fdin
, 0, 0, &delta
) < 0) {
590 die(2, "select (keepalive): %s", strerror(errno
));
593 gettimeofday(&now
, 0);
594 if (TV_CMP(&now
, >, &when
)) {
595 cleanup(); die(3, "timeout (keepalive)");
597 if (FD_ISSET(sk
, &fdin
)) {
598 n
= read(sk
, buf
, sizeof(buf
));
599 if (!n
) { cleanup(); die(3, "end-of-file (keepalive)"); }
601 if (errno
== EAGAIN
) ;
604 die(2, "read (client, keepalive): %s", strerror(errno
));
607 for (p
= buf
, q
= p
+ n
; p
< q
; p
++) {
608 switch (tokmatch_update(&tm
, *p
)) {
611 TV_ADDL(&when
, &now
, TO_KEEPALIVE
, 0);
618 die(3, "unknown token (keepalive)");
627 if (writetok(T_THAWED
, sk
))
628 die(2, "write (thaw): %s", strerror(errno
));
633 /*----- That's all, folks -------------------------------------------------*/