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