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 specific HAMMER filesystems or all HAMMER filesystems. 38 * 39 * If no filesystems are specified any HAMMER- or null-mounted hammer PFS's 40 * are cleaned. 41 * 42 * Each HAMMER filesystem may contain a configuration file. If no 43 * configuration file is present one will be created with the following 44 * defaults: 45 * 46 * snapshots 1d 60d (0d 0d for /tmp, /var/tmp, /usr/obj) 47 * prune 1d 5m 48 * rebalance 1d 5m 49 * #dedup 1d 5m (not enabled by default) 50 * reblock 1d 5m 51 * recopy 30d 10m 52 * 53 * All hammer commands create and maintain cycle files in the snapshots 54 * directory. 55 * 56 * For HAMMER version 2- the configuration file is a named 'config' in 57 * the snapshots directory, which defaults to <pfs>/snapshots. 58 * For HAMMER version 3+ the configuration file is saved in filesystem 59 * meta-data. The snapshots directory defaults to /var/hammer/<pfs> 60 * (/var/hammer/root for root mount). 61 */ 62 63 #include "hammer.h" 64 65 struct didpfs { 66 struct didpfs *next; 67 uuid_t uuid; 68 }; 69 70 static void do_cleanup(const char *path); 71 static void config_init(const char *path, struct hammer_ioc_config *config); 72 static void migrate_config(FILE *fp, struct hammer_ioc_config *config); 73 static void migrate_snapshots(int fd, const char *snapshots_path); 74 static void migrate_one_snapshot(int fd, const char *fpath, 75 struct hammer_ioc_snapshot *snapshot); 76 static int strtosecs(char *ptr); 77 static const char *dividing_slash(const char *path); 78 static int check_period(const char *snapshots_path, const char *cmd, int arg1, 79 time_t *savep); 80 static void save_period(const char *snapshots_path, const char *cmd, 81 time_t savet); 82 static int check_softlinks(int fd, int new_config, const char *snapshots_path); 83 static void cleanup_softlinks(int fd, int new_config, 84 const char *snapshots_path, int arg2, char *arg3); 85 static void delete_snapshots(int fd, struct hammer_ioc_snapshot *dsnapshot); 86 static int check_expired(const char *fpath, int arg2); 87 88 static int create_snapshot(const char *path, const char *snapshots_path); 89 static int cleanup_rebalance(const char *path, const char *snapshots_path, 90 int arg1, int arg2); 91 static int cleanup_prune(const char *path, const char *snapshots_path, 92 int arg1, int arg2, int snapshots_disabled); 93 static int cleanup_reblock(const char *path, const char *snapshots_path, 94 int arg1, int arg2); 95 static int cleanup_recopy(const char *path, const char *snapshots_path, 96 int arg1, int arg2); 97 static int cleanup_dedup(const char *path, const char *snapshots_path, 98 int arg1, int arg2); 99 100 static void runcmd(int *resp, const char *ctl, ...) __printflike(2, 3); 101 102 /* 103 * WARNING: Do not make the SNAPSHOTS_BASE "/var/snapshots" because 104 * it will interfere with the older HAMMER VERS < 3 snapshots directory 105 * for the /var PFS. 106 */ 107 #define SNAPSHOTS_BASE "/var/hammer" /* HAMMER VERS >= 3 */ 108 #define WS " \t\r\n" 109 110 struct didpfs *FirstPFS; 111 112 void 113 hammer_cmd_cleanup(char **av, int ac) 114 { 115 char *fstype, *fs, *path; 116 struct statfs *stfsbuf; 117 int mntsize, i; 118 119 tzset(); 120 if (ac == 0) { 121 mntsize = getmntinfo(&stfsbuf, MNT_NOWAIT); 122 if (mntsize > 0) { 123 for (i=0; i < mntsize; i++) { 124 /* 125 * We will cleanup in the case fstype is hammer. 126 * If we have null-mounted PFS, we check the 127 * mount source. If it looks like a PFS, we 128 * proceed to cleanup also. 129 */ 130 fstype = stfsbuf[i].f_fstypename; 131 fs = stfsbuf[i].f_mntfromname; 132 if ((strcmp(fstype, "hammer") == 0) || 133 ((strcmp(fstype, "null") == 0) && 134 (strstr(fs, "/@@0x") != NULL || 135 strstr(fs, "/@@-1") != NULL))) { 136 path = stfsbuf[i].f_mntonname; 137 do_cleanup(path); 138 } 139 } 140 } 141 142 } else { 143 while (ac) { 144 do_cleanup(*av); 145 --ac; 146 ++av; 147 } 148 } 149 } 150 151 static 152 void 153 do_cleanup(const char *path) 154 { 155 struct hammer_ioc_pseudofs_rw pfs; 156 struct hammer_ioc_config config; 157 struct hammer_ioc_version version; 158 union hammer_ioc_mrecord_any mrec_tmp; 159 char *snapshots_path = NULL; 160 char *config_path; 161 struct stat st; 162 char *cmd; 163 char *ptr; 164 int arg1; 165 int arg2; 166 char *arg3; 167 time_t savet; 168 char buf[256]; 169 char *cbase; 170 char *cptr; 171 FILE *fp = NULL; 172 struct didpfs *didpfs; 173 int snapshots_disabled = 0; 174 int prune_warning = 0; 175 int new_config = 0; 176 int snapshots_from_pfs = 0; 177 int fd; 178 int r; 179 int found_rebal = 0; 180 181 bzero(&pfs, sizeof(pfs)); 182 bzero(&mrec_tmp, sizeof(mrec_tmp)); 183 pfs.ondisk = &mrec_tmp.pfs.pfsd; 184 pfs.bytes = sizeof(mrec_tmp.pfs.pfsd); 185 pfs.pfs_id = -1; 186 187 printf("cleanup %-20s -", path); 188 fd = open(path, O_RDONLY); 189 if (fd < 0) { 190 printf(" unable to access directory: %s\n", strerror(errno)); 191 return; 192 } 193 if (ioctl(fd, HAMMERIOC_GET_PSEUDOFS, &pfs) < 0) { 194 printf(" not a HAMMER filesystem: %s\n", strerror(errno)); 195 close(fd); 196 return; 197 } 198 if (pfs.version != HAMMER_IOC_PSEUDOFS_VERSION) { 199 printf(" unrecognized HAMMER version\n"); 200 close(fd); 201 return; 202 } 203 bzero(&version, sizeof(version)); 204 if (ioctl(fd, HAMMERIOC_GET_VERSION, &version) < 0) { 205 printf(" HAMMER filesystem but couldn't retrieve version!\n"); 206 close(fd); 207 return; 208 } 209 210 bzero(&config, sizeof(config)); 211 if (version.cur_version >= 3) { 212 if (ioctl(fd, HAMMERIOC_GET_CONFIG, &config) == 0 && 213 config.head.error == 0) { 214 new_config = 1; 215 } 216 } 217 218 /* 219 * Make sure we have not already handled this PFS. Several nullfs 220 * mounts might alias the same PFS. 221 */ 222 for (didpfs = FirstPFS; didpfs; didpfs = didpfs->next) { 223 if (bcmp(&didpfs->uuid, &mrec_tmp.pfs.pfsd.unique_uuid, sizeof(uuid_t)) == 0) { 224 printf(" PFS #%d already handled\n", pfs.pfs_id); 225 close(fd); 226 return; 227 } 228 } 229 didpfs = malloc(sizeof(*didpfs)); 230 didpfs->next = FirstPFS; 231 FirstPFS = didpfs; 232 didpfs->uuid = mrec_tmp.pfs.pfsd.unique_uuid; 233 234 /* 235 * Calculate the old snapshots directory for HAMMER VERSION < 3 236 * 237 * If the directory is explicitly specified in the PFS config 238 * we flag it and will not migrate it later. 239 */ 240 if (mrec_tmp.pfs.pfsd.snapshots[0] == '/') { 241 asprintf(&snapshots_path, "%s", mrec_tmp.pfs.pfsd.snapshots); 242 snapshots_from_pfs = 1; 243 } else if (mrec_tmp.pfs.pfsd.snapshots[0]) { 244 printf(" WARNING: pfs-slave's snapshots dir is not absolute\n"); 245 close(fd); 246 return; 247 } else if (mrec_tmp.pfs.pfsd.mirror_flags & HAMMER_PFSD_SLAVE) { 248 if (version.cur_version < 3) { 249 printf(" WARNING: must configure snapshot dir for PFS slave\n"); 250 printf("\tWe suggest <fs>/var/slaves/<name> where " 251 "<fs> is the base HAMMER fs\n"); 252 printf("\tcontaining the slave\n"); 253 close(fd); 254 return; 255 } 256 } else { 257 asprintf(&snapshots_path, 258 "%s%ssnapshots", path, dividing_slash(path)); 259 } 260 261 /* 262 * Check for old-style config file 263 */ 264 if (snapshots_path) { 265 asprintf(&config_path, "%s/config", snapshots_path); 266 fp = fopen(config_path, "r"); 267 } 268 269 /* 270 * Handle upgrades to hammer version 3, move the config 271 * file into meta-data. 272 * 273 * For the old config read the file into the config structure, 274 * we will parse it out of the config structure regardless. 275 */ 276 if (version.cur_version >= 3) { 277 if (fp) { 278 printf("(migrating) "); 279 fflush(stdout); 280 migrate_config(fp, &config); 281 migrate_snapshots(fd, snapshots_path); 282 fclose(fp); 283 if (ioctl(fd, HAMMERIOC_SET_CONFIG, &config) < 0) { 284 printf(" cannot init meta-data config!\n"); 285 close(fd); 286 return; 287 } 288 remove(config_path); 289 } else if (new_config == 0) { 290 config_init(path, &config); 291 if (ioctl(fd, HAMMERIOC_SET_CONFIG, &config) < 0) { 292 printf(" cannot init meta-data config!\n"); 293 close(fd); 294 return; 295 } 296 } 297 new_config = 1; 298 } else { 299 /* 300 * Create missing snapshots directory for HAMMER VERSION < 3 301 */ 302 if (stat(snapshots_path, &st) < 0) { 303 if (mkdir(snapshots_path, 0755) != 0) { 304 free(snapshots_path); 305 printf(" unable to create snapshot dir \"%s\": %s\n", 306 snapshots_path, strerror(errno)); 307 close(fd); 308 return; 309 } 310 } 311 312 /* 313 * Create missing config file for HAMMER VERSION < 3 314 */ 315 if (fp == NULL) { 316 config_init(path, &config); 317 fp = fopen(config_path, "w"); 318 if (fp) { 319 fwrite(config.config.text, 1, 320 strlen(config.config.text), fp); 321 fclose(fp); 322 } 323 } else { 324 migrate_config(fp, &config); 325 fclose(fp); 326 } 327 } 328 329 /* 330 * If snapshots_from_pfs is not set we calculate the new snapshots 331 * directory default (in /var) for HAMMER VERSION >= 3 and migrate 332 * the old snapshots directory over. 333 * 334 * People who have set an explicit snapshots directory will have 335 * to migrate the data manually into /var/hammer, or not bother at 336 * all. People running slaves may wish to migrate it and then 337 * clear the snapshots specification in the PFS config for the 338 * slave. 339 */ 340 if (new_config && snapshots_from_pfs == 0) { 341 char *npath; 342 343 assert(path[0] == '/'); 344 if (strcmp(path, "/") == 0) 345 asprintf(&npath, "%s/root", SNAPSHOTS_BASE); 346 else 347 asprintf(&npath, "%s/%s", SNAPSHOTS_BASE, path + 1); 348 if (snapshots_path) { 349 if (stat(npath, &st) < 0 && errno == ENOENT) { 350 if (stat(snapshots_path, &st) < 0 && errno == ENOENT) { 351 printf(" HAMMER UPGRADE: Creating snapshots\n" 352 "\tCreating snapshots in %s\n", 353 npath); 354 runcmd(&r, "mkdir -p %s", npath); 355 } else { 356 printf(" HAMMER UPGRADE: Moving snapshots\n" 357 "\tMoving snapshots from %s to %s\n", 358 snapshots_path, npath); 359 runcmd(&r, "mkdir -p %s", npath); 360 runcmd(&r, "cpdup %s %s", snapshots_path, npath); 361 if (r != 0) { 362 printf("Unable to move snapshots directory!\n"); 363 printf("Please fix this critical error.\n"); 364 printf("Aborting cleanup of %s\n", path); 365 close(fd); 366 return; 367 } 368 runcmd(&r, "rm -rf %s", snapshots_path); 369 } 370 } 371 free(snapshots_path); 372 } else if (stat(npath, &st) < 0 && errno == ENOENT) { 373 runcmd(&r, "mkdir -p %s", npath); 374 } 375 snapshots_path = npath; 376 } 377 378 /* 379 * Lock the PFS. fd is the base directory of the mounted PFS. 380 */ 381 if (flock(fd, LOCK_EX|LOCK_NB) == -1) { 382 if (errno == EWOULDBLOCK) 383 printf(" PFS #%d locked by other process\n", pfs.pfs_id); 384 else 385 printf(" can not lock %s: %s\n", config_path, strerror(errno)); 386 close(fd); 387 return; 388 } 389 390 printf(" handle PFS #%d using %s\n", pfs.pfs_id, snapshots_path); 391 392 /* 393 * Process the config file 394 */ 395 cbase = config.config.text; 396 397 while ((cptr = strchr(cbase, '\n')) != NULL) { 398 bcopy(cbase, buf, cptr - cbase); 399 buf[cptr - cbase] = 0; 400 cbase = cptr + 1; 401 402 cmd = strtok(buf, WS); 403 if (cmd == NULL || cmd[0] == '#') 404 continue; 405 406 arg1 = 0; 407 arg2 = 0; 408 arg3 = NULL; 409 if ((ptr = strtok(NULL, WS)) != NULL) { 410 arg1 = strtosecs(ptr); 411 if ((ptr = strtok(NULL, WS)) != NULL) { 412 arg2 = strtosecs(ptr); 413 arg3 = strtok(NULL, WS); 414 } 415 } 416 417 printf("%20s - ", cmd); 418 fflush(stdout); 419 420 r = 1; 421 if (strcmp(cmd, "snapshots") == 0) { 422 if (arg1 == 0) { 423 if (arg2 && 424 check_softlinks(fd, new_config, 425 snapshots_path)) { 426 printf("only removing old snapshots\n"); 427 prune_warning = 1; 428 cleanup_softlinks(fd, new_config, 429 snapshots_path, 430 arg2, arg3); 431 } else { 432 printf("disabled\n"); 433 snapshots_disabled = 1; 434 } 435 } else 436 if (check_period(snapshots_path, cmd, arg1, &savet)) { 437 printf("run\n"); 438 cleanup_softlinks(fd, new_config, 439 snapshots_path, 440 arg2, arg3); 441 r = create_snapshot(path, snapshots_path); 442 } else { 443 printf("skip\n"); 444 } 445 } else if (arg1 == 0) { 446 /* 447 * The commands following this check can't handle 448 * a period of 0, so call the feature disabled and 449 * ignore the directive. 450 */ 451 printf("disabled\n"); 452 } else if (strcmp(cmd, "prune") == 0) { 453 if (check_period(snapshots_path, cmd, arg1, &savet)) { 454 if (prune_warning) { 455 printf("run - WARNING snapshot " 456 "softlinks present " 457 "but snapshots disabled\n"); 458 } else { 459 printf("run\n"); 460 } 461 r = cleanup_prune(path, snapshots_path, 462 arg1, arg2, snapshots_disabled); 463 } else { 464 printf("skip\n"); 465 } 466 } else if (strcmp(cmd, "rebalance") == 0) { 467 found_rebal = 1; 468 if (check_period(snapshots_path, cmd, arg1, &savet)) { 469 printf("run"); 470 fflush(stdout); 471 if (VerboseOpt) 472 printf("\n"); 473 r = cleanup_rebalance(path, snapshots_path, 474 arg1, arg2); 475 } else { 476 printf("skip\n"); 477 } 478 } else if (strcmp(cmd, "reblock") == 0) { 479 if (check_period(snapshots_path, cmd, arg1, &savet)) { 480 printf("run"); 481 fflush(stdout); 482 if (VerboseOpt) 483 printf("\n"); 484 r = cleanup_reblock(path, snapshots_path, 485 arg1, arg2); 486 } else { 487 printf("skip\n"); 488 } 489 } else if (strcmp(cmd, "recopy") == 0) { 490 if (check_period(snapshots_path, cmd, arg1, &savet)) { 491 printf("run"); 492 fflush(stdout); 493 if (VerboseOpt) 494 printf("\n"); 495 r = cleanup_recopy(path, snapshots_path, 496 arg1, arg2); 497 } else { 498 printf("skip\n"); 499 } 500 } else if (strcmp(cmd, "dedup") == 0) { 501 if (check_period(snapshots_path, cmd, arg1, &savet)) { 502 printf("run"); 503 fflush(stdout); 504 if (VerboseOpt) 505 printf("\n"); 506 r = cleanup_dedup(path, snapshots_path, 507 arg1, arg2); 508 } else { 509 printf("skip\n"); 510 } 511 } else { 512 printf("unknown directive\n"); 513 r = 1; 514 } 515 if (r == 0) 516 save_period(snapshots_path, cmd, savet); 517 } 518 519 /* 520 * Add new rebalance feature if the config doesn't have it. 521 * (old style config only). 522 */ 523 if (new_config == 0 && found_rebal == 0) { 524 if ((fp = fopen(config_path, "r+")) != NULL) { 525 fseek(fp, 0L, 2); 526 fprintf(fp, "rebalance 1d 5m\n"); 527 fclose(fp); 528 } 529 } 530 531 /* 532 * Cleanup, and delay a little 533 */ 534 close(fd); 535 usleep(1000); 536 } 537 538 /* 539 * Initialize new config data (new or old style) 540 */ 541 static void 542 config_init(const char *path, struct hammer_ioc_config *config) 543 { 544 const char *snapshots; 545 546 if (strcmp(path, "/tmp") == 0 || 547 strcmp(path, "/var/tmp") == 0 || 548 strcmp(path, "/usr/obj") == 0) { 549 snapshots = "snapshots 0d 0d\n"; 550 } else { 551 snapshots = "snapshots 1d 60d\n"; 552 } 553 bzero(config->config.text, sizeof(config->config.text)); 554 snprintf(config->config.text, sizeof(config->config.text) - 1, "%s%s", 555 snapshots, 556 "prune 1d 5m\n" 557 "rebalance 1d 5m\n" 558 "#dedup 1d 5m\n" 559 "reblock 1d 5m\n" 560 "recopy 30d 10m\n"); 561 } 562 563 /* 564 * Migrate configuration data from the old snapshots/config 565 * file to the new meta-data format. 566 */ 567 static void 568 migrate_config(FILE *fp, struct hammer_ioc_config *config) 569 { 570 int n; 571 572 n = fread(config->config.text, 1, sizeof(config->config.text) - 1, fp); 573 if (n >= 0) 574 bzero(config->config.text + n, sizeof(config->config.text) - n); 575 } 576 577 /* 578 * Migrate snapshot softlinks in the snapshots directory to the 579 * new meta-data format. The softlinks are left intact, but 580 * this way the pruning code won't lose track of them if you 581 * happen to blow away the snapshots directory. 582 */ 583 static void 584 migrate_snapshots(int fd, const char *snapshots_path) 585 { 586 struct hammer_ioc_snapshot snapshot; 587 struct dirent *den; 588 struct stat st; 589 DIR *dir; 590 char *fpath; 591 592 bzero(&snapshot, sizeof(snapshot)); 593 594 if ((dir = opendir(snapshots_path)) != NULL) { 595 while ((den = readdir(dir)) != NULL) { 596 if (den->d_name[0] == '.') 597 continue; 598 asprintf(&fpath, "%s/%s", snapshots_path, den->d_name); 599 if (lstat(fpath, &st) == 0 && S_ISLNK(st.st_mode)) { 600 migrate_one_snapshot(fd, fpath, &snapshot); 601 } 602 free(fpath); 603 } 604 closedir(dir); 605 } 606 migrate_one_snapshot(fd, NULL, &snapshot); 607 608 } 609 610 /* 611 * Migrate a single snapshot. If fpath is NULL the ioctl is flushed, 612 * otherwise it is flushed when it fills up. 613 */ 614 static void 615 migrate_one_snapshot(int fd, const char *fpath, 616 struct hammer_ioc_snapshot *snapshot) 617 { 618 if (fpath) { 619 struct hammer_snapshot_data *snap; 620 struct tm tm; 621 time_t t; 622 int year; 623 int month; 624 int day = 0; 625 int hour = 0; 626 int minute = 0; 627 int r; 628 char linkbuf[1024]; 629 const char *ptr; 630 hammer_tid_t tid; 631 632 t = (time_t)-1; 633 tid = (hammer_tid_t)(int64_t)-1; 634 635 /* fpath may contain directory components */ 636 if ((ptr = strrchr(fpath, '/')) != NULL) 637 ++ptr; 638 else 639 ptr = fpath; 640 while (*ptr && *ptr != '-' && *ptr != '.') 641 ++ptr; 642 if (*ptr) 643 ++ptr; 644 r = sscanf(ptr, "%4d%2d%2d-%2d%2d", 645 &year, &month, &day, &hour, &minute); 646 647 if (r >= 3) { 648 bzero(&tm, sizeof(tm)); 649 tm.tm_isdst = -1; 650 tm.tm_min = minute; 651 tm.tm_hour = hour; 652 tm.tm_mday = day; 653 tm.tm_mon = month - 1; 654 tm.tm_year = year - 1900; 655 t = mktime(&tm); 656 } 657 bzero(linkbuf, sizeof(linkbuf)); 658 if (readlink(fpath, linkbuf, sizeof(linkbuf) - 1) > 0 && 659 (ptr = strrchr(linkbuf, '@')) != NULL && 660 ptr > linkbuf && ptr[-1] == '@') { 661 tid = strtoull(ptr + 1, NULL, 16); 662 } 663 if (t != (time_t)-1 && tid != (hammer_tid_t)(int64_t)-1) { 664 snap = &snapshot->snaps[snapshot->count]; 665 bzero(snap, sizeof(*snap)); 666 snap->tid = tid; 667 snap->ts = (u_int64_t)t * 1000000ULL; 668 snprintf(snap->label, sizeof(snap->label), 669 "migrated"); 670 ++snapshot->count; 671 } else { 672 printf(" non-canonical snapshot softlink: %s->%s\n", 673 fpath, linkbuf); 674 } 675 } 676 677 if ((fpath == NULL && snapshot->count) || 678 snapshot->count == HAMMER_SNAPS_PER_IOCTL) { 679 printf(" (%d snapshots)", snapshot->count); 680 again: 681 if (ioctl(fd, HAMMERIOC_ADD_SNAPSHOT, snapshot) < 0) { 682 printf(" Ioctl to migrate snapshots failed: %s\n", 683 strerror(errno)); 684 } else if (snapshot->head.error == EALREADY) { 685 ++snapshot->index; 686 goto again; 687 } else if (snapshot->head.error) { 688 printf(" Ioctl to migrate snapshots failed: %s\n", 689 strerror(snapshot->head.error)); 690 } 691 printf("index %d\n", snapshot->index); 692 snapshot->index = 0; 693 snapshot->count = 0; 694 snapshot->head.error = 0; 695 } 696 } 697 698 static 699 int 700 strtosecs(char *ptr) 701 { 702 int val; 703 704 val = strtol(ptr, &ptr, 0); 705 switch(*ptr) { 706 case 'd': 707 val *= 24; 708 /* fall through */ 709 case 'h': 710 val *= 60; 711 /* fall through */ 712 case 'm': 713 val *= 60; 714 /* fall through */ 715 case 's': 716 break; 717 default: 718 errx(1, "illegal suffix converting %s\n", ptr); 719 break; 720 } 721 return(val); 722 } 723 724 static const char * 725 dividing_slash(const char *path) 726 { 727 int len = strlen(path); 728 if (len && path[len-1] == '/') 729 return(""); 730 else 731 return("/"); 732 } 733 734 /* 735 * Check whether the desired period has elapsed since the last successful 736 * run. The run may take a while and cross a boundary so we remember the 737 * current time_t so we can save it later on. 738 * 739 * Periods in minutes, hours, or days are assumed to have been crossed 740 * if the local time crosses a minute, hour, or day boundary regardless 741 * of how close the last operation actually was. 742 * 743 * If ForceOpt is set always return true. 744 */ 745 static int 746 check_period(const char *snapshots_path, const char *cmd, int arg1, 747 time_t *savep) 748 { 749 char *check_path; 750 struct tm tp1; 751 struct tm tp2; 752 FILE *fp; 753 time_t baset, lastt; 754 char buf[256]; 755 756 time(savep); 757 localtime_r(savep, &tp1); 758 759 /* 760 * Force run if -F 761 */ 762 if (ForceOpt) 763 return(1); 764 765 /* 766 * Retrieve the start time of the last successful operation. 767 */ 768 asprintf(&check_path, "%s/.%s.period", snapshots_path, cmd); 769 fp = fopen(check_path, "r"); 770 free(check_path); 771 if (fp == NULL) 772 return(1); 773 if (fgets(buf, sizeof(buf), fp) == NULL) { 774 fclose(fp); 775 return(1); 776 } 777 fclose(fp); 778 779 lastt = strtol(buf, NULL, 0); 780 localtime_r(&lastt, &tp2); 781 782 /* 783 * Normalize the times. e.g. if asked to do something on a 1-day 784 * interval the operation will be performed as soon as the day 785 * turns over relative to the previous operation, even if the previous 786 * operation ran a few seconds ago just before midnight. 787 */ 788 if (arg1 % 60 == 0) { 789 tp1.tm_sec = 0; 790 tp2.tm_sec = 0; 791 } 792 if (arg1 % (60 * 60) == 0) { 793 tp1.tm_min = 0; 794 tp2.tm_min = 0; 795 } 796 if (arg1 % (24 * 60 * 60) == 0) { 797 tp1.tm_hour = 0; 798 tp2.tm_hour = 0; 799 } 800 801 baset = mktime(&tp1); 802 lastt = mktime(&tp2); 803 804 #if 0 805 printf("%lld vs %lld\n", (long long)(baset - lastt), (long long)arg1); 806 #endif 807 808 if ((int)(baset - lastt) >= arg1) 809 return(1); 810 return(0); 811 } 812 813 /* 814 * Store the start time of the last successful operation. 815 */ 816 static void 817 save_period(const char *snapshots_path, const char *cmd, 818 time_t savet) 819 { 820 char *ocheck_path; 821 char *ncheck_path; 822 FILE *fp; 823 824 asprintf(&ocheck_path, "%s/.%s.period", snapshots_path, cmd); 825 asprintf(&ncheck_path, "%s/.%s.period.new", snapshots_path, cmd); 826 fp = fopen(ncheck_path, "w"); 827 if (fp) { 828 fprintf(fp, "0x%08llx\n", (long long)savet); 829 if (fclose(fp) == 0) 830 rename(ncheck_path, ocheck_path); 831 remove(ncheck_path); 832 } else { 833 fprintf(stderr, "hammer: Unable to create period-file %s: %s\n", 834 ncheck_path, strerror(errno)); 835 } 836 } 837 838 /* 839 * Simply count the number of softlinks in the snapshots dir 840 */ 841 static int 842 check_softlinks(int fd, int new_config, const char *snapshots_path) 843 { 844 struct dirent *den; 845 struct stat st; 846 DIR *dir; 847 char *fpath; 848 int res = 0; 849 850 /* 851 * Old-style softlink-based snapshots 852 */ 853 if ((dir = opendir(snapshots_path)) != NULL) { 854 while ((den = readdir(dir)) != NULL) { 855 if (den->d_name[0] == '.') 856 continue; 857 asprintf(&fpath, "%s/%s", snapshots_path, den->d_name); 858 if (lstat(fpath, &st) == 0 && S_ISLNK(st.st_mode)) 859 ++res; 860 free(fpath); 861 } 862 closedir(dir); 863 } 864 865 /* 866 * New-style snapshots are stored as filesystem meta-data, 867 * count those too. 868 */ 869 if (new_config) { 870 struct hammer_ioc_snapshot snapshot; 871 872 bzero(&snapshot, sizeof(snapshot)); 873 do { 874 if (ioctl(fd, HAMMERIOC_GET_SNAPSHOT, &snapshot) < 0) { 875 err(2, "hammer cleanup: check_softlink " 876 "snapshot error"); 877 /* not reached */ 878 } 879 res += snapshot.count; 880 } while (snapshot.head.error == 0 && snapshot.count); 881 } 882 return (res); 883 } 884 885 /* 886 * Clean up expired softlinks in the snapshots dir 887 */ 888 static void 889 cleanup_softlinks(int fd, int new_config, 890 const char *snapshots_path, int arg2, char *arg3) 891 { 892 struct dirent *den; 893 struct stat st; 894 DIR *dir; 895 char *fpath; 896 int anylink = 0; 897 898 if (arg3 != NULL && strstr(arg3, "any") != NULL) 899 anylink = 1; 900 901 if ((dir = opendir(snapshots_path)) != NULL) { 902 while ((den = readdir(dir)) != NULL) { 903 if (den->d_name[0] == '.') 904 continue; 905 asprintf(&fpath, "%s/%s", snapshots_path, den->d_name); 906 if (lstat(fpath, &st) == 0 && S_ISLNK(st.st_mode) && 907 (anylink || strncmp(den->d_name, "snap-", 5) == 0) 908 ) { 909 if (check_expired(den->d_name, arg2)) { 910 if (VerboseOpt) { 911 printf(" expire %s\n", 912 fpath); 913 } 914 remove(fpath); 915 } 916 } 917 free(fpath); 918 } 919 closedir(dir); 920 } 921 922 /* 923 * New-style snapshots are stored as filesystem meta-data, 924 * count those too. 925 */ 926 if (new_config) { 927 struct hammer_ioc_snapshot snapshot; 928 struct hammer_ioc_snapshot dsnapshot; 929 struct hammer_snapshot_data *snap; 930 struct tm *tp; 931 time_t t; 932 time_t dt; 933 char snapts[32]; 934 u_int32_t i; 935 936 bzero(&snapshot, sizeof(snapshot)); 937 bzero(&dsnapshot, sizeof(dsnapshot)); 938 do { 939 if (ioctl(fd, HAMMERIOC_GET_SNAPSHOT, &snapshot) < 0) { 940 err(2, "hammer cleanup: check_softlink " 941 "snapshot error"); 942 /* not reached */ 943 } 944 for (i = 0; i < snapshot.count; ++i) { 945 snap = &snapshot.snaps[i]; 946 t = snap->ts / 1000000ULL; 947 dt = time(NULL) - t; 948 if ((int)dt > arg2 || snap->tid == 0) { 949 dsnapshot.snaps[dsnapshot.count++] = 950 *snap; 951 } 952 if ((int)dt > arg2 && VerboseOpt) { 953 tp = localtime(&t); 954 strftime(snapts, sizeof(snapts), 955 "%Y-%m-%d %H:%M:%S %Z", tp); 956 printf(" expire 0x%016jx %s %s\n", 957 (uintmax_t)snap->tid, 958 snapts, 959 snap->label); 960 } 961 if (dsnapshot.count == HAMMER_SNAPS_PER_IOCTL) 962 delete_snapshots(fd, &dsnapshot); 963 } 964 } while (snapshot.head.error == 0 && snapshot.count); 965 966 if (dsnapshot.count) 967 delete_snapshots(fd, &dsnapshot); 968 } 969 } 970 971 static void 972 delete_snapshots(int fd, struct hammer_ioc_snapshot *dsnapshot) 973 { 974 for (;;) { 975 if (ioctl(fd, HAMMERIOC_DEL_SNAPSHOT, dsnapshot) < 0) { 976 printf(" Ioctl to delete snapshots failed: %s\n", 977 strerror(errno)); 978 break; 979 } 980 if (dsnapshot->head.error) { 981 printf(" Ioctl to delete snapshots failed at " 982 "snap=%016jx: %s\n", 983 dsnapshot->snaps[dsnapshot->index].tid, 984 strerror(dsnapshot->head.error)); 985 if (++dsnapshot->index < dsnapshot->count) 986 continue; 987 } 988 break; 989 } 990 dsnapshot->index = 0; 991 dsnapshot->count = 0; 992 dsnapshot->head.error = 0; 993 } 994 995 /* 996 * Take a softlink path in the form snap-yyyymmdd-hhmm and the 997 * expiration in seconds (arg2) and return non-zero if the softlink 998 * has expired. 999 */ 1000 static int 1001 check_expired(const char *fpath, int arg2) 1002 { 1003 struct tm tm; 1004 time_t t; 1005 int year; 1006 int month; 1007 int day = 0; 1008 int hour = 0; 1009 int minute = 0; 1010 int r; 1011 1012 while (*fpath && *fpath != '-' && *fpath != '.') 1013 ++fpath; 1014 if (*fpath) 1015 ++fpath; 1016 1017 r = sscanf(fpath, "%4d%2d%2d-%2d%2d", 1018 &year, &month, &day, &hour, &minute); 1019 1020 if (r >= 3) { 1021 bzero(&tm, sizeof(tm)); 1022 tm.tm_isdst = -1; 1023 tm.tm_min = minute; 1024 tm.tm_hour = hour; 1025 tm.tm_mday = day; 1026 tm.tm_mon = month - 1; 1027 tm.tm_year = year - 1900; 1028 t = mktime(&tm); 1029 if (t == (time_t)-1) 1030 return(0); 1031 t = time(NULL) - t; 1032 if ((int)t > arg2) 1033 return(1); 1034 } 1035 return(0); 1036 } 1037 1038 /* 1039 * Issue a snapshot. 1040 */ 1041 static int 1042 create_snapshot(const char *path, const char *snapshots_path) 1043 { 1044 int r; 1045 1046 runcmd(&r, "hammer snapshot %s %s", path, snapshots_path); 1047 return(r); 1048 } 1049 1050 static int 1051 cleanup_prune(const char *path __unused, const char *snapshots_path, 1052 int arg1 __unused, int arg2, int snapshots_disabled) 1053 { 1054 /* 1055 * If snapshots have been disabled run prune-everything instead 1056 * of prune. 1057 */ 1058 if (snapshots_disabled && arg2) { 1059 runcmd(NULL, "hammer -c %s/.prune.cycle -t %d prune-everything %s", 1060 snapshots_path, arg2, path); 1061 } else if (snapshots_disabled) { 1062 runcmd(NULL, "hammer prune-everything %s", path); 1063 } else if (arg2) { 1064 runcmd(NULL, "hammer -c %s/.prune.cycle -t %d prune %s", 1065 snapshots_path, arg2, snapshots_path); 1066 } else { 1067 runcmd(NULL, "hammer prune %s", snapshots_path); 1068 } 1069 return(0); 1070 } 1071 1072 static int 1073 cleanup_rebalance(const char *path, const char *snapshots_path, 1074 int arg1 __unused, int arg2) 1075 { 1076 if (VerboseOpt == 0) { 1077 printf("."); 1078 fflush(stdout); 1079 } 1080 1081 runcmd(NULL, 1082 "hammer -c %s/.rebalance.cycle -t %d rebalance %s", 1083 snapshots_path, arg2, path); 1084 if (VerboseOpt == 0) { 1085 printf("."); 1086 fflush(stdout); 1087 } 1088 if (VerboseOpt == 0) 1089 printf("\n"); 1090 return(0); 1091 } 1092 1093 static int 1094 cleanup_reblock(const char *path, const char *snapshots_path, 1095 int arg1 __unused, int arg2) 1096 { 1097 if (VerboseOpt == 0) { 1098 printf("."); 1099 fflush(stdout); 1100 } 1101 1102 /* 1103 * When reblocking the B-Tree always reblock everything in normal 1104 * mode. 1105 */ 1106 runcmd(NULL, 1107 "hammer -c %s/.reblock-1.cycle -t %d reblock-btree %s", 1108 snapshots_path, arg2, path); 1109 if (VerboseOpt == 0) { 1110 printf("."); 1111 fflush(stdout); 1112 } 1113 1114 /* 1115 * When reblocking the inodes always reblock everything in normal 1116 * mode. 1117 */ 1118 runcmd(NULL, 1119 "hammer -c %s/.reblock-2.cycle -t %d reblock-inodes %s", 1120 snapshots_path, arg2, path); 1121 if (VerboseOpt == 0) { 1122 printf("."); 1123 fflush(stdout); 1124 } 1125 1126 /* 1127 * When reblocking the directories always reblock everything in normal 1128 * mode. 1129 */ 1130 runcmd(NULL, 1131 "hammer -c %s/.reblock-4.cycle -t %d reblock-dirs %s", 1132 snapshots_path, arg2, path); 1133 if (VerboseOpt == 0) { 1134 printf("."); 1135 fflush(stdout); 1136 } 1137 1138 /* 1139 * Do not reblock all the data in normal mode. 1140 */ 1141 runcmd(NULL, 1142 "hammer -c %s/.reblock-3.cycle -t %d reblock-data %s 95", 1143 snapshots_path, arg2, path); 1144 if (VerboseOpt == 0) 1145 printf("\n"); 1146 return(0); 1147 } 1148 1149 static int 1150 cleanup_recopy(const char *path, const char *snapshots_path, 1151 int arg1 __unused, int arg2) 1152 { 1153 if (VerboseOpt == 0) { 1154 printf("."); 1155 fflush(stdout); 1156 } 1157 runcmd(NULL, 1158 "hammer -c %s/.recopy-1.cycle -t %d reblock-btree %s", 1159 snapshots_path, arg2, path); 1160 if (VerboseOpt == 0) { 1161 printf("."); 1162 fflush(stdout); 1163 } 1164 runcmd(NULL, 1165 "hammer -c %s/.recopy-2.cycle -t %d reblock-inodes %s", 1166 snapshots_path, arg2, path); 1167 if (VerboseOpt == 0) { 1168 printf("."); 1169 fflush(stdout); 1170 } 1171 runcmd(NULL, 1172 "hammer -c %s/.recopy-4.cycle -t %d reblock-dirs %s", 1173 snapshots_path, arg2, path); 1174 if (VerboseOpt == 0) { 1175 printf("."); 1176 fflush(stdout); 1177 } 1178 runcmd(NULL, 1179 "hammer -c %s/.recopy-3.cycle -t %d reblock-data %s", 1180 snapshots_path, arg2, path); 1181 if (VerboseOpt == 0) 1182 printf("\n"); 1183 return(0); 1184 } 1185 1186 static int 1187 cleanup_dedup(const char *path, const char *snapshots_path __unused, 1188 int arg1 __unused, int arg2) 1189 { 1190 if (VerboseOpt == 0) { 1191 printf("."); 1192 fflush(stdout); 1193 } 1194 1195 runcmd(NULL, "hammer -t %d dedup %s", arg2, path); 1196 if (VerboseOpt == 0) { 1197 printf("."); 1198 fflush(stdout); 1199 } 1200 if (VerboseOpt == 0) 1201 printf("\n"); 1202 return(0); 1203 } 1204 1205 static 1206 void 1207 runcmd(int *resp, const char *ctl, ...) 1208 { 1209 va_list va; 1210 char *cmd; 1211 char *arg; 1212 char **av; 1213 int n; 1214 int nmax; 1215 int res; 1216 pid_t pid; 1217 1218 /* 1219 * Generate the command 1220 */ 1221 va_start(va, ctl); 1222 vasprintf(&cmd, ctl, va); 1223 va_end(va); 1224 if (VerboseOpt) 1225 printf(" %s\n", cmd); 1226 1227 /* 1228 * Break us down into arguments. We do not just use system() here 1229 * because it blocks SIGINT and friends. 1230 */ 1231 n = 0; 1232 nmax = 16; 1233 av = malloc(sizeof(char *) * nmax); 1234 1235 for (arg = strtok(cmd, WS); arg; arg = strtok(NULL, WS)) { 1236 if (n == nmax - 1) { 1237 nmax += 16; 1238 av = realloc(av, sizeof(char *) * nmax); 1239 } 1240 av[n++] = arg; 1241 } 1242 av[n++] = NULL; 1243 1244 /* 1245 * Run the command. 1246 */ 1247 RunningIoctl = 1; 1248 if ((pid = fork()) == 0) { 1249 if (VerboseOpt < 2) { 1250 int fd = open("/dev/null", O_RDWR); 1251 dup2(fd, 1); 1252 close(fd); 1253 } 1254 execvp(av[0], av); 1255 _exit(127); 1256 } else if (pid < 0) { 1257 res = 127; 1258 } else { 1259 int status; 1260 1261 while (waitpid(pid, &status, 0) != pid) 1262 ; 1263 res = WEXITSTATUS(status); 1264 } 1265 RunningIoctl = 0; 1266 if (DidInterrupt) 1267 _exit(1); 1268 1269 free(cmd); 1270 free(av); 1271 if (resp) 1272 *resp = res; 1273 } 1274