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