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 * 34 * $DragonFly: src/sbin/mountctl/mountctl.c,v 1.9 2007/11/25 01:28:23 swildner Exp $ 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> 42 #include <sys/param.h> 43 #include <sys/ucred.h> 44 #include <sys/mount.h> 45 #include <sys/time.h> 46 #include <sys/mountctl.h> 47 #include <stdio.h> 48 #include <stdlib.h> 49 #include <fcntl.h> 50 #include <string.h> 51 #include <unistd.h> 52 #include <errno.h> 53 54 static volatile void usage(void); 55 static void parse_option_keyword(const char *opt, 56 const char **wopt, const char **xopt); 57 static int64_t getsize(const char *str); 58 static const char *numtostr(int64_t num); 59 60 static int mountctl_scan(void (*func)(const char *, const char *, int, void *), 61 const char *keyword, const char *mountpt, int fd); 62 static void mountctl_list(const char *keyword, const char *mountpt, 63 int __unused fd, void *info); 64 static void mountctl_add(const char *keyword, const char *mountpt, int fd); 65 static void mountctl_restart(const char *keyword, const char *mountpt, 66 int fd, void __unused *); 67 static void mountctl_delete(const char *keyword, const char *mountpt, 68 int __unused fd, void __unused *); 69 static void mountctl_modify(const char *keyword, const char *mountpt, int fd, void __unused *); 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 */ 75 static int exitCode; 76 static int freeze_opt; 77 static int start_opt; 78 static int close_opt; 79 static int abort_opt; 80 static int flush_opt; 81 static int reversable_opt; 82 static int twoway_opt; 83 static int output_safety_override_opt; 84 static int64_t memfifo_opt; 85 static int64_t swapfifo_opt; 86 87 int 88 main(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; 97 int ropt = 0; 98 int mimplied = 0; 99 const char *wopt = NULL; 100 const char *xopt = NULL; 101 const char *keyword = NULL; 102 const char *mountpt = NULL; 103 char *tmp; 104 105 while ((ch = getopt(ac, av, "2adflmo:rw:x:ACFSW:X:Z")) != -1) { 106 switch(ch) { 107 case '2': 108 twoway_opt = 1; 109 break; 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; 117 case 'a': 118 aopt = 1; 119 if (aopt + dopt + lopt + mopt + ropt != 1) { 120 fprintf(stderr, "too many action options specified\n"); 121 usage(); 122 } 123 break; 124 case 'd': 125 dopt = 1; 126 if (aopt + dopt + lopt + mopt + ropt != 1) { 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; 136 if (aopt + dopt + lopt + mopt + ropt != 1) { 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; 146 if (aopt + dopt + lopt + mopt + ropt != 1) { 147 fprintf(stderr, "too many action options specified\n"); 148 usage(); 149 } 150 break; 151 case 'W': 152 output_safety_override_opt = 1; 153 /* fall through */ 154 case 'w': 155 wopt = optarg; 156 mimplied = 1; 157 break; 158 case 'X': 159 output_safety_override_opt = 1; 160 /* fall through */ 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: 198 if (aopt || ropt) { 199 fprintf(stderr, "action requires a tag and/or mount " 200 "point to be specified\n"); 201 usage(); 202 } 203 break; 204 case 1: 205 if (av[0][0] == '/') { 206 mountpt = av[0]; 207 if ((keyword = strchr(mountpt, ':')) != NULL) { 208 ++keyword; 209 tmp = strdup(mountpt); 210 *strchr(tmp, ':') = 0; 211 mountpt = tmp; 212 } 213 } else { 214 keyword = av[0]; 215 } 216 break; 217 default: 218 fprintf(stderr, "unexpected extra arguments to command\n"); 219 usage(); 220 } 221 222 /* 223 * Additional sanity checks 224 */ 225 if (aopt + dopt + lopt + mopt + ropt + mimplied == 0) { 226 fprintf(stderr, "no action or implied action options were specified\n"); 227 usage(); 228 } 229 if (mimplied && aopt + dopt + lopt + ropt == 0) 230 mopt = 1; 231 if ((wopt || xopt) && !(aopt || ropt || mopt)) { 232 fprintf(stderr, "-w/-x/path/fd options may only be used with -m/-a/-r\n"); 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); 260 } else if (aopt || ropt) { 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); 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 } 280 if (dopt) { 281 ch = mountctl_scan(mountctl_delete, keyword, mountpt, -1); 282 if (ch) 283 fprintf(stderr, "%d journals deleted\n", ch); 284 else 285 fprintf(stderr, "Unable to locate any matching journals\n"); 286 } 287 if (mopt) { 288 ch = mountctl_scan(mountctl_modify, keyword, mountpt, fd); 289 if (ch) 290 fprintf(stderr, "%d journals modified\n", ch); 291 else 292 fprintf(stderr, "Unable to locate any matching journals\n"); 293 } 294 295 return(exitCode); 296 } 297 298 static void 299 parse_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 */ 336 if (strcmp(name, "undo") == 0) { 337 reversable_opt = negate; 338 } else if (strcmp(name, "reversable") == 0) { 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 409 static int 410 mountctl_scan(void (*func)(const char *, const char *, int, void *), 411 const char *keyword, const char *mountpt, int fd) 412 { 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); 456 } 457 458 static void 459 mountctl_list(const char *keyword, const char *mountpt, int __unused fd, void *info) 460 { 461 struct mountctl_journal_ret_status *rstat = info; 462 463 printf("%s:%s\n", mountpt, rstat->id[0] ? rstat->id : "<NOID>"); 464 printf(" membufsize=%s\n", numtostr(rstat->membufsize)); 465 printf(" membufused=%s\n", numtostr(rstat->membufused)); 466 printf(" membufunacked=%s\n", numtostr(rstat->membufunacked)); 467 printf(" total_bytes=%s\n", numtostr(rstat->bytessent)); 468 printf(" fifo_stalls=%lld\n", rstat->fifostalls); 469 } 470 471 static void 472 mountctl_add(const char *keyword, const char *mountpt, int fd) 473 { 474 struct mountctl_install_journal joinfo; 475 struct stat st1; 476 struct stat st2; 477 int error; 478 479 /* 480 * Make sure the file descriptor is not on the same filesystem as the 481 * mount point. This isn't a perfect test, but it should catch most 482 * foot shooting. 483 */ 484 if (output_safety_override_opt == 0 && 485 fstat(fd, &st1) == 0 && S_ISREG(st1.st_mode) && 486 stat(mountpt, &st2) == 0 && st1.st_dev == st2.st_dev 487 ) { 488 fprintf(stderr, "%s:%s failed to add, the journal cannot be on the " 489 "same filesystem being journaled!\n", 490 mountpt, keyword); 491 exitCode = 1; 492 return; 493 } 494 495 /* 496 * Setup joinfo and issue the add 497 */ 498 bzero(&joinfo, sizeof(joinfo)); 499 snprintf(joinfo.id, sizeof(joinfo.id), "%s", keyword); 500 if (memfifo_opt > 0) 501 joinfo.membufsize = memfifo_opt; 502 if (twoway_opt > 0) 503 joinfo.flags |= MC_JOURNAL_WANT_FULLDUPLEX; 504 if (reversable_opt > 0) 505 joinfo.flags |= MC_JOURNAL_WANT_REVERSABLE; 506 507 error = mountctl(mountpt, MOUNTCTL_INSTALL_VFS_JOURNAL, fd, 508 &joinfo, sizeof(joinfo), NULL, 0); 509 if (error == 0) { 510 fprintf(stderr, "%s:%s added\n", mountpt, joinfo.id); 511 } else { 512 fprintf(stderr, "%s:%s failed to add, error %s\n", mountpt, joinfo.id, strerror(errno)); 513 exitCode = 1; 514 } 515 } 516 517 static void 518 mountctl_restart(const char *keyword, const char *mountpt, 519 int fd, void __unused *info) 520 { 521 struct mountctl_restart_journal joinfo; 522 int error; 523 524 /* XXX make sure descriptor is not on same filesystem as journal */ 525 526 bzero(&joinfo, sizeof(joinfo)); 527 528 snprintf(joinfo.id, sizeof(joinfo.id), "%s", keyword); 529 if (twoway_opt > 0) 530 joinfo.flags |= MC_JOURNAL_WANT_FULLDUPLEX; 531 if (reversable_opt > 0) 532 joinfo.flags |= MC_JOURNAL_WANT_REVERSABLE; 533 534 error = mountctl(mountpt, MOUNTCTL_RESTART_VFS_JOURNAL, fd, 535 &joinfo, sizeof(joinfo), NULL, 0); 536 if (error == 0) { 537 fprintf(stderr, "%s:%s restarted\n", mountpt, joinfo.id); 538 } else { 539 fprintf(stderr, "%s:%s restart failed, error %s\n", mountpt, joinfo.id, strerror(errno)); 540 } 541 } 542 543 static void 544 mountctl_delete(const char *keyword, const char *mountpt, 545 int __unused fd, void __unused *info) 546 { 547 struct mountctl_remove_journal joinfo; 548 int error; 549 550 bzero(&joinfo, sizeof(joinfo)); 551 snprintf(joinfo.id, sizeof(joinfo.id), "%s", keyword); 552 error = mountctl(mountpt, MOUNTCTL_REMOVE_VFS_JOURNAL, -1, 553 &joinfo, sizeof(joinfo), NULL, 0); 554 if (error == 0) { 555 fprintf(stderr, "%s:%s deleted\n", mountpt, joinfo.id); 556 } else { 557 fprintf(stderr, "%s:%s deletion failed, error %s\n", mountpt, joinfo.id, strerror(errno)); 558 } 559 } 560 561 static void 562 mountctl_modify(const char *keyword, const char *mountpt, int fd, void __unused *info) 563 { 564 fprintf(stderr, "modify not yet implemented\n"); 565 } 566 567 568 static volatile void 569 usage(void) 570 { 571 printf( 572 " mountctl -l [tag/mountpt | mountpt:tag]\n" 573 " mountctl -a [-w output_path] [-x filedesc]\n" 574 " [-o option] [-o option ...] mountpt:tag\n" 575 " mountctl -d [tag/mountpt | mountpt:tag]\n" 576 " mountctl -m [-o option] [-o option ...] [tag/mountpt | mountpt:tag]\n" 577 " mountctl -FZSCA [tag/mountpt | mountpt:tag]\n" 578 ); 579 exit(1); 580 } 581 582 static int64_t 583 getsize(const char *str) 584 { 585 const char *suffix; 586 int64_t val; 587 588 val = strtoll(str, &suffix, 0); 589 if (suffix) { 590 switch(*suffix) { 591 case 'b': 592 break; 593 case 't': 594 val *= 1024; 595 /* fall through */ 596 case 'g': 597 val *= 1024; 598 /* fall through */ 599 case 'm': 600 val *= 1024; 601 /* fall through */ 602 case 'k': 603 val *= 1024; 604 /* fall through */ 605 break; 606 default: 607 fprintf(stderr, "data value '%s' has unknown suffix\n", str); 608 exit(1); 609 } 610 } 611 return(val); 612 } 613 614 static const char * 615 numtostr(int64_t num) 616 { 617 static char buf[64]; 618 int n; 619 double v = num; 620 621 if (num < 1024) 622 snprintf(buf, sizeof(buf), "%lld", num); 623 else if (num < 10 * 1024) 624 snprintf(buf, sizeof(buf), "%3.2fK", num / 1024.0); 625 else if (num < 1024 * 1024) 626 snprintf(buf, sizeof(buf), "%3.0fK", num / 1024.0); 627 else if (num < 10 * 1024 * 1024) 628 snprintf(buf, sizeof(buf), "%3.2fM", num / (1024.0 * 1024.0)); 629 else if (num < 1024 * 1024 * 1024) 630 snprintf(buf, sizeof(buf), "%3.0fM", num / (1024.0 * 1024.0)); 631 else if (num < 10LL * 1024 * 1024 * 1024) 632 snprintf(buf, sizeof(buf), "%3.2fG", num / (1024.0 * 1024.0 * 1024.0)); 633 else 634 snprintf(buf, sizeof(buf), "%3.0fG", num / (1024.0 * 1024.0 * 1024.0)); 635 return(buf); 636 } 637 638