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 char *dirpath; 216 char *ptr; 217 218 for (i = 0; i < ac; ++i) { 219 if (lstat(av[i], &st) < 0) { 220 tid = strtoull(av[i], &ptr, 16); 221 if (*ptr) { 222 err(2, "hammer snaprm: not a file or tid: %s", 223 av[i]); 224 /* not reached */ 225 } 226 if (fsfd < 0) 227 fsfd = open(".", O_RDONLY); 228 snapshot_del(fsfd, tid); 229 } else if (S_ISDIR(st.st_mode)) { 230 if (fsfd >= 0) 231 close(fsfd); 232 fsfd = open(av[i], O_RDONLY); 233 if (fsfd < 0) { 234 err(2, "hammer snaprm: cannot open dir %s", 235 av[i]); 236 /* not reached */ 237 } 238 } else if (S_ISLNK(st.st_mode)) { 239 dirpath = dirpart(av[i]); 240 bzero(linkbuf, sizeof(linkbuf)); 241 if (readlink(av[i], linkbuf, sizeof(linkbuf) - 1) < 0) { 242 err(2, "hammer snaprm: cannot read softlink: " 243 "%s", av[i]); 244 /* not reached */ 245 } 246 if (linkbuf[0] == '/') { 247 free(dirpath); 248 dirpath = dirpart(linkbuf); 249 } else { 250 asprintf(&ptr, "%s/%s", dirpath, linkbuf); 251 free(dirpath); 252 dirpath = dirpart(ptr); 253 } 254 255 if (fsfd >= 0) 256 close(fsfd); 257 fsfd = open(dirpath, O_RDONLY); 258 if (fsfd < 0) { 259 err(2, "hammer snaprm: cannot open dir %s", 260 dirpath); 261 /* not reached */ 262 } 263 264 if ((ptr = strrchr(linkbuf, '@')) && 265 ptr > linkbuf && ptr[-1] == '@') { 266 tid = strtoull(ptr + 1, NULL, 16); 267 snapshot_del(fsfd, tid); 268 } 269 remove(av[i]); 270 free(dirpath); 271 } else { 272 err(2, "hammer snaprm: not directory or snapshot " 273 "softlink: %s", av[i]); 274 /* not reached */ 275 } 276 } 277 if (fsfd >= 0) 278 close(fsfd); 279 } 280 281 /* 282 * snapshot <softlink-dir> 283 * snapshot <filesystem> <softlink-dir> [<note>] 284 */ 285 void 286 hammer_cmd_snapshot(char **av, int ac) 287 { 288 const char *filesystem; 289 const char *softlink_dir; 290 char *softlink_fmt; 291 struct statfs buf; 292 struct stat st; 293 struct hammer_ioc_synctid synctid; 294 char *from; 295 char *to; 296 char *note = NULL; 297 298 if (ac == 1) { 299 filesystem = NULL; 300 softlink_dir = av[0]; 301 } else if (ac == 2) { 302 filesystem = av[0]; 303 softlink_dir = av[1]; 304 } else if (ac == 3) { 305 filesystem = av[0]; 306 softlink_dir = av[1]; 307 note = av[2]; 308 } else { 309 snapshot_usage(1); 310 /* not reached */ 311 softlink_dir = NULL; 312 filesystem = NULL; 313 } 314 315 if (stat(softlink_dir, &st) == 0) { 316 if (!S_ISDIR(st.st_mode)) 317 err(2, "File %s already exists", softlink_dir); 318 319 if (filesystem == NULL) { 320 if (statfs(softlink_dir, &buf) != 0) { 321 err(2, "Unable to determine filesystem of %s", 322 softlink_dir); 323 } 324 filesystem = buf.f_mntonname; 325 } 326 327 softlink_fmt = malloc(strlen(softlink_dir) + 1 + 1 + 328 sizeof(DEFAULT_SNAPSHOT_NAME)); 329 if (softlink_fmt == NULL) 330 err(2, "Failed to allocate string"); 331 332 strcpy(softlink_fmt, softlink_dir); 333 if (softlink_fmt[strlen(softlink_fmt)-1] != '/') 334 strcat(softlink_fmt, "/"); 335 strcat(softlink_fmt, DEFAULT_SNAPSHOT_NAME); 336 } else { 337 softlink_fmt = strdup(softlink_dir); 338 339 if (filesystem == NULL) { 340 /* 341 * strip-off last '/path' segment to get the softlink 342 * directory, which we need to determine the filesystem 343 * we are on. 344 */ 345 char *pos = strrchr(softlink_fmt, '/'); 346 if (pos != NULL) 347 *pos = '\0'; 348 349 if (stat(softlink_fmt, &st) != 0 || 350 !S_ISDIR(st.st_mode)) { 351 err(2, "Unable to determine softlink dir %s", 352 softlink_fmt); 353 } 354 if (statfs(softlink_fmt, &buf) != 0) { 355 err(2, "Unable to determine filesystem of %s", 356 softlink_fmt); 357 } 358 filesystem = buf.f_mntonname; 359 360 /* restore '/' */ 361 if (pos != NULL) 362 *pos = '/'; 363 } 364 } 365 366 /* 367 * Synctid 368 */ 369 bzero(&synctid, sizeof(synctid)); 370 synctid.op = HAMMER_SYNCTID_SYNC2; 371 372 int fd = open(filesystem, O_RDONLY); 373 if (fd < 0) 374 err(2, "Unable to open %s", filesystem); 375 if (ioctl(fd, HAMMERIOC_SYNCTID, &synctid) < 0) 376 err(2, "Synctid %s failed", filesystem); 377 378 asprintf(&from, "%s/@@0x%016jx", filesystem, (uintmax_t)synctid.tid); 379 if (from == NULL) 380 err(2, "Couldn't generate string"); 381 382 int sz = strlen(softlink_fmt) + 50; 383 to = malloc(sz); 384 if (to == NULL) 385 err(2, "Failed to allocate string"); 386 387 time_t t = time(NULL); 388 if (strftime(to, sz, softlink_fmt, localtime(&t)) == 0) 389 err(2, "String buffer too small"); 390 391 asprintf(&from, "%s/@@0x%016jx", filesystem, (uintmax_t)synctid.tid); 392 393 snapshot_add(fd, from, to, note, synctid.tid); 394 395 close(fd); 396 printf("%s\n", to); 397 398 free(softlink_fmt); 399 free(from); 400 free(to); 401 } 402 403 static 404 void 405 snapshot_add(int fd, const char *fsym, const char *tsym, const char *label, 406 hammer_tid_t tid) 407 { 408 struct hammer_ioc_version version; 409 struct hammer_ioc_snapshot snapshot; 410 411 bzero(&version, sizeof(version)); 412 bzero(&snapshot, sizeof(snapshot)); 413 414 /* 415 * For HAMMER filesystem v3+ the snapshot is recorded in meta-data. 416 */ 417 if (ioctl(fd, HAMMERIOC_GET_VERSION, &version) == 0 && 418 version.cur_version >= 3) { 419 snapshot.index = 0; 420 snapshot.count = 1; 421 snapshot.snaps[0].tid = tid; 422 snapshot.snaps[0].ts = time(NULL) * 1000000ULL; 423 if (label) { 424 snprintf(snapshot.snaps[0].label, 425 sizeof(snapshot.snaps[0].label), 426 "%s", 427 label); 428 } 429 if (ioctl(fd, HAMMERIOC_ADD_SNAPSHOT, &snapshot) < 0) { 430 err(2, "Unable to create snapshot"); 431 } else if (snapshot.head.error && 432 snapshot.head.error != EEXIST) { 433 errx(2, "Unable to create snapshot: %s\n", 434 strerror(snapshot.head.error)); 435 } 436 } 437 438 /* 439 * Create a symlink for the snapshot. If a file exists with the same 440 * name the new symlink will replace it. 441 */ 442 if (fsym && tsym) { 443 remove(tsym); 444 if (symlink(fsym, tsym) < 0) { 445 err(2, "Unable to create symlink %s", tsym); 446 } 447 } 448 } 449 450 static 451 void 452 snapshot_ls(const char *path) 453 { 454 /*struct hammer_ioc_version version;*/ 455 struct hammer_ioc_info info; 456 struct hammer_ioc_snapshot snapshot; 457 struct hammer_ioc_pseudofs_rw pfs; 458 struct hammer_pseudofs_data pfs_od; 459 struct hammer_snapshot_data *snap; 460 struct tm *tp; 461 time_t t; 462 u_int32_t i; 463 int fd, ismaster; 464 char snapts[64]; 465 char *mntpoint; 466 467 fd = open(path, O_RDONLY); 468 if (fd < 0) { 469 err(2, "hammer snapls: cannot open %s", path); 470 /* not reached */ 471 } 472 473 bzero(&pfs, sizeof(pfs)); 474 bzero(&pfs_od, sizeof(pfs_od)); 475 pfs.pfs_id = -1; 476 pfs.ondisk = &pfs_od; 477 pfs.bytes = sizeof(struct hammer_pseudofs_data); 478 if (ioctl(fd, HAMMERIOC_GET_PSEUDOFS, &pfs) < 0) { 479 err(2, "hammer snapls: cannot retrieve PFS info on %s", path); 480 /* not reached */ 481 } 482 483 bzero(&info, sizeof(info)); 484 if ((ioctl(fd, HAMMERIOC_GET_INFO, &info)) < 0) { 485 err(2, "hammer snapls: cannot retrieve HAMMER info"); 486 /* not reached */ 487 } 488 489 ismaster = (pfs_od.mirror_flags & HAMMER_PFSD_SLAVE) ? 0 : 1; 490 mntpoint = find_pfs_mount(pfs.pfs_id, info.vol_fsid, ismaster); 491 492 /* Note the degenerate case of PFS #0 */ 493 printf("Snapshots on %s\tPFS #%d\n", 494 pfs.pfs_id == 0 ? path : mntpoint, pfs.pfs_id); 495 printf("Transaction ID\t\tTimestamp\t\tNote\n"); 496 497 if (mntpoint) 498 free(mntpoint); 499 500 bzero(&snapshot, sizeof(snapshot)); 501 do { 502 if (ioctl(fd, HAMMERIOC_GET_SNAPSHOT, &snapshot) < 0) { 503 err(2, "hammer snapls: %s: not HAMMER fs or " 504 "version < 3", path); 505 /* not reached */ 506 } 507 for (i = 0; i < snapshot.count; ++i) { 508 snap = &snapshot.snaps[i]; 509 510 t = snap->ts / 1000000ULL; 511 tp = localtime(&t); 512 strftime(snapts, sizeof(snapts), 513 "%Y-%m-%d %H:%M:%S %Z", tp); 514 printf("0x%016jx\t%s\t%s\n", 515 (uintmax_t)snap->tid, snapts, 516 strlen(snap->label) ? snap->label : "-"); 517 } 518 } while (snapshot.head.error == 0 && snapshot.count); 519 } 520 521 static 522 void 523 snapshot_del(int fsfd, hammer_tid_t tid) 524 { 525 struct hammer_ioc_snapshot snapshot; 526 struct hammer_ioc_version version; 527 528 bzero(&version, sizeof(version)); 529 530 if (ioctl(fsfd, HAMMERIOC_GET_VERSION, &version) < 0) { 531 err(2, "hammer snaprm 0x%016jx", (uintmax_t)tid); 532 } 533 if (version.cur_version < 3) { 534 errx(2, "hammer snaprm 0x%016jx: You must upgrade to version " 535 " 3 to use this directive", (uintmax_t)tid); 536 } 537 538 bzero(&snapshot, sizeof(snapshot)); 539 snapshot.count = 1; 540 snapshot.snaps[0].tid = tid; 541 542 /* 543 * Do not abort if we are unable to remove the meta-data. 544 */ 545 if (ioctl(fsfd, HAMMERIOC_DEL_SNAPSHOT, &snapshot) < 0) { 546 err(2, "hammer snaprm 0x%016jx", 547 (uintmax_t)tid); 548 } else if (snapshot.head.error == ENOENT) { 549 fprintf(stderr, "Warning: hammer snaprm 0x%016jx: " 550 "meta-data not found\n", 551 (uintmax_t)tid); 552 } else if (snapshot.head.error) { 553 fprintf(stderr, "Warning: hammer snaprm 0x%016jx: %s\n", 554 (uintmax_t)tid, strerror(snapshot.head.error)); 555 } 556 } 557 558 static 559 void 560 snapshot_usage(int exit_code) 561 { 562 fprintf(stderr, 563 "hammer snap <path> [<note>]\t\tcreate snapshot & link, points to\n" 564 "\t\t\t\t\tbase of PFS mount\n" 565 "hammer snaplo <path> [<note>]\t\tcreate snapshot & link, points to\n" 566 "\t\t\t\t\ttarget dir\n" 567 "hammer snapq <dir> [<note>]\t\tcreate snapshot, output path to stdout\n" 568 "hammer snaprm <path> ...\t\tdelete snapshots; filesystem is CWD\n" 569 "hammer snaprm <transid> ...\t\tdelete snapshots\n" 570 "hammer snaprm <filesystem> <transid> ...\tdelete snapshots\n" 571 "hammer snapls [<path> ...]\t\tlist available snapshots\n" 572 "\n" 573 "NOTE: Snapshots are created in filesystem meta-data, any directory\n" 574 " in a HAMMER filesystem or PFS may be specified. If the path\n" 575 " specified does not exist this function will also create a\n" 576 " softlink.\n" 577 "\n" 578 " When deleting snapshots transaction ids may be directly specified\n" 579 " or file paths to snapshot softlinks may be specified. If a\n" 580 " softlink is specified the softlink will also be deleted.\n" 581 "\n" 582 "NOTE: The old 'hammer snapshot [<filesystem>] <snapshot-dir>' form\n" 583 " is still accepted but is a deprecated form. This form will\n" 584 " work for older hammer versions. The new forms only work for\n" 585 " HAMMER version 3 or later filesystems. HAMMER can be upgraded\n" 586 " to version 3 in-place.\n" 587 ); 588 exit(exit_code); 589 } 590 591 static 592 char * 593 dirpart(const char *path) 594 { 595 const char *ptr; 596 char *res; 597 598 ptr = strrchr(path, '/'); 599 if (ptr) { 600 while (ptr > path && ptr[-1] == '/') 601 --ptr; 602 if (ptr == path) 603 ptr = NULL; 604 } 605 if (ptr == NULL) { 606 path = "."; 607 ptr = path + 1; 608 } 609 res = malloc(ptr - path + 1); 610 bcopy(path, res, ptr - path); 611 res[ptr - path] = 0; 612 return(res); 613 } 614