Merge branch 'vendor/GCC50'
[dragonfly.git] / sbin / hammer / cmd_snapshot.c
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 }