1 /* 2 * Copyright (c) 2008 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/hammer/cmd_pseudofs.c,v 1.12 2008/10/08 21:01:54 thomas Exp $ 35 */ 36 37 #include "hammer.h" 38 39 static int scanpfsid(struct hammer_ioc_pseudofs_rw *pfs, const char *path); 40 static void parse_pfsd_options(char **av, int ac, hammer_pseudofs_data_t pfsd); 41 static void init_pfsd(hammer_pseudofs_data_t pfsd, int is_slave); 42 static void pseudofs_usage(int code); 43 static int timetosecs(char *str); 44 45 void 46 clrpfs(struct hammer_ioc_pseudofs_rw *pfs, hammer_pseudofs_data_t pfsd, 47 int pfs_id) 48 { 49 bzero(pfs, sizeof(*pfs)); 50 51 if (pfsd) 52 pfs->ondisk = pfsd; 53 else 54 pfs->ondisk = malloc(sizeof(*pfs->ondisk)); 55 bzero(pfs->ondisk, sizeof(*pfs->ondisk)); 56 57 pfs->pfs_id = pfs_id; 58 pfs->bytes = sizeof(*pfs->ondisk); 59 pfs->version = HAMMER_IOC_PSEUDOFS_VERSION; 60 } 61 62 /* 63 * If path is a symlink, return strdup'd path. 64 * If it's a directory via symlink, strip trailing / 65 * from strdup'd path and return the symlink. 66 */ 67 static 68 char* 69 getlink(const char *path) 70 { 71 int i; 72 char *linkpath; 73 struct stat st; 74 75 if (lstat(path, &st)) 76 return(NULL); 77 linkpath = strdup(path); 78 79 if (S_ISDIR(st.st_mode)) { 80 i = strlen(linkpath) - 1; 81 while (i > 0 && linkpath[i] == '/') 82 linkpath[i--] = 0; 83 lstat(linkpath, &st); 84 } 85 if (S_ISLNK(st.st_mode)) 86 return(linkpath); 87 88 free(linkpath); 89 return(NULL); 90 } 91 92 /* 93 * Calculate the PFS id given a path to a file/directory or 94 * a @@%llx:%d softlink. 95 */ 96 int 97 getpfs(struct hammer_ioc_pseudofs_rw *pfs, const char *path) 98 { 99 char *tmpbuf; 100 int fd; 101 102 clrpfs(pfs, NULL, -1); 103 104 /* 105 * Extract the PFS id. 106 * dirname(path) is supposed to be a directory in root PFS. 107 * 108 * strips trailing / first if any. 109 */ 110 tmpbuf = strdup(path); 111 if (scanpfsid(pfs, path) == 0) 112 path = dirname(tmpbuf); 113 114 /* 115 * Open the path regardless of scanpfsid() result, since some 116 * commands can take a regular file/directory (e.g. pfs-status). 117 */ 118 fd = open(path, O_RDONLY); 119 if (fd < 0) { 120 err(1, "Failed to open %s", path); 121 /* not reached */ 122 } 123 124 /* 125 * If pfs.pfs_id has been set to non -1, the file descriptor fd 126 * could be any fd of HAMMER inodes since HAMMERIOC_GET_PSEUDOFS 127 * doesn't depend on inode attributes if it's set to a valid id. 128 */ 129 if (ioctl(fd, HAMMERIOC_GET_PSEUDOFS, pfs) < 0) { 130 err(1, "Cannot access %s", path); 131 /* not reached */ 132 } 133 134 free(tmpbuf); 135 136 return(fd); 137 } 138 139 /* 140 * Extract the PFS id from path. 141 */ 142 static 143 int 144 scanpfsid(struct hammer_ioc_pseudofs_rw *pfs, const char *path) 145 { 146 char *linkpath; 147 char *tmpbuf; 148 char buf[64]; 149 uintmax_t dummy_tid; 150 struct stat st; 151 152 if (stat(path, &st)) 153 ; /* possibly slave PFS */ 154 else if (S_ISDIR(st.st_mode)) 155 ; /* possibly master or slave PFS */ 156 else 157 return(-1); /* neither */ 158 159 linkpath = getlink(path); 160 if (linkpath) { 161 /* 162 * Read the symlink assuming it's a link to PFS. 163 */ 164 bzero(buf, sizeof(buf)); 165 if (readlink(linkpath, buf, sizeof(buf) - 1) < 0) { 166 free(linkpath); 167 return(-1); 168 } 169 free(linkpath); 170 path = buf; 171 } 172 173 /* 174 * The symlink created by pfs-master|slave is just a symlink. 175 * One could happen to remove a symlink and relink PFS as 176 * # ln -s ./@@-1:00001 ./link 177 * which results PFS having something extra before @@. 178 * One could also directly use the PFS and results the same. 179 * Get rid of it before we extract the PFS id. 180 */ 181 tmpbuf = strdup(path); 182 if (strchr(path, '/')) { 183 path = basename(tmpbuf); /* strips trailing / first if any */ 184 if (path == NULL) { 185 err(1, "basename"); 186 /* not reached */ 187 } 188 } 189 190 /* 191 * Test and extract the PFS id from the link. 192 * "@@%jx:%d" covers both "@@-1:%05d" format for master PFS 193 * and "@@0x%016jx:%05d" format for slave PFS. 194 */ 195 if (sscanf(path, "@@%jx:%d", &dummy_tid, &pfs->pfs_id) == 2) { 196 free(tmpbuf); 197 assert(pfs->pfs_id > 0); 198 return(0); 199 } 200 free(tmpbuf); 201 202 return(-1); 203 } 204 205 void 206 relpfs(int fd, struct hammer_ioc_pseudofs_rw *pfs) 207 { 208 if (fd >= 0) 209 close(fd); 210 if (pfs->ondisk) { 211 free(pfs->ondisk); 212 pfs->ondisk = NULL; 213 } 214 } 215 216 static 217 void 218 print_pfs_status(char *path) 219 { 220 struct hammer_ioc_pseudofs_rw pfs; 221 int fd; 222 223 fd = getpfs(&pfs, path); 224 printf("%s\t", path); 225 if (fd < 0 || ioctl(fd, HAMMERIOC_GET_PSEUDOFS, &pfs) < 0) { 226 printf("Invalid PFS path %s\n", path); 227 } else { 228 printf("PFS#%d {\n", pfs.pfs_id); 229 dump_pfsd(pfs.ondisk, fd); 230 printf("}\n"); 231 } 232 if (fd >= 0) 233 close(fd); 234 if (pfs.ondisk) 235 free(pfs.ondisk); 236 relpfs(fd, &pfs); 237 } 238 239 void 240 hammer_cmd_pseudofs_status(char **av, int ac) 241 { 242 int i; 243 244 if (ac == 0) { 245 char buf[2] = "."; /* can't be readonly string */ 246 print_pfs_status(buf); 247 return; 248 } 249 250 for (i = 0; i < ac; ++i) 251 print_pfs_status(av[i]); 252 } 253 254 void 255 hammer_cmd_pseudofs_create(char **av, int ac, int is_slave) 256 { 257 struct hammer_ioc_pseudofs_rw pfs; 258 struct hammer_pseudofs_data pfsd; 259 struct stat st; 260 const char *path; 261 char *tmpbuf; 262 char *dirpath; 263 char *linkpath; 264 int pfs_id; 265 int fd; 266 267 if (ac == 0) { 268 pseudofs_usage(1); 269 /* not reached */ 270 } 271 path = av[0]; 272 if (lstat(path, &st) == 0) { 273 errx(1, "Cannot create %s, file exists!", path); 274 /* not reached */ 275 } else if (path[strlen(path) - 1] == '/') { 276 errx(1, "Invalid PFS path %s with trailing /", path); 277 /* not reached */ 278 } 279 280 /* 281 * Figure out the directory prefix, taking care of degenerate 282 * cases. 283 */ 284 tmpbuf = strdup(path); 285 dirpath = dirname(tmpbuf); 286 fd = open(dirpath, O_RDONLY); 287 if (fd < 0) { 288 err(1, "Cannot open directory %s", dirpath); 289 /* not reached */ 290 } 291 292 /* 293 * Avoid foot-shooting. Don't let the user create a PFS 294 * softlink via a PFS. PFS softlinks may only be accessed 295 * via the master filesystem. Checking it here ensures 296 * other PFS commands access PFS under the master filesystem. 297 */ 298 clrpfs(&pfs, &pfsd, -1); 299 300 ioctl(fd, HAMMERIOC_GET_PSEUDOFS, &pfs); 301 if (pfs.pfs_id != HAMMER_ROOT_PFSID) { 302 fprintf(stderr, 303 "You are attempting to access a PFS softlink " 304 "from a PFS. It may not represent the PFS\n" 305 "on the main filesystem mount that you " 306 "expect! You may only access PFS softlinks\n" 307 "via the main filesystem mount!\n"); 308 exit(1); 309 } 310 311 for (pfs_id = 0; pfs_id < HAMMER_MAX_PFS; ++pfs_id) { 312 clrpfs(&pfs, &pfsd, pfs_id); 313 if (ioctl(fd, HAMMERIOC_GET_PSEUDOFS, &pfs) < 0) { 314 if (errno != ENOENT) { 315 err(1, "Cannot create %s", path); 316 /* not reached */ 317 } 318 break; 319 } 320 } 321 if (pfs_id == HAMMER_MAX_PFS) { 322 errx(1, "Cannot create %s, all PFSs in use", path); 323 /* not reached */ 324 } else if (pfs_id == HAMMER_ROOT_PFSID) { 325 errx(1, "Fatal error: PFS#%d must exist", HAMMER_ROOT_PFSID); 326 /* not reached */ 327 } 328 329 /* 330 * Create the new PFS 331 */ 332 printf("Creating PFS#%d\t", pfs_id); 333 clrpfs(&pfs, &pfsd, pfs_id); 334 init_pfsd(&pfsd, is_slave); 335 336 if (ioctl(fd, HAMMERIOC_SET_PSEUDOFS, &pfs) < 0) { 337 printf("failed: %s\n", strerror(errno)); 338 } else { 339 /* special symlink, must be exactly 10 characters */ 340 asprintf(&linkpath, "@@PFS%05d", pfs_id); 341 if (symlink(linkpath, path) < 0) { 342 printf("failed: cannot create symlink: %s\n", 343 strerror(errno)); 344 } else { 345 printf("succeeded!\n"); 346 hammer_cmd_pseudofs_update(av, ac); 347 } 348 } 349 free(tmpbuf); 350 351 close(fd); 352 } 353 354 void 355 hammer_cmd_pseudofs_destroy(char **av, int ac) 356 { 357 struct hammer_ioc_pseudofs_rw pfs; 358 char *linkpath; 359 int fd; 360 int i; 361 362 if (ac == 0) { 363 pseudofs_usage(1); 364 /* not reached */ 365 } 366 fd = getpfs(&pfs, av[0]); 367 368 if (pfs.pfs_id == HAMMER_ROOT_PFSID) { 369 errx(1, "You cannot destroy PFS#%d", HAMMER_ROOT_PFSID); 370 /* not reached */ 371 } 372 373 printf("You have requested that PFS#%d (%s) be destroyed\n", 374 pfs.pfs_id, pfs.ondisk->label); 375 printf("This will irrevocably destroy all data on this PFS!!!!!\n"); 376 printf("Do you really want to do this? [y/n] "); 377 fflush(stdout); 378 if (getyn() == 0) { 379 errx(1, "No action taken on PFS#%d", pfs.pfs_id); 380 /* not reached */ 381 } 382 383 if (hammer_is_pfs_master(pfs.ondisk)) { 384 printf("This PFS is currently setup as a MASTER!\n"); 385 printf("Are you absolutely sure you want to destroy it? [y/n] "); 386 fflush(stdout); 387 if (getyn() == 0) { 388 errx(1, "No action taken on PFS#%d", pfs.pfs_id); 389 /* not reached */ 390 } 391 } 392 393 printf("Destroying PFS#%d (%s)", pfs.pfs_id, pfs.ondisk->label); 394 if (DebugOpt) { 395 printf("\n"); 396 } else { 397 printf(" in"); 398 for (i = 5; i; --i) { 399 printf(" %d", i); 400 fflush(stdout); 401 sleep(1); 402 } 403 printf(".. starting destruction pass\n"); 404 } 405 406 /* 407 * Remove the softlink on success. 408 */ 409 if (ioctl(fd, HAMMERIOC_RMR_PSEUDOFS, &pfs) == 0) { 410 printf("pfs-destroy of PFS#%d succeeded!\n", pfs.pfs_id); 411 linkpath = getlink(av[0]); 412 if (linkpath) { 413 if (remove(linkpath) < 0) { 414 err(1, "Unable to remove softlink %s", linkpath); 415 /* not reached */ 416 } 417 free(linkpath); 418 } 419 } else { 420 printf("pfs-destroy of PFS#%d failed: %s\n", 421 pfs.pfs_id, strerror(errno)); 422 } 423 relpfs(fd, &pfs); 424 } 425 426 void 427 hammer_cmd_pseudofs_upgrade(char **av, int ac) 428 { 429 struct hammer_ioc_pseudofs_rw pfs; 430 int fd; 431 432 if (ac == 0) { 433 pseudofs_usage(1); 434 /* not reached */ 435 } 436 fd = getpfs(&pfs, av[0]); 437 438 if (pfs.pfs_id == HAMMER_ROOT_PFSID) { 439 errx(1, "You cannot upgrade PFS#%d" 440 " (It should already be a master)", 441 HAMMER_ROOT_PFSID); 442 /* not reached */ 443 } else if (hammer_is_pfs_master(pfs.ondisk)) { 444 errx(1, "It is already a master"); 445 /* not reached */ 446 } 447 448 if (ioctl(fd, HAMMERIOC_UPG_PSEUDOFS, &pfs) == 0) { 449 printf("pfs-upgrade of PFS#%d (%s) succeeded\n", 450 pfs.pfs_id, pfs.ondisk->label); 451 } else { 452 err(1, "pfs-upgrade of PFS#%d (%s) failed", 453 pfs.pfs_id, pfs.ondisk->label); 454 /* not reached */ 455 } 456 relpfs(fd, &pfs); 457 } 458 459 void 460 hammer_cmd_pseudofs_downgrade(char **av, int ac) 461 { 462 struct hammer_ioc_pseudofs_rw pfs; 463 int fd; 464 465 if (ac == 0) { 466 pseudofs_usage(1); 467 /* not reached */ 468 } 469 fd = getpfs(&pfs, av[0]); 470 471 if (pfs.pfs_id == HAMMER_ROOT_PFSID) { 472 errx(1, "You cannot downgrade PFS#%d", HAMMER_ROOT_PFSID); 473 /* not reached */ 474 } else if (hammer_is_pfs_slave(pfs.ondisk)) { 475 errx(1, "It is already a slave"); 476 /* not reached */ 477 } 478 479 if (ioctl(fd, HAMMERIOC_DGD_PSEUDOFS, &pfs) == 0) { 480 printf("pfs-downgrade of PFS#%d (%s) succeeded\n", 481 pfs.pfs_id, pfs.ondisk->label); 482 } else { 483 err(1, "pfs-downgrade of PFS#%d (%s) failed", 484 pfs.pfs_id, pfs.ondisk->label); 485 /* not reached */ 486 } 487 relpfs(fd, &pfs); 488 } 489 490 void 491 hammer_cmd_pseudofs_update(char **av, int ac) 492 { 493 struct hammer_ioc_pseudofs_rw pfs; 494 int fd; 495 496 if (ac == 0) { 497 pseudofs_usage(1); 498 /* not reached */ 499 } 500 fd = getpfs(&pfs, av[0]); 501 502 printf("%s\n", av[0]); 503 fflush(stdout); 504 505 if (ioctl(fd, HAMMERIOC_GET_PSEUDOFS, &pfs) == 0) { 506 parse_pfsd_options(av + 1, ac - 1, pfs.ondisk); 507 if (hammer_is_pfs_slave(pfs.ondisk) && 508 pfs.pfs_id == HAMMER_ROOT_PFSID) { 509 errx(1, "The real mount point cannot be made a PFS " 510 "slave, only PFS sub-directories can be made " 511 "slaves"); 512 /* not reached */ 513 } 514 pfs.bytes = sizeof(*pfs.ondisk); 515 if (ioctl(fd, HAMMERIOC_SET_PSEUDOFS, &pfs) == 0) { 516 if (ioctl(fd, HAMMERIOC_GET_PSEUDOFS, &pfs) == 0) { 517 dump_pfsd(pfs.ondisk, fd); 518 } else { 519 err(1, "Unable to retrieve PFS configuration " 520 "after successful update"); 521 /* not reached */ 522 } 523 } else { 524 err(1, "Unable to adjust PFS configuration"); 525 /* not reached */ 526 } 527 } 528 relpfs(fd, &pfs); 529 } 530 531 static 532 void 533 init_pfsd(hammer_pseudofs_data_t pfsd, int is_slave) 534 { 535 bzero(pfsd, sizeof(*pfsd)); 536 pfsd->sync_beg_tid = 1; 537 pfsd->sync_end_tid = 1; 538 pfsd->sync_beg_ts = 0; 539 pfsd->sync_end_ts = 0; 540 hammer_uuid_create(&pfsd->shared_uuid); 541 hammer_uuid_create(&pfsd->unique_uuid); 542 if (is_slave) 543 pfsd->mirror_flags |= HAMMER_PFSD_SLAVE; 544 } 545 546 void 547 dump_pfsd(hammer_pseudofs_data_t pfsd, int fd) 548 { 549 struct hammer_ioc_version version; 550 char *str = NULL; 551 552 printf(" sync-beg-tid=0x%016jx\n", (uintmax_t)pfsd->sync_beg_tid); 553 printf(" sync-end-tid=0x%016jx\n", (uintmax_t)pfsd->sync_end_tid); 554 hammer_uuid_to_string(&pfsd->shared_uuid, &str); 555 printf(" shared-uuid=%s\n", str); 556 free(str); 557 hammer_uuid_to_string(&pfsd->unique_uuid, &str); 558 printf(" unique-uuid=%s\n", str); 559 free(str); 560 printf(" label=\"%s\"\n", pfsd->label); 561 if (pfsd->snapshots[0]) 562 printf(" snapshots=\"%s\"\n", pfsd->snapshots); 563 if (pfsd->prune_min < (60 * 60 * 24)) { 564 printf(" prune-min=%02d:%02d:%02d\n", 565 pfsd->prune_min / 60 / 60 % 24, 566 pfsd->prune_min / 60 % 60, 567 pfsd->prune_min % 60); 568 } else if (pfsd->prune_min % (60 * 60 * 24)) { 569 printf(" prune-min=%dd/%02d:%02d:%02d\n", 570 pfsd->prune_min / 60 / 60 / 24, 571 pfsd->prune_min / 60 / 60 % 24, 572 pfsd->prune_min / 60 % 60, 573 pfsd->prune_min % 60); 574 } else { 575 printf(" prune-min=%dd\n", pfsd->prune_min / 60 / 60 / 24); 576 } 577 578 if (hammer_is_pfs_slave(pfsd)) 579 printf(" operating as a SLAVE\n"); 580 else 581 printf(" operating as a MASTER\n"); 582 583 /* 584 * Snapshots directory cannot be shown when there is no fd since 585 * hammer version can't be retrieved. mirror-dump passes -1 because 586 * its input came from mirror-read output thus no path is available 587 * to open(2). 588 */ 589 if (fd >= 0 && pfsd->snapshots[0] == 0) { 590 bzero(&version, sizeof(version)); 591 if (ioctl(fd, HAMMERIOC_GET_VERSION, &version) < 0) 592 return; 593 HammerVersion = version.cur_version; 594 if (version.cur_version < 3) { 595 if (hammer_is_pfs_slave(pfsd)) { 596 printf(" snapshots directory not set for " 597 "slave\n"); 598 } else { 599 printf(" snapshots directory for master " 600 "defaults to <pfs>/snapshots\n"); 601 } 602 } else { 603 printf(" snapshots directory defaults to " 604 "/var/hammer/<pfs>\n"); 605 } 606 } 607 } 608 609 static 610 void 611 parse_pfsd_options(char **av, int ac, hammer_pseudofs_data_t pfsd) 612 { 613 char *cmd; 614 char *ptr; 615 int len; 616 617 while (ac) { 618 cmd = *av; 619 if ((ptr = strchr(cmd, '=')) != NULL) 620 *ptr++ = 0; 621 622 /* 623 * Basic assignment value test 624 */ 625 if (ptr == NULL) { 626 errx(1, "option %s requires an assignment", cmd); 627 /* not reached */ 628 } 629 630 if (strcmp(cmd, "sync-beg-tid") == 0) { 631 pfsd->sync_beg_tid = strtoull(ptr, NULL, 16); 632 } else if (strcmp(cmd, "sync-end-tid") == 0) { 633 pfsd->sync_end_tid = strtoull(ptr, NULL, 16); 634 } else if (strcmp(cmd, "shared-uuid") == 0) { 635 if (hammer_uuid_from_string(ptr, &pfsd->shared_uuid)) { 636 errx(1, "option %s: error parsing uuid %s", 637 cmd, ptr); 638 /* not reached */ 639 } 640 } else if (strcmp(cmd, "unique-uuid") == 0) { 641 if (hammer_uuid_from_string(ptr, &pfsd->unique_uuid)) { 642 errx(1, "option %s: error parsing uuid %s", 643 cmd, ptr); 644 /* not reached */ 645 } 646 } else if (strcmp(cmd, "label") == 0) { 647 len = strlen(ptr); 648 if (ptr[0] == '"' && ptr[len-1] == '"') { 649 ptr[len-1] = 0; 650 ++ptr; 651 } else if (ptr[0] == '"') { 652 errx(1, "option %s: malformed string", cmd); 653 /* not reached */ 654 } 655 snprintf(pfsd->label, sizeof(pfsd->label), "%s", ptr); 656 } else if (strcmp(cmd, "snapshots") == 0) { 657 len = strlen(ptr); 658 if (ptr[0] != '/') { 659 fprintf(stderr, 660 "option %s: '%s' must be an " 661 "absolute path\n", cmd, ptr); 662 if (ptr[0] == 0) { 663 fprintf(stderr, 664 "use 'snapshots-clear' " 665 "to unset snapshots dir\n"); 666 } 667 exit(1); 668 } 669 if (len >= (int)sizeof(pfsd->snapshots)) { 670 errx(1, "option %s: path too long, %d " 671 "character limit", cmd, len); 672 /* not reached */ 673 } 674 snprintf(pfsd->snapshots, sizeof(pfsd->snapshots), 675 "%s", ptr); 676 } else if (strcmp(cmd, "snapshots-clear") == 0) { 677 pfsd->snapshots[0] = 0; 678 } else if (strcmp(cmd, "prune-min") == 0) { 679 pfsd->prune_min = timetosecs(ptr); 680 if (pfsd->prune_min < 0) { 681 errx(1, "option %s: illegal time spec, " 682 "use Nd or [Nd/]hh[:mm[:ss]]", ptr); 683 /* not reached */ 684 } 685 } else { 686 errx(1, "invalid option: %s", cmd); 687 /* not reached */ 688 } 689 --ac; 690 ++av; 691 } 692 } 693 694 static 695 void 696 pseudofs_usage(int code) 697 { 698 fprintf(stderr, 699 "hammer pfs-status <dirpath> ...\n" 700 "hammer pfs-master <dirpath> [options]\n" 701 "hammer pfs-slave <dirpath> [options]\n" 702 "hammer pfs-update <dirpath> [options]\n" 703 "hammer pfs-upgrade <dirpath>\n" 704 "hammer pfs-downgrade <dirpath>\n" 705 "hammer pfs-destroy <dirpath>\n" 706 "\n" 707 "options:\n" 708 " sync-beg-tid=0x16llx\n" 709 " sync-end-tid=0x16llx\n" 710 " shared-uuid=0x16llx\n" 711 " unique-uuid=0x16llx\n" 712 " label=\"string\"\n" 713 " snapshots=\"/path\"\n" 714 " snapshots-clear\n" 715 " prune-min=Nd\n" 716 " prune-min=[Nd/]hh[:mm[:ss]]\n" 717 ); 718 exit(code); 719 } 720 721 /* 722 * Convert time in the form [Nd/]hh[:mm[:ss]] to seconds. 723 * 724 * Return -1 if a parse error occurs. 725 * Return 0x7FFFFFFF if the time exceeds the maximum allowed. 726 */ 727 static 728 int 729 timetosecs(char *str) 730 { 731 int days = 0; 732 int hrs = 0; 733 int mins = 0; 734 int secs = 0; 735 int n; 736 long long v; 737 char *ptr; 738 739 n = strtol(str, &ptr, 10); 740 if (n < 0) 741 return(-1); 742 if (*ptr == 'd') { 743 days = n; 744 ++ptr; 745 if (*ptr == '/') 746 n = strtol(ptr + 1, &ptr, 10); 747 else 748 n = 0; 749 } 750 if (n < 0) 751 return(-1); 752 hrs = n; 753 if (*ptr == ':') { 754 n = strtol(ptr + 1, &ptr, 10); 755 if (n < 0) 756 return(-1); 757 mins = n; 758 if (*ptr == ':') { 759 n = strtol(ptr + 1, &ptr, 10); 760 if (n < 0) 761 return(-1); 762 secs = n; 763 } 764 } 765 if (*ptr) 766 return(-1); 767 v = days * 24 * 60 * 60 + hrs * 60 * 60 + mins * 60 + secs; 768 if (v > 0x7FFFFFFF) 769 v = 0x7FFFFFFF; 770 return((int)v); 771 } 772