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