sbin/hammer: Don't ioctl(SET_PFS) before attempt to destroy PFS
[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          * 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                  * Restore the pfsd as we don't want to keep it downgraded.
422                  * This simply restores ondisk PFS with the original data
423                  * without creating a new root inode as it already exists.
424                  */
425                 bcopy(&pfsd, pfs.ondisk, sizeof(pfsd));
426                 if (ioctl(fd, HAMMERIOC_SET_PSEUDOFS, &pfs) < 0) {
427                         printf("Failed to restore the original PFS#%d data\n",
428                                 pfs.pfs_id);
429                         exit(1);
430                 }
431         }
432         relpfs(fd, &pfs);
433 }
434
435 void
436 hammer_cmd_pseudofs_upgrade(char **av, int ac)
437 {
438         struct hammer_ioc_pseudofs_rw pfs;
439         int fd;
440
441         if (ac == 0)
442                 pseudofs_usage(1);
443         fd = getpfs(&pfs, av[0]);
444
445         if (pfs.pfs_id == HAMMER_ROOT_PFSID) {
446                 fprintf(stderr, "You cannot upgrade PFS#0"
447                                 " (It should already be a master)\n");
448                 exit(1);
449         } else if (hammer_is_pfs_master(pfs.ondisk)) {
450                 printf("It is already a master\n");
451                 exit(1);
452         }
453
454         if (ioctl(fd, HAMMERIOC_UPG_PSEUDOFS, &pfs) == 0) {
455                 printf("pfs-upgrade of PFS#%d (%s) succeeded\n",
456                         pfs.pfs_id, pfs.ondisk->label);
457         } else {
458                 fprintf(stderr, "pfs-upgrade of PFS#%d (%s) failed: %s\n",
459                         pfs.pfs_id, pfs.ondisk->label, strerror(errno));
460         }
461         relpfs(fd, &pfs);
462 }
463
464 void
465 hammer_cmd_pseudofs_downgrade(char **av, int ac)
466 {
467         struct hammer_ioc_pseudofs_rw pfs;
468         int fd;
469
470         if (ac == 0)
471                 pseudofs_usage(1);
472         fd = getpfs(&pfs, av[0]);
473
474         if (pfs.pfs_id == HAMMER_ROOT_PFSID) {
475                 fprintf(stderr, "You cannot downgrade PFS#0\n");
476                 exit(1);
477         } else if (hammer_is_pfs_slave(pfs.ondisk)) {
478                 printf("It is already a slave\n");
479                 exit(1);
480         }
481
482         if (ioctl(fd, HAMMERIOC_DGD_PSEUDOFS, &pfs) == 0) {
483                 printf("pfs-downgrade of PFS#%d (%s) succeeded\n",
484                         pfs.pfs_id, pfs.ondisk->label);
485         } else {
486                 fprintf(stderr, "pfs-downgrade of PFS#%d (%s) failed: %s\n",
487                         pfs.pfs_id, pfs.ondisk->label, strerror(errno));
488         }
489         relpfs(fd, &pfs);
490 }
491
492 void
493 hammer_cmd_pseudofs_update(char **av, int ac)
494 {
495         struct hammer_ioc_pseudofs_rw pfs;
496         int fd;
497
498         if (ac == 0)
499                 pseudofs_usage(1);
500         fd = getpfs(&pfs, av[0]);
501
502         printf("%s\n", av[0]);
503         fflush(stdout);
504
505         if (ioctl(fd, HAMMERIOC_GET_PSEUDOFS, &pfs) == 0) {
506                 parse_pfsd_options(av + 1, ac - 1, pfs.ondisk);
507                 if (hammer_is_pfs_slave(pfs.ondisk) &&
508                     pfs.pfs_id == HAMMER_ROOT_PFSID) {
509                         printf("The real mount point cannot be made a PFS "
510                                "slave, only PFS sub-directories can be made "
511                                "slaves\n");
512                         exit(1);
513                 }
514                 pfs.bytes = sizeof(*pfs.ondisk);
515                 if (ioctl(fd, HAMMERIOC_SET_PSEUDOFS, &pfs) == 0) {
516                         if (ioctl(fd, HAMMERIOC_GET_PSEUDOFS, &pfs) == 0) {
517                                 dump_pfsd(pfs.ondisk, fd);
518                         } else {
519                                 printf("Unable to retrieve PFS configuration "
520                                         "after successful update: %s\n",
521                                         strerror(errno));
522                                 exit(1);
523                         }
524                 } else {
525                         printf("Unable to adjust PFS configuration: %s\n",
526                                 strerror(errno));
527                         exit(1);
528                 }
529         }
530         relpfs(fd, &pfs);
531 }
532
533 static void
534 init_pfsd(hammer_pseudofs_data_t pfsd, int is_slave)
535 {
536         uint32_t status;
537
538         bzero(pfsd, sizeof(*pfsd));
539         pfsd->sync_beg_tid = 1;
540         pfsd->sync_end_tid = 1;
541         pfsd->sync_beg_ts = 0;
542         pfsd->sync_end_ts = 0;
543         uuid_create(&pfsd->shared_uuid, &status);
544         uuid_create(&pfsd->unique_uuid, &status);
545         if (is_slave)
546                 pfsd->mirror_flags |= HAMMER_PFSD_SLAVE;
547 }
548
549 void
550 dump_pfsd(hammer_pseudofs_data_t pfsd, int fd)
551 {
552         struct hammer_ioc_version       version;
553         uint32_t status;
554         char *str = NULL;
555
556         printf("    sync-beg-tid=0x%016jx\n", (uintmax_t)pfsd->sync_beg_tid);
557         printf("    sync-end-tid=0x%016jx\n", (uintmax_t)pfsd->sync_end_tid);
558         uuid_to_string(&pfsd->shared_uuid, &str, &status);
559         printf("    shared-uuid=%s\n", str);
560         free(str);
561         uuid_to_string(&pfsd->unique_uuid, &str, &status);
562         printf("    unique-uuid=%s\n", str);
563         free(str);
564         printf("    label=\"%s\"\n", pfsd->label);
565         if (pfsd->snapshots[0])
566                 printf("    snapshots=\"%s\"\n", pfsd->snapshots);
567         if (pfsd->prune_min < (60 * 60 * 24)) {
568                 printf("    prune-min=%02d:%02d:%02d\n",
569                         pfsd->prune_min / 60 / 60 % 24,
570                         pfsd->prune_min / 60 % 60,
571                         pfsd->prune_min % 60);
572         } else if (pfsd->prune_min % (60 * 60 * 24)) {
573                 printf("    prune-min=%dd/%02d:%02d:%02d\n",
574                         pfsd->prune_min / 60 / 60 / 24,
575                         pfsd->prune_min / 60 / 60 % 24,
576                         pfsd->prune_min / 60 % 60,
577                         pfsd->prune_min % 60);
578         } else {
579                 printf("    prune-min=%dd\n", pfsd->prune_min / 60 / 60 / 24);
580         }
581
582         if (hammer_is_pfs_slave(pfsd)) {
583                 printf("    operating as a SLAVE\n");
584         } else {
585                 printf("    operating as a MASTER\n");
586         }
587
588         /*
589          * Snapshots directory cannot be shown when there is no fd since
590          * hammer version can't be retrieved. mirror-dump passes -1 because
591          * its input came from mirror-read output thus no path is available
592          * to open(2).
593          */
594         if (fd >= 0 && pfsd->snapshots[0] == 0) {
595                 bzero(&version, sizeof(version));
596                 if (ioctl(fd, HAMMERIOC_GET_VERSION, &version) < 0)
597                         return;
598                 if (version.cur_version < 3) {
599                         if (hammer_is_pfs_slave(pfsd)) {
600                                 printf("    snapshots directory not set for "
601                                        "slave\n");
602                         } else {
603                                 printf("    snapshots directory for master "
604                                        "defaults to <pfs>/snapshots\n");
605                         }
606                 } else {
607                         printf("    snapshots directory defaults to "
608                                "/var/hammer/<pfs>\n");
609                 }
610         }
611 }
612
613 static void
614 parse_pfsd_options(char **av, int ac, hammer_pseudofs_data_t pfsd)
615 {
616         char *cmd;
617         char *ptr;
618         int len;
619         uint32_t status;
620
621         while (ac) {
622                 cmd = *av;
623                 if ((ptr = strchr(cmd, '=')) != NULL)
624                         *ptr++ = 0;
625
626                 /*
627                  * Basic assignment value test
628                  */
629                 if (ptr == NULL) {
630                         fprintf(stderr,
631                                 "option %s requires an assignment\n",
632                                 cmd);
633                         exit(1);
634                 }
635
636                 status = uuid_s_ok;
637                 if (strcmp(cmd, "sync-beg-tid") == 0) {
638                         pfsd->sync_beg_tid = strtoull(ptr, NULL, 16);
639                 } else if (strcmp(cmd, "sync-end-tid") == 0) {
640                         pfsd->sync_end_tid = strtoull(ptr, NULL, 16);
641                 } else if (strcmp(cmd, "shared-uuid") == 0) {
642                         uuid_from_string(ptr, &pfsd->shared_uuid, &status);
643                 } else if (strcmp(cmd, "unique-uuid") == 0) {
644                         uuid_from_string(ptr, &pfsd->unique_uuid, &status);
645                 } else if (strcmp(cmd, "label") == 0) {
646                         len = strlen(ptr);
647                         if (ptr[0] == '"' && ptr[len-1] == '"') {
648                                 ptr[len-1] = 0;
649                                 ++ptr;
650                         } else if (ptr[0] == '"') {
651                                 fprintf(stderr,
652                                         "option %s: malformed string\n",
653                                         cmd);
654                                 exit(1);
655                         }
656                         snprintf(pfsd->label, sizeof(pfsd->label), "%s", ptr);
657                 } else if (strcmp(cmd, "snapshots") == 0) {
658                         len = strlen(ptr);
659                         if (ptr[0] != '/') {
660                                 fprintf(stderr,
661                                         "option %s: '%s' must be an "
662                                         "absolute path\n", cmd, ptr);
663                                 if (ptr[0] == 0) {
664                                         fprintf(stderr,
665                                                 "use 'snapshots-clear' "
666                                                 "to unset snapshots dir\n");
667                                 }
668                                 exit(1);
669                         }
670                         if (len >= (int)sizeof(pfsd->snapshots)) {
671                                 fprintf(stderr,
672                                         "option %s: path too long, %d "
673                                         "character limit\n", cmd, len);
674                                 exit(1);
675                         }
676                         snprintf(pfsd->snapshots, sizeof(pfsd->snapshots),
677                                  "%s", ptr);
678                 } else if (strcmp(cmd, "snapshots-clear") == 0) {
679                         pfsd->snapshots[0] = 0;
680                 } else if (strcmp(cmd, "prune-min") == 0) {
681                         pfsd->prune_min = timetosecs(ptr);
682                         if (pfsd->prune_min < 0) {
683                                 fprintf(stderr,
684                                         "option %s: illegal time spec, "
685                                         "use Nd or [Nd/]hh[:mm[:ss]]\n", ptr);
686                                 exit(1);
687                         }
688                 } else {
689                         fprintf(stderr, "invalid option: %s\n", cmd);
690                         exit(1);
691                 }
692                 if (status != uuid_s_ok) {
693                         fprintf(stderr, "option %s: error parsing uuid %s\n",
694                                 cmd, ptr);
695                         exit(1);
696                 }
697                 --ac;
698                 ++av;
699         }
700 }
701
702 static
703 void
704 pseudofs_usage(int code)
705 {
706         fprintf(stderr,
707                 "hammer pfs-status <dirpath> ...\n"
708                 "hammer pfs-master <dirpath> [options]\n"
709                 "hammer pfs-slave <dirpath> [options]\n"
710                 "hammer pfs-update <dirpath> [options]\n"
711                 "hammer pfs-upgrade <dirpath>\n"
712                 "hammer pfs-downgrade <dirpath>\n"
713                 "hammer pfs-destroy <dirpath>\n"
714                 "\n"
715                 "options:\n"
716                 "    sync-beg-tid=0x16llx\n"
717                 "    sync-end-tid=0x16llx\n"
718                 "    shared-uuid=0x16llx\n"
719                 "    unique-uuid=0x16llx\n"
720                 "    label=\"string\"\n"
721                 "    snapshots=\"/path\"\n"
722                 "    snapshots-clear\n"
723                 "    prune-min=Nd\n"
724                 "    prune-min=[Nd/]hh[:mm[:ss]]\n"
725         );
726         exit(code);
727 }
728
729 /*
730  * Convert time in the form [Nd/]hh[:mm[:ss]] to seconds.
731  *
732  * Return -1 if a parse error occurs.
733  * Return 0x7FFFFFFF if the time exceeds the maximum allowed.
734  */
735 static
736 int
737 timetosecs(char *str)
738 {
739         int days = 0;
740         int hrs = 0;
741         int mins = 0;
742         int secs = 0;
743         int n;
744         long long v;
745         char *ptr;
746
747         n = strtol(str, &ptr, 10);
748         if (n < 0)
749                 return(-1);
750         if (*ptr == 'd') {
751                 days = n;
752                 ++ptr;
753                 if (*ptr == '/')
754                     n = strtol(ptr + 1, &ptr, 10);
755                 else
756                     n = 0;
757         }
758         if (n < 0)
759                 return(-1);
760         hrs = n;
761         if (*ptr == ':') {
762                 n = strtol(ptr + 1, &ptr, 10);
763                 if (n < 0)
764                         return(-1);
765                 mins = n;
766                 if (*ptr == ':') {
767                         n = strtol(ptr + 1, &ptr, 10);
768                         if (n < 0)
769                                 return(-1);
770                         secs = n;
771                 }
772         }
773         if (*ptr)
774                 return(-1);
775         v = days * 24 * 60 * 60 + hrs *  60 * 60 + mins * 60 + secs;
776         if (v > 0x7FFFFFFF)
777                 v = 0x7FFFFFFF;
778         return((int)v);
779 }