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