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