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