sbin/hammer: Add /* not reached */
[dragonfly.git] / sbin / hammer / cmd_pfs.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_pseudofs.c,v 1.12 2008/10/08 21:01:54 thomas Exp $
35  */
36
37 #include <libgen.h>
38
39 #include "hammer.h"
40
41 static int scanpfsid(struct hammer_ioc_pseudofs_rw *pfs, const char *path);
42 static void parse_pfsd_options(char **av, int ac, hammer_pseudofs_data_t pfsd);
43 static void init_pfsd(hammer_pseudofs_data_t pfsd, int is_slave);
44 static void pseudofs_usage(int code);
45 static int timetosecs(char *str);
46
47 void
48 clrpfs(struct hammer_ioc_pseudofs_rw *pfs, hammer_pseudofs_data_t pfsd,
49         int pfs_id)
50 {
51         bzero(pfs, sizeof(*pfs));
52
53         if (pfsd)
54                 pfs->ondisk = pfsd;
55         else
56                 pfs->ondisk = malloc(sizeof(*pfs->ondisk));
57         bzero(pfs->ondisk, sizeof(*pfs->ondisk));
58
59         pfs->pfs_id = pfs_id;
60         pfs->bytes = sizeof(*pfs->ondisk);
61         pfs->version = HAMMER_IOC_PSEUDOFS_VERSION;
62 }
63
64 /*
65  * If path is a symlink, return strdup'd path.
66  * If it's a directory via symlink, strip trailing /
67  * from strdup'd path and return the symlink.
68  */
69 static char*
70 getlink(const char *path)
71 {
72         int i;
73         char *linkpath;
74         struct stat st;
75
76         if (lstat(path, &st))
77                 return(NULL);
78         linkpath = strdup(path);
79
80         if (S_ISDIR(st.st_mode)) {
81                 i = strlen(linkpath) - 1;
82                 while (i > 0 && linkpath[i] == '/')
83                         linkpath[i--] = 0;
84                 lstat(linkpath, &st);
85         }
86         if (S_ISLNK(st.st_mode))
87                 return(linkpath);
88
89         free(linkpath);
90         return(NULL);
91 }
92
93 /*
94  * Calculate the PFS id given a path to a file/directory or
95  * a @@%llx:%d softlink.
96  */
97 int
98 getpfs(struct hammer_ioc_pseudofs_rw *pfs, const char *path)
99 {
100         int fd;
101
102         clrpfs(pfs, NULL, -1);
103
104         /*
105          * Extract the PFS id.
106          * dirname(path) is supposed to be a directory in root PFS.
107          */
108         if (scanpfsid(pfs, path) == 0)
109                 path = dirname(path); /* strips trailing / first if any */
110
111         /*
112          * Open the path regardless of scanpfsid() result, since some
113          * commands can take a regular file/directory (e.g. pfs-status).
114          */
115         fd = open(path, O_RDONLY);
116         if (fd < 0) {
117                 err(1, "Failed to open %s", path);
118                 /* not reached */
119         }
120
121         /*
122          * If pfs.pfs_id has been set to non -1, the file descriptor fd
123          * could be any fd of HAMMER inodes since HAMMERIOC_GET_PSEUDOFS
124          * doesn't depend on inode attributes if it's set to a valid id.
125          */
126         if (ioctl(fd, HAMMERIOC_GET_PSEUDOFS, pfs) < 0) {
127                 err(1, "Cannot access %s", path);
128                 /* not reached */
129         }
130
131         return(fd);
132 }
133
134 /*
135  * Extract the PFS id from path.
136  */
137 static int
138 scanpfsid(struct hammer_ioc_pseudofs_rw *pfs, const char *path)
139 {
140         char *linkpath;
141         char buf[64];
142         uintmax_t dummy_tid;
143         struct stat st;
144
145         if (stat(path, &st))
146                 ; /* possibly slave PFS */
147         else if (S_ISDIR(st.st_mode))
148                 ; /* possibly master or slave PFS */
149         else
150                 return(-1);  /* neither */
151
152         linkpath = getlink(path);
153         if (linkpath) {
154                 /*
155                  * Read the symlink assuming it's a link to PFS.
156                  */
157                 bzero(buf, sizeof(buf));
158                 if (readlink(linkpath, buf, sizeof(buf) - 1) < 0) {
159                         free(linkpath);
160                         return(-1);
161                 }
162                 free(linkpath);
163                 path = buf;
164         }
165
166         /*
167          * The symlink created by pfs-master|slave is just a symlink.
168          * One could happen to remove a symlink and relink PFS as
169          * # ln -s ./@@-1:00001 ./link
170          * which results PFS having something extra before @@.
171          * One could also directly use the PFS and results the same.
172          * Get rid of it before we extract the PFS id.
173          */
174         if (strchr(path, '/')) {
175                 path = basename(path); /* strips trailing / first if any */
176                 if (path == NULL) {
177                         err(1, "basename");
178                         /* not reached */
179                 }
180         }
181
182         /*
183          * Test and extract the PFS id from the link.
184          * "@@%jx:%d" covers both "@@-1:%05d" format for master PFS
185          * and "@@0x%016jx:%05d" format for slave PFS.
186          */
187         if (sscanf(path, "@@%jx:%d", &dummy_tid, &pfs->pfs_id) == 2) {
188                 assert(pfs->pfs_id > 0);
189                 return(0);
190         }
191
192         return(-1);
193 }
194
195 void
196 relpfs(int fd, struct hammer_ioc_pseudofs_rw *pfs)
197 {
198         if (fd >= 0)
199                 close(fd);
200         if (pfs->ondisk) {
201                 free(pfs->ondisk);
202                 pfs->ondisk = NULL;
203         }
204 }
205
206 static void
207 print_pfs_status(char *path)
208 {
209         struct hammer_ioc_pseudofs_rw pfs;
210         int fd;
211
212         fd = getpfs(&pfs, path);
213         printf("%s\t", path);
214         if (fd < 0 || ioctl(fd, HAMMERIOC_GET_PSEUDOFS, &pfs) < 0) {
215                 printf("Invalid PFS path %s\n", path);
216         } else {
217                 printf("PFS#%d {\n", pfs.pfs_id);
218                 dump_pfsd(pfs.ondisk, fd);
219                 printf("}\n");
220         }
221         if (fd >= 0)
222                 close(fd);
223         if (pfs.ondisk)
224                 free(pfs.ondisk);
225         relpfs(fd, &pfs);
226 }
227
228 void
229 hammer_cmd_pseudofs_status(char **av, int ac)
230 {
231         int i;
232
233         if (ac == 0) {
234                 char buf[2] = "."; /* can't be readonly string */
235                 print_pfs_status(buf);
236                 return;
237         }
238
239         for (i = 0; i < ac; ++i)
240                 print_pfs_status(av[i]);
241 }
242
243 void
244 hammer_cmd_pseudofs_create(char **av, int ac, int is_slave)
245 {
246         struct hammer_ioc_pseudofs_rw pfs;
247         struct hammer_pseudofs_data pfsd;
248         struct stat st;
249         const char *path;
250         char *dirpath;
251         char *linkpath;
252         int pfs_id;
253         int fd;
254
255         if (ac == 0)
256                 pseudofs_usage(1);
257         path = av[0];
258         if (lstat(path, &st) == 0) {
259                 errx(1, "Cannot create %s, file exists!", path);
260                 /* not reached */
261         } else if (path[strlen(path) - 1] == '/') {
262                 errx(1, "Invalid PFS path %s with trailing /", path);
263                 /* not reached */
264         }
265
266         /*
267          * Figure out the directory prefix, taking care of degenerate
268          * cases.
269          */
270         dirpath = dirname(path);
271         fd = open(dirpath, O_RDONLY);
272         if (fd < 0) {
273                 err(1, "Cannot open directory %s", dirpath);
274                 /* not reached */
275         }
276
277         /*
278          * Avoid foot-shooting.  Don't let the user create a PFS
279          * softlink via a PFS.  PFS softlinks may only be accessed
280          * via the master filesystem.  Checking it here ensures
281          * other PFS commands access PFS under the master filesystem.
282          */
283         clrpfs(&pfs, &pfsd, -1);
284
285         ioctl(fd, HAMMERIOC_GET_PSEUDOFS, &pfs);
286         if (pfs.pfs_id != HAMMER_ROOT_PFSID) {
287                 fprintf(stderr,
288                         "You are attempting to access a PFS softlink "
289                         "from a PFS.  It may not represent the PFS\n"
290                         "on the main filesystem mount that you "
291                         "expect!  You may only access PFS softlinks\n"
292                         "via the main filesystem mount!\n");
293                 exit(1);
294         }
295
296         for (pfs_id = 0; pfs_id < HAMMER_MAX_PFS; ++pfs_id) {
297                 clrpfs(&pfs, &pfsd, pfs_id);
298                 if (ioctl(fd, HAMMERIOC_GET_PSEUDOFS, &pfs) < 0) {
299                         if (errno != ENOENT) {
300                                 err(1, "Cannot create %s", path);
301                                 /* not reached */
302                         }
303                         break;
304                 }
305         }
306         if (pfs_id == HAMMER_MAX_PFS) {
307                 errx(1, "Cannot create %s, all PFSs in use", path);
308                 /* not reached */
309         } else if (pfs_id == HAMMER_ROOT_PFSID) {
310                 errx(1, "Fatal error: PFS#%d must exist", HAMMER_ROOT_PFSID);
311                 /* not reached */
312         }
313
314         /*
315          * Create the new PFS
316          */
317         printf("Creating PFS#%d\t", pfs_id);
318         clrpfs(&pfs, &pfsd, pfs_id);
319         init_pfsd(&pfsd, is_slave);
320
321         if (ioctl(fd, HAMMERIOC_SET_PSEUDOFS, &pfs) < 0) {
322                 printf("failed: %s\n", strerror(errno));
323         } else {
324                 /* special symlink, must be exactly 10 characters */
325                 asprintf(&linkpath, "@@PFS%05d", pfs_id);
326                 if (symlink(linkpath, path) < 0) {
327                         printf("failed: cannot create symlink: %s\n",
328                                 strerror(errno));
329                 } else {
330                         printf("succeeded!\n");
331                         hammer_cmd_pseudofs_update(av, ac);
332                 }
333         }
334         free(dirpath);
335         close(fd);
336 }
337
338 void
339 hammer_cmd_pseudofs_destroy(char **av, int ac)
340 {
341         struct hammer_ioc_pseudofs_rw pfs;
342         char *linkpath;
343         int fd;
344         int i;
345
346         if (ac == 0)
347                 pseudofs_usage(1);
348         fd = getpfs(&pfs, av[0]);
349
350         if (pfs.pfs_id == HAMMER_ROOT_PFSID) {
351                 errx(1, "You cannot destroy PFS#%d", HAMMER_ROOT_PFSID);
352                 /* not reached */
353         }
354
355         printf("You have requested that PFS#%d (%s) be destroyed\n",
356                 pfs.pfs_id, pfs.ondisk->label);
357         printf("This will irrevocably destroy all data on this PFS!!!!!\n");
358         printf("Do you really want to do this? [y/n] ");
359         fflush(stdout);
360         if (getyn() == 0) {
361                 errx(1, "No action taken on PFS#%d", pfs.pfs_id);
362                 /* not reached */
363         }
364
365         if (hammer_is_pfs_master(pfs.ondisk)) {
366                 printf("This PFS is currently setup as a MASTER!\n");
367                 printf("Are you absolutely sure you want to destroy it? [y/n] ");
368                 fflush(stdout);
369                 if (getyn() == 0) {
370                         errx(1, "No action taken on PFS#%d", pfs.pfs_id);
371                         /* not reached */
372                 }
373         }
374
375         printf("Destroying PFS#%d (%s)", pfs.pfs_id, pfs.ondisk->label);
376         if (DebugOpt) {
377                 printf("\n");
378         } else {
379                 printf(" in");
380                 for (i = 5; i; --i) {
381                         printf(" %d", i);
382                         fflush(stdout);
383                         sleep(1);
384                 }
385                 printf(".. starting destruction pass\n");
386         }
387
388         /*
389          * Remove the softlink on success.
390          */
391         if (ioctl(fd, HAMMERIOC_RMR_PSEUDOFS, &pfs) == 0) {
392                 printf("pfs-destroy of PFS#%d succeeded!\n", pfs.pfs_id);
393                 linkpath = getlink(av[0]);
394                 if (linkpath) {
395                         if (remove(linkpath) < 0) {
396                                 err(1, "Unable to remove softlink %s", linkpath);
397                                 /* not reached */
398                         }
399                         free(linkpath);
400                 }
401         } else {
402                 printf("pfs-destroy of PFS#%d failed: %s\n",
403                         pfs.pfs_id, strerror(errno));
404         }
405         relpfs(fd, &pfs);
406 }
407
408 void
409 hammer_cmd_pseudofs_upgrade(char **av, int ac)
410 {
411         struct hammer_ioc_pseudofs_rw pfs;
412         int fd;
413
414         if (ac == 0)
415                 pseudofs_usage(1);
416         fd = getpfs(&pfs, av[0]);
417
418         if (pfs.pfs_id == HAMMER_ROOT_PFSID) {
419                 errx(1, "You cannot upgrade PFS#%d"
420                         " (It should already be a master)",
421                         HAMMER_ROOT_PFSID);
422                 /* not reached */
423         } else if (hammer_is_pfs_master(pfs.ondisk)) {
424                 errx(1, "It is already a master");
425                 /* not reached */
426         }
427
428         if (ioctl(fd, HAMMERIOC_UPG_PSEUDOFS, &pfs) == 0) {
429                 printf("pfs-upgrade of PFS#%d (%s) succeeded\n",
430                         pfs.pfs_id, pfs.ondisk->label);
431         } else {
432                 err(1, "pfs-upgrade of PFS#%d (%s) failed",
433                         pfs.pfs_id, pfs.ondisk->label);
434                 /* not reached */
435         }
436         relpfs(fd, &pfs);
437 }
438
439 void
440 hammer_cmd_pseudofs_downgrade(char **av, int ac)
441 {
442         struct hammer_ioc_pseudofs_rw pfs;
443         int fd;
444
445         if (ac == 0)
446                 pseudofs_usage(1);
447         fd = getpfs(&pfs, av[0]);
448
449         if (pfs.pfs_id == HAMMER_ROOT_PFSID) {
450                 errx(1, "You cannot downgrade PFS#%d", HAMMER_ROOT_PFSID);
451                 /* not reached */
452         } else if (hammer_is_pfs_slave(pfs.ondisk)) {
453                 errx(1, "It is already a slave");
454                 /* not reached */
455         }
456
457         if (ioctl(fd, HAMMERIOC_DGD_PSEUDOFS, &pfs) == 0) {
458                 printf("pfs-downgrade of PFS#%d (%s) succeeded\n",
459                         pfs.pfs_id, pfs.ondisk->label);
460         } else {
461                 err(1, "pfs-downgrade of PFS#%d (%s) failed",
462                         pfs.pfs_id, pfs.ondisk->label);
463                 /* not reached */
464         }
465         relpfs(fd, &pfs);
466 }
467
468 void
469 hammer_cmd_pseudofs_update(char **av, int ac)
470 {
471         struct hammer_ioc_pseudofs_rw pfs;
472         int fd;
473
474         if (ac == 0)
475                 pseudofs_usage(1);
476         fd = getpfs(&pfs, av[0]);
477
478         printf("%s\n", av[0]);
479         fflush(stdout);
480
481         if (ioctl(fd, HAMMERIOC_GET_PSEUDOFS, &pfs) == 0) {
482                 parse_pfsd_options(av + 1, ac - 1, pfs.ondisk);
483                 if (hammer_is_pfs_slave(pfs.ondisk) &&
484                     pfs.pfs_id == HAMMER_ROOT_PFSID) {
485                         errx(1, "The real mount point cannot be made a PFS "
486                                "slave, only PFS sub-directories can be made "
487                                "slaves");
488                         /* not reached */
489                 }
490                 pfs.bytes = sizeof(*pfs.ondisk);
491                 if (ioctl(fd, HAMMERIOC_SET_PSEUDOFS, &pfs) == 0) {
492                         if (ioctl(fd, HAMMERIOC_GET_PSEUDOFS, &pfs) == 0) {
493                                 dump_pfsd(pfs.ondisk, fd);
494                         } else {
495                                 err(1, "Unable to retrieve PFS configuration "
496                                         "after successful update");
497                                 /* not reached */
498                         }
499                 } else {
500                         err(1, "Unable to adjust PFS configuration");
501                         /* not reached */
502                 }
503         }
504         relpfs(fd, &pfs);
505 }
506
507 static void
508 init_pfsd(hammer_pseudofs_data_t pfsd, int is_slave)
509 {
510         uint32_t status;
511
512         bzero(pfsd, sizeof(*pfsd));
513         pfsd->sync_beg_tid = 1;
514         pfsd->sync_end_tid = 1;
515         pfsd->sync_beg_ts = 0;
516         pfsd->sync_end_ts = 0;
517         uuid_create(&pfsd->shared_uuid, &status);
518         uuid_create(&pfsd->unique_uuid, &status);
519         if (is_slave)
520                 pfsd->mirror_flags |= HAMMER_PFSD_SLAVE;
521 }
522
523 void
524 dump_pfsd(hammer_pseudofs_data_t pfsd, int fd)
525 {
526         struct hammer_ioc_version       version;
527         uint32_t status;
528         char *str = NULL;
529
530         printf("    sync-beg-tid=0x%016jx\n", (uintmax_t)pfsd->sync_beg_tid);
531         printf("    sync-end-tid=0x%016jx\n", (uintmax_t)pfsd->sync_end_tid);
532         uuid_to_string(&pfsd->shared_uuid, &str, &status);
533         printf("    shared-uuid=%s\n", str);
534         free(str);
535         uuid_to_string(&pfsd->unique_uuid, &str, &status);
536         printf("    unique-uuid=%s\n", str);
537         free(str);
538         printf("    label=\"%s\"\n", pfsd->label);
539         if (pfsd->snapshots[0])
540                 printf("    snapshots=\"%s\"\n", pfsd->snapshots);
541         if (pfsd->prune_min < (60 * 60 * 24)) {
542                 printf("    prune-min=%02d:%02d:%02d\n",
543                         pfsd->prune_min / 60 / 60 % 24,
544                         pfsd->prune_min / 60 % 60,
545                         pfsd->prune_min % 60);
546         } else if (pfsd->prune_min % (60 * 60 * 24)) {
547                 printf("    prune-min=%dd/%02d:%02d:%02d\n",
548                         pfsd->prune_min / 60 / 60 / 24,
549                         pfsd->prune_min / 60 / 60 % 24,
550                         pfsd->prune_min / 60 % 60,
551                         pfsd->prune_min % 60);
552         } else {
553                 printf("    prune-min=%dd\n", pfsd->prune_min / 60 / 60 / 24);
554         }
555
556         if (hammer_is_pfs_slave(pfsd))
557                 printf("    operating as a SLAVE\n");
558         else
559                 printf("    operating as a MASTER\n");
560
561         /*
562          * Snapshots directory cannot be shown when there is no fd since
563          * hammer version can't be retrieved. mirror-dump passes -1 because
564          * its input came from mirror-read output thus no path is available
565          * to open(2).
566          */
567         if (fd >= 0 && pfsd->snapshots[0] == 0) {
568                 bzero(&version, sizeof(version));
569                 if (ioctl(fd, HAMMERIOC_GET_VERSION, &version) < 0)
570                         return;
571                 HammerVersion = version.cur_version;
572                 if (version.cur_version < 3) {
573                         if (hammer_is_pfs_slave(pfsd)) {
574                                 printf("    snapshots directory not set for "
575                                        "slave\n");
576                         } else {
577                                 printf("    snapshots directory for master "
578                                        "defaults to <pfs>/snapshots\n");
579                         }
580                 } else {
581                         printf("    snapshots directory defaults to "
582                                "/var/hammer/<pfs>\n");
583                 }
584         }
585 }
586
587 static void
588 parse_pfsd_options(char **av, int ac, hammer_pseudofs_data_t pfsd)
589 {
590         char *cmd;
591         char *ptr;
592         int len;
593         uint32_t status;
594
595         while (ac) {
596                 cmd = *av;
597                 if ((ptr = strchr(cmd, '=')) != NULL)
598                         *ptr++ = 0;
599
600                 /*
601                  * Basic assignment value test
602                  */
603                 if (ptr == NULL) {
604                         errx(1, "option %s requires an assignment", cmd);
605                         /* not reached */
606                 }
607
608                 status = uuid_s_ok;
609                 if (strcmp(cmd, "sync-beg-tid") == 0) {
610                         pfsd->sync_beg_tid = strtoull(ptr, NULL, 16);
611                 } else if (strcmp(cmd, "sync-end-tid") == 0) {
612                         pfsd->sync_end_tid = strtoull(ptr, NULL, 16);
613                 } else if (strcmp(cmd, "shared-uuid") == 0) {
614                         uuid_from_string(ptr, &pfsd->shared_uuid, &status);
615                 } else if (strcmp(cmd, "unique-uuid") == 0) {
616                         uuid_from_string(ptr, &pfsd->unique_uuid, &status);
617                 } else if (strcmp(cmd, "label") == 0) {
618                         len = strlen(ptr);
619                         if (ptr[0] == '"' && ptr[len-1] == '"') {
620                                 ptr[len-1] = 0;
621                                 ++ptr;
622                         } else if (ptr[0] == '"') {
623                                 errx(1, "option %s: malformed string", cmd);
624                                 /* not reached */
625                         }
626                         snprintf(pfsd->label, sizeof(pfsd->label), "%s", ptr);
627                 } else if (strcmp(cmd, "snapshots") == 0) {
628                         len = strlen(ptr);
629                         if (ptr[0] != '/') {
630                                 fprintf(stderr,
631                                         "option %s: '%s' must be an "
632                                         "absolute path\n", cmd, ptr);
633                                 if (ptr[0] == 0) {
634                                         fprintf(stderr,
635                                                 "use 'snapshots-clear' "
636                                                 "to unset snapshots dir\n");
637                                 }
638                                 exit(1);
639                         }
640                         if (len >= (int)sizeof(pfsd->snapshots)) {
641                                 errx(1, "option %s: path too long, %d "
642                                         "character limit", cmd, len);
643                                 /* not reached */
644                         }
645                         snprintf(pfsd->snapshots, sizeof(pfsd->snapshots),
646                                  "%s", ptr);
647                 } else if (strcmp(cmd, "snapshots-clear") == 0) {
648                         pfsd->snapshots[0] = 0;
649                 } else if (strcmp(cmd, "prune-min") == 0) {
650                         pfsd->prune_min = timetosecs(ptr);
651                         if (pfsd->prune_min < 0) {
652                                 errx(1, "option %s: illegal time spec, "
653                                         "use Nd or [Nd/]hh[:mm[:ss]]", ptr);
654                                 /* not reached */
655                         }
656                 } else {
657                         errx(1, "invalid option: %s", cmd);
658                         /* not reached */
659                 }
660                 if (status != uuid_s_ok) {
661                         errx(1, "option %s: error parsing uuid %s", cmd, ptr);
662                         /* not reached */
663                 }
664                 --ac;
665                 ++av;
666         }
667 }
668
669 static
670 void
671 pseudofs_usage(int code)
672 {
673         fprintf(stderr,
674                 "hammer pfs-status <dirpath> ...\n"
675                 "hammer pfs-master <dirpath> [options]\n"
676                 "hammer pfs-slave <dirpath> [options]\n"
677                 "hammer pfs-update <dirpath> [options]\n"
678                 "hammer pfs-upgrade <dirpath>\n"
679                 "hammer pfs-downgrade <dirpath>\n"
680                 "hammer pfs-destroy <dirpath>\n"
681                 "\n"
682                 "options:\n"
683                 "    sync-beg-tid=0x16llx\n"
684                 "    sync-end-tid=0x16llx\n"
685                 "    shared-uuid=0x16llx\n"
686                 "    unique-uuid=0x16llx\n"
687                 "    label=\"string\"\n"
688                 "    snapshots=\"/path\"\n"
689                 "    snapshots-clear\n"
690                 "    prune-min=Nd\n"
691                 "    prune-min=[Nd/]hh[:mm[:ss]]\n"
692         );
693         exit(code);
694 }
695
696 /*
697  * Convert time in the form [Nd/]hh[:mm[:ss]] to seconds.
698  *
699  * Return -1 if a parse error occurs.
700  * Return 0x7FFFFFFF if the time exceeds the maximum allowed.
701  */
702 static
703 int
704 timetosecs(char *str)
705 {
706         int days = 0;
707         int hrs = 0;
708         int mins = 0;
709         int secs = 0;
710         int n;
711         long long v;
712         char *ptr;
713
714         n = strtol(str, &ptr, 10);
715         if (n < 0)
716                 return(-1);
717         if (*ptr == 'd') {
718                 days = n;
719                 ++ptr;
720                 if (*ptr == '/')
721                     n = strtol(ptr + 1, &ptr, 10);
722                 else
723                     n = 0;
724         }
725         if (n < 0)
726                 return(-1);
727         hrs = n;
728         if (*ptr == ':') {
729                 n = strtol(ptr + 1, &ptr, 10);
730                 if (n < 0)
731                         return(-1);
732                 mins = n;
733                 if (*ptr == ':') {
734                         n = strtol(ptr + 1, &ptr, 10);
735                         if (n < 0)
736                                 return(-1);
737                         secs = n;
738                 }
739         }
740         if (*ptr)
741                 return(-1);
742         v = days * 24 * 60 * 60 + hrs *  60 * 60 + mins * 60 + secs;
743         if (v > 0x7FFFFFFF)
744                 v = 0x7FFFFFFF;
745         return((int)v);
746 }