1 /* 2 * Copyright (c) 2011-2019 The DragonFly Project. All rights reserved. 3 * 4 * This code is derived from software contributed to The DragonFly Project 5 * by Matthew Dillon <dillon@dragonflybsd.org> 6 * by Venkatesh Srinivas <vsrinivas@dragonflybsd.org> 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in 16 * the documentation and/or other materials provided with the 17 * distribution. 18 * 3. Neither the name of The DragonFly Project nor the names of its 19 * contributors may be used to endorse or promote products derived 20 * from this software without specific, prior written permission. 21 * 22 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 25 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 30 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 31 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 32 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 33 * SUCH DAMAGE. 34 */ 35 36 #include "hammer2.h" 37 38 int DebugOpt; 39 int VerboseOpt; 40 int QuietOpt; 41 int ForceOpt; 42 int RecurseOpt; 43 int NormalExit = 1; /* if set to 0 main() has to pthread_exit() */ 44 size_t MemOpt; 45 46 static void usage(int code); 47 48 int 49 main(int ac, char **av) 50 { 51 char *sel_path = NULL; 52 const char *uuid_str = NULL; 53 const char *arg; 54 char *opt; 55 int pfs_type = HAMMER2_PFSTYPE_NONE; 56 int all_opt = 0; 57 int ecode = 0; 58 int ch; 59 60 srandomdev(); 61 signal(SIGPIPE, SIG_IGN); 62 dmsg_crypto_setup(); 63 64 /* 65 * Core options 66 */ 67 while ((ch = getopt(ac, av, "adfm:rs:t:u:vq")) != -1) { 68 switch(ch) { 69 case 'a': 70 all_opt = 1; 71 break; 72 case 'd': 73 if (DebugOpt) 74 ++DMsgDebugOpt; 75 DebugOpt = 1; 76 break; 77 case 'f': 78 ForceOpt = 1; 79 break; 80 case 'm': 81 MemOpt = strtoul(optarg, &opt, 0); 82 switch(*opt) { 83 case 'g': 84 case 'G': 85 MemOpt *= 1024; 86 /* FALLTHROUGH */ 87 case 'm': 88 case 'M': 89 MemOpt *= 1024; 90 /* FALLTHROUGH */ 91 case 'k': 92 case 'K': 93 MemOpt *= 1024; 94 break; 95 case 0: 96 break; 97 default: 98 fprintf(stderr, "-m: Unrecognized suffix\n"); 99 usage(1); 100 break; 101 } 102 break; 103 case 'r': 104 RecurseOpt = 1; 105 break; 106 case 's': 107 sel_path = strdup(optarg); 108 break; 109 case 't': 110 /* 111 * set node type for pfs-create 112 */ 113 if (strcasecmp(optarg, "CACHE") == 0) { 114 pfs_type = HAMMER2_PFSTYPE_CACHE; 115 } else if (strcasecmp(optarg, "DUMMY") == 0) { 116 pfs_type = HAMMER2_PFSTYPE_DUMMY; 117 } else if (strcasecmp(optarg, "SLAVE") == 0) { 118 pfs_type = HAMMER2_PFSTYPE_SLAVE; 119 } else if (strcasecmp(optarg, "SOFT_SLAVE") == 0) { 120 pfs_type = HAMMER2_PFSTYPE_SOFT_SLAVE; 121 } else if (strcasecmp(optarg, "SOFT_MASTER") == 0) { 122 pfs_type = HAMMER2_PFSTYPE_SOFT_MASTER; 123 } else if (strcasecmp(optarg, "MASTER") == 0) { 124 pfs_type = HAMMER2_PFSTYPE_MASTER; 125 } else { 126 fprintf(stderr, "-t: Unrecognized node type\n"); 127 usage(1); 128 } 129 break; 130 case 'u': 131 /* 132 * set uuid for pfs-create, else one will be generated 133 * (required for all except the MASTER node_type) 134 */ 135 uuid_str = optarg; 136 break; 137 case 'v': 138 if (QuietOpt) 139 --QuietOpt; 140 else 141 ++VerboseOpt; 142 break; 143 case 'q': 144 if (VerboseOpt) 145 --VerboseOpt; 146 else 147 ++QuietOpt; 148 break; 149 default: 150 fprintf(stderr, "Unknown option: %c\n", ch); 151 usage(1); 152 /* not reached */ 153 break; 154 } 155 } 156 157 /* 158 * Adjust, then process the command 159 */ 160 ac -= optind; 161 av += optind; 162 if (ac < 1) { 163 fprintf(stderr, "Missing command\n"); 164 usage(1); 165 /* not reached */ 166 } 167 168 if (strcmp(av[0], "connect") == 0) { 169 /* 170 * Add cluster connection 171 */ 172 if (ac < 2) { 173 fprintf(stderr, "connect: missing argument\n"); 174 usage(1); 175 } 176 ecode = cmd_remote_connect(sel_path, av[1]); 177 } else if (strcmp(av[0], "dumpchain") == 0) { 178 if (ac < 2) 179 ecode = cmd_dumpchain(".", (u_int)-1); 180 else if (ac < 3) 181 ecode = cmd_dumpchain(av[1], (u_int)-1); 182 else 183 ecode = cmd_dumpchain(av[1], 184 (u_int)strtoul(av[2], NULL, 0)); 185 } else if (strcmp(av[0], "debugspan") == 0) { 186 /* 187 * Debug connection to the target hammer2 service and run 188 * the CONN/SPAN protocol. 189 */ 190 if (ac < 2) { 191 fprintf(stderr, "debugspan: requires hostname\n"); 192 usage(1); 193 } 194 ecode = cmd_debugspan(av[1]); 195 } else if (strcmp(av[0], "disconnect") == 0) { 196 /* 197 * Remove cluster connection 198 */ 199 if (ac < 2) { 200 fprintf(stderr, "disconnect: missing argument\n"); 201 usage(1); 202 } 203 ecode = cmd_remote_disconnect(sel_path, av[1]); 204 } else if (strcmp(av[0], "destroy") == 0) { 205 if (ac < 2) { 206 fprintf(stderr, 207 "destroy: specify one or more paths to " 208 "destroy\n"); 209 usage(1); 210 } 211 ecode = cmd_destroy_path(ac - 1, (const char **)(void *)&av[1]); 212 } else if (strcmp(av[0], "destroy-inum") == 0) { 213 if (ac < 2) { 214 fprintf(stderr, 215 "destroy-inum: specify one or more inode " 216 "numbers to destroy\n"); 217 usage(1); 218 } 219 ecode = cmd_destroy_inum(sel_path, ac - 1, 220 (const char **)(void *)&av[1]); 221 } else if (strcmp(av[0], "emergency-mode-enable") == 0) { 222 ecode = cmd_emergency_mode(sel_path, 1, ac - 1, 223 (const char **)(void *)&av[1]); 224 } else if (strcmp(av[0], "emergency-mode-disable") == 0) { 225 ecode = cmd_emergency_mode(sel_path, 0, ac - 1, 226 (const char **)(void *)&av[1]); 227 } else if (strcmp(av[0], "growfs") == 0) { 228 ecode = cmd_growfs(sel_path, ac - 1, 229 (const char **)(void *)&av[1]); 230 } else if (strcmp(av[0], "hash") == 0) { 231 ecode = cmd_hash(ac - 1, (const char **)(void *)&av[1]); 232 } else if (strcmp(av[0], "dhash") == 0) { 233 ecode = cmd_dhash(ac - 1, (const char **)(void *)&av[1]); 234 } else if (strcmp(av[0], "info") == 0) { 235 ecode = cmd_info(ac - 1, (const char **)(void *)&av[1]); 236 } else if (strcmp(av[0], "mountall") == 0) { 237 ecode = cmd_mountall(ac - 1, (const char **)(void *)&av[1]); 238 } else if (strcmp(av[0], "status") == 0) { 239 /* 240 * Get status of PFS and its connections (-a for all PFSs) 241 */ 242 if (ac < 2) { 243 ecode = cmd_remote_status(sel_path, all_opt); 244 } else { 245 int i; 246 for (i = 1; i < ac; ++i) 247 ecode = cmd_remote_status(av[i], all_opt); 248 } 249 } else if (strcmp(av[0], "pfs-clid") == 0) { 250 /* 251 * Print cluster id (uuid) for specific PFS 252 */ 253 if (ac < 2) { 254 fprintf(stderr, "pfs-clid: requires name\n"); 255 usage(1); 256 } 257 ecode = cmd_pfs_getid(sel_path, av[1], 0); 258 } else if (strcmp(av[0], "pfs-fsid") == 0) { 259 /* 260 * Print private id (uuid) for specific PFS 261 */ 262 if (ac < 2) { 263 fprintf(stderr, "pfs-fsid: requires name\n"); 264 usage(1); 265 } 266 ecode = cmd_pfs_getid(sel_path, av[1], 1); 267 } else if (strcmp(av[0], "pfs-list") == 0) { 268 /* 269 * List all PFSs 270 */ 271 if (ac >= 2) { 272 ecode = cmd_pfs_list(ac - 1, 273 (char **)(void *)&av[1]); 274 } else { 275 ecode = cmd_pfs_list(1, &sel_path); 276 } 277 } else if (strcmp(av[0], "pfs-create") == 0) { 278 /* 279 * Create new PFS using pfs_type 280 */ 281 if (ac < 2) { 282 fprintf(stderr, "pfs-create: requires name\n"); 283 usage(1); 284 } 285 ecode = cmd_pfs_create(sel_path, av[1], pfs_type, uuid_str); 286 } else if (strcmp(av[0], "pfs-delete") == 0) { 287 /* 288 * Delete a PFS by name 289 */ 290 if (ac < 2) { 291 fprintf(stderr, "pfs-delete: requires name\n"); 292 usage(1); 293 } 294 ecode = cmd_pfs_delete(sel_path, av, ac); 295 } else if (strcmp(av[0], "recover") == 0 || 296 strcmp(av[0], "recover-relaxed") == 0 || 297 strcmp(av[0], "recover-file") == 0) 298 { 299 /* 300 * Recover a relative path (unanchored match), absolute path, 301 * specific file, or directory sub-tree. File restorals are 302 * fully validated. 303 */ 304 if (ac != 4) { 305 fprintf(stderr, "recover device [/]path destdir\n"); 306 usage(1); 307 } else { 308 int strict = (strcmp(av[0], "recover-relaxed") != 0); 309 int isafile = (strcmp(av[0], "recover-file") == 0); 310 cmd_recover(av[1], av[2], av[3], strict, isafile); 311 } 312 } else if (strcmp(av[0], "snapshot") == 0 || 313 strcmp(av[0], "snapshot-debug") == 0) { 314 /* 315 * Create snapshot with optional pfs-type and optional 316 * label override. 317 */ 318 uint32_t flags = 0; 319 320 if (strcmp(av[0], "snapshot-debug") == 0) 321 flags = HAMMER2_PFSFLAGS_NOSYNC; 322 323 if (ac > 3) { 324 fprintf(stderr, "%s: too many arguments\n", av[0]); 325 usage(1); 326 } 327 switch(ac) { 328 case 1: 329 ecode = cmd_pfs_snapshot(sel_path, NULL, NULL, flags); 330 break; 331 case 2: 332 ecode = cmd_pfs_snapshot(sel_path, av[1], NULL, flags); 333 break; 334 case 3: 335 ecode = cmd_pfs_snapshot(sel_path, av[1], av[2], flags); 336 break; 337 } 338 } else if (strcmp(av[0], "service") == 0) { 339 /* 340 * Start the service daemon. This daemon accepts 341 * connections from local and remote clients, handles 342 * the security handshake, and manages the core messaging 343 * protocol. 344 */ 345 ecode = cmd_service(); 346 } else if (strcmp(av[0], "stat") == 0) { 347 ecode = cmd_stat(ac - 1, (const char **)(void *)&av[1]); 348 } else if (strcmp(av[0], "leaf") == 0) { 349 /* 350 * Start the management daemon for a specific PFS. 351 * 352 * This will typically connect to the local master node 353 * daemon, register the PFS, and then pass its side of 354 * the socket descriptor to the kernel HAMMER2 VFS via an 355 * ioctl(). The process and/or thread context remains in the 356 * kernel until the PFS is unmounted or the connection is 357 * lost, then returns from the ioctl. 358 * 359 * It is possible to connect directly to a remote master node 360 * instead of the local master node in situations where 361 * encryption is not desired or no local master node is 362 * desired. This is not recommended because it represents 363 * a single point of failure for the PFS's communications. 364 * 365 * Direct kernel<->kernel communication between HAMMER2 VFSs 366 * is theoretically possible for directly-connected 367 * registrations (i.e. where the spanning tree is degenerate), 368 * but not recommended. We specifically try to reduce the 369 * complexity of the HAMMER2 VFS kernel code. 370 */ 371 ecode = cmd_leaf(sel_path); 372 } else if (strcmp(av[0], "shell") == 0) { 373 /* 374 * Connect to the command line monitor in the hammer2 master 375 * node for the machine using HAMMER2_DBG_SHELL messages. 376 */ 377 ecode = cmd_shell((ac < 2) ? NULL : av[1]); 378 } else if (strcmp(av[0], "rsainit") == 0) { 379 /* 380 * Initialize a RSA keypair. If no target directory is 381 * specified we default to "/etc/hammer2". 382 */ 383 arg = (ac < 2) ? HAMMER2_DEFAULT_DIR : av[1]; 384 ecode = cmd_rsainit(arg); 385 } else if (strcmp(av[0], "rsaenc") == 0) { 386 /* 387 * Encrypt the input symmetrically by running it through 388 * the specified public and/or private key files. 389 * 390 * If no key files are specified data is encoded using 391 * "/etc/hammer2/rsa.pub". 392 * 393 * WARNING: no padding is added, data stream must contain 394 * random padding for this to be secure. 395 * 396 * Used for debugging only 397 */ 398 if (ac == 1) { 399 const char *rsapath = HAMMER2_DEFAULT_DIR "/rsa.pub"; 400 ecode = cmd_rsaenc(&rsapath, 1); 401 } else { 402 ecode = cmd_rsaenc((const char **)(void *)&av[1], 403 ac - 1); 404 } 405 } else if (strcmp(av[0], "rsadec") == 0) { 406 /* 407 * Decrypt the input symmetrically by running it through 408 * the specified public and/or private key files. 409 * 410 * If no key files are specified data is decoded using 411 * "/etc/hammer2/rsa.prv". 412 * 413 * WARNING: no padding is added, data stream must contain 414 * random padding for this to be secure. 415 * 416 * Used for debugging only 417 */ 418 if (ac == 1) { 419 const char *rsapath = HAMMER2_DEFAULT_DIR "/rsa.prv"; 420 ecode = cmd_rsadec(&rsapath, 1); 421 } else { 422 ecode = cmd_rsadec((const char **)(void *)&av[1], 423 ac - 1); 424 } 425 } else if (strcmp(av[0], "show") == 0) { 426 /* 427 * Raw dump of filesystem. Use -v to check all crc's, and 428 * -vv to dump bulk file data. 429 */ 430 if (ac != 2) { 431 fprintf(stderr, "show: requires device path\n"); 432 usage(1); 433 } else { 434 cmd_show(av[1], 0); 435 } 436 } else if (strcmp(av[0], "freemap") == 0) { 437 /* 438 * Raw dump of freemap. Use -v to check all crc's, and 439 * -vv to dump bulk file data. 440 */ 441 if (ac != 2) { 442 fprintf(stderr, "freemap: requires device path\n"); 443 usage(1); 444 } else { 445 cmd_show(av[1], 1); 446 } 447 } else if (strcmp(av[0], "volhdr") == 0) { 448 /* 449 * Dump the volume header. 450 */ 451 if (ac != 2) { 452 fprintf(stderr, "volhdr: requires device path\n"); 453 usage(1); 454 } else { 455 cmd_show(av[1], 2); 456 } 457 } else if (strcmp(av[0], "volume-list") == 0) { 458 /* 459 * List all volumes 460 */ 461 if (ac >= 2) { 462 ecode = cmd_volume_list(ac - 1, 463 (char **)(void *)&av[1]); 464 } else { 465 ecode = cmd_volume_list(1, &sel_path); 466 } 467 } else if (strcmp(av[0], "setcomp") == 0) { 468 if (ac < 3) { 469 /* 470 * Missing compression method and at least one 471 * path. 472 */ 473 fprintf(stderr, 474 "setcomp: requires compression method and " 475 "directory/file path\n"); 476 usage(1); 477 } else { 478 /* 479 * Multiple paths may be specified 480 */ 481 ecode = cmd_setcomp(av[1], &av[2]); 482 } 483 } else if (strcmp(av[0], "setcheck") == 0) { 484 if (ac < 3) { 485 /* 486 * Missing compression method and at least one 487 * path. 488 */ 489 fprintf(stderr, 490 "setcheck: requires check code method and " 491 "directory/file path\n"); 492 usage(1); 493 } else { 494 /* 495 * Multiple paths may be specified 496 */ 497 ecode = cmd_setcheck(av[1], &av[2]); 498 } 499 } else if (strcmp(av[0], "clrcheck") == 0) { 500 ecode = cmd_setcheck("none", &av[1]); 501 } else if (strcmp(av[0], "setcrc32") == 0) { 502 ecode = cmd_setcheck("crc32", &av[1]); 503 } else if (strcmp(av[0], "setxxhash64") == 0) { 504 ecode = cmd_setcheck("xxhash64", &av[1]); 505 } else if (strcmp(av[0], "setsha192") == 0) { 506 ecode = cmd_setcheck("sha192", &av[1]); 507 } else if (strcmp(av[0], "printinode") == 0) { 508 if (ac != 2) { 509 fprintf(stderr, 510 "printinode: requires directory/file path\n"); 511 usage(1); 512 } else { 513 print_inode(av[1]); 514 } 515 } else if (strcmp(av[0], "bulkfree") == 0) { 516 if (ac != 2) { 517 fprintf(stderr, "bulkfree: requires path to mount\n"); 518 usage(1); 519 } else { 520 ecode = cmd_bulkfree(av[1]); 521 } 522 #if 0 523 } else if (strcmp(av[0], "bulkfree-async") == 0) { 524 if (ac != 2) { 525 fprintf(stderr, 526 "bulkfree-async: requires path to mount\n"); 527 usage(1); 528 } else { 529 ecode = cmd_bulkfree_async(av[1]); 530 } 531 #endif 532 } else if (strcmp(av[0], "cleanup") == 0) { 533 ecode = cmd_cleanup(av[1]); /* can be NULL */ 534 } else { 535 fprintf(stderr, "Unrecognized command: %s\n", av[0]); 536 usage(1); 537 } 538 539 /* 540 * In DebugMode we may wind up starting several pthreads in the 541 * original process, in which case we have to let them run and 542 * not actually exit. 543 */ 544 if (NormalExit) { 545 return (ecode); 546 } else { 547 pthread_exit(NULL); 548 _exit(2); /* NOT REACHED */ 549 } 550 } 551 552 static 553 void 554 usage(int code) 555 { 556 fprintf(stderr, 557 "hammer2 [options] command [argument ...]\n" 558 " -s path Select filesystem\n" 559 " -t type PFS type for pfs-create\n" 560 " -u uuid uuid for pfs-create\n" 561 " -m mem[k,m,g] buffer memory (bulkfree)\n" 562 "\n" 563 " cleanup [<path>] " 564 "Run cleanup passes\n" 565 " connect <target> " 566 "Add cluster link\n" 567 " destroy <path>... " 568 "Destroy directory entries (only use if inode bad)\n" 569 " destroy-inum <inum>... " 570 "Destroy inodes (only use if inode bad)\n" 571 " disconnect <target> " 572 "Del cluster link\n" 573 " emergency-mode-enable <target> " 574 "Enable emergency operations mode on filesystem\n" 575 " " 576 "THIS IS A VERY DANGEROUS MODE\n" 577 " emergency-mode-disable <target> " 578 "Disable emergency operations mode on filesystem\n" 579 " info [<devpath>...] " 580 "Info on all offline or online H2 partitions\n" 581 " mountall [<devpath>...] " 582 "Mount @LOCAL for all H2 partitions\n" 583 " status [<path>...] " 584 "Report active cluster status\n" 585 " hash [<filename>...] " 586 "Print directory hash (key) for name\n" 587 " dhash [<filename>...] " 588 "Print data hash for long directory entry\n" 589 " pfs-list [<path>...] " 590 "List PFSs\n" 591 " pfs-clid <label> " 592 "Print cluster id for specific PFS\n" 593 " pfs-fsid <label> " 594 "Print private id for specific PFS\n" 595 " pfs-create <label> " 596 "Create a PFS\n" 597 " pfs-delete <label> " 598 "Destroy a PFS\n" 599 " recover <devpath> <path> <destdir> " 600 "Recover deleted or corrupt files or trees\n" 601 " recover-relaxed <devpath> <path> <destdir> " 602 "Recover deleted or corrupt files or trees\n" 603 " recover-file <devpath> <path> <destdir> " 604 "Recover, target is explicitly a regular file\n" 605 " snapshot <path> [<label>] " 606 "Snapshot a PFS or directory\n" 607 " snapshot-debug <path> [<label>] " 608 "Snapshot without filesystem sync\n" 609 " service " 610 "Start service daemon\n" 611 " stat [<path>...] " 612 "Return inode quota & config\n" 613 " leaf " 614 "Start pfs leaf daemon\n" 615 " shell [<host>] " 616 "Connect to debug shell\n" 617 " debugspan <target> " 618 "Connect to target, run CONN/SPAN\n" 619 " growfs [<path>...] " 620 "Grow a filesystem into resized partition\n" 621 " rsainit [<path>] " 622 "Initialize rsa fields\n" 623 " show <devpath> " 624 "Raw hammer2 media dump for topology\n" 625 " freemap <devpath> " 626 "Raw hammer2 media dump for freemap\n" 627 " volhdr <devpath> " 628 "Raw hammer2 media dump for the volume header(s)\n" 629 " volume-list [<path>...] " 630 "List volumes\n" 631 " setcomp <comp[:level]> <path>... " 632 "Set comp algo {none, autozero, lz4, zlib} & level\n" 633 " setcheck <check> <path>... " 634 "Set check algo {none, crc32, xxhash64, sha192}\n" 635 " clrcheck [<path>...] " 636 "Clear check code override\n" 637 " setcrc32 [<path>...] " 638 "Set check algo to crc32\n" 639 " setxxhash64 [<path>...] " 640 "Set check algo to xxhash64\n" 641 " setsha192 [<path>...] " 642 "Set check algo to sha192\n" 643 " bulkfree <path> " 644 "Run bulkfree pass\n" 645 " printinode <path> " 646 "Dump inode\n" 647 " dumpchain [<path> [<chnflags>]] " 648 "Dump in-memory chain topology (ONFLUSH flag is 0x200)\n" 649 #if 0 650 " bulkfree-async path " 651 "Run bulkfree pass asynchronously\n" 652 #endif 653 ); 654 exit(code); 655 } 656