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_cleanup.c,v 1.6 2008/10/07 22:28:41 thomas Exp $ 35 */ 36 /* 37 * Clean up a specific HAMMER filesystem or all HAMMER filesystems. 38 * 39 * Each filesystem is expected to have a <mount>/snapshots directory. 40 * No cleanup will be performed on any filesystem that does not. If 41 * no filesystems are specified the 'df' program is run and any HAMMER 42 * or null-mounted hammer PFS's are extracted. 43 * 44 * The snapshots directory may contain a config file called 'config'. If 45 * no config file is present one will be created with the following 46 * defaults: 47 * 48 * snapshots 1d 60d (0d 0d for /tmp, /var/tmp, /usr/obj) 49 * prune 1d 5m 50 * reblock 1d 5m 51 * recopy 30d 5m 52 * 53 * All hammer commands create and maintain cycle files in the snapshots 54 * directory. 55 */ 56 57 #include "hammer.h" 58 59 struct didpfs { 60 struct didpfs *next; 61 uuid_t uuid; 62 }; 63 64 static void do_cleanup(const char *path); 65 static int strtosecs(char *ptr); 66 static const char *dividing_slash(const char *path); 67 static int check_period(const char *snapshots_path, const char *cmd, int arg1, 68 time_t *savep); 69 static void save_period(const char *snapshots_path, const char *cmd, 70 time_t savet); 71 static int check_softlinks(const char *snapshots_path); 72 static void cleanup_softlinks(const char *path, const char *snapshots_path, 73 int arg2, char *arg3); 74 static int check_expired(const char *fpath, int arg2); 75 76 static int cleanup_snapshots(const char *path, const char *snapshots_path, 77 int arg1, int arg2); 78 static int cleanup_prune(const char *path, const char *snapshots_path, 79 int arg1, int arg2, int snapshots_disabled); 80 static int cleanup_reblock(const char *path, const char *snapshots_path, 81 int arg1, int arg2); 82 static int cleanup_recopy(const char *path, const char *snapshots_path, 83 int arg1, int arg2); 84 85 static void runcmd(int *resp, const char *ctl, ...); 86 87 #define WS " \t\r\n" 88 89 struct didpfs *FirstPFS; 90 91 void 92 hammer_cmd_cleanup(char **av, int ac) 93 { 94 FILE *fp; 95 char *fs, *ptr, *path; 96 char buf[256]; 97 98 tzset(); 99 if (ac == 0) { 100 fp = popen("/sbin/mount -t hammer,null", "r"); 101 if (fp == NULL) 102 errx(1, "hammer cleanup: 'mount' failed"); 103 while (fgets(buf, sizeof(buf), fp) != NULL) { 104 fs = strtok(buf, WS); 105 if (fs == NULL) 106 continue; 107 ptr = strtok(NULL, WS); 108 if (ptr == NULL) 109 continue; 110 path = strtok(NULL, WS); 111 if (path == NULL) 112 continue; 113 ptr = strtok(NULL, WS); 114 if (ptr == NULL) 115 continue; 116 if ((strncmp(ptr, "(hammer,", 8) == 0) || 117 ((strncmp(ptr, "(null,", 6) == 0) && 118 (strncmp(fs, "/pfs", 4) == 0))) 119 do_cleanup(path); 120 } 121 fclose(fp); 122 } else { 123 while (ac) { 124 do_cleanup(*av); 125 --ac; 126 ++av; 127 } 128 } 129 } 130 131 static 132 void 133 do_cleanup(const char *path) 134 { 135 struct hammer_ioc_pseudofs_rw pfs; 136 union hammer_ioc_mrecord_any mrec_tmp; 137 char *snapshots_path; 138 char *config_path; 139 struct stat st; 140 char *cmd; 141 char *ptr; 142 int arg1; 143 int arg2; 144 char *arg3; 145 time_t savet; 146 char buf[256]; 147 FILE *fp; 148 struct didpfs *didpfs; 149 int snapshots_disabled = 0; 150 int prune_warning = 0; 151 int fd; 152 int r; 153 154 bzero(&pfs, sizeof(pfs)); 155 bzero(&mrec_tmp, sizeof(mrec_tmp)); 156 pfs.ondisk = &mrec_tmp.pfs.pfsd; 157 pfs.bytes = sizeof(mrec_tmp.pfs.pfsd); 158 pfs.pfs_id = -1; 159 160 printf("cleanup %-20s -", path); 161 fd = open(path, O_RDONLY); 162 if (fd < 0) { 163 printf(" unable to access directory: %s\n", strerror(errno)); 164 return; 165 } 166 if (ioctl(fd, HAMMERIOC_GET_PSEUDOFS, &pfs) != 0) { 167 printf(" not a HAMMER filesystem: %s\n", strerror(errno)); 168 return; 169 } 170 close(fd); 171 if (pfs.version != HAMMER_IOC_PSEUDOFS_VERSION) { 172 printf(" unrecognized HAMMER version\n"); 173 return; 174 } 175 176 /* 177 * Make sure we have not already handled this PFS. Several nullfs 178 * mounts might alias the same PFS. 179 */ 180 for (didpfs = FirstPFS; didpfs; didpfs = didpfs->next) { 181 if (bcmp(&didpfs->uuid, &mrec_tmp.pfs.pfsd.unique_uuid, sizeof(uuid_t)) == 0) { 182 printf(" PFS #%d already handled\n", pfs.pfs_id); 183 return; 184 } 185 } 186 didpfs = malloc(sizeof(*didpfs)); 187 didpfs->next = FirstPFS; 188 FirstPFS = didpfs; 189 didpfs->uuid = mrec_tmp.pfs.pfsd.unique_uuid; 190 191 /* 192 * Figure out where the snapshot directory is. 193 */ 194 if (mrec_tmp.pfs.pfsd.snapshots[0] == '/') { 195 asprintf(&snapshots_path, "%s", mrec_tmp.pfs.pfsd.snapshots); 196 } else if (mrec_tmp.pfs.pfsd.snapshots[0]) { 197 printf(" WARNING: pfs-slave's snapshots dir is not absolute\n"); 198 return; 199 } else if (mrec_tmp.pfs.pfsd.mirror_flags & HAMMER_PFSD_SLAVE) { 200 printf(" WARNING: must configure snapshot dir for PFS slave\n"); 201 printf("\tWe suggest <fs>/var/slaves/<name> where " 202 "<fs> is the base HAMMER fs\n"); 203 printf("\tcontaining the slave\n"); 204 return; 205 } else { 206 asprintf(&snapshots_path, 207 "%s%ssnapshots", path, dividing_slash(path)); 208 } 209 210 /* 211 * Create a snapshot directory if necessary, and a config file if 212 * necessary. 213 */ 214 if (stat(snapshots_path, &st) < 0) { 215 if (mkdir(snapshots_path, 0755) != 0) { 216 free(snapshots_path); 217 printf(" unable to create snapshot dir \"%s\": %s\n", 218 snapshots_path, strerror(errno)); 219 return; 220 } 221 } 222 asprintf(&config_path, "%s/config", snapshots_path); 223 if ((fp = fopen(config_path, "r")) == NULL) { 224 fp = fopen(config_path, "w"); 225 if (fp == NULL) { 226 printf(" cannot create %s: %s\n", 227 config_path, strerror(errno)); 228 return; 229 } 230 if (strcmp(path, "/tmp") == 0 || 231 strcmp(path, "/var/tmp") == 0 || 232 strcmp(path, "/usr/obj") == 0) { 233 fprintf(fp, "snapshots 0d 0d\n"); 234 } else { 235 fprintf(fp, "snapshots 1d 60d\n"); 236 } 237 fprintf(fp, 238 "prune 1d 5m\n" 239 "reblock 1d 5m\n" 240 "recopy 30d 10m\n"); 241 fclose(fp); 242 fp = fopen(config_path, "r"); 243 } 244 if (fp == NULL) { 245 printf(" cannot access %s: %s\n", 246 config_path, strerror(errno)); 247 return; 248 } 249 250 printf(" handle PFS #%d using %s\n", pfs.pfs_id, snapshots_path); 251 252 /* 253 * Process the config file 254 */ 255 while (fgets(buf, sizeof(buf), fp) != NULL) { 256 cmd = strtok(buf, WS); 257 arg1 = 0; 258 arg2 = 0; 259 arg3 = NULL; 260 if ((ptr = strtok(NULL, WS)) != NULL) { 261 arg1 = strtosecs(ptr); 262 if ((ptr = strtok(NULL, WS)) != NULL) { 263 arg2 = strtosecs(ptr); 264 arg3 = strtok(NULL, WS); 265 } 266 } 267 268 printf("%20s - ", cmd); 269 fflush(stdout); 270 271 r = 1; 272 if (strcmp(cmd, "snapshots") == 0) { 273 if (arg1 == 0) { 274 if (arg2 && check_softlinks(snapshots_path)) { 275 printf("only removing old snapshots\n"); 276 prune_warning = 1; 277 cleanup_softlinks(path, snapshots_path, 278 arg2, arg3); 279 } else { 280 printf("disabled\n"); 281 snapshots_disabled = 1; 282 } 283 } else 284 if (check_period(snapshots_path, cmd, arg1, &savet)) { 285 printf("run\n"); 286 cleanup_softlinks(path, snapshots_path, 287 arg2, arg3); 288 r = cleanup_snapshots(path, snapshots_path, 289 arg1, arg2); 290 } else { 291 printf("skip\n"); 292 } 293 } else if (arg1 == 0) { 294 /* 295 * The commands following this check can't handle 296 * a period of 0, so call the feature disabled and 297 * ignore the directive. 298 */ 299 printf("disabled\n"); 300 } else if (strcmp(cmd, "prune") == 0) { 301 if (check_period(snapshots_path, cmd, arg1, &savet)) { 302 if (prune_warning) { 303 printf("run - WARNING snapshot " 304 "softlinks present " 305 "but snapshots disabled\n"); 306 } else { 307 printf("run\n"); 308 } 309 r = cleanup_prune(path, snapshots_path, 310 arg1, arg2, snapshots_disabled); 311 } else { 312 printf("skip\n"); 313 } 314 } else if (strcmp(cmd, "reblock") == 0) { 315 if (check_period(snapshots_path, cmd, arg1, &savet)) { 316 printf("run"); 317 fflush(stdout); 318 if (VerboseOpt) 319 printf("\n"); 320 r = cleanup_reblock(path, snapshots_path, 321 arg1, arg2); 322 } else { 323 printf("skip\n"); 324 } 325 } else if (strcmp(cmd, "recopy") == 0) { 326 if (check_period(snapshots_path, cmd, arg1, &savet)) { 327 printf("run"); 328 fflush(stdout); 329 if (VerboseOpt) 330 printf("\n"); 331 r = cleanup_recopy(path, snapshots_path, 332 arg1, arg2); 333 } else { 334 printf("skip\n"); 335 } 336 } else { 337 printf("unknown directive\n"); 338 r = 1; 339 } 340 if (r == 0) 341 save_period(snapshots_path, cmd, savet); 342 } 343 fclose(fp); 344 usleep(1000); 345 } 346 347 static 348 int 349 strtosecs(char *ptr) 350 { 351 int val; 352 353 val = strtol(ptr, &ptr, 0); 354 switch(*ptr) { 355 case 'd': 356 val *= 24; 357 /* fall through */ 358 case 'h': 359 val *= 60; 360 /* fall through */ 361 case 'm': 362 val *= 60; 363 /* fall through */ 364 case 's': 365 break; 366 default: 367 errx(1, "illegal suffix converting %s\n", ptr); 368 break; 369 } 370 return(val); 371 } 372 373 static const char * 374 dividing_slash(const char *path) 375 { 376 int len = strlen(path); 377 if (len && path[len-1] == '/') 378 return(""); 379 else 380 return("/"); 381 } 382 383 /* 384 * Check whether the desired period has elapsed since the last successful 385 * run. The run may take a while and cross a boundary so we remember the 386 * current time_t so we can save it later on. 387 * 388 * Periods in minutes, hours, or days are assumed to have been crossed 389 * if the local time crosses a minute, hour, or day boundary regardless 390 * of how close the last operation actually was. 391 */ 392 static int 393 check_period(const char *snapshots_path, const char *cmd, int arg1, 394 time_t *savep) 395 { 396 char *check_path; 397 struct tm tp1; 398 struct tm tp2; 399 FILE *fp; 400 time_t baset, lastt; 401 char buf[256]; 402 403 time(savep); 404 localtime_r(savep, &tp1); 405 406 /* 407 * Retrieve the start time of the last successful operation. 408 */ 409 asprintf(&check_path, "%s/.%s.period", snapshots_path, cmd); 410 fp = fopen(check_path, "r"); 411 free(check_path); 412 if (fp == NULL) 413 return(1); 414 if (fgets(buf, sizeof(buf), fp) == NULL) { 415 fclose(fp); 416 return(1); 417 } 418 fclose(fp); 419 420 lastt = strtol(buf, NULL, 0); 421 localtime_r(&lastt, &tp2); 422 423 /* 424 * Normalize the times. e.g. if asked to do something on a 1-day 425 * interval the operation will be performed as soon as the day 426 * turns over relative to the previous operation, even if the previous 427 * operation ran a few seconds ago just before midnight. 428 */ 429 if (arg1 % 60 == 0) { 430 tp1.tm_sec = 0; 431 tp2.tm_sec = 0; 432 } 433 if (arg1 % (60 * 60) == 0) { 434 tp1.tm_min = 0; 435 tp2.tm_min = 0; 436 } 437 if (arg1 % (24 * 60 * 60) == 0) { 438 tp1.tm_hour = 0; 439 tp2.tm_hour = 0; 440 } 441 442 baset = mktime(&tp1); 443 lastt = mktime(&tp2); 444 445 #if 0 446 printf("%lld vs %lld\n", (long long)(baset - lastt), (long long)arg1); 447 #endif 448 449 if ((int)(baset - lastt) >= arg1) 450 return(1); 451 return(0); 452 } 453 454 /* 455 * Store the start time of the last successful operation. 456 */ 457 static void 458 save_period(const char *snapshots_path, const char *cmd, 459 time_t savet) 460 { 461 char *ocheck_path; 462 char *ncheck_path; 463 FILE *fp; 464 465 asprintf(&ocheck_path, "%s/.%s.period", snapshots_path, cmd); 466 asprintf(&ncheck_path, "%s/.%s.period.new", snapshots_path, cmd); 467 fp = fopen(ncheck_path, "w"); 468 fprintf(fp, "0x%08llx\n", (long long)savet); 469 if (fclose(fp) == 0) 470 rename(ncheck_path, ocheck_path); 471 remove(ncheck_path); 472 } 473 474 /* 475 * Simply count the number of softlinks in the snapshots dir 476 */ 477 static int 478 check_softlinks(const char *snapshots_path) 479 { 480 struct dirent *den; 481 struct stat st; 482 DIR *dir; 483 char *fpath; 484 int res = 0; 485 486 if ((dir = opendir(snapshots_path)) != NULL) { 487 while ((den = readdir(dir)) != NULL) { 488 if (den->d_name[0] == '.') 489 continue; 490 asprintf(&fpath, "%s/%s", snapshots_path, den->d_name); 491 if (lstat(fpath, &st) == 0 && S_ISLNK(st.st_mode)) 492 ++res; 493 free(fpath); 494 } 495 closedir(dir); 496 } 497 return(res); 498 } 499 500 /* 501 * Clean up expired softlinks in the snapshots dir 502 */ 503 static void 504 cleanup_softlinks(const char *path __unused, const char *snapshots_path, 505 int arg2, char *arg3) 506 { 507 struct dirent *den; 508 struct stat st; 509 DIR *dir; 510 char *fpath; 511 int anylink = 0; 512 513 if (arg3 != NULL && strstr(arg3, "any") != NULL) 514 anylink = 1; 515 516 if ((dir = opendir(snapshots_path)) != NULL) { 517 while ((den = readdir(dir)) != NULL) { 518 if (den->d_name[0] == '.') 519 continue; 520 asprintf(&fpath, "%s/%s", snapshots_path, den->d_name); 521 if (lstat(fpath, &st) == 0 && S_ISLNK(st.st_mode) && 522 (anylink || strncmp(den->d_name, "snap-", 5) == 0) 523 ) { 524 if (check_expired(den->d_name, arg2)) { 525 if (VerboseOpt) { 526 printf(" expire %s\n", 527 fpath); 528 } 529 remove(fpath); 530 } 531 } 532 free(fpath); 533 } 534 closedir(dir); 535 } 536 } 537 538 /* 539 * Take a softlink path in the form snap-yyyymmdd-hhmm and the 540 * expiration in seconds (arg2) and return non-zero if the softlink 541 * has expired. 542 */ 543 static int 544 check_expired(const char *fpath, int arg2) 545 { 546 struct tm tm; 547 time_t t; 548 int year; 549 int month; 550 int day = 0; 551 int hour = 0; 552 int minute = 0; 553 int r; 554 555 while (*fpath && *fpath != '-' && *fpath != '.') 556 ++fpath; 557 if (*fpath) 558 ++fpath; 559 560 r = sscanf(fpath, "%4d%2d%2d-%2d%2d", 561 &year, &month, &day, &hour, &minute); 562 563 if (r >= 3) { 564 bzero(&tm, sizeof(tm)); 565 tm.tm_isdst = -1; 566 tm.tm_min = minute; 567 tm.tm_hour = hour; 568 tm.tm_mday = day; 569 tm.tm_mon = month - 1; 570 tm.tm_year = year - 1900; 571 t = mktime(&tm); 572 if (t == (time_t)-1) 573 return(0); 574 t = time(NULL) - t; 575 if ((int)t > arg2) 576 return(1); 577 } 578 return(0); 579 } 580 581 /* 582 * Issue a snapshot. 583 */ 584 static int 585 cleanup_snapshots(const char *path __unused, const char *snapshots_path, 586 int arg1 __unused, int arg2 __unused) 587 { 588 int r; 589 590 runcmd(&r, "hammer snapshot %s %s", path, snapshots_path); 591 return(r); 592 } 593 594 static int 595 cleanup_prune(const char *path __unused, const char *snapshots_path, 596 int arg1 __unused, int arg2, int snapshots_disabled) 597 { 598 /* 599 * If snapshots have been disabled run prune-everything instead 600 * of prune. 601 */ 602 if (snapshots_disabled && arg2) { 603 runcmd(NULL, "hammer -c %s/.prune.cycle -t %d prune-everything %s", 604 snapshots_path, arg2, path); 605 } else if (snapshots_disabled) { 606 runcmd(NULL, "hammer prune-everything %s", path); 607 } else if (arg2) { 608 runcmd(NULL, "hammer -c %s/.prune.cycle -t %d prune %s", 609 snapshots_path, arg2, snapshots_path); 610 } else { 611 runcmd(NULL, "hammer prune %s", snapshots_path); 612 } 613 return(0); 614 } 615 616 static int 617 cleanup_reblock(const char *path, const char *snapshots_path, 618 int arg1 __unused, int arg2) 619 { 620 if (VerboseOpt == 0) { 621 printf("."); 622 fflush(stdout); 623 } 624 runcmd(NULL, 625 "hammer -c %s/.reblock-1.cycle -t %d reblock-btree %s 95", 626 snapshots_path, arg2, path); 627 if (VerboseOpt == 0) { 628 printf("."); 629 fflush(stdout); 630 } 631 runcmd(NULL, 632 "hammer -c %s/.reblock-2.cycle -t %d reblock-inodes %s 95", 633 snapshots_path, arg2, path); 634 if (VerboseOpt == 0) { 635 printf("."); 636 fflush(stdout); 637 } 638 runcmd(NULL, 639 "hammer -c %s/.reblock-3.cycle -t %d reblock-data %s 95", 640 snapshots_path, arg2, path); 641 if (VerboseOpt == 0) 642 printf("\n"); 643 return(0); 644 } 645 646 static int 647 cleanup_recopy(const char *path, const char *snapshots_path, 648 int arg1 __unused, int arg2) 649 { 650 if (VerboseOpt == 0) { 651 printf("."); 652 fflush(stdout); 653 } 654 runcmd(NULL, 655 "hammer -c %s/.recopy-1.cycle -t %d reblock-btree %s", 656 snapshots_path, arg2, path); 657 if (VerboseOpt == 0) { 658 printf("."); 659 fflush(stdout); 660 } 661 runcmd(NULL, 662 "hammer -c %s/.recopy-2.cycle -t %d reblock-inodes %s", 663 snapshots_path, arg2, path); 664 if (VerboseOpt == 0) { 665 printf("."); 666 fflush(stdout); 667 } 668 runcmd(NULL, 669 "hammer -c %s/.recopy-3.cycle -t %d reblock-data %s", 670 snapshots_path, arg2, path); 671 if (VerboseOpt == 0) 672 printf("\n"); 673 return(0); 674 } 675 676 static 677 void 678 runcmd(int *resp, const char *ctl, ...) 679 { 680 va_list va; 681 char *cmd; 682 char *arg; 683 char **av; 684 int n; 685 int nmax; 686 int res; 687 pid_t pid; 688 689 /* 690 * Generate the command 691 */ 692 va_start(va, ctl); 693 vasprintf(&cmd, ctl, va); 694 va_end(va); 695 if (VerboseOpt) 696 printf(" %s\n", cmd); 697 698 /* 699 * Break us down into arguments. We do not just use system() here 700 * because it blocks SIGINT and friends. 701 */ 702 n = 0; 703 nmax = 16; 704 av = malloc(sizeof(char *) * nmax); 705 706 for (arg = strtok(cmd, WS); arg; arg = strtok(NULL, WS)) { 707 if (n == nmax - 1) { 708 nmax += 16; 709 av = realloc(av, sizeof(char *) * nmax); 710 } 711 av[n++] = arg; 712 } 713 av[n++] = NULL; 714 715 /* 716 * Run the command. 717 */ 718 RunningIoctl = 1; 719 if ((pid = fork()) == 0) { 720 if (VerboseOpt < 2) { 721 int fd = open("/dev/null", O_RDWR); 722 dup2(fd, 1); 723 close(fd); 724 } 725 execvp(av[0], av); 726 _exit(127); 727 } else if (pid < 0) { 728 res = 127; 729 } else { 730 int status; 731 732 while (waitpid(pid, &status, 0) != pid) 733 ; 734 res = WEXITSTATUS(status); 735 } 736 RunningIoctl = 0; 737 if (DidInterrupt) 738 _exit(1); 739 740 free(cmd); 741 free(av); 742 if (resp) 743 *resp = res; 744 } 745 746 747