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