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 /* 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 struct pidfh *pfh = NULL; 393 static char pidfile[PIDFILE_BUFSIZE]; 394 395 snprintf (pidfile, PIDFILE_BUFSIZE, "%s/hammer.cleanup.%d", 396 pidfile_loc, getpid()); 397 pfh = pidfile_open(pidfile, 0644, NULL); 398 if (pfh == NULL) { 399 warn ("Unable to open or create %s", pidfile); 400 } 401 pidfile_write(pfh); 402 403 /* 404 * Process the config file 405 */ 406 cbase = config.config.text; 407 408 while ((cptr = strchr(cbase, '\n')) != NULL) { 409 bcopy(cbase, buf, cptr - cbase); 410 buf[cptr - cbase] = 0; 411 cbase = cptr + 1; 412 413 cmd = strtok(buf, WS); 414 if (cmd == NULL || cmd[0] == '#') 415 continue; 416 417 arg1 = 0; 418 arg2 = 0; 419 arg3 = NULL; 420 if ((ptr = strtok(NULL, WS)) != NULL) { 421 arg1 = strtosecs(ptr); 422 if ((ptr = strtok(NULL, WS)) != NULL) { 423 arg2 = strtosecs(ptr); 424 arg3 = strtok(NULL, WS); 425 } 426 } 427 428 printf("%20s - ", cmd); 429 fflush(stdout); 430 431 r = 1; 432 if (strcmp(cmd, "snapshots") == 0) { 433 if (arg1 == 0) { 434 if (arg2 && 435 check_softlinks(fd, new_config, 436 snapshots_path)) { 437 printf("only removing old snapshots\n"); 438 prune_warning = 1; 439 cleanup_softlinks(fd, new_config, 440 snapshots_path, 441 arg2, arg3); 442 } else { 443 printf("disabled\n"); 444 snapshots_disabled = 1; 445 } 446 } else 447 if (check_period(snapshots_path, cmd, arg1, &savet)) { 448 printf("run\n"); 449 cleanup_softlinks(fd, new_config, 450 snapshots_path, 451 arg2, arg3); 452 r = create_snapshot(path, snapshots_path); 453 } else { 454 printf("skip\n"); 455 } 456 } else if (arg1 == 0) { 457 /* 458 * The commands following this check can't handle 459 * a period of 0, so call the feature disabled and 460 * ignore the directive. 461 */ 462 printf("disabled\n"); 463 } else if (strcmp(cmd, "prune") == 0) { 464 if (check_period(snapshots_path, cmd, arg1, &savet)) { 465 if (prune_warning) { 466 printf("run - WARNING snapshot " 467 "softlinks present " 468 "but snapshots disabled\n"); 469 } else { 470 printf("run\n"); 471 } 472 r = cleanup_prune(path, snapshots_path, 473 arg1, arg2, snapshots_disabled); 474 } else { 475 printf("skip\n"); 476 } 477 } else if (strcmp(cmd, "rebalance") == 0) { 478 found_rebal = 1; 479 if (check_period(snapshots_path, cmd, arg1, &savet)) { 480 printf("run"); 481 fflush(stdout); 482 if (VerboseOpt) 483 printf("\n"); 484 r = cleanup_rebalance(path, snapshots_path, 485 arg1, arg2); 486 } else { 487 printf("skip\n"); 488 } 489 } else if (strcmp(cmd, "reblock") == 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_reblock(path, snapshots_path, 496 arg1, arg2); 497 } else { 498 printf("skip\n"); 499 } 500 } else if (strcmp(cmd, "recopy") == 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_recopy(path, snapshots_path, 507 arg1, arg2); 508 } else { 509 printf("skip\n"); 510 } 511 } else if (strcmp(cmd, "dedup") == 0) { 512 if (check_period(snapshots_path, cmd, arg1, &savet)) { 513 printf("run"); 514 fflush(stdout); 515 if (VerboseOpt) 516 printf("\n"); 517 r = cleanup_dedup(path, snapshots_path, 518 arg1, arg2); 519 } else { 520 printf("skip\n"); 521 } 522 } else { 523 printf("unknown directive\n"); 524 r = 1; 525 } 526 if (r == 0) 527 save_period(snapshots_path, cmd, savet); 528 } 529 530 /* 531 * Add new rebalance feature if the config doesn't have it. 532 * (old style config only). 533 */ 534 if (new_config == 0 && found_rebal == 0) { 535 if ((fp = fopen(config_path, "r+")) != NULL) { 536 fseek(fp, 0L, 2); 537 fprintf(fp, "rebalance 1d 5m\n"); 538 fclose(fp); 539 } 540 } 541 542 /* 543 * Cleanup, and delay a little 544 */ 545 close(fd); 546 usleep(1000); 547 pidfile_close(pfh); 548 pidfile_remove(pfh); 549 } 550 551 /* 552 * Initialize new config data (new or old style) 553 */ 554 static void 555 config_init(const char *path, struct hammer_ioc_config *config) 556 { 557 const char *snapshots; 558 559 if (strcmp(path, "/tmp") == 0 || 560 strcmp(path, "/var/tmp") == 0 || 561 strcmp(path, "/usr/obj") == 0) { 562 snapshots = "snapshots 0d 0d\n"; 563 } else { 564 snapshots = "snapshots 1d 60d\n"; 565 } 566 bzero(config->config.text, sizeof(config->config.text)); 567 snprintf(config->config.text, sizeof(config->config.text) - 1, "%s%s", 568 snapshots, 569 "prune 1d 5m\n" 570 "rebalance 1d 5m\n" 571 "#dedup 1d 5m\n" 572 "reblock 1d 5m\n" 573 "recopy 30d 10m\n"); 574 } 575 576 /* 577 * Migrate configuration data from the old snapshots/config 578 * file to the new meta-data format. 579 */ 580 static void 581 migrate_config(FILE *fp, struct hammer_ioc_config *config) 582 { 583 int n; 584 585 n = fread(config->config.text, 1, sizeof(config->config.text) - 1, fp); 586 if (n >= 0) 587 bzero(config->config.text + n, sizeof(config->config.text) - n); 588 } 589 590 /* 591 * Migrate snapshot softlinks in the snapshots directory to the 592 * new meta-data format. The softlinks are left intact, but 593 * this way the pruning code won't lose track of them if you 594 * happen to blow away the snapshots directory. 595 */ 596 static void 597 migrate_snapshots(int fd, const char *snapshots_path) 598 { 599 struct hammer_ioc_snapshot snapshot; 600 struct dirent *den; 601 struct stat st; 602 DIR *dir; 603 char *fpath; 604 605 bzero(&snapshot, sizeof(snapshot)); 606 607 if ((dir = opendir(snapshots_path)) != NULL) { 608 while ((den = readdir(dir)) != NULL) { 609 if (den->d_name[0] == '.') 610 continue; 611 asprintf(&fpath, "%s/%s", snapshots_path, den->d_name); 612 if (lstat(fpath, &st) == 0 && S_ISLNK(st.st_mode)) { 613 migrate_one_snapshot(fd, fpath, &snapshot); 614 } 615 free(fpath); 616 } 617 closedir(dir); 618 } 619 migrate_one_snapshot(fd, NULL, &snapshot); 620 621 } 622 623 /* 624 * Migrate a single snapshot. If fpath is NULL the ioctl is flushed, 625 * otherwise it is flushed when it fills up. 626 */ 627 static void 628 migrate_one_snapshot(int fd, const char *fpath, 629 struct hammer_ioc_snapshot *snapshot) 630 { 631 if (fpath) { 632 struct hammer_snapshot_data *snap; 633 struct tm tm; 634 time_t t; 635 int year; 636 int month; 637 int day = 0; 638 int hour = 0; 639 int minute = 0; 640 int r; 641 char linkbuf[1024]; 642 const char *ptr; 643 hammer_tid_t tid; 644 645 t = (time_t)-1; 646 tid = (hammer_tid_t)(int64_t)-1; 647 648 /* fpath may contain directory components */ 649 if ((ptr = strrchr(fpath, '/')) != NULL) 650 ++ptr; 651 else 652 ptr = fpath; 653 while (*ptr && *ptr != '-' && *ptr != '.') 654 ++ptr; 655 if (*ptr) 656 ++ptr; 657 r = sscanf(ptr, "%4d%2d%2d-%2d%2d", 658 &year, &month, &day, &hour, &minute); 659 660 if (r >= 3) { 661 bzero(&tm, sizeof(tm)); 662 tm.tm_isdst = -1; 663 tm.tm_min = minute; 664 tm.tm_hour = hour; 665 tm.tm_mday = day; 666 tm.tm_mon = month - 1; 667 tm.tm_year = year - 1900; 668 t = mktime(&tm); 669 } 670 bzero(linkbuf, sizeof(linkbuf)); 671 if (readlink(fpath, linkbuf, sizeof(linkbuf) - 1) > 0 && 672 (ptr = strrchr(linkbuf, '@')) != NULL && 673 ptr > linkbuf && ptr[-1] == '@') { 674 tid = strtoull(ptr + 1, NULL, 16); 675 } 676 if (t != (time_t)-1 && tid != (hammer_tid_t)(int64_t)-1) { 677 snap = &snapshot->snaps[snapshot->count]; 678 bzero(snap, sizeof(*snap)); 679 snap->tid = tid; 680 snap->ts = (u_int64_t)t * 1000000ULL; 681 snprintf(snap->label, sizeof(snap->label), 682 "migrated"); 683 ++snapshot->count; 684 } else { 685 printf(" non-canonical snapshot softlink: %s->%s\n", 686 fpath, linkbuf); 687 } 688 } 689 690 if ((fpath == NULL && snapshot->count) || 691 snapshot->count == HAMMER_SNAPS_PER_IOCTL) { 692 printf(" (%d snapshots)", snapshot->count); 693 again: 694 if (ioctl(fd, HAMMERIOC_ADD_SNAPSHOT, snapshot) < 0) { 695 printf(" Ioctl to migrate snapshots failed: %s\n", 696 strerror(errno)); 697 } else if (snapshot->head.error == EALREADY) { 698 ++snapshot->index; 699 goto again; 700 } else if (snapshot->head.error) { 701 printf(" Ioctl to migrate snapshots failed: %s\n", 702 strerror(snapshot->head.error)); 703 } 704 printf("index %d\n", snapshot->index); 705 snapshot->index = 0; 706 snapshot->count = 0; 707 snapshot->head.error = 0; 708 } 709 } 710 711 static 712 int 713 strtosecs(char *ptr) 714 { 715 int val; 716 717 val = strtol(ptr, &ptr, 0); 718 switch(*ptr) { 719 case 'd': 720 val *= 24; 721 /* fall through */ 722 case 'h': 723 val *= 60; 724 /* fall through */ 725 case 'm': 726 val *= 60; 727 /* fall through */ 728 case 's': 729 break; 730 default: 731 errx(1, "illegal suffix converting %s\n", ptr); 732 break; 733 } 734 return(val); 735 } 736 737 static const char * 738 dividing_slash(const char *path) 739 { 740 int len = strlen(path); 741 if (len && path[len-1] == '/') 742 return(""); 743 else 744 return("/"); 745 } 746 747 /* 748 * Check whether the desired period has elapsed since the last successful 749 * run. The run may take a while and cross a boundary so we remember the 750 * current time_t so we can save it later on. 751 * 752 * Periods in minutes, hours, or days are assumed to have been crossed 753 * if the local time crosses a minute, hour, or day boundary regardless 754 * of how close the last operation actually was. 755 * 756 * If ForceOpt is set always return true. 757 */ 758 static int 759 check_period(const char *snapshots_path, const char *cmd, int arg1, 760 time_t *savep) 761 { 762 char *check_path; 763 struct tm tp1; 764 struct tm tp2; 765 FILE *fp; 766 time_t baset, lastt; 767 char buf[256]; 768 769 time(savep); 770 localtime_r(savep, &tp1); 771 772 /* 773 * Force run if -F 774 */ 775 if (ForceOpt) 776 return(1); 777 778 /* 779 * Retrieve the start time of the last successful operation. 780 */ 781 asprintf(&check_path, "%s/.%s.period", snapshots_path, cmd); 782 fp = fopen(check_path, "r"); 783 free(check_path); 784 if (fp == NULL) 785 return(1); 786 if (fgets(buf, sizeof(buf), fp) == NULL) { 787 fclose(fp); 788 return(1); 789 } 790 fclose(fp); 791 792 lastt = strtol(buf, NULL, 0); 793 localtime_r(&lastt, &tp2); 794 795 /* 796 * Normalize the times. e.g. if asked to do something on a 1-day 797 * interval the operation will be performed as soon as the day 798 * turns over relative to the previous operation, even if the previous 799 * operation ran a few seconds ago just before midnight. 800 */ 801 if (arg1 % 60 == 0) { 802 tp1.tm_sec = 0; 803 tp2.tm_sec = 0; 804 } 805 if (arg1 % (60 * 60) == 0) { 806 tp1.tm_min = 0; 807 tp2.tm_min = 0; 808 } 809 if (arg1 % (24 * 60 * 60) == 0) { 810 tp1.tm_hour = 0; 811 tp2.tm_hour = 0; 812 } 813 814 baset = mktime(&tp1); 815 lastt = mktime(&tp2); 816 817 #if 0 818 printf("%lld vs %lld\n", (long long)(baset - lastt), (long long)arg1); 819 #endif 820 821 if ((int)(baset - lastt) >= arg1) 822 return(1); 823 return(0); 824 } 825 826 /* 827 * Store the start time of the last successful operation. 828 */ 829 static void 830 save_period(const char *snapshots_path, const char *cmd, 831 time_t savet) 832 { 833 char *ocheck_path; 834 char *ncheck_path; 835 FILE *fp; 836 837 asprintf(&ocheck_path, "%s/.%s.period", snapshots_path, cmd); 838 asprintf(&ncheck_path, "%s/.%s.period.new", snapshots_path, cmd); 839 fp = fopen(ncheck_path, "w"); 840 if (fp) { 841 fprintf(fp, "0x%08llx\n", (long long)savet); 842 if (fclose(fp) == 0) 843 rename(ncheck_path, ocheck_path); 844 remove(ncheck_path); 845 } else { 846 fprintf(stderr, "hammer: Unable to create period-file %s: %s\n", 847 ncheck_path, strerror(errno)); 848 } 849 } 850 851 /* 852 * Simply count the number of softlinks in the snapshots dir 853 */ 854 static int 855 check_softlinks(int fd, int new_config, const char *snapshots_path) 856 { 857 struct dirent *den; 858 struct stat st; 859 DIR *dir; 860 char *fpath; 861 int res = 0; 862 863 /* 864 * Old-style softlink-based snapshots 865 */ 866 if ((dir = opendir(snapshots_path)) != NULL) { 867 while ((den = readdir(dir)) != NULL) { 868 if (den->d_name[0] == '.') 869 continue; 870 asprintf(&fpath, "%s/%s", snapshots_path, den->d_name); 871 if (lstat(fpath, &st) == 0 && S_ISLNK(st.st_mode)) 872 ++res; 873 free(fpath); 874 } 875 closedir(dir); 876 } 877 878 /* 879 * New-style snapshots are stored as filesystem meta-data, 880 * count those too. 881 */ 882 if (new_config) { 883 struct hammer_ioc_snapshot snapshot; 884 885 bzero(&snapshot, sizeof(snapshot)); 886 do { 887 if (ioctl(fd, HAMMERIOC_GET_SNAPSHOT, &snapshot) < 0) { 888 err(2, "hammer cleanup: check_softlink " 889 "snapshot error"); 890 /* not reached */ 891 } 892 res += snapshot.count; 893 } while (snapshot.head.error == 0 && snapshot.count); 894 } 895 return (res); 896 } 897 898 /* 899 * Clean up expired softlinks in the snapshots dir 900 */ 901 static void 902 cleanup_softlinks(int fd, int new_config, 903 const char *snapshots_path, int arg2, char *arg3) 904 { 905 struct dirent *den; 906 struct stat st; 907 DIR *dir; 908 char *fpath; 909 int anylink = 0; 910 911 if (arg3 != NULL && strstr(arg3, "any") != NULL) 912 anylink = 1; 913 914 if ((dir = opendir(snapshots_path)) != NULL) { 915 while ((den = readdir(dir)) != NULL) { 916 if (den->d_name[0] == '.') 917 continue; 918 asprintf(&fpath, "%s/%s", snapshots_path, den->d_name); 919 if (lstat(fpath, &st) == 0 && S_ISLNK(st.st_mode) && 920 (anylink || strncmp(den->d_name, "snap-", 5) == 0)) { 921 if (check_expired(den->d_name, arg2)) { 922 if (VerboseOpt) { 923 printf(" expire %s\n", 924 fpath); 925 } 926 remove(fpath); 927 } 928 } 929 free(fpath); 930 } 931 closedir(dir); 932 } 933 934 /* 935 * New-style snapshots are stored as filesystem meta-data, 936 * count those too. 937 */ 938 if (new_config) { 939 struct hammer_ioc_snapshot snapshot; 940 struct hammer_ioc_snapshot dsnapshot; 941 struct hammer_snapshot_data *snap; 942 struct tm *tp; 943 time_t t; 944 time_t dt; 945 char snapts[32]; 946 u_int32_t i; 947 948 bzero(&snapshot, sizeof(snapshot)); 949 bzero(&dsnapshot, sizeof(dsnapshot)); 950 do { 951 if (ioctl(fd, HAMMERIOC_GET_SNAPSHOT, &snapshot) < 0) { 952 err(2, "hammer cleanup: check_softlink " 953 "snapshot error"); 954 /* not reached */ 955 } 956 for (i = 0; i < snapshot.count; ++i) { 957 snap = &snapshot.snaps[i]; 958 t = snap->ts / 1000000ULL; 959 dt = time(NULL) - t; 960 if ((int)dt > arg2 || snap->tid == 0) { 961 dsnapshot.snaps[dsnapshot.count++] = 962 *snap; 963 } 964 if ((int)dt > arg2 && VerboseOpt) { 965 tp = localtime(&t); 966 strftime(snapts, sizeof(snapts), 967 "%Y-%m-%d %H:%M:%S %Z", tp); 968 printf(" expire 0x%016jx %s %s\n", 969 (uintmax_t)snap->tid, 970 snapts, 971 snap->label); 972 } 973 if (dsnapshot.count == HAMMER_SNAPS_PER_IOCTL) 974 delete_snapshots(fd, &dsnapshot); 975 } 976 } while (snapshot.head.error == 0 && snapshot.count); 977 978 if (dsnapshot.count) 979 delete_snapshots(fd, &dsnapshot); 980 } 981 } 982 983 static void 984 delete_snapshots(int fd, struct hammer_ioc_snapshot *dsnapshot) 985 { 986 for (;;) { 987 if (ioctl(fd, HAMMERIOC_DEL_SNAPSHOT, dsnapshot) < 0) { 988 printf(" Ioctl to delete snapshots failed: %s\n", 989 strerror(errno)); 990 break; 991 } 992 if (dsnapshot->head.error) { 993 printf(" Ioctl to delete snapshots failed at " 994 "snap=%016jx: %s\n", 995 dsnapshot->snaps[dsnapshot->index].tid, 996 strerror(dsnapshot->head.error)); 997 if (++dsnapshot->index < dsnapshot->count) 998 continue; 999 } 1000 break; 1001 } 1002 dsnapshot->index = 0; 1003 dsnapshot->count = 0; 1004 dsnapshot->head.error = 0; 1005 } 1006 1007 /* 1008 * Take a softlink path in the form snap-yyyymmdd-hhmm and the 1009 * expiration in seconds (arg2) and return non-zero if the softlink 1010 * has expired. 1011 */ 1012 static int 1013 check_expired(const char *fpath, int arg2) 1014 { 1015 struct tm tm; 1016 time_t t; 1017 int year; 1018 int month; 1019 int day = 0; 1020 int hour = 0; 1021 int minute = 0; 1022 int r; 1023 1024 while (*fpath && *fpath != '-' && *fpath != '.') 1025 ++fpath; 1026 if (*fpath) 1027 ++fpath; 1028 1029 r = sscanf(fpath, "%4d%2d%2d-%2d%2d", 1030 &year, &month, &day, &hour, &minute); 1031 1032 if (r >= 3) { 1033 bzero(&tm, sizeof(tm)); 1034 tm.tm_isdst = -1; 1035 tm.tm_min = minute; 1036 tm.tm_hour = hour; 1037 tm.tm_mday = day; 1038 tm.tm_mon = month - 1; 1039 tm.tm_year = year - 1900; 1040 t = mktime(&tm); 1041 if (t == (time_t)-1) 1042 return(0); 1043 t = time(NULL) - t; 1044 if ((int)t > arg2) 1045 return(1); 1046 } 1047 return(0); 1048 } 1049 1050 /* 1051 * Issue a snapshot. 1052 */ 1053 static int 1054 create_snapshot(const char *path, const char *snapshots_path) 1055 { 1056 int r; 1057 1058 runcmd(&r, "hammer snapshot %s %s", path, snapshots_path); 1059 return(r); 1060 } 1061 1062 static int 1063 cleanup_prune(const char *path, const char *snapshots_path, 1064 int arg1 __unused, int arg2, int snapshots_disabled) 1065 { 1066 const char *path_or_snapshots_path; 1067 struct softprune *base = NULL; 1068 struct hammer_ioc_prune dummy_template; 1069 1070 bzero(&dummy_template, sizeof(dummy_template)); 1071 hammer_softprune_scandir(&base, &dummy_template, snapshots_path); 1072 1073 /* 1074 * If the snapshots_path (e.g. /var/hammer/...) has no snapshots 1075 * in it then prune will get confused and prune the filesystem 1076 * containing the snapshots_path instead of the requested 1077 * filesystem. De-confuse prune. We need a better way. 1078 */ 1079 path_or_snapshots_path = base ? snapshots_path : path; 1080 1081 /* 1082 * If snapshots have been disabled run prune-everything instead 1083 * of prune. 1084 */ 1085 if (snapshots_disabled && arg2) { 1086 runcmd(NULL, 1087 "hammer -c %s/.prune.cycle -t %d prune-everything %s", 1088 snapshots_path, arg2, path); 1089 } else if (snapshots_disabled) { 1090 runcmd(NULL, "hammer prune-everything %s", path); 1091 } else if (arg2) { 1092 runcmd(NULL, "hammer -c %s/.prune.cycle -t %d prune %s", 1093 snapshots_path, arg2, path_or_snapshots_path); 1094 } else { 1095 runcmd(NULL, "hammer prune %s", path_or_snapshots_path); 1096 } 1097 return(0); 1098 } 1099 1100 static int 1101 cleanup_rebalance(const char *path, const char *snapshots_path, 1102 int arg1 __unused, int arg2) 1103 { 1104 if (VerboseOpt == 0) { 1105 printf("."); 1106 fflush(stdout); 1107 } 1108 1109 runcmd(NULL, 1110 "hammer -c %s/.rebalance.cycle -t %d rebalance %s", 1111 snapshots_path, arg2, path); 1112 if (VerboseOpt == 0) { 1113 printf("."); 1114 fflush(stdout); 1115 } 1116 if (VerboseOpt == 0) 1117 printf("\n"); 1118 return(0); 1119 } 1120 1121 static int 1122 cleanup_reblock(const char *path, const char *snapshots_path, 1123 int arg1 __unused, int arg2) 1124 { 1125 if (VerboseOpt == 0) { 1126 printf("."); 1127 fflush(stdout); 1128 } 1129 1130 /* 1131 * When reblocking the B-Tree always reblock everything in normal 1132 * mode. 1133 */ 1134 runcmd(NULL, 1135 "hammer -c %s/.reblock-1.cycle -t %d reblock-btree %s", 1136 snapshots_path, arg2, path); 1137 if (VerboseOpt == 0) { 1138 printf("."); 1139 fflush(stdout); 1140 } 1141 1142 /* 1143 * When reblocking the inodes always reblock everything in normal 1144 * mode. 1145 */ 1146 runcmd(NULL, 1147 "hammer -c %s/.reblock-2.cycle -t %d reblock-inodes %s", 1148 snapshots_path, arg2, path); 1149 if (VerboseOpt == 0) { 1150 printf("."); 1151 fflush(stdout); 1152 } 1153 1154 /* 1155 * When reblocking the directories always reblock everything in normal 1156 * mode. 1157 */ 1158 runcmd(NULL, 1159 "hammer -c %s/.reblock-4.cycle -t %d reblock-dirs %s", 1160 snapshots_path, arg2, path); 1161 if (VerboseOpt == 0) { 1162 printf("."); 1163 fflush(stdout); 1164 } 1165 1166 /* 1167 * Do not reblock all the data in normal mode. 1168 */ 1169 runcmd(NULL, 1170 "hammer -c %s/.reblock-3.cycle -t %d reblock-data %s 95", 1171 snapshots_path, arg2, path); 1172 if (VerboseOpt == 0) 1173 printf("\n"); 1174 return(0); 1175 } 1176 1177 static int 1178 cleanup_recopy(const char *path, const char *snapshots_path, 1179 int arg1 __unused, int arg2) 1180 { 1181 if (VerboseOpt == 0) { 1182 printf("."); 1183 fflush(stdout); 1184 } 1185 runcmd(NULL, 1186 "hammer -c %s/.recopy-1.cycle -t %d reblock-btree %s", 1187 snapshots_path, arg2, path); 1188 if (VerboseOpt == 0) { 1189 printf("."); 1190 fflush(stdout); 1191 } 1192 runcmd(NULL, 1193 "hammer -c %s/.recopy-2.cycle -t %d reblock-inodes %s", 1194 snapshots_path, arg2, path); 1195 if (VerboseOpt == 0) { 1196 printf("."); 1197 fflush(stdout); 1198 } 1199 runcmd(NULL, 1200 "hammer -c %s/.recopy-4.cycle -t %d reblock-dirs %s", 1201 snapshots_path, arg2, path); 1202 if (VerboseOpt == 0) { 1203 printf("."); 1204 fflush(stdout); 1205 } 1206 runcmd(NULL, 1207 "hammer -c %s/.recopy-3.cycle -t %d reblock-data %s", 1208 snapshots_path, arg2, path); 1209 if (VerboseOpt == 0) 1210 printf("\n"); 1211 return(0); 1212 } 1213 1214 static int 1215 cleanup_dedup(const char *path, const char *snapshots_path __unused, 1216 int arg1 __unused, int arg2) 1217 { 1218 if (VerboseOpt == 0) { 1219 printf("."); 1220 fflush(stdout); 1221 } 1222 1223 runcmd(NULL, "hammer -t %d dedup %s", arg2, path); 1224 if (VerboseOpt == 0) { 1225 printf("."); 1226 fflush(stdout); 1227 } 1228 if (VerboseOpt == 0) 1229 printf("\n"); 1230 return(0); 1231 } 1232 1233 static 1234 void 1235 runcmd(int *resp, const char *ctl, ...) 1236 { 1237 va_list va; 1238 char *cmd; 1239 char *arg; 1240 char **av; 1241 int n; 1242 int nmax; 1243 int res; 1244 pid_t pid; 1245 1246 /* 1247 * Generate the command 1248 */ 1249 va_start(va, ctl); 1250 vasprintf(&cmd, ctl, va); 1251 va_end(va); 1252 if (VerboseOpt) 1253 printf(" %s\n", cmd); 1254 1255 /* 1256 * Break us down into arguments. We do not just use system() here 1257 * because it blocks SIGINT and friends. 1258 */ 1259 n = 0; 1260 nmax = 16; 1261 av = malloc(sizeof(char *) * nmax); 1262 1263 for (arg = strtok(cmd, WS); arg; arg = strtok(NULL, WS)) { 1264 if (n == nmax - 1) { 1265 nmax += 16; 1266 av = realloc(av, sizeof(char *) * nmax); 1267 } 1268 av[n++] = arg; 1269 } 1270 av[n++] = NULL; 1271 1272 /* 1273 * Run the command. 1274 */ 1275 RunningIoctl = 1; 1276 if ((pid = fork()) == 0) { 1277 if (VerboseOpt < 2) { 1278 int fd = open("/dev/null", O_RDWR); 1279 dup2(fd, 1); 1280 close(fd); 1281 } 1282 execvp(av[0], av); 1283 _exit(127); 1284 } else if (pid < 0) { 1285 res = 127; 1286 } else { 1287 int status; 1288 1289 while (waitpid(pid, &status, 0) != pid) 1290 ; 1291 res = WEXITSTATUS(status); 1292 } 1293 RunningIoctl = 0; 1294 if (DidInterrupt) 1295 _exit(1); 1296 1297 free(cmd); 1298 free(av); 1299 if (resp) 1300 *resp = res; 1301 } 1302