mountctl(8): Improve Synopsis & sync usage(), also improve markup
[dragonfly.git] / sbin / mountctl / mountctl.c
CommitLineData
e5a066b0
MD
1/*
2 * Copyright (c) 2003,2004 The DragonFly Project. All rights reserved.
3 *
4 * This code is derived from software contributed to The DragonFly Project
5 * by Matthew Dillon <dillon@backplane.com>
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 *
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in
15 * the documentation and/or other materials provided with the
16 * distribution.
17 * 3. Neither the name of The DragonFly Project nor the names of its
18 * contributors may be used to endorse or promote products derived
19 * from this software without specific, prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26 * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
27 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
31 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32 * SUCH DAMAGE.
33 *
1aa89f17 34 * $DragonFly: src/sbin/mountctl/mountctl.c,v 1.10 2008/02/05 20:49:50 dillon Exp $
e5a066b0
MD
35 */
36/*
37 * This utility implements the userland mountctl command which is used to
38 * manage high level journaling on mount points.
39 */
40
41#include <sys/types.h>
7ced4a1a
MD
42#include <sys/param.h>
43#include <sys/ucred.h>
44#include <sys/mount.h>
e5a066b0
MD
45#include <sys/time.h>
46#include <sys/mountctl.h>
47#include <stdio.h>
7bc7e232 48#include <stdlib.h>
e5a066b0
MD
49#include <fcntl.h>
50#include <string.h>
51#include <unistd.h>
52#include <errno.h>
53
1aa89f17 54static void usage(void);
e5a066b0
MD
55static void parse_option_keyword(const char *opt,
56 const char **wopt, const char **xopt);
57static int64_t getsize(const char *str);
7ced4a1a 58static const char *numtostr(int64_t num);
e5a066b0 59
7ced4a1a 60static int mountctl_scan(void (*func)(const char *, const char *, int, void *),
e5a066b0
MD
61 const char *keyword, const char *mountpt, int fd);
62static void mountctl_list(const char *keyword, const char *mountpt,
7ced4a1a 63 int __unused fd, void *info);
e5a066b0 64static void mountctl_add(const char *keyword, const char *mountpt, int fd);
500b6a22
MD
65static void mountctl_restart(const char *keyword, const char *mountpt,
66 int fd, void __unused *);
e5a066b0 67static void mountctl_delete(const char *keyword, const char *mountpt,
7ced4a1a
MD
68 int __unused fd, void __unused *);
69static void mountctl_modify(const char *keyword, const char *mountpt, int fd, void __unused *);
e5a066b0
MD
70
71/*
72 * For all options 0 means unspecified, -1 means noOPT or nonOPT, and a
73 * positive number indicates enabling or execution of the option.
74 */
dfc22fd2 75static int exitCode;
e5a066b0
MD
76static int freeze_opt;
77static int start_opt;
78static int close_opt;
79static int abort_opt;
80static int flush_opt;
81static int reversable_opt;
82static int twoway_opt;
243d58ba 83static int output_safety_override_opt;
e5a066b0
MD
84static int64_t memfifo_opt;
85static int64_t swapfifo_opt;
86
87int
88main(int ac, char **av)
89{
90 int fd;
91 int ch;
92 int aopt = 0;
93 int dopt = 0;
94 int fopt = 0;
95 int lopt = 0;
96 int mopt = 0;
500b6a22 97 int ropt = 0;
e5a066b0
MD
98 int mimplied = 0;
99 const char *wopt = NULL;
100 const char *xopt = NULL;
101 const char *keyword = NULL;
102 const char *mountpt = NULL;
7ced4a1a 103 char *tmp;
e5a066b0 104
243d58ba 105 while ((ch = getopt(ac, av, "2adflmo:rw:x:ACFSW:X:Z")) != -1) {
e5a066b0 106 switch(ch) {
7d344f83
MD
107 case '2':
108 twoway_opt = 1;
109 break;
500b6a22
MD
110 case 'r':
111 ropt = 1;
112 if (aopt + dopt + lopt + mopt + ropt != 1) {
113 fprintf(stderr, "too many action options specified\n");
114 usage();
115 }
116 break;
e5a066b0
MD
117 case 'a':
118 aopt = 1;
500b6a22 119 if (aopt + dopt + lopt + mopt + ropt != 1) {
e5a066b0
MD
120 fprintf(stderr, "too many action options specified\n");
121 usage();
122 }
123 break;
124 case 'd':
125 dopt = 1;
500b6a22 126 if (aopt + dopt + lopt + mopt + ropt != 1) {
e5a066b0
MD
127 fprintf(stderr, "too many action options specified\n");
128 usage();
129 }
130 break;
131 case 'f':
132 fopt = 1;
133 break;
134 case 'l':
135 lopt = 1;
500b6a22 136 if (aopt + dopt + lopt + mopt + ropt != 1) {
e5a066b0
MD
137 fprintf(stderr, "too many action options specified\n");
138 usage();
139 }
140 break;
141 case 'o':
142 parse_option_keyword(optarg, &wopt, &xopt);
143 break;
144 case 'm':
145 mopt = 1;
500b6a22 146 if (aopt + dopt + lopt + mopt + ropt != 1) {
e5a066b0
MD
147 fprintf(stderr, "too many action options specified\n");
148 usage();
149 }
150 break;
243d58ba
MD
151 case 'W':
152 output_safety_override_opt = 1;
153 /* fall through */
e5a066b0
MD
154 case 'w':
155 wopt = optarg;
156 mimplied = 1;
157 break;
243d58ba
MD
158 case 'X':
159 output_safety_override_opt = 1;
160 /* fall through */
e5a066b0
MD
161 case 'x':
162 xopt = optarg;
163 mimplied = 1;
164 break;
165 case 'A':
166 mimplied = 1;
167 abort_opt = 1;
168 break;
169 case 'C':
170 mimplied = 1;
171 close_opt = 1;
172 break;
173 case 'F':
174 mimplied = 1;
175 flush_opt = 1;
176 break;
177 case 'S':
178 mimplied = 1;
179 start_opt = 1;
180 break;
181 case 'Z':
182 mimplied = 1;
183 freeze_opt = 1;
184 break;
185 default:
186 fprintf(stderr, "unknown option: -%c\n", optopt);
187 usage();
188 }
189 }
190 ac -= optind;
191 av += optind;
192
193 /*
194 * Parse the keyword and/or mount point.
195 */
196 switch(ac) {
197 case 0:
500b6a22 198 if (aopt || ropt) {
e5a066b0
MD
199 fprintf(stderr, "action requires a tag and/or mount "
200 "point to be specified\n");
201 usage();
202 }
203 break;
204 case 1:
7ced4a1a 205 if (av[0][0] == '/') {
e5a066b0 206 mountpt = av[0];
7ced4a1a
MD
207 if ((keyword = strchr(mountpt, ':')) != NULL) {
208 ++keyword;
209 tmp = strdup(mountpt);
210 *strchr(tmp, ':') = 0;
211 mountpt = tmp;
212 }
213 } else {
e5a066b0 214 keyword = av[0];
e5a066b0
MD
215 }
216 break;
217 default:
218 fprintf(stderr, "unexpected extra arguments to command\n");
219 usage();
220 }
221
222 /*
223 * Additional sanity checks
224 */
500b6a22 225 if (aopt + dopt + lopt + mopt + ropt + mimplied == 0) {
e5a066b0
MD
226 fprintf(stderr, "no action or implied action options were specified\n");
227 usage();
228 }
500b6a22 229 if (mimplied && aopt + dopt + lopt + ropt == 0)
e5a066b0 230 mopt = 1;
500b6a22
MD
231 if ((wopt || xopt) && !(aopt || ropt || mopt)) {
232 fprintf(stderr, "-w/-x/path/fd options may only be used with -m/-a/-r\n");
e5a066b0
MD
233 usage();
234 }
235 if (aopt && (keyword == NULL || mountpt == NULL)) {
236 fprintf(stderr, "a keyword AND a mountpt must be specified "
237 "when adding a journal\n");
238 usage();
239 }
240 if (fopt == 0 && mopt + dopt && keyword == NULL && mountpt == NULL) {
241 fprintf(stderr, "a keyword, a mountpt, or both must be specified "
242 "when modifying or deleting a journal, unless "
243 "-f is also specified for safety\n");
244 usage();
245 }
246
247 /*
248 * Open the journaling file descriptor if required.
249 */
250 if (wopt && xopt) {
251 fprintf(stderr, "you must specify only one of -w/-x/path/fd\n");
252 exit(1);
253 } else if (wopt) {
254 if ((fd = open(wopt, O_RDWR|O_CREAT|O_APPEND, 0666)) < 0) {
255 fprintf(stderr, "unable to create %s: %s\n", wopt, strerror(errno));
256 exit(1);
257 }
258 } else if (xopt) {
259 fd = strtol(xopt, NULL, 0);
500b6a22 260 } else if (aopt || ropt) {
e5a066b0
MD
261 fd = 1; /* stdout default for -a */
262 } else {
263 fd = -1;
264 }
265
266 /*
267 * And finally execute the core command.
268 */
269 if (lopt)
270 mountctl_scan(mountctl_list, keyword, mountpt, fd);
271 if (aopt)
272 mountctl_add(keyword, mountpt, fd);
500b6a22
MD
273 if (ropt) {
274 ch = mountctl_scan(mountctl_restart, keyword, mountpt, fd);
275 if (ch)
276 fprintf(stderr, "%d journals restarted\n", ch);
277 else
278 fprintf(stderr, "Unable to locate any matching journals\n");
279 }
7ced4a1a
MD
280 if (dopt) {
281 ch = mountctl_scan(mountctl_delete, keyword, mountpt, -1);
282 if (ch)
500b6a22 283 fprintf(stderr, "%d journals deleted\n", ch);
7ced4a1a 284 else
500b6a22 285 fprintf(stderr, "Unable to locate any matching journals\n");
7ced4a1a
MD
286 }
287 if (mopt) {
288 ch = mountctl_scan(mountctl_modify, keyword, mountpt, fd);
289 if (ch)
500b6a22 290 fprintf(stderr, "%d journals modified\n", ch);
7ced4a1a 291 else
500b6a22 292 fprintf(stderr, "Unable to locate any matching journals\n");
7ced4a1a 293 }
e5a066b0 294
dfc22fd2 295 return(exitCode);
e5a066b0
MD
296}
297
298static void
299parse_option_keyword(const char *opt, const char **wopt, const char **xopt)
300{
301 char *str = strdup(opt);
302 char *name;
303 char *val;
304 int negate;
305 int hasval;
306 int cannotnegate;
307
308 /*
309 * multiple comma delimited options may be specified.
310 */
311 while ((name = strsep(&str, ",")) != NULL) {
312 /*
313 * some options have associated data.
314 */
315 if ((val = strchr(name, '=')) != NULL)
316 *val++ = 0;
317
318 /*
319 * options beginning with 'no' or 'non' are negated. A positive
320 * number means not negated, a negative number means negated.
321 */
322 negate = 1;
323 cannotnegate = 0;
324 hasval = 0;
325 if (strncmp(name, "non", 3) == 0) {
326 name += 3;
327 negate = -1;
328 } else if (strncmp(name, "no", 2) == 0) {
329 name += 2;
330 negate = -1;
331 }
332
333 /*
334 * Parse supported options
335 */
243d58ba
MD
336 if (strcmp(name, "undo") == 0) {
337 reversable_opt = negate;
338 } else if (strcmp(name, "reversable") == 0) {
e5a066b0
MD
339 reversable_opt = negate;
340 } else if (strcmp(name, "twoway") == 0) {
341 twoway_opt = negate;
342 } else if (strcmp(name, "memfifo") == 0) {
343 cannotnegate = 1;
344 hasval = 1;
345 if (val) {
346 if ((memfifo_opt = getsize(val)) == 0)
347 memfifo_opt = -1;
348 }
349 } else if (strcmp(name, "swapfifo") == 0) {
350 if (val) {
351 hasval = 1;
352 if ((swapfifo_opt = getsize(val)) == 0)
353 swapfifo_opt = -1;
354 } else if (negate < 0) {
355 swapfifo_opt = -1;
356 } else {
357 hasval = 1; /* force error */
358 }
359 } else if (strcmp(name, "fd") == 0) {
360 cannotnegate = 1;
361 hasval = 1;
362 if (val)
363 *xopt = val;
364 } else if (strcmp(name, "path") == 0) {
365 cannotnegate = 1;
366 hasval = 1;
367 if (val)
368 *wopt = val;
369 } else if (strcmp(name, "freeze") == 0 || strcmp(name, "stop") == 0) {
370 if (negate < 0)
371 start_opt = -negate;
372 else
373 freeze_opt = negate;
374 } else if (strcmp(name, "start") == 0) {
375 if (negate < 0)
376 freeze_opt = -negate;
377 else
378 start_opt = negate;
379 } else if (strcmp(name, "close") == 0) {
380 close_opt = negate;
381 } else if (strcmp(name, "abort") == 0) {
382 abort_opt = negate;
383 } else if (strcmp(name, "flush") == 0) {
384 flush_opt = negate;
385 } else {
386 fprintf(stderr, "unknown option keyword: %s\n", name);
387 exit(1);
388 }
389
390 /*
391 * Sanity checks
392 */
393 if (cannotnegate && negate < 0) {
394 fprintf(stderr, "option %s may not be negated\n", name);
395 exit(1);
396 }
397 if (hasval && val == NULL) {
398 fprintf(stderr, "option %s requires assigned data\n", name);
399 exit(1);
400 }
401 if (hasval == 0 && val) {
402 fprintf(stderr, "option %s does not take an assignment\n", name);
403 exit(1);
404 }
405
406 }
407}
408
7ced4a1a
MD
409static int
410mountctl_scan(void (*func)(const char *, const char *, int, void *),
e5a066b0
MD
411 const char *keyword, const char *mountpt, int fd)
412{
7ced4a1a
MD
413 struct statfs *sfs;
414 int count;
415 int calls;
416 int i;
417 struct mountctl_status_journal statreq;
418 struct mountctl_journal_ret_status rstat[4]; /* BIG */
419
420 calls = 0;
421 if (mountpt) {
422 bzero(&statreq, sizeof(statreq));
423 if (keyword) {
424 statreq.index = MC_JOURNAL_INDEX_ID;
425 count = strlen(keyword);
426 if (count > JIDMAX)
427 count = JIDMAX;
428 bcopy(keyword, statreq.id, count);
429 } else {
430 statreq.index = MC_JOURNAL_INDEX_ALL;
431 }
432 count = mountctl(mountpt, MOUNTCTL_STATUS_VFS_JOURNAL, -1,
433 &statreq, sizeof(statreq), &rstat, sizeof(rstat));
434 if (count > 0 && rstat[0].recsize != sizeof(rstat[0])) {
435 fprintf(stderr, "Unable to access status, "
436 "structure size mismatch\n");
437 exit(1);
438 }
439 if (count > 0) {
440 count /= sizeof(rstat[0]);
441 for (i = 0; i < count; ++i) {
442 func(rstat[i].id, mountpt, fd, &rstat[i]);
443 ++calls;
444 }
445 }
446 } else {
447 if ((count = getmntinfo(&sfs, MNT_WAIT)) > 0) {
448 for (i = 0; i < count; ++i) {
449 calls += mountctl_scan(func, keyword, sfs[i].f_mntonname, fd);
450 }
451 } else if (count < 0) {
452 /* XXX */
453 }
454 }
455 return(calls);
e5a066b0
MD
456}
457
458static void
1aa89f17
MD
459mountctl_list(const char *keyword __unused, const char *mountpt,
460 int fd __unused, void *info)
e5a066b0 461{
7ced4a1a
MD
462 struct mountctl_journal_ret_status *rstat = info;
463
464 printf("%s:%s\n", mountpt, rstat->id[0] ? rstat->id : "<NOID>");
465 printf(" membufsize=%s\n", numtostr(rstat->membufsize));
466 printf(" membufused=%s\n", numtostr(rstat->membufused));
7d344f83 467 printf(" membufunacked=%s\n", numtostr(rstat->membufunacked));
7ced4a1a 468 printf(" total_bytes=%s\n", numtostr(rstat->bytessent));
a276dc6b 469 printf(" fifo_stalls=%jd\n", (intmax_t)rstat->fifostalls);
e5a066b0
MD
470}
471
472static void
473mountctl_add(const char *keyword, const char *mountpt, int fd)
474{
7ced4a1a 475 struct mountctl_install_journal joinfo;
dfc22fd2
MD
476 struct stat st1;
477 struct stat st2;
7ced4a1a
MD
478 int error;
479
dfc22fd2
MD
480 /*
481 * Make sure the file descriptor is not on the same filesystem as the
482 * mount point. This isn't a perfect test, but it should catch most
483 * foot shooting.
484 */
243d58ba
MD
485 if (output_safety_override_opt == 0 &&
486 fstat(fd, &st1) == 0 && S_ISREG(st1.st_mode) &&
dfc22fd2
MD
487 stat(mountpt, &st2) == 0 && st1.st_dev == st2.st_dev
488 ) {
489 fprintf(stderr, "%s:%s failed to add, the journal cannot be on the "
490 "same filesystem being journaled!\n",
491 mountpt, keyword);
492 exitCode = 1;
493 return;
494 }
495
496 /*
497 * Setup joinfo and issue the add
498 */
7ced4a1a
MD
499 bzero(&joinfo, sizeof(joinfo));
500 snprintf(joinfo.id, sizeof(joinfo.id), "%s", keyword);
d0f61a54
MD
501 if (memfifo_opt > 0)
502 joinfo.membufsize = memfifo_opt;
7d344f83
MD
503 if (twoway_opt > 0)
504 joinfo.flags |= MC_JOURNAL_WANT_FULLDUPLEX;
505 if (reversable_opt > 0)
506 joinfo.flags |= MC_JOURNAL_WANT_REVERSABLE;
7ced4a1a
MD
507
508 error = mountctl(mountpt, MOUNTCTL_INSTALL_VFS_JOURNAL, fd,
509 &joinfo, sizeof(joinfo), NULL, 0);
510 if (error == 0) {
511 fprintf(stderr, "%s:%s added\n", mountpt, joinfo.id);
512 } else {
513 fprintf(stderr, "%s:%s failed to add, error %s\n", mountpt, joinfo.id, strerror(errno));
dfc22fd2 514 exitCode = 1;
7ced4a1a 515 }
e5a066b0
MD
516}
517
518static void
500b6a22
MD
519mountctl_restart(const char *keyword, const char *mountpt,
520 int fd, void __unused *info)
521{
522 struct mountctl_restart_journal joinfo;
523 int error;
524
525 /* XXX make sure descriptor is not on same filesystem as journal */
526
527 bzero(&joinfo, sizeof(joinfo));
528
529 snprintf(joinfo.id, sizeof(joinfo.id), "%s", keyword);
530 if (twoway_opt > 0)
531 joinfo.flags |= MC_JOURNAL_WANT_FULLDUPLEX;
532 if (reversable_opt > 0)
533 joinfo.flags |= MC_JOURNAL_WANT_REVERSABLE;
534
535 error = mountctl(mountpt, MOUNTCTL_RESTART_VFS_JOURNAL, fd,
536 &joinfo, sizeof(joinfo), NULL, 0);
537 if (error == 0) {
538 fprintf(stderr, "%s:%s restarted\n", mountpt, joinfo.id);
539 } else {
540 fprintf(stderr, "%s:%s restart failed, error %s\n", mountpt, joinfo.id, strerror(errno));
541 }
542}
543
544static void
545mountctl_delete(const char *keyword, const char *mountpt,
546 int __unused fd, void __unused *info)
e5a066b0 547{
7ced4a1a
MD
548 struct mountctl_remove_journal joinfo;
549 int error;
550
551 bzero(&joinfo, sizeof(joinfo));
552 snprintf(joinfo.id, sizeof(joinfo.id), "%s", keyword);
553 error = mountctl(mountpt, MOUNTCTL_REMOVE_VFS_JOURNAL, -1,
554 &joinfo, sizeof(joinfo), NULL, 0);
555 if (error == 0) {
556 fprintf(stderr, "%s:%s deleted\n", mountpt, joinfo.id);
557 } else {
558 fprintf(stderr, "%s:%s deletion failed, error %s\n", mountpt, joinfo.id, strerror(errno));
559 }
e5a066b0
MD
560}
561
562static void
1aa89f17
MD
563mountctl_modify(const char *keyword __unused, const char *mountpt __unused,
564 int fd __unused, void *info __unused)
e5a066b0
MD
565{
566 fprintf(stderr, "modify not yet implemented\n");
567}
568
569
1aa89f17 570static void
e5a066b0
MD
571usage(void)
572{
573 printf(
9ef84c09
TN
574 "usage: mountctl -l {mountpt | tag | mountpt:tag}\n"
575 " mountctl -a [-2] [-w/W output_path] [-x/X filedesc]\n"
576 " [-o options] mountpt:tag\n"
577 " mountctl -r [-2] [-w/W output_path] [-x/X filedesc] mountpt:tag\n"
578 " mountctl -d {mountpt | tag | mountpt:tag}\n"
579 " mountctl -m [-o options] {mountpt | tag | mountpt:tag}\n"
580 " mountctl -FZSCA {mountpt | tag | mountpt:tag}\n"
e5a066b0
MD
581 );
582 exit(1);
583}
584
bf85b728 585static int64_t
e5a066b0
MD
586getsize(const char *str)
587{
1aa89f17 588 char *suffix;
e5a066b0
MD
589 int64_t val;
590
591 val = strtoll(str, &suffix, 0);
592 if (suffix) {
593 switch(*suffix) {
594 case 'b':
595 break;
596 case 't':
597 val *= 1024;
598 /* fall through */
599 case 'g':
600 val *= 1024;
601 /* fall through */
602 case 'm':
603 val *= 1024;
604 /* fall through */
605 case 'k':
606 val *= 1024;
607 /* fall through */
608 break;
609 default:
610 fprintf(stderr, "data value '%s' has unknown suffix\n", str);
611 exit(1);
612 }
613 }
614 return(val);
615}
616
bf85b728 617static const char *
7ced4a1a
MD
618numtostr(int64_t num)
619{
620 static char buf[64];
7ced4a1a
MD
621
622 if (num < 1024)
a276dc6b 623 snprintf(buf, sizeof(buf), "%jd", (intmax_t)num);
7ced4a1a
MD
624 else if (num < 10 * 1024)
625 snprintf(buf, sizeof(buf), "%3.2fK", num / 1024.0);
626 else if (num < 1024 * 1024)
627 snprintf(buf, sizeof(buf), "%3.0fK", num / 1024.0);
628 else if (num < 10 * 1024 * 1024)
629 snprintf(buf, sizeof(buf), "%3.2fM", num / (1024.0 * 1024.0));
630 else if (num < 1024 * 1024 * 1024)
631 snprintf(buf, sizeof(buf), "%3.0fM", num / (1024.0 * 1024.0));
632 else if (num < 10LL * 1024 * 1024 * 1024)
633 snprintf(buf, sizeof(buf), "%3.2fG", num / (1024.0 * 1024.0 * 1024.0));
634 else
635 snprintf(buf, sizeof(buf), "%3.0fG", num / (1024.0 * 1024.0 * 1024.0));
636 return(buf);
637}
638