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