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 #include "hammer.h" 63 64 struct didpfs { 65 struct didpfs *next; 66 uuid_t uuid; 67 }; 68 69 static void do_cleanup(const char *path); 70 static void config_init(const char *path, struct hammer_ioc_config *config); 71 static void migrate_config(FILE *fp, struct hammer_ioc_config *config); 72 static void migrate_snapshots(int fd, const char *snapshots_path); 73 static void migrate_one_snapshot(int fd, const char *fpath, 74 struct hammer_ioc_snapshot *snapshot); 75 static int strtosecs(char *ptr); 76 static const char *dividing_slash(const char *path); 77 static int check_period(const char *snapshots_path, const char *cmd, int arg1, 78 time_t *savep); 79 static void save_period(const char *snapshots_path, const char *cmd, 80 time_t savet); 81 static int check_softlinks(int fd, int new_config, const char *snapshots_path); 82 static void cleanup_softlinks(int fd, int new_config, 83 const char *snapshots_path, int arg2, char *arg3); 84 static void delete_snapshots(int fd, struct hammer_ioc_snapshot *dsnapshot); 85 static int check_expired(const char *fpath, int arg2); 86 87 static int create_snapshot(const char *path, const char *snapshots_path); 88 static int cleanup_rebalance(const char *path, const char *snapshots_path, 89 int arg1, int arg2); 90 static int cleanup_prune(const char *path, const char *snapshots_path, 91 int arg1, int arg2, int snapshots_disabled); 92 static int cleanup_reblock(const char *path, const char *snapshots_path, 93 int arg1, int arg2); 94 static int cleanup_recopy(const char *path, const char *snapshots_path, 95 int arg1, int arg2); 96 static int cleanup_dedup(const char *path, const char *snapshots_path, 97 int arg1, int arg2); 98 99 static void runcmd(int *resp, const char *ctl, ...) __printflike(2, 3); 100 101 /* 102 * WARNING: Do not make the SNAPSHOTS_BASE "/var/snapshots" because 103 * it will interfere with the older HAMMER VERS < 3 snapshots directory 104 * for the /var PFS. 105 */ 106 #define SNAPSHOTS_BASE "/var/hammer" /* HAMMER VERS >= 3 */ 107 #define WS " \t\r\n" 108 109 struct didpfs *FirstPFS; 110 111 void 112 hammer_cmd_cleanup(char **av, int ac) 113 { 114 char *fstype, *fs, *path; 115 struct statfs *stfsbuf; 116 int mntsize, i; 117 118 tzset(); 119 if (ac == 0) { 120 mntsize = getmntinfo(&stfsbuf, MNT_NOWAIT); 121 if (mntsize > 0) { 122 for (i=0; i < mntsize; i++) { 123 /* 124 * We will cleanup in the case fstype is hammer. 125 * If we have null-mounted PFS, we check the 126 * mount source. If it looks like a PFS, we 127 * proceed to cleanup also. 128 */ 129 fstype = stfsbuf[i].f_fstypename; 130 fs = stfsbuf[i].f_mntfromname; 131 if ((strcmp(fstype, "hammer") == 0) || 132 ((strcmp(fstype, "null") == 0) && 133 (strstr(fs, "/@@0x") != NULL || 134 strstr(fs, "/@@-1") != NULL))) { 135 path = stfsbuf[i].f_mntonname; 136 do_cleanup(path); 137 } 138 } 139 } 140 141 } else { 142 while (ac) { 143 do_cleanup(*av); 144 --ac; 145 ++av; 146 } 147 } 148 } 149 150 static 151 void 152 do_cleanup(const char *path) 153 { 154 struct hammer_ioc_pseudofs_rw pfs; 155 struct hammer_ioc_config config; 156 struct hammer_ioc_version version; 157 union hammer_ioc_mrecord_any mrec_tmp; 158 char *snapshots_path = NULL; 159 char *config_path; 160 struct stat st; 161 char *cmd; 162 char *ptr; 163 int arg1; 164 int arg2; 165 char *arg3; 166 time_t savet; 167 char buf[256]; 168 char *cbase; 169 char *cptr; 170 FILE *fp = NULL; 171 struct didpfs *didpfs; 172 int snapshots_disabled = 0; 173 int prune_warning = 0; 174 int new_config = 0; 175 int snapshots_from_pfs = 0; 176 int fd; 177 int r; 178 int found_rebal = 0; 179 180 bzero(&pfs, sizeof(pfs)); 181 bzero(&mrec_tmp, sizeof(mrec_tmp)); 182 pfs.ondisk = &mrec_tmp.pfs.pfsd; 183 pfs.bytes = sizeof(mrec_tmp.pfs.pfsd); 184 pfs.pfs_id = -1; 185 186 printf("cleanup %-20s -", path); 187 fd = open(path, O_RDONLY); 188 if (fd < 0) { 189 printf(" unable to access directory: %s\n", strerror(errno)); 190 return; 191 } 192 if (ioctl(fd, HAMMERIOC_GET_PSEUDOFS, &pfs) < 0) { 193 printf(" not a HAMMER filesystem: %s\n", strerror(errno)); 194 close(fd); 195 return; 196 } 197 if (pfs.version != HAMMER_IOC_PSEUDOFS_VERSION) { 198 printf(" unrecognized HAMMER version\n"); 199 close(fd); 200 return; 201 } 202 bzero(&version, sizeof(version)); 203 if (ioctl(fd, HAMMERIOC_GET_VERSION, &version) < 0) { 204 printf(" HAMMER filesystem but couldn't retrieve version!\n"); 205 close(fd); 206 return; 207 } 208 209 bzero(&config, sizeof(config)); 210 if (version.cur_version >= 3) { 211 if (ioctl(fd, HAMMERIOC_GET_CONFIG, &config) == 0 && 212 config.head.error == 0) { 213 new_config = 1; 214 } 215 } 216 217 /* 218 * Make sure we have not already handled this PFS. Several nullfs 219 * mounts might alias the same PFS. 220 */ 221 for (didpfs = FirstPFS; didpfs; didpfs = didpfs->next) { 222 if (bcmp(&didpfs->uuid, &mrec_tmp.pfs.pfsd.unique_uuid, sizeof(uuid_t)) == 0) { 223 printf(" PFS #%d already handled\n", pfs.pfs_id); 224 close(fd); 225 return; 226 } 227 } 228 didpfs = malloc(sizeof(*didpfs)); 229 didpfs->next = FirstPFS; 230 FirstPFS = didpfs; 231 didpfs->uuid = mrec_tmp.pfs.pfsd.unique_uuid; 232 233 /* 234 * Calculate the old snapshots directory for HAMMER VERSION < 3 235 * 236 * If the directory is explicitly specified in the PFS config 237 * we flag it and will not migrate it later. 238 */ 239 if (mrec_tmp.pfs.pfsd.snapshots[0] == '/') { 240 asprintf(&snapshots_path, "%s", mrec_tmp.pfs.pfsd.snapshots); 241 snapshots_from_pfs = 1; 242 } else if (mrec_tmp.pfs.pfsd.snapshots[0]) { 243 printf(" WARNING: pfs-slave's snapshots dir is not absolute\n"); 244 close(fd); 245 return; 246 } else if (mrec_tmp.pfs.pfsd.mirror_flags & HAMMER_PFSD_SLAVE) { 247 if (version.cur_version < 3) { 248 printf(" WARNING: must configure snapshot dir for PFS slave\n"); 249 printf("\tWe suggest <fs>/var/slaves/<name> where " 250 "<fs> is the base HAMMER fs\n"); 251 printf("\tcontaining the slave\n"); 252 close(fd); 253 return; 254 } 255 } else { 256 asprintf(&snapshots_path, 257 "%s%ssnapshots", path, dividing_slash(path)); 258 } 259 260 /* 261 * Check for old-style config file 262 */ 263 if (snapshots_path) { 264 asprintf(&config_path, "%s/config", snapshots_path); 265 fp = fopen(config_path, "r"); 266 } 267 268 /* 269 * Handle upgrades to hammer version 3, move the config 270 * file into meta-data. 271 * 272 * For the old config read the file into the config structure, 273 * we will parse it out of the config structure regardless. 274 */ 275 if (version.cur_version >= 3) { 276 if (fp) { 277 printf("(migrating) "); 278 fflush(stdout); 279 migrate_config(fp, &config); 280 migrate_snapshots(fd, snapshots_path); 281 fclose(fp); 282 if (ioctl(fd, HAMMERIOC_SET_CONFIG, &config) < 0) { 283 printf(" cannot init meta-data config!\n"); 284 close(fd); 285 return; 286 } 287 remove(config_path); 288 } else if (new_config == 0) { 289 config_init(path, &config); 290 if (ioctl(fd, HAMMERIOC_SET_CONFIG, &config) < 0) { 291 printf(" cannot init meta-data config!\n"); 292 close(fd); 293 return; 294 } 295 } 296 new_config = 1; 297 } else { 298 /* 299 * Create missing snapshots directory for HAMMER VERSION < 3 300 */ 301 if (stat(snapshots_path, &st) < 0) { 302 if (mkdir(snapshots_path, 0755) != 0) { 303 free(snapshots_path); 304 printf(" unable to create snapshot dir \"%s\": %s\n", 305 snapshots_path, strerror(errno)); 306 close(fd); 307 return; 308 } 309 } 310 311 /* 312 * Create missing config file for HAMMER VERSION < 3 313 */ 314 if (fp == NULL) { 315 config_init(path, &config); 316 fp = fopen(config_path, "w"); 317 if (fp) { 318 fwrite(config.config.text, 1, 319 strlen(config.config.text), fp); 320 fclose(fp); 321 } 322 } else { 323 migrate_config(fp, &config); 324 fclose(fp); 325 } 326 } 327 328 /* 329 * If snapshots_from_pfs is not set we calculate the new snapshots 330 * directory default (in /var) for HAMMER VERSION >= 3 and migrate 331 * the old snapshots directory over. 332 * 333 * People who have set an explicit snapshots directory will have 334 * to migrate the data manually into /var/hammer, or not bother at 335 * all. People running slaves may wish to migrate it and then 336 * clear the snapshots specification in the PFS config for the 337 * slave. 338 */ 339 if (new_config && snapshots_from_pfs == 0) { 340 char *npath; 341 342 assert(path[0] == '/'); 343 if (strcmp(path, "/") == 0) 344 asprintf(&npath, "%s/root", SNAPSHOTS_BASE); 345 else 346 asprintf(&npath, "%s/%s", SNAPSHOTS_BASE, path + 1); 347 if (snapshots_path) { 348 if (stat(npath, &st) < 0 && errno == ENOENT) { 349 if (stat(snapshots_path, &st) < 0 && errno == ENOENT) { 350 printf(" HAMMER UPGRADE: Creating snapshots\n" 351 "\tCreating snapshots in %s\n", 352 npath); 353 runcmd(&r, "mkdir -p %s", npath); 354 } else { 355 printf(" HAMMER UPGRADE: Moving snapshots\n" 356 "\tMoving snapshots from %s to %s\n", 357 snapshots_path, npath); 358 runcmd(&r, "mkdir -p %s", npath); 359 runcmd(&r, "cpdup %s %s", snapshots_path, npath); 360 if (r != 0) { 361 printf("Unable to move snapshots directory!\n"); 362 printf("Please fix this critical error.\n"); 363 printf("Aborting cleanup of %s\n", path); 364 close(fd); 365 return; 366 } 367 runcmd(&r, "rm -rf %s", snapshots_path); 368 } 369 } 370 free(snapshots_path); 371 } else if (stat(npath, &st) < 0 && errno == ENOENT) { 372 runcmd(&r, "mkdir -p %s", npath); 373 } 374 snapshots_path = npath; 375 } 376 377 /* 378 * Lock the PFS. fd is the base directory of the mounted PFS. 379 */ 380 if (flock(fd, LOCK_EX|LOCK_NB) == -1) { 381 if (errno == EWOULDBLOCK) 382 printf(" PFS #%d locked by other process\n", pfs.pfs_id); 383 else 384 printf(" can not lock %s: %s\n", config_path, strerror(errno)); 385 close(fd); 386 return; 387 } 388 389 printf(" handle PFS #%d using %s\n", pfs.pfs_id, snapshots_path); 390 391 struct pidfh *pfh = NULL; 392 static char pidfile[PIDFILE_BUFSIZE]; 393 394 snprintf (pidfile, PIDFILE_BUFSIZE, "%s/hammer.cleanup.%d", 395 pidfile_loc, getpid()); 396 pfh = pidfile_open(pidfile, 0644, NULL); 397 if (pfh == NULL) { 398 warn ("Unable to open or create %s", pidfile); 399 } 400 pidfile_write(pfh); 401 402 /* 403 * Process the config file 404 */ 405 cbase = config.config.text; 406 407 while ((cptr = strchr(cbase, '\n')) != NULL) { 408 bcopy(cbase, buf, cptr - cbase); 409 buf[cptr - cbase] = 0; 410 cbase = cptr + 1; 411 412 cmd = strtok(buf, WS); 413 if (cmd == NULL || cmd[0] == '#') 414 continue; 415 416 arg1 = 0; 417 arg2 = 0; 418 arg3 = NULL; 419 if ((ptr = strtok(NULL, WS)) != NULL) { 420 arg1 = strtosecs(ptr); 421 if ((ptr = strtok(NULL, WS)) != NULL) { 422 arg2 = strtosecs(ptr); 423 arg3 = strtok(NULL, WS); 424 } 425 } 426 427 printf("%20s - ", cmd); 428 fflush(stdout); 429 430 r = 1; 431 if (strcmp(cmd, "snapshots") == 0) { 432 if (arg1 == 0) { 433 if (arg2 && 434 check_softlinks(fd, new_config, 435 snapshots_path)) { 436 printf("only removing old snapshots\n"); 437 prune_warning = 1; 438 cleanup_softlinks(fd, new_config, 439 snapshots_path, 440 arg2, arg3); 441 } else { 442 printf("disabled\n"); 443 snapshots_disabled = 1; 444 } 445 } else 446 if (check_period(snapshots_path, cmd, arg1, &savet)) { 447 printf("run\n"); 448 cleanup_softlinks(fd, new_config, 449 snapshots_path, 450 arg2, arg3); 451 r = create_snapshot(path, snapshots_path); 452 } else { 453 printf("skip\n"); 454 } 455 } else if (arg1 == 0) { 456 /* 457 * The commands following this check can't handle 458 * a period of 0, so call the feature disabled and 459 * ignore the directive. 460 */ 461 printf("disabled\n"); 462 } else if (strcmp(cmd, "prune") == 0) { 463 if (check_period(snapshots_path, cmd, arg1, &savet)) { 464 if (prune_warning) { 465 printf("run - WARNING snapshot " 466 "softlinks present " 467 "but snapshots disabled\n"); 468 } else { 469 printf("run\n"); 470 } 471 r = cleanup_prune(path, snapshots_path, 472 arg1, arg2, snapshots_disabled); 473 } else { 474 printf("skip\n"); 475 } 476 } else if (strcmp(cmd, "rebalance") == 0) { 477 found_rebal = 1; 478 if (check_period(snapshots_path, cmd, arg1, &savet)) { 479 printf("run"); 480 fflush(stdout); 481 if (VerboseOpt) 482 printf("\n"); 483 r = cleanup_rebalance(path, snapshots_path, 484 arg1, arg2); 485 } else { 486 printf("skip\n"); 487 } 488 } else if (strcmp(cmd, "reblock") == 0) { 489 if (check_period(snapshots_path, cmd, arg1, &savet)) { 490 printf("run"); 491 fflush(stdout); 492 if (VerboseOpt) 493 printf("\n"); 494 r = cleanup_reblock(path, snapshots_path, 495 arg1, arg2); 496 } else { 497 printf("skip\n"); 498 } 499 } else if (strcmp(cmd, "recopy") == 0) { 500 if (check_period(snapshots_path, cmd, arg1, &savet)) { 501 printf("run"); 502 fflush(stdout); 503 if (VerboseOpt) 504 printf("\n"); 505 r = cleanup_recopy(path, snapshots_path, 506 arg1, arg2); 507 } else { 508 printf("skip\n"); 509 } 510 } else if (strcmp(cmd, "dedup") == 0) { 511 if (check_period(snapshots_path, cmd, arg1, &savet)) { 512 printf("run"); 513 fflush(stdout); 514 if (VerboseOpt) 515 printf("\n"); 516 r = cleanup_dedup(path, snapshots_path, 517 arg1, arg2); 518 } else { 519 printf("skip\n"); 520 } 521 } else { 522 printf("unknown directive\n"); 523 r = 1; 524 } 525 if (r == 0) 526 save_period(snapshots_path, cmd, savet); 527 } 528 529 /* 530 * Add new rebalance feature if the config doesn't have it. 531 * (old style config only). 532 */ 533 if (new_config == 0 && found_rebal == 0) { 534 if ((fp = fopen(config_path, "r+")) != NULL) { 535 fseek(fp, 0L, 2); 536 fprintf(fp, "rebalance 1d 5m\n"); 537 fclose(fp); 538 } 539 } 540 541 /* 542 * Cleanup, and delay a little 543 */ 544 close(fd); 545 usleep(1000); 546 pidfile_close(pfh); 547 pidfile_remove(pfh); 548 } 549 550 /* 551 * Initialize new config data (new or old style) 552 */ 553 static void 554 config_init(const char *path, struct hammer_ioc_config *config) 555 { 556 const char *snapshots; 557 558 if (strcmp(path, "/tmp") == 0 || 559 strcmp(path, "/var/tmp") == 0 || 560 strcmp(path, "/usr/obj") == 0) { 561 snapshots = "snapshots 0d 0d\n"; 562 } else { 563 snapshots = "snapshots 1d 60d\n"; 564 } 565 bzero(config->config.text, sizeof(config->config.text)); 566 snprintf(config->config.text, sizeof(config->config.text) - 1, "%s%s", 567 snapshots, 568 "prune 1d 5m\n" 569 "rebalance 1d 5m\n" 570 "#dedup 1d 5m\n" 571 "reblock 1d 5m\n" 572 "recopy 30d 10m\n"); 573 } 574 575 /* 576 * Migrate configuration data from the old snapshots/config 577 * file to the new meta-data format. 578 */ 579 static void 580 migrate_config(FILE *fp, struct hammer_ioc_config *config) 581 { 582 int n; 583 584 n = fread(config->config.text, 1, sizeof(config->config.text) - 1, fp); 585 if (n >= 0) 586 bzero(config->config.text + n, sizeof(config->config.text) - n); 587 } 588 589 /* 590 * Migrate snapshot softlinks in the snapshots directory to the 591 * new meta-data format. The softlinks are left intact, but 592 * this way the pruning code won't lose track of them if you 593 * happen to blow away the snapshots directory. 594 */ 595 static void 596 migrate_snapshots(int fd, const char *snapshots_path) 597 { 598 struct hammer_ioc_snapshot snapshot; 599 struct dirent *den; 600 struct stat st; 601 DIR *dir; 602 char *fpath; 603 604 bzero(&snapshot, sizeof(snapshot)); 605 606 if ((dir = opendir(snapshots_path)) != NULL) { 607 while ((den = readdir(dir)) != NULL) { 608 if (den->d_name[0] == '.') 609 continue; 610 asprintf(&fpath, "%s/%s", snapshots_path, den->d_name); 611 if (lstat(fpath, &st) == 0 && S_ISLNK(st.st_mode)) { 612 migrate_one_snapshot(fd, fpath, &snapshot); 613 } 614 free(fpath); 615 } 616 closedir(dir); 617 } 618 migrate_one_snapshot(fd, NULL, &snapshot); 619 620 } 621 622 /* 623 * Migrate a single snapshot. If fpath is NULL the ioctl is flushed, 624 * otherwise it is flushed when it fills up. 625 */ 626 static void 627 migrate_one_snapshot(int fd, const char *fpath, 628 struct hammer_ioc_snapshot *snapshot) 629 { 630 if (fpath) { 631 struct hammer_snapshot_data *snap; 632 struct tm tm; 633 time_t t; 634 int year; 635 int month; 636 int day = 0; 637 int hour = 0; 638 int minute = 0; 639 int r; 640 char linkbuf[1024]; 641 const char *ptr; 642 hammer_tid_t tid; 643 644 t = (time_t)-1; 645 tid = (hammer_tid_t)(int64_t)-1; 646 647 /* fpath may contain directory components */ 648 if ((ptr = strrchr(fpath, '/')) != NULL) 649 ++ptr; 650 else 651 ptr = fpath; 652 while (*ptr && *ptr != '-' && *ptr != '.') 653 ++ptr; 654 if (*ptr) 655 ++ptr; 656 r = sscanf(ptr, "%4d%2d%2d-%2d%2d", 657 &year, &month, &day, &hour, &minute); 658 659 if (r >= 3) { 660 bzero(&tm, sizeof(tm)); 661 tm.tm_isdst = -1; 662 tm.tm_min = minute; 663 tm.tm_hour = hour; 664 tm.tm_mday = day; 665 tm.tm_mon = month - 1; 666 tm.tm_year = year - 1900; 667 t = mktime(&tm); 668 } 669 bzero(linkbuf, sizeof(linkbuf)); 670 if (readlink(fpath, linkbuf, sizeof(linkbuf) - 1) > 0 && 671 (ptr = strrchr(linkbuf, '@')) != NULL && 672 ptr > linkbuf && ptr[-1] == '@') { 673 tid = strtoull(ptr + 1, NULL, 16); 674 } 675 if (t != (time_t)-1 && tid != (hammer_tid_t)(int64_t)-1) { 676 snap = &snapshot->snaps[snapshot->count]; 677 bzero(snap, sizeof(*snap)); 678 snap->tid = tid; 679 snap->ts = (u_int64_t)t * 1000000ULL; 680 snprintf(snap->label, sizeof(snap->label), 681 "migrated"); 682 ++snapshot->count; 683 } else { 684 printf(" non-canonical snapshot softlink: %s->%s\n", 685 fpath, linkbuf); 686 } 687 } 688 689 if ((fpath == NULL && snapshot->count) || 690 snapshot->count == HAMMER_SNAPS_PER_IOCTL) { 691 printf(" (%d snapshots)", snapshot->count); 692 again: 693 if (ioctl(fd, HAMMERIOC_ADD_SNAPSHOT, snapshot) < 0) { 694 printf(" Ioctl to migrate snapshots failed: %s\n", 695 strerror(errno)); 696 } else if (snapshot->head.error == EALREADY) { 697 ++snapshot->index; 698 goto again; 699 } else if (snapshot->head.error) { 700 printf(" Ioctl to migrate snapshots failed: %s\n", 701 strerror(snapshot->head.error)); 702 } 703 printf("index %d\n", snapshot->index); 704 snapshot->index = 0; 705 snapshot->count = 0; 706 snapshot->head.error = 0; 707 } 708 } 709 710 static 711 int 712 strtosecs(char *ptr) 713 { 714 int val; 715 716 val = strtol(ptr, &ptr, 0); 717 switch(*ptr) { 718 case 'd': 719 val *= 24; 720 /* fall through */ 721 case 'h': 722 val *= 60; 723 /* fall through */ 724 case 'm': 725 val *= 60; 726 /* fall through */ 727 case 's': 728 break; 729 default: 730 errx(1, "illegal suffix converting %s\n", ptr); 731 break; 732 } 733 return(val); 734 } 735 736 static const char * 737 dividing_slash(const char *path) 738 { 739 int len = strlen(path); 740 if (len && path[len-1] == '/') 741 return(""); 742 else 743 return("/"); 744 } 745 746 /* 747 * Check whether the desired period has elapsed since the last successful 748 * run. The run may take a while and cross a boundary so we remember the 749 * current time_t so we can save it later on. 750 * 751 * Periods in minutes, hours, or days are assumed to have been crossed 752 * if the local time crosses a minute, hour, or day boundary regardless 753 * of how close the last operation actually was. 754 * 755 * If ForceOpt is set always return true. 756 */ 757 static int 758 check_period(const char *snapshots_path, const char *cmd, int arg1, 759 time_t *savep) 760 { 761 char *check_path; 762 struct tm tp1; 763 struct tm tp2; 764 FILE *fp; 765 time_t baset, lastt; 766 char buf[256]; 767 768 time(savep); 769 localtime_r(savep, &tp1); 770 771 /* 772 * Force run if -F 773 */ 774 if (ForceOpt) 775 return(1); 776 777 /* 778 * Retrieve the start time of the last successful operation. 779 */ 780 asprintf(&check_path, "%s/.%s.period", snapshots_path, cmd); 781 fp = fopen(check_path, "r"); 782 free(check_path); 783 if (fp == NULL) 784 return(1); 785 if (fgets(buf, sizeof(buf), fp) == NULL) { 786 fclose(fp); 787 return(1); 788 } 789 fclose(fp); 790 791 lastt = strtol(buf, NULL, 0); 792 localtime_r(&lastt, &tp2); 793 794 /* 795 * Normalize the times. e.g. if asked to do something on a 1-day 796 * interval the operation will be performed as soon as the day 797 * turns over relative to the previous operation, even if the previous 798 * operation ran a few seconds ago just before midnight. 799 */ 800 if (arg1 % 60 == 0) { 801 tp1.tm_sec = 0; 802 tp2.tm_sec = 0; 803 } 804 if (arg1 % (60 * 60) == 0) { 805 tp1.tm_min = 0; 806 tp2.tm_min = 0; 807 } 808 if (arg1 % (24 * 60 * 60) == 0) { 809 tp1.tm_hour = 0; 810 tp2.tm_hour = 0; 811 } 812 813 baset = mktime(&tp1); 814 lastt = mktime(&tp2); 815 816 #if 0 817 printf("%lld vs %lld\n", (long long)(baset - lastt), (long long)arg1); 818 #endif 819 820 if ((int)(baset - lastt) >= arg1) 821 return(1); 822 return(0); 823 } 824 825 /* 826 * Store the start time of the last successful operation. 827 */ 828 static void 829 save_period(const char *snapshots_path, const char *cmd, 830 time_t savet) 831 { 832 char *ocheck_path; 833 char *ncheck_path; 834 FILE *fp; 835 836 asprintf(&ocheck_path, "%s/.%s.period", snapshots_path, cmd); 837 asprintf(&ncheck_path, "%s/.%s.period.new", snapshots_path, cmd); 838 fp = fopen(ncheck_path, "w"); 839 if (fp) { 840 fprintf(fp, "0x%08llx\n", (long long)savet); 841 if (fclose(fp) == 0) 842 rename(ncheck_path, ocheck_path); 843 remove(ncheck_path); 844 } else { 845 fprintf(stderr, "hammer: Unable to create period-file %s: %s\n", 846 ncheck_path, strerror(errno)); 847 } 848 } 849 850 /* 851 * Simply count the number of softlinks in the snapshots dir 852 */ 853 static int 854 check_softlinks(int fd, int new_config, const char *snapshots_path) 855 { 856 struct dirent *den; 857 struct stat st; 858 DIR *dir; 859 char *fpath; 860 int res = 0; 861 862 /* 863 * Old-style softlink-based snapshots 864 */ 865 if ((dir = opendir(snapshots_path)) != NULL) { 866 while ((den = readdir(dir)) != NULL) { 867 if (den->d_name[0] == '.') 868 continue; 869 asprintf(&fpath, "%s/%s", snapshots_path, den->d_name); 870 if (lstat(fpath, &st) == 0 && S_ISLNK(st.st_mode)) 871 ++res; 872 free(fpath); 873 } 874 closedir(dir); 875 } 876 877 /* 878 * New-style snapshots are stored as filesystem meta-data, 879 * count those too. 880 */ 881 if (new_config) { 882 struct hammer_ioc_snapshot snapshot; 883 884 bzero(&snapshot, sizeof(snapshot)); 885 do { 886 if (ioctl(fd, HAMMERIOC_GET_SNAPSHOT, &snapshot) < 0) { 887 err(2, "hammer cleanup: check_softlink " 888 "snapshot error"); 889 /* not reached */ 890 } 891 res += snapshot.count; 892 } while (snapshot.head.error == 0 && snapshot.count); 893 } 894 return (res); 895 } 896 897 /* 898 * Clean up expired softlinks in the snapshots dir 899 */ 900 static void 901 cleanup_softlinks(int fd, int new_config, 902 const char *snapshots_path, int arg2, char *arg3) 903 { 904 struct dirent *den; 905 struct stat st; 906 DIR *dir; 907 char *fpath; 908 int anylink = 0; 909 910 if (arg3 != NULL && strstr(arg3, "any") != NULL) 911 anylink = 1; 912 913 if ((dir = opendir(snapshots_path)) != NULL) { 914 while ((den = readdir(dir)) != NULL) { 915 if (den->d_name[0] == '.') 916 continue; 917 asprintf(&fpath, "%s/%s", snapshots_path, den->d_name); 918 if (lstat(fpath, &st) == 0 && S_ISLNK(st.st_mode) && 919 (anylink || strncmp(den->d_name, "snap-", 5) == 0) 920 ) { 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