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