1 /* 2 * Copyright (c) 2008 The DragonFly Project. All rights reserved. 3 * 4 * This code is derived from software contributed to The DragonFly Project 5 * by Matthew Dillon <dillon@backplane.com> 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in 15 * the documentation and/or other materials provided with the 16 * distribution. 17 * 3. Neither the name of The DragonFly Project nor the names of its 18 * contributors may be used to endorse or promote products derived 19 * from this software without specific, prior written permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 24 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 25 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 26 * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, 27 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 28 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 29 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 30 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 31 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 32 * SUCH DAMAGE. 33 * 34 * $DragonFly: src/sbin/hammer/cmd_snapshot.c,v 1.7 2008/07/10 18:47:22 mneumann Exp $ 35 */ 36 37 #include "hammer.h" 38 #include <sys/param.h> 39 #include <sys/mount.h> 40 #include <sys/types.h> 41 #include <sys/stat.h> 42 #include <unistd.h> 43 #include <string.h> 44 #include <time.h> 45 46 extern char *find_pfs_mount(int pfsid, uuid_t parentuuid, int ismaster); 47 48 #define DEFAULT_SNAPSHOT_NAME "snap-%Y%m%d-%H%M" 49 50 static void snapshot_usage(int exit_code); 51 static void snapshot_add(int fd, const char *fsym, const char *tsym, 52 const char *label, hammer_tid_t tid); 53 static void snapshot_ls(const char *path); 54 static void snapshot_del(int fsfd, hammer_tid_t tid); 55 static char *dirpart(const char *path); 56 57 /* 58 * hammer snap <path> [<note>] 59 * 60 * Path may be a directory, softlink, or non-existent (a softlink will be 61 * created). 62 */ 63 void 64 hammer_cmd_snap(char **av, int ac, int tostdout, int fsbase) 65 { 66 struct hammer_ioc_synctid synctid; 67 struct hammer_ioc_version version; 68 char *dirpath; 69 char *fsym; 70 char *tsym; 71 struct stat st; 72 char note[64]; 73 int fsfd; 74 75 if (ac == 0 || ac > 2) { 76 snapshot_usage(1); 77 /* not reached */ 78 exit(1); 79 } 80 81 if (ac == 2) 82 snprintf(note, sizeof(note), "%s", av[1]); 83 else 84 note[0] = 0; 85 86 /* 87 * Figure out the softlink path and directory path 88 */ 89 if (stat(av[0], &st) < 0) { 90 dirpath = dirpart(av[0]); 91 tsym = av[0]; 92 } else if (S_ISLNK(st.st_mode)) { 93 dirpath = dirpart(av[0]); 94 tsym = av[0]; 95 } else if (S_ISDIR(st.st_mode)) { 96 time_t t = time(NULL); 97 struct tm *tp; 98 char extbuf[64]; 99 100 tp = localtime(&t); 101 strftime(extbuf, sizeof(extbuf), DEFAULT_SNAPSHOT_NAME, tp); 102 103 dirpath = strdup(av[0]); 104 asprintf(&tsym, "%s/%s", dirpath, extbuf); 105 } else { 106 err(2, "hammer snap: File %s exists and is not a softlink\n", 107 av[0]); 108 /* not reached */ 109 } 110 111 /* 112 * Get a handle on some directory in the filesystem for the 113 * ioctl (so it is stored in the correct PFS). 114 */ 115 fsfd = open(dirpath, O_RDONLY); 116 if (fsfd < 0) { 117 err(2, "hammer snap: Cannot open directory %s\n", dirpath); 118 /* not reached */ 119 } 120 121 /* 122 * Must be at least version 3 to use this command. 123 */ 124 bzero(&version, sizeof(version)); 125 126 if (ioctl(fsfd, HAMMERIOC_GET_VERSION, &version) < 0) { 127 err(2, "Unable to create snapshot"); 128 /* not reached */ 129 } else if (version.cur_version < 3) { 130 errx(2, "Unable to create snapshot: This directive requires " 131 "you to upgrade\n" 132 "the filesystem to version 3. " 133 "Use 'hammer snapshot' for legacy operation."); 134 /* not reached */ 135 } 136 137 /* 138 * Synctid to get a transaction id for the snapshot. 139 */ 140 bzero(&synctid, sizeof(synctid)); 141 synctid.op = HAMMER_SYNCTID_SYNC2; 142 if (ioctl(fsfd, HAMMERIOC_SYNCTID, &synctid) < 0) { 143 err(2, "hammer snap: Synctid %s failed", 144 dirpath); 145 } 146 if (tostdout) { 147 if (strcmp(dirpath, ".") == 0 || strcmp(dirpath, "..") == 0) { 148 printf("%s/@@0x%016jx\n", 149 dirpath, (uintmax_t)synctid.tid); 150 } else { 151 printf("%s@@0x%016jx\n", 152 dirpath, (uintmax_t)synctid.tid); 153 } 154 fsym = NULL; 155 tsym = NULL; 156 } 157 158 /* 159 * Contents of the symlink being created. 160 */ 161 if (fsbase) { 162 struct statfs buf; 163 164 if (statfs(dirpath, &buf) < 0) { 165 err(2, "hammer snap: Cannot determine mount for %s", 166 dirpath); 167 } 168 asprintf(&fsym, "%s/@@0x%016jx", 169 buf.f_mntonname, (uintmax_t)synctid.tid); 170 } else if (strcmp(dirpath, ".") == 0 || strcmp(dirpath, "..") == 0) { 171 asprintf(&fsym, "%s/@@0x%016jx", 172 dirpath, (uintmax_t)synctid.tid); 173 } else { 174 asprintf(&fsym, "%s@@0x%016jx", 175 dirpath, (uintmax_t)synctid.tid); 176 } 177 178 /* 179 * Create the snapshot. 180 */ 181 snapshot_add(fsfd, fsym, tsym, note, synctid.tid); 182 free(dirpath); 183 } 184 185 /* 186 * hammer snapls [<path> ...] 187 * 188 * If no arguments are specified snapshots for the PFS containing the 189 * current directory are listed. 190 */ 191 void 192 hammer_cmd_snapls(char **av, int ac) 193 { 194 int i; 195 196 for (i = 0; i < ac; ++i) 197 snapshot_ls(av[i]); 198 if (ac == 0) 199 snapshot_ls("."); 200 } 201 202 /* 203 * hammer snaprm <path> ... 204 * hammer snaprm <transid> ... 205 * hammer snaprm <filesystem> <transid> ... 206 */ 207 void 208 hammer_cmd_snaprm(char **av, int ac) 209 { 210 struct stat st; 211 char linkbuf[1024]; 212 intmax_t tid; 213 int fsfd = -1; 214 int i; 215 int delete; 216 enum snaprm_mode { none_m, path_m, tid_m } mode = none_m; 217 char *dirpath; 218 char *ptr, *ptr2; 219 220 if (ac == 0) { 221 snapshot_usage(1); 222 /* not reached */ 223 } 224 225 for (i = 0; i < ac; ++i) { 226 if (lstat(av[i], &st) < 0) { 227 tid = strtoull(av[i], &ptr, 16); 228 if (*ptr) { 229 err(2, "hammer snaprm: not a file or tid: %s", 230 av[i]); 231 /* not reached */ 232 } 233 if (mode == path_m) { 234 snapshot_usage(1); 235 /* not reached */ 236 } 237 mode = tid_m; 238 if (fsfd < 0) 239 fsfd = open(".", O_RDONLY); 240 snapshot_del(fsfd, tid); 241 } else if (S_ISDIR(st.st_mode)) { 242 if (i != 0 || ac < 2) { 243 snapshot_usage(1); 244 /* not reached */ 245 } 246 if (fsfd >= 0) 247 close(fsfd); 248 fsfd = open(av[i], O_RDONLY); 249 if (fsfd < 0) { 250 err(2, "hammer snaprm: cannot open dir %s", 251 av[i]); 252 /* not reached */ 253 } 254 mode = tid_m; 255 } else if (S_ISLNK(st.st_mode)) { 256 dirpath = dirpart(av[i]); 257 bzero(linkbuf, sizeof(linkbuf)); 258 if (readlink(av[i], linkbuf, sizeof(linkbuf) - 1) < 0) { 259 err(2, "hammer snaprm: cannot read softlink: " 260 "%s", av[i]); 261 /* not reached */ 262 } 263 if (linkbuf[0] == '/') { 264 free(dirpath); 265 dirpath = dirpart(linkbuf); 266 } else { 267 asprintf(&ptr, "%s/%s", dirpath, linkbuf); 268 free(dirpath); 269 dirpath = dirpart(ptr); 270 } 271 272 if (fsfd >= 0) 273 close(fsfd); 274 fsfd = open(dirpath, O_RDONLY); 275 if (fsfd < 0) { 276 err(2, "hammer snaprm: cannot open dir %s", 277 dirpath); 278 /* not reached */ 279 } 280 281 delete = 1; 282 if (i == 0 && ac > 1) { 283 mode = path_m; 284 if (lstat(av[1], &st) < 0) { 285 tid = strtoull(av[1], &ptr, 16); 286 if (*ptr == '\0') { 287 delete = 0; 288 mode = tid_m; 289 } 290 } 291 } else { 292 if (mode == tid_m) { 293 snapshot_usage(1); 294 /* not reached */ 295 } 296 mode = path_m; 297 } 298 if (delete && (ptr = strrchr(linkbuf, '@')) && 299 ptr > linkbuf && ptr[-1] == '@' && ptr[1]) { 300 tid = strtoull(ptr + 1, &ptr2, 16); 301 if (*ptr2 == '\0') { 302 snapshot_del(fsfd, tid); 303 remove(av[i]); 304 } 305 } 306 free(dirpath); 307 } else { 308 err(2, "hammer snaprm: not directory or snapshot " 309 "softlink: %s", av[i]); 310 /* not reached */ 311 } 312 } 313 if (fsfd >= 0) 314 close(fsfd); 315 } 316 317 /* 318 * snapshot <softlink-dir> 319 * snapshot <filesystem> <softlink-dir> [<note>] 320 */ 321 void 322 hammer_cmd_snapshot(char **av, int ac) 323 { 324 const char *filesystem; 325 const char *softlink_dir; 326 char *softlink_fmt; 327 struct statfs buf; 328 struct stat st; 329 struct hammer_ioc_synctid synctid; 330 char *from; 331 char *to; 332 char *note = NULL; 333 334 if (ac == 1) { 335 filesystem = NULL; 336 softlink_dir = av[0]; 337 } else if (ac == 2) { 338 filesystem = av[0]; 339 softlink_dir = av[1]; 340 } else if (ac == 3) { 341 filesystem = av[0]; 342 softlink_dir = av[1]; 343 note = av[2]; 344 } else { 345 snapshot_usage(1); 346 /* not reached */ 347 softlink_dir = NULL; 348 filesystem = NULL; 349 } 350 351 if (stat(softlink_dir, &st) == 0) { 352 if (!S_ISDIR(st.st_mode)) 353 err(2, "File %s already exists", softlink_dir); 354 355 if (filesystem == NULL) { 356 if (statfs(softlink_dir, &buf) != 0) { 357 err(2, "Unable to determine filesystem of %s", 358 softlink_dir); 359 } 360 filesystem = buf.f_mntonname; 361 } 362 363 softlink_fmt = malloc(strlen(softlink_dir) + 1 + 1 + 364 sizeof(DEFAULT_SNAPSHOT_NAME)); 365 if (softlink_fmt == NULL) 366 err(2, "Failed to allocate string"); 367 368 strcpy(softlink_fmt, softlink_dir); 369 if (softlink_fmt[strlen(softlink_fmt)-1] != '/') 370 strcat(softlink_fmt, "/"); 371 strcat(softlink_fmt, DEFAULT_SNAPSHOT_NAME); 372 } else { 373 softlink_fmt = strdup(softlink_dir); 374 375 if (filesystem == NULL) { 376 /* 377 * strip-off last '/path' segment to get the softlink 378 * directory, which we need to determine the filesystem 379 * we are on. 380 */ 381 char *pos = strrchr(softlink_fmt, '/'); 382 if (pos != NULL) 383 *pos = '\0'; 384 385 if (stat(softlink_fmt, &st) != 0 || 386 !S_ISDIR(st.st_mode)) { 387 err(2, "Unable to determine softlink dir %s", 388 softlink_fmt); 389 } 390 if (statfs(softlink_fmt, &buf) != 0) { 391 err(2, "Unable to determine filesystem of %s", 392 softlink_fmt); 393 } 394 filesystem = buf.f_mntonname; 395 396 /* restore '/' */ 397 if (pos != NULL) 398 *pos = '/'; 399 } 400 } 401 402 /* 403 * Synctid 404 */ 405 bzero(&synctid, sizeof(synctid)); 406 synctid.op = HAMMER_SYNCTID_SYNC2; 407 408 int fd = open(filesystem, O_RDONLY); 409 if (fd < 0) 410 err(2, "Unable to open %s", filesystem); 411 if (ioctl(fd, HAMMERIOC_SYNCTID, &synctid) < 0) 412 err(2, "Synctid %s failed", filesystem); 413 414 asprintf(&from, "%s/@@0x%016jx", filesystem, (uintmax_t)synctid.tid); 415 if (from == NULL) 416 err(2, "Couldn't generate string"); 417 418 int sz = strlen(softlink_fmt) + 50; 419 to = malloc(sz); 420 if (to == NULL) 421 err(2, "Failed to allocate string"); 422 423 time_t t = time(NULL); 424 if (strftime(to, sz, softlink_fmt, localtime(&t)) == 0) 425 err(2, "String buffer too small"); 426 427 asprintf(&from, "%s/@@0x%016jx", filesystem, (uintmax_t)synctid.tid); 428 429 snapshot_add(fd, from, to, note, synctid.tid); 430 431 close(fd); 432 printf("%s\n", to); 433 434 free(softlink_fmt); 435 free(from); 436 free(to); 437 } 438 439 static 440 void 441 snapshot_add(int fd, const char *fsym, const char *tsym, const char *label, 442 hammer_tid_t tid) 443 { 444 struct hammer_ioc_version version; 445 struct hammer_ioc_snapshot snapshot; 446 447 bzero(&version, sizeof(version)); 448 bzero(&snapshot, sizeof(snapshot)); 449 450 /* 451 * For HAMMER filesystem v3+ the snapshot is recorded in meta-data. 452 */ 453 if (ioctl(fd, HAMMERIOC_GET_VERSION, &version) == 0 && 454 version.cur_version >= 3) { 455 snapshot.index = 0; 456 snapshot.count = 1; 457 snapshot.snaps[0].tid = tid; 458 snapshot.snaps[0].ts = time(NULL) * 1000000ULL; 459 if (label) { 460 snprintf(snapshot.snaps[0].label, 461 sizeof(snapshot.snaps[0].label), 462 "%s", 463 label); 464 } 465 if (ioctl(fd, HAMMERIOC_ADD_SNAPSHOT, &snapshot) < 0) { 466 err(2, "Unable to create snapshot"); 467 } else if (snapshot.head.error && 468 snapshot.head.error != EEXIST) { 469 errx(2, "Unable to create snapshot: %s\n", 470 strerror(snapshot.head.error)); 471 } 472 } 473 474 /* 475 * Create a symlink for the snapshot. If a file exists with the same 476 * name the new symlink will replace it. 477 */ 478 if (fsym && tsym) { 479 remove(tsym); 480 if (symlink(fsym, tsym) < 0) { 481 err(2, "Unable to create symlink %s", tsym); 482 } 483 } 484 } 485 486 static 487 void 488 snapshot_ls(const char *path) 489 { 490 /*struct hammer_ioc_version version;*/ 491 struct hammer_ioc_info info; 492 struct hammer_ioc_snapshot snapshot; 493 struct hammer_ioc_pseudofs_rw pfs; 494 struct hammer_pseudofs_data pfs_od; 495 struct hammer_snapshot_data *snap; 496 struct tm *tp; 497 time_t t; 498 u_int32_t i; 499 int fd, ismaster; 500 char snapts[64]; 501 char *mntpoint; 502 503 fd = open(path, O_RDONLY); 504 if (fd < 0) { 505 err(2, "hammer snapls: cannot open %s", path); 506 /* not reached */ 507 } 508 509 bzero(&pfs, sizeof(pfs)); 510 bzero(&pfs_od, sizeof(pfs_od)); 511 pfs.pfs_id = -1; 512 pfs.ondisk = &pfs_od; 513 pfs.bytes = sizeof(struct hammer_pseudofs_data); 514 if (ioctl(fd, HAMMERIOC_GET_PSEUDOFS, &pfs) < 0) { 515 err(2, "hammer snapls: cannot retrieve PFS info on %s", path); 516 /* not reached */ 517 } 518 519 bzero(&info, sizeof(info)); 520 if ((ioctl(fd, HAMMERIOC_GET_INFO, &info)) < 0) { 521 err(2, "hammer snapls: cannot retrieve HAMMER info"); 522 /* not reached */ 523 } 524 525 ismaster = (pfs_od.mirror_flags & HAMMER_PFSD_SLAVE) ? 0 : 1; 526 mntpoint = libhammer_find_pfs_mount(pfs.pfs_id, info.vol_fsid, ismaster); 527 528 /* Note the degenerate case of PFS #0 */ 529 printf("Snapshots on %s\tPFS #%d\n", 530 pfs.pfs_id == 0 ? path : mntpoint, pfs.pfs_id); 531 printf("Transaction ID\t\tTimestamp\t\tNote\n"); 532 533 if (mntpoint) 534 free(mntpoint); 535 536 bzero(&snapshot, sizeof(snapshot)); 537 do { 538 if (ioctl(fd, HAMMERIOC_GET_SNAPSHOT, &snapshot) < 0) { 539 err(2, "hammer snapls: %s: not HAMMER fs or " 540 "version < 3", path); 541 /* not reached */ 542 } 543 for (i = 0; i < snapshot.count; ++i) { 544 snap = &snapshot.snaps[i]; 545 546 t = snap->ts / 1000000ULL; 547 tp = localtime(&t); 548 strftime(snapts, sizeof(snapts), 549 "%Y-%m-%d %H:%M:%S %Z", tp); 550 printf("0x%016jx\t%s\t%s\n", 551 (uintmax_t)snap->tid, snapts, 552 strlen(snap->label) ? snap->label : "-"); 553 } 554 } while (snapshot.head.error == 0 && snapshot.count); 555 } 556 557 static 558 void 559 snapshot_del(int fsfd, hammer_tid_t tid) 560 { 561 struct hammer_ioc_snapshot snapshot; 562 struct hammer_ioc_version version; 563 564 bzero(&version, sizeof(version)); 565 566 if (ioctl(fsfd, HAMMERIOC_GET_VERSION, &version) < 0) { 567 err(2, "hammer snaprm 0x%016jx", (uintmax_t)tid); 568 } 569 if (version.cur_version < 3) { 570 errx(2, "hammer snaprm 0x%016jx: You must upgrade to version " 571 " 3 to use this directive", (uintmax_t)tid); 572 } 573 574 bzero(&snapshot, sizeof(snapshot)); 575 snapshot.count = 1; 576 snapshot.snaps[0].tid = tid; 577 578 /* 579 * Do not abort if we are unable to remove the meta-data. 580 */ 581 if (ioctl(fsfd, HAMMERIOC_DEL_SNAPSHOT, &snapshot) < 0) { 582 err(2, "hammer snaprm 0x%016jx", 583 (uintmax_t)tid); 584 } else if (snapshot.head.error == ENOENT) { 585 fprintf(stderr, "Warning: hammer snaprm 0x%016jx: " 586 "meta-data not found\n", 587 (uintmax_t)tid); 588 } else if (snapshot.head.error) { 589 fprintf(stderr, "Warning: hammer snaprm 0x%016jx: %s\n", 590 (uintmax_t)tid, strerror(snapshot.head.error)); 591 } 592 } 593 594 static 595 void 596 snapshot_usage(int exit_code) 597 { 598 fprintf(stderr, 599 "hammer snap <path> [<note>]\t\tcreate snapshot & link, points to\n" 600 "\t\t\t\t\tbase of PFS mount\n" 601 "hammer snaplo <path> [<note>]\t\tcreate snapshot & link, points to\n" 602 "\t\t\t\t\ttarget dir\n" 603 "hammer snapq <dir> [<note>]\t\tcreate snapshot, output path to stdout\n" 604 "hammer snaprm <path> ...\t\tdelete snapshots; filesystem is CWD\n" 605 "hammer snaprm <transid> ...\t\tdelete snapshots\n" 606 "hammer snaprm <filesystem> <transid> ...\tdelete snapshots\n" 607 "hammer snapls [<path> ...]\t\tlist available snapshots\n" 608 "\n" 609 "NOTE: Snapshots are created in filesystem meta-data, any directory\n" 610 " in a HAMMER filesystem or PFS may be specified. If the path\n" 611 " specified does not exist this function will also create a\n" 612 " softlink.\n" 613 "\n" 614 " When deleting snapshots transaction ids may be directly specified\n" 615 " or file paths to snapshot softlinks may be specified. If a\n" 616 " softlink is specified the softlink will also be deleted.\n" 617 "\n" 618 "NOTE: The old 'hammer snapshot [<filesystem>] <snapshot-dir>' form\n" 619 " is still accepted but is a deprecated form. This form will\n" 620 " work for older hammer versions. The new forms only work for\n" 621 " HAMMER version 3 or later filesystems. HAMMER can be upgraded\n" 622 " to version 3 in-place.\n" 623 ); 624 exit(exit_code); 625 } 626 627 static 628 char * 629 dirpart(const char *path) 630 { 631 const char *ptr; 632 char *res; 633 634 ptr = strrchr(path, '/'); 635 if (ptr) { 636 while (ptr > path && ptr[-1] == '/') 637 --ptr; 638 if (ptr == path) 639 ptr = NULL; 640 } 641 if (ptr == NULL) { 642 path = "."; 643 ptr = path + 1; 644 } 645 res = malloc(ptr - path + 1); 646 bcopy(path, res, ptr - path); 647 res[ptr - path] = 0; 648 return(res); 649 } 650