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