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