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