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