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