sbin/hammer: Make getyn() non static
[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 void
214 hammer_cmd_pseudofs_status(char **av, int ac)
215 {
216         struct hammer_ioc_pseudofs_rw pfs;
217         int i;
218         int fd;
219
220         if (ac == 0)
221                 pseudofs_usage(1);
222
223         for (i = 0; i < ac; ++i) {
224                 fd = getpfs(&pfs, av[i]);
225                 printf("%s\t", av[i]);
226                 if (fd < 0 || ioctl(fd, HAMMERIOC_GET_PSEUDOFS, &pfs) < 0) {
227                         printf("Invalid PFS path %s\n", av[i]);
228                 } else {
229                         printf("PFS #%d {\n", pfs.pfs_id);
230                         dump_pfsd(pfs.ondisk, fd);
231                         printf("}\n");
232                 }
233                 if (fd >= 0)
234                         close(fd);
235                 if (pfs.ondisk)
236                         free(pfs.ondisk);
237                 relpfs(fd, &pfs);
238         }
239 }
240
241 void
242 hammer_cmd_pseudofs_create(char **av, int ac, int is_slave)
243 {
244         struct hammer_ioc_pseudofs_rw pfs;
245         struct hammer_pseudofs_data pfsd;
246         struct stat st;
247         const char *path;
248         char *dirpath;
249         char *linkpath;
250         int pfs_id;
251         int fd;
252         int error;
253
254         if (ac == 0)
255                 pseudofs_usage(1);
256         path = av[0];
257         if (lstat(path, &st) == 0) {
258                 fprintf(stderr, "Cannot create %s, file exists!\n", path);
259                 exit(1);
260         }
261
262         /*
263          * Figure out the directory prefix, taking care of degenerate
264          * cases.
265          */
266         dirpath = getdir(path);
267         fd = open(dirpath, O_RDONLY);
268         if (fd < 0) {
269                 fprintf(stderr, "Cannot open directory %s\n", dirpath);
270                 exit(1);
271         }
272
273         /*
274          * Avoid foot-shooting.  Don't let the user create a PFS
275          * softlink via a PFS.  PFS softlinks may only be accessed
276          * via the master filesystem.  Checking it here ensures
277          * other PFS commands access PFS under the master filesystem.
278          */
279         bzero(&pfs, sizeof(pfs));
280         bzero(&pfsd, sizeof(pfsd));
281         pfs.pfs_id = -1;
282         pfs.ondisk = &pfsd;
283         pfs.bytes = sizeof(pfsd);
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         error = 0;
297         for (pfs_id = 0; pfs_id < HAMMER_MAX_PFS; ++pfs_id) {
298                 bzero(&pfs, sizeof(pfs));
299                 bzero(&pfsd, sizeof(pfsd));
300                 pfs.pfs_id = pfs_id;
301                 pfs.ondisk = &pfsd;
302                 pfs.bytes = sizeof(pfsd);
303                 pfs.version = HAMMER_IOC_PSEUDOFS_VERSION;
304                 if (ioctl(fd, HAMMERIOC_GET_PSEUDOFS, &pfs) < 0) {
305                         error = errno;
306                         break;
307                 }
308         }
309         if (pfs_id == HAMMER_MAX_PFS) {
310                 fprintf(stderr, "Cannot create %s, all PFSs in use\n", path);
311                 exit(1);
312         } else if (pfs_id == HAMMER_ROOT_PFSID) {
313                 fprintf(stderr, "Fatal error: PFS#%d must exist\n",
314                         HAMMER_ROOT_PFSID);
315                 exit(1);
316         }
317
318         if (error != ENOENT) {
319                 fprintf(stderr, "Cannot create %s, got %s during scan\n",
320                         path, strerror(error));
321                 exit(1);
322         }
323
324         /*
325          * Create the new PFS
326          */
327         printf("Creating PFS #%d\t", pfs_id);
328         init_pfsd(&pfsd, is_slave);
329         pfs.pfs_id = pfs_id;
330         pfs.ondisk = &pfsd;
331         pfs.bytes = sizeof(pfsd);
332         pfs.version = HAMMER_IOC_PSEUDOFS_VERSION;
333
334         if (ioctl(fd, HAMMERIOC_SET_PSEUDOFS, &pfs) < 0) {
335                 printf("failed: %s\n", strerror(errno));
336         } else {
337                 /* special symlink, must be exactly 10 characters */
338                 asprintf(&linkpath, "@@PFS%05d", pfs_id);
339                 if (symlink(linkpath, path) < 0) {
340                         printf("failed: cannot create symlink: %s\n",
341                                 strerror(errno));
342                 } else {
343                         printf("succeeded!\n");
344                         hammer_cmd_pseudofs_update(av, ac);
345                 }
346         }
347         free(dirpath);
348         close(fd);
349 }
350
351 void
352 hammer_cmd_pseudofs_destroy(char **av, int ac)
353 {
354         struct hammer_ioc_pseudofs_rw pfs;
355         struct hammer_pseudofs_data pfsd;
356         struct stat st;
357         int fd;
358         int i;
359
360         if (ac == 0)
361                 pseudofs_usage(1);
362         fd = getpfs(&pfs, av[0]);
363
364         if (pfs.pfs_id == HAMMER_ROOT_PFSID) {
365                 fprintf(stderr, "You cannot destroy PFS#0\n");
366                 exit(1);
367         }
368         printf("You have requested that PFS#%d (%s) be destroyed\n",
369                 pfs.pfs_id, pfs.ondisk->label);
370         printf("This will irrevocably destroy all data on this PFS!!!!!\n");
371         printf("Do you really want to do this? ");
372         fflush(stdout);
373         if (getyn() == 0) {
374                 fprintf(stderr, "No action taken on PFS#%d\n", pfs.pfs_id);
375                 exit(1);
376         }
377
378         if (hammer_is_pfs_master(pfs.ondisk)) {
379                 printf("This PFS is currently setup as a MASTER!\n");
380                 printf("Are you absolutely sure you want to destroy it? ");
381                 fflush(stdout);
382                 if (getyn() == 0) {
383                         fprintf(stderr, "No action taken on PFS#%d\n",
384                                 pfs.pfs_id);
385                         exit(1);
386                 }
387         }
388
389         printf("Destroying PFS #%d (%s) in ", pfs.pfs_id, pfs.ondisk->label);
390         for (i = 5; i; --i) {
391                 printf(" %d", i);
392                 fflush(stdout);
393                 sleep(1);
394         }
395         printf(".. starting destruction pass\n");
396         fflush(stdout);
397
398         /*
399          * Save the original PFS data so we can restore if on failure.
400          * It fails to destroy, for example, if the PFS is still mounted.
401          */
402         bcopy(pfs.ondisk, &pfsd, sizeof(pfsd));
403
404         /*
405          * Set the sync_beg_tid and sync_end_tid's to 1, once we start the
406          * RMR the PFS is basically destroyed even if someone ^C's it.
407          */
408         pfs.ondisk->mirror_flags |= HAMMER_PFSD_SLAVE;
409         pfs.ondisk->reserved01 = -1;
410         pfs.ondisk->sync_beg_tid = 1;
411         pfs.ondisk->sync_end_tid = 1;
412
413         if (ioctl(fd, HAMMERIOC_SET_PSEUDOFS, &pfs) < 0) {
414                 fprintf(stderr, "Unable to update the PFS configuration: %s\n",
415                         strerror(errno));
416                 exit(1);
417         }
418
419         /*
420          * Ok, do it.  Remove the softlink on success.
421          */
422         if (ioctl(fd, HAMMERIOC_RMR_PSEUDOFS, &pfs) == 0) {
423                 printf("pfs-destroy of PFS#%d succeeded!\n", pfs.pfs_id);
424                 if (lstat(av[0], &st) == 0 && S_ISLNK(st.st_mode)) {
425                         if (remove(av[0]) < 0) {
426                                 fprintf(stderr, "Unable to remove softlink: %s "
427                                         "(but the PFS has been destroyed)\n",
428                                         av[0]);
429                                 /* exit status 0 anyway */
430                         }
431                 }
432         } else {
433                 printf("pfs-destroy of PFS#%d failed: %s\n",
434                         pfs.pfs_id, strerror(errno));
435                 /*
436                  * Restore the pfsd as we don't want to keep it downgraded.
437                  * This simply restores ondisk PFS with the original data
438                  * without creating a new root inode as it already exists.
439                  */
440                 bcopy(&pfsd, pfs.ondisk, sizeof(pfsd));
441                 if (ioctl(fd, HAMMERIOC_SET_PSEUDOFS, &pfs) < 0) {
442                         printf("Failed to restore the original PFS#%d data\n",
443                                 pfs.pfs_id);
444                         exit(1);
445                 }
446         }
447         relpfs(fd, &pfs);
448 }
449
450 void
451 hammer_cmd_pseudofs_upgrade(char **av, int ac)
452 {
453         struct hammer_ioc_pseudofs_rw pfs;
454         int fd;
455
456         if (ac == 0)
457                 pseudofs_usage(1);
458         fd = getpfs(&pfs, av[0]);
459
460         if (pfs.pfs_id == HAMMER_ROOT_PFSID) {
461                 fprintf(stderr, "You cannot upgrade PFS#0"
462                                 " (It should already be a master)\n");
463                 exit(1);
464         } else if (hammer_is_pfs_master(pfs.ondisk)) {
465                 printf("It is already a master\n");
466                 exit(1);
467         }
468
469         if (ioctl(fd, HAMMERIOC_UPG_PSEUDOFS, &pfs) == 0) {
470                 printf("pfs-upgrade of PFS#%d (%s) succeeded\n",
471                         pfs.pfs_id, pfs.ondisk->label);
472         } else {
473                 fprintf(stderr, "pfs-upgrade of PFS#%d (%s) failed: %s\n",
474                         pfs.pfs_id, pfs.ondisk->label, strerror(errno));
475         }
476         relpfs(fd, &pfs);
477 }
478
479 void
480 hammer_cmd_pseudofs_downgrade(char **av, int ac)
481 {
482         struct hammer_ioc_pseudofs_rw pfs;
483         int fd;
484
485         if (ac == 0)
486                 pseudofs_usage(1);
487         fd = getpfs(&pfs, av[0]);
488
489         if (pfs.pfs_id == HAMMER_ROOT_PFSID) {
490                 fprintf(stderr, "You cannot downgrade PFS#0\n");
491                 exit(1);
492         } else if (hammer_is_pfs_slave(pfs.ondisk)) {
493                 printf("It is already a slave\n");
494                 exit(1);
495         }
496
497         if (ioctl(fd, HAMMERIOC_DGD_PSEUDOFS, &pfs) == 0) {
498                 printf("pfs-downgrade of PFS#%d (%s) succeeded\n",
499                         pfs.pfs_id, pfs.ondisk->label);
500         } else {
501                 fprintf(stderr, "pfs-downgrade of PFS#%d (%s) failed: %s\n",
502                         pfs.pfs_id, pfs.ondisk->label, strerror(errno));
503         }
504         relpfs(fd, &pfs);
505 }
506
507 void
508 hammer_cmd_pseudofs_update(char **av, int ac)
509 {
510         struct hammer_ioc_pseudofs_rw pfs;
511         int fd;
512
513         if (ac == 0)
514                 pseudofs_usage(1);
515         fd = getpfs(&pfs, av[0]);
516
517         printf("%s\n", av[0]);
518         fflush(stdout);
519
520         if (ioctl(fd, HAMMERIOC_GET_PSEUDOFS, &pfs) == 0) {
521                 parse_pfsd_options(av + 1, ac - 1, pfs.ondisk);
522                 if (hammer_is_pfs_slave(pfs.ondisk) &&
523                     pfs.pfs_id == HAMMER_ROOT_PFSID) {
524                         printf("The real mount point cannot be made a PFS "
525                                "slave, only PFS sub-directories can be made "
526                                "slaves\n");
527                         exit(1);
528                 }
529                 pfs.bytes = sizeof(*pfs.ondisk);
530                 if (ioctl(fd, HAMMERIOC_SET_PSEUDOFS, &pfs) == 0) {
531                         if (ioctl(fd, HAMMERIOC_GET_PSEUDOFS, &pfs) == 0) {
532                                 dump_pfsd(pfs.ondisk, fd);
533                         } else {
534                                 printf("Unable to retrieve PFS configuration "
535                                         "after successful update: %s\n",
536                                         strerror(errno));
537                                 exit(1);
538                         }
539                 } else {
540                         printf("Unable to adjust PFS configuration: %s\n",
541                                 strerror(errno));
542                         exit(1);
543                 }
544         }
545         relpfs(fd, &pfs);
546 }
547
548 static void
549 init_pfsd(hammer_pseudofs_data_t pfsd, int is_slave)
550 {
551         uint32_t status;
552
553         bzero(pfsd, sizeof(*pfsd));
554         pfsd->sync_beg_tid = 1;
555         pfsd->sync_end_tid = 1;
556         pfsd->sync_beg_ts = 0;
557         pfsd->sync_end_ts = 0;
558         uuid_create(&pfsd->shared_uuid, &status);
559         uuid_create(&pfsd->unique_uuid, &status);
560         if (is_slave)
561                 pfsd->mirror_flags |= HAMMER_PFSD_SLAVE;
562 }
563
564 void
565 dump_pfsd(hammer_pseudofs_data_t pfsd, int fd)
566 {
567         struct hammer_ioc_version       version;
568         uint32_t status;
569         char *str = NULL;
570
571         printf("    sync-beg-tid=0x%016jx\n", (uintmax_t)pfsd->sync_beg_tid);
572         printf("    sync-end-tid=0x%016jx\n", (uintmax_t)pfsd->sync_end_tid);
573         uuid_to_string(&pfsd->shared_uuid, &str, &status);
574         printf("    shared-uuid=%s\n", str);
575         free(str);
576         uuid_to_string(&pfsd->unique_uuid, &str, &status);
577         printf("    unique-uuid=%s\n", str);
578         free(str);
579         printf("    label=\"%s\"\n", pfsd->label);
580         if (pfsd->snapshots[0])
581                 printf("    snapshots=\"%s\"\n", pfsd->snapshots);
582         if (pfsd->prune_min < (60 * 60 * 24)) {
583                 printf("    prune-min=%02d:%02d:%02d\n",
584                         pfsd->prune_min / 60 / 60 % 24,
585                         pfsd->prune_min / 60 % 60,
586                         pfsd->prune_min % 60);
587         } else if (pfsd->prune_min % (60 * 60 * 24)) {
588                 printf("    prune-min=%dd/%02d:%02d:%02d\n",
589                         pfsd->prune_min / 60 / 60 / 24,
590                         pfsd->prune_min / 60 / 60 % 24,
591                         pfsd->prune_min / 60 % 60,
592                         pfsd->prune_min % 60);
593         } else {
594                 printf("    prune-min=%dd\n", pfsd->prune_min / 60 / 60 / 24);
595         }
596
597         if (hammer_is_pfs_slave(pfsd)) {
598                 printf("    operating as a SLAVE\n");
599         } else {
600                 printf("    operating as a MASTER\n");
601         }
602
603         /*
604          * Snapshots directory cannot be shown when there is no fd since
605          * hammer version can't be retrieved. mirror-dump passes -1 because
606          * its input came from mirror-read output thus no path is available
607          * to open(2).
608          */
609         if (fd >= 0 && pfsd->snapshots[0] == 0) {
610                 bzero(&version, sizeof(version));
611                 if (ioctl(fd, HAMMERIOC_GET_VERSION, &version) < 0)
612                         return;
613                 if (version.cur_version < 3) {
614                         if (hammer_is_pfs_slave(pfsd)) {
615                                 printf("    snapshots directory not set for "
616                                        "slave\n");
617                         } else {
618                                 printf("    snapshots directory for master "
619                                        "defaults to <pfs>/snapshots\n");
620                         }
621                 } else {
622                         printf("    snapshots directory defaults to "
623                                "/var/hammer/<pfs>\n");
624                 }
625         }
626 }
627
628 static void
629 parse_pfsd_options(char **av, int ac, hammer_pseudofs_data_t pfsd)
630 {
631         char *cmd;
632         char *ptr;
633         int len;
634         uint32_t status;
635
636         while (ac) {
637                 cmd = *av;
638                 if ((ptr = strchr(cmd, '=')) != NULL)
639                         *ptr++ = 0;
640
641                 /*
642                  * Basic assignment value test
643                  */
644                 if (ptr == NULL) {
645                         fprintf(stderr,
646                                 "option %s requires an assignment\n",
647                                 cmd);
648                         exit(1);
649                 }
650
651                 status = uuid_s_ok;
652                 if (strcmp(cmd, "sync-beg-tid") == 0) {
653                         pfsd->sync_beg_tid = strtoull(ptr, NULL, 16);
654                 } else if (strcmp(cmd, "sync-end-tid") == 0) {
655                         pfsd->sync_end_tid = strtoull(ptr, NULL, 16);
656                 } else if (strcmp(cmd, "shared-uuid") == 0) {
657                         uuid_from_string(ptr, &pfsd->shared_uuid, &status);
658                 } else if (strcmp(cmd, "unique-uuid") == 0) {
659                         uuid_from_string(ptr, &pfsd->unique_uuid, &status);
660                 } else if (strcmp(cmd, "label") == 0) {
661                         len = strlen(ptr);
662                         if (ptr[0] == '"' && ptr[len-1] == '"') {
663                                 ptr[len-1] = 0;
664                                 ++ptr;
665                         } else if (ptr[0] == '"') {
666                                 fprintf(stderr,
667                                         "option %s: malformed string\n",
668                                         cmd);
669                                 exit(1);
670                         }
671                         snprintf(pfsd->label, sizeof(pfsd->label), "%s", ptr);
672                 } else if (strcmp(cmd, "snapshots") == 0) {
673                         len = strlen(ptr);
674                         if (ptr[0] != '/') {
675                                 fprintf(stderr,
676                                         "option %s: '%s' must be an "
677                                         "absolute path\n", cmd, ptr);
678                                 if (ptr[0] == 0) {
679                                         fprintf(stderr,
680                                                 "use 'snapshots-clear' "
681                                                 "to unset snapshots dir\n");
682                                 }
683                                 exit(1);
684                         }
685                         if (len >= (int)sizeof(pfsd->snapshots)) {
686                                 fprintf(stderr,
687                                         "option %s: path too long, %d "
688                                         "character limit\n", cmd, len);
689                                 exit(1);
690                         }
691                         snprintf(pfsd->snapshots, sizeof(pfsd->snapshots),
692                                  "%s", ptr);
693                 } else if (strcmp(cmd, "snapshots-clear") == 0) {
694                         pfsd->snapshots[0] = 0;
695                 } else if (strcmp(cmd, "prune-min") == 0) {
696                         pfsd->prune_min = timetosecs(ptr);
697                         if (pfsd->prune_min < 0) {
698                                 fprintf(stderr,
699                                         "option %s: illegal time spec, "
700                                         "use Nd or [Nd/]hh[:mm[:ss]]\n", ptr);
701                                 exit(1);
702                         }
703                 } else {
704                         fprintf(stderr, "invalid option: %s\n", cmd);
705                         exit(1);
706                 }
707                 if (status != uuid_s_ok) {
708                         fprintf(stderr, "option %s: error parsing uuid %s\n",
709                                 cmd, ptr);
710                         exit(1);
711                 }
712                 --ac;
713                 ++av;
714         }
715 }
716
717 static
718 void
719 pseudofs_usage(int code)
720 {
721         fprintf(stderr,
722                 "hammer pfs-status <dirpath> ...\n"
723                 "hammer pfs-master <dirpath> [options]\n"
724                 "hammer pfs-slave <dirpath> [options]\n"
725                 "hammer pfs-update <dirpath> [options]\n"
726                 "hammer pfs-upgrade <dirpath>\n"
727                 "hammer pfs-downgrade <dirpath>\n"
728                 "hammer pfs-destroy <dirpath>\n"
729                 "\n"
730                 "options:\n"
731                 "    sync-beg-tid=0x16llx\n"
732                 "    sync-end-tid=0x16llx\n"
733                 "    shared-uuid=0x16llx\n"
734                 "    unique-uuid=0x16llx\n"
735                 "    label=\"string\"\n"
736                 "    snapshots=\"/path\"\n"
737                 "    snapshots-clear\n"
738                 "    prune-min=Nd\n"
739                 "    prune-min=[Nd/]hh[:mm[:ss]]\n"
740         );
741         exit(code);
742 }
743
744 /*
745  * Convert time in the form [Nd/]hh[:mm[:ss]] to seconds.
746  *
747  * Return -1 if a parse error occurs.
748  * Return 0x7FFFFFFF if the time exceeds the maximum allowed.
749  */
750 static
751 int
752 timetosecs(char *str)
753 {
754         int days = 0;
755         int hrs = 0;
756         int mins = 0;
757         int secs = 0;
758         int n;
759         long long v;
760         char *ptr;
761
762         n = strtol(str, &ptr, 10);
763         if (n < 0)
764                 return(-1);
765         if (*ptr == 'd') {
766                 days = n;
767                 ++ptr;
768                 if (*ptr == '/')
769                     n = strtol(ptr + 1, &ptr, 10);
770                 else
771                     n = 0;
772         }
773         if (n < 0)
774                 return(-1);
775         hrs = n;
776         if (*ptr == ':') {
777                 n = strtol(ptr + 1, &ptr, 10);
778                 if (n < 0)
779                         return(-1);
780                 mins = n;
781                 if (*ptr == ':') {
782                         n = strtol(ptr + 1, &ptr, 10);
783                         if (n < 0)
784                                 return(-1);
785                         secs = n;
786                 }
787         }
788         if (*ptr)
789                 return(-1);
790         v = days * 24 * 60 * 60 + hrs *  60 * 60 + mins * 60 + secs;
791         if (v > 0x7FFFFFFF)
792                 v = 0x7FFFFFFF;
793         return((int)v);
794 }