usr.sbin/makefs/hammer2: Allow broken symlink when populating
[dragonfly.git] / usr.sbin / makefs / hammer2.c
1 /*
2  * SPDX-License-Identifier: BSD-3-Clause
3  *
4  * Copyright (c) 2022 Tomohiro Kusumi <tkusumi@netbsd.org>
5  * Copyright (c) 2011-2022 The DragonFly Project.  All rights reserved.
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
35 #if HAVE_NBTOOL_CONFIG_H
36 #include "nbtool_config.h"
37 #endif
38
39 #include <sys/param.h>
40 #include <sys/stat.h>
41 #include <sys/sysctl.h>
42 #include <sys/mman.h>
43
44 #include <stdio.h>
45 #include <stdlib.h>
46 #include <stdbool.h>
47 #include <string.h>
48 #include <ctype.h>
49 #include <unistd.h>
50 #include <fcntl.h>
51 #include <time.h>
52 #include <err.h>
53 #include <assert.h>
54 #include <util.h>
55
56 #include "makefs.h"
57 #include "hammer2.h"
58
59 #define APRINTF(X, ...) \
60     printf("%s: " X, __func__, ## __VA_ARGS__)
61
62 static void hammer2_parse_pfs_opts(const char *, fsinfo_t *);
63 static void hammer2_parse_inode_opts(const char *, fsinfo_t *);
64 static void hammer2_dump_fsinfo(fsinfo_t *);
65 static int hammer2_create_image(const char *, fsinfo_t *);
66 static int hammer2_populate_dir(struct m_vnode *, const char *, fsnode *,
67     fsnode *, fsinfo_t *, int);
68 static void hammer2_validate(const char *, fsnode *, fsinfo_t *);
69 static void hammer2_size_dir(fsnode *, fsinfo_t *);
70 static int hammer2_write_file(struct m_vnode *, const char *, fsnode *);
71 static int hammer2_version_get(struct m_vnode *);
72 static int hammer2_pfs_get(struct m_vnode *);
73 static int hammer2_pfs_lookup(struct m_vnode *, const char *);
74 static int hammer2_pfs_create(struct m_vnode *, const char *);
75 static int hammer2_pfs_delete(struct m_vnode *, const char *);
76 static int hammer2_pfs_snapshot(struct m_vnode *, const char *, const char *);
77 static int hammer2_inode_getx(struct m_vnode *, const char *);
78 static int hammer2_inode_setcheck(struct m_vnode *, const char *);
79 static int hammer2_inode_setcomp(struct m_vnode *, const char *);
80 static int hammer2_bulkfree(struct m_vnode *);
81 static int hammer2_destroy_path(struct m_vnode *, const char *);
82 static int hammer2_destroy_inum(struct m_vnode *, hammer2_tid_t);
83 static int hammer2_growfs(struct m_vnode *, hammer2_off_t);
84 static void unittest_trim_slash(void);
85
86 fsnode *hammer2_curnode;
87
88 void
89 hammer2_prep_opts(fsinfo_t *fsopts)
90 {
91         hammer2_makefs_options_t *h2_opt = ecalloc(1, sizeof(*h2_opt));
92         hammer2_mkfs_options_t *opt = &h2_opt->mkfs_options;
93
94         const option_t hammer2_options[] = {
95                 /* newfs_hammer2(8) compatible options */
96                 { 'b', "BootAreaSize", NULL, OPT_STRBUF, 0, 0, "boot area size" },
97                 { 'r', "AuxAreaSize", NULL, OPT_STRBUF, 0, 0, "aux area size" },
98                 { 'V', "Hammer2Version", NULL, OPT_STRBUF, 0, 0, "file system version" },
99                 { 'L', "Label", NULL, OPT_STRBUF, 0, 0, "PFS label" },
100                 /* makefs(8) specific options */
101                 { 'm', "MountLabel", NULL, OPT_STRBUF, 0, 0, "destination PFS label" },
102                 { 'v', "NumVolhdr", &h2_opt->num_volhdr, OPT_INT32,
103                     1, HAMMER2_NUM_VOLHDRS, "number of volume headers" },
104                 { 'd', "Hammer2Debug", NULL, OPT_STRBUF, 0, 0, "debug tunable" },
105                 { 'E', "EmergencyMode", &h2_opt->emergency_mode, OPT_BOOL, 0, 0,
106                     "emergency mode" },
107                 { 'P', "PFS", NULL, OPT_STRBUF, 0, 0, "offline PFS" },
108                 { 'I', "Inode", NULL, OPT_STRBUF, 0, 0, "offline inode" },
109                 { 'B', "Bulkfree", NULL, OPT_STRBUF, 0, 0, "offline bulkfree" },
110                 { 'D', "Destroy", NULL, OPT_STRBUF, 0, 0, "offline destroy" },
111                 { 'G', "Growfs", NULL, OPT_STRBUF, 0, 0, "offline growfs" },
112                 { .name = NULL },
113         };
114
115         hammer2_mkfs_init(opt);
116
117         /* make this tunable ? */
118         assert(opt->CompType == HAMMER2_COMP_NEWFS_DEFAULT);
119         assert(opt->CheckType == HAMMER2_CHECK_XXHASH64);
120
121         /* force debug mode for mkfs */
122         opt->DebugOpt = 1;
123
124         fsopts->fs_specific = h2_opt;
125         fsopts->fs_options = copy_opts(hammer2_options);
126         fsopts->sectorsize = DEV_BSIZE;
127 }
128
129 void
130 hammer2_cleanup_opts(fsinfo_t *fsopts)
131 {
132         hammer2_makefs_options_t *h2_opt = fsopts->fs_specific;
133         hammer2_mkfs_options_t *opt = &h2_opt->mkfs_options;
134
135         hammer2_mkfs_cleanup(opt);
136
137         free(h2_opt);
138         free(fsopts->fs_options);
139 }
140
141 int
142 hammer2_parse_opts(const char *option, fsinfo_t *fsopts)
143 {
144         hammer2_makefs_options_t *h2_opt = fsopts->fs_specific;
145         hammer2_mkfs_options_t *opt = &h2_opt->mkfs_options;
146
147         option_t *hammer2_options = fsopts->fs_options;
148         char buf[1024]; /* > HAMMER2_INODE_MAXNAME */
149         int i;
150
151         assert(option != NULL);
152         assert(fsopts != NULL);
153
154         if (debug & DEBUG_FS_PARSE_OPTS)
155                 APRINTF("got `%s'\n", option);
156
157         i = set_option(hammer2_options, option, buf, sizeof(buf));
158         if (i == -1)
159                 return 0;
160
161         if (hammer2_options[i].name == NULL)
162                 abort();
163
164         switch (hammer2_options[i].letter) {
165         case 'b':
166                 opt->BootAreaSize = getsize(buf, HAMMER2_NEWFS_ALIGN,
167                     HAMMER2_BOOT_MAX_BYTES, 2);
168                 break;
169         case 'r':
170                 opt->AuxAreaSize = getsize(buf, HAMMER2_NEWFS_ALIGN,
171                     HAMMER2_AUX_MAX_BYTES, 2);
172                 break;
173         case 'V':
174                 if (strlen(buf) == 0) {
175                         h2_opt->ioctl_cmd = HAMMER2IOC_VERSION_GET;
176                 } else {
177                         opt->Hammer2Version = strtol(buf, NULL, 0);
178                         if (opt->Hammer2Version < HAMMER2_VOL_VERSION_MIN ||
179                             opt->Hammer2Version >= HAMMER2_VOL_VERSION_WIP)
180                                 errx(1, "I don't understand how to format "
181                                     "HAMMER2 version %d",
182                                     opt->Hammer2Version);
183                 }
184                 break;
185         case 'L':
186                 h2_opt->label_specified = 1;
187                 if (strcasecmp(buf, "none") == 0)
188                         break;
189                 if (opt->NLabels >= MAXLABELS)
190                         errx(1, "Limit of %d local labels", MAXLABELS - 1);
191                 if (strlen(buf) == 0)
192                         errx(1, "Volume label '%s' cannot be 0-length", buf);
193                 if (strlen(buf) >= HAMMER2_INODE_MAXNAME)
194                         errx(1, "Volume label '%s' is too long (%d chars max)",
195                             buf, HAMMER2_INODE_MAXNAME - 1);
196                 opt->Label[opt->NLabels++] = strdup(buf);
197                 break;
198         case 'm':
199                 if (strlen(buf) == 0)
200                         errx(1, "Volume label '%s' cannot be 0-length", buf);
201                 if (strlen(buf) >= HAMMER2_INODE_MAXNAME)
202                         errx(1, "Volume label '%s' is too long (%d chars max)",
203                             buf, HAMMER2_INODE_MAXNAME - 1);
204                 strlcpy(h2_opt->mount_label, buf, sizeof(h2_opt->mount_label));
205                 break;
206         case 'd':
207                 hammer2_debug = strtoll(buf, NULL, 0);
208                 break;
209         case 'P':
210                 if (strlen(buf) == 0)
211                         errx(1, "PFS argument '%s' cannot be 0-length", buf);
212                 hammer2_parse_pfs_opts(buf, fsopts);
213                 break;
214         case 'I':
215                 if (strlen(buf) == 0)
216                         errx(1, "Inode argument '%s' cannot be 0-length", buf);
217                 hammer2_parse_inode_opts(buf, fsopts);
218                 break;
219         case 'B':
220                 h2_opt->ioctl_cmd = HAMMER2IOC_BULKFREE_SCAN;
221                 break;
222         case 'D':
223                 h2_opt->ioctl_cmd = HAMMER2IOC_DESTROY;
224                 if (strlen(buf) == 0)
225                         errx(1, "Destroy argument '%s' cannot be 0-length", buf);
226                 if (buf[0] == '/') {
227                         strlcpy(h2_opt->destroy_path, buf,
228                             sizeof(h2_opt->destroy_path));
229                 } else if (strncmp(buf, "0x", 2) == 0 ||
230                     (buf[0] >= '0' && buf[0] <= '9')) {
231                         h2_opt->destroy_inum = strtoull(buf, NULL, 0);
232                         if (errno)
233                                 err(1, "strtoull");
234                 } else {
235                         errx(1, "Invalid destroy argument %s", buf);
236                 }
237                 break;
238         case 'G':
239                 h2_opt->ioctl_cmd = HAMMER2IOC_GROWFS;
240                 break;
241         default:
242                 break;
243         }
244
245         if (hammer2_debug && h2_opt->ioctl_cmd)
246                 unittest_trim_slash();
247
248         return 1;
249 }
250
251 void
252 hammer2_makefs(const char *image, const char *dir, fsnode *root,
253     fsinfo_t *fsopts)
254 {
255         hammer2_makefs_options_t *h2_opt = fsopts->fs_specific;
256         struct mount mp;
257         struct hammer2_mount_info info;
258         struct m_vnode devvp, *vroot;
259         hammer2_inode_t *iroot;
260         struct timeval start;
261         int error;
262
263         /* ioctl commands could have NULL dir / root */
264         assert(image != NULL);
265         assert(fsopts != NULL);
266
267         if (debug & DEBUG_FS_MAKEFS)
268                 APRINTF("image \"%s\" directory \"%s\" root %p\n",
269                     image, dir, root);
270
271         /* validate tree and options */
272         TIMER_START(start);
273         hammer2_validate(dir, root, fsopts);
274         TIMER_RESULTS(start, "hammer2_validate");
275
276         if (h2_opt->ioctl_cmd) {
277                 /* open existing image */
278                 fsopts->fd = open(image, O_RDWR);
279                 if (fsopts->fd < 0)
280                         err(1, "failed to open `%s'", image);
281         } else {
282                 /* create image */
283                 TIMER_START(start);
284                 if (hammer2_create_image(image, fsopts) == -1)
285                         errx(1, "image file `%s' not created", image);
286                 TIMER_RESULTS(start, "hammer2_create_image");
287         }
288         assert(fsopts->fd > 0);
289
290         if (debug & DEBUG_FS_MAKEFS)
291                 putchar('\n');
292
293         /* vfs init */
294         error = hammer2_vfs_init();
295         if (error)
296                 errx(1, "failed to vfs init, error %d", error);
297
298         /* mount image */
299         memset(&devvp, 0, sizeof(devvp));
300         devvp.fs = fsopts;
301         memset(&mp, 0, sizeof(mp));
302         memset(&info, 0, sizeof(info));
303         error = hammer2_vfs_mount(&devvp, &mp, h2_opt->mount_label, &info);
304         if (error)
305                 errx(1, "failed to mount, error %d", error);
306         assert(mp.mnt_data);
307
308         /* get root vnode */
309         vroot = NULL;
310         error = hammer2_vfs_root(&mp, &vroot);
311         if (error)
312                 errx(1, "failed to get root vnode, error %d", error);
313         assert(vroot);
314
315         iroot = VTOI(vroot);
316         assert(iroot);
317         printf("root inode inum %lld, mode 0%o, refs %d\n",
318             (long long)iroot->meta.inum, iroot->meta.mode, iroot->refs);
319
320         if (h2_opt->emergency_mode)
321                 hammer2_ioctl_emerg_mode(iroot, 1);
322
323         switch (h2_opt->ioctl_cmd) {
324         case HAMMER2IOC_VERSION_GET:
325                 printf("version get `%s'\n", image);
326                 TIMER_START(start);
327                 error = hammer2_version_get(vroot);
328                 if (error)
329                         errx(1, "version get `%s' failed '%s'", image,
330                             strerror(error));
331                 TIMER_RESULTS(start, "hammer2_version_get");
332                 break;
333         case HAMMER2IOC_PFS_GET:
334                 printf("PFS %s `%s'\n", h2_opt->pfs_cmd_name, image);
335                 TIMER_START(start);
336                 error = hammer2_pfs_get(vroot);
337                 if (error)
338                         errx(1, "PFS %s`%s' failed '%s'", h2_opt->pfs_cmd_name,
339                             image, strerror(error));
340                 TIMER_RESULTS(start, "hammer2_pfs_get");
341                 break;
342         case HAMMER2IOC_PFS_LOOKUP:
343                 printf("PFS %s `%s'\n", h2_opt->pfs_cmd_name, image);
344                 TIMER_START(start);
345                 error = hammer2_pfs_lookup(vroot, h2_opt->pfs_name);
346                 if (error)
347                         errx(1, "PFS %s`%s' failed '%s'", h2_opt->pfs_cmd_name,
348                             image, strerror(error));
349                 TIMER_RESULTS(start, "hammer2_pfs_lookup");
350                 break;
351         case HAMMER2IOC_PFS_CREATE:
352                 printf("PFS %s `%s'\n", h2_opt->pfs_cmd_name, image);
353                 TIMER_START(start);
354                 error = hammer2_pfs_create(vroot, h2_opt->pfs_name);
355                 if (error)
356                         errx(1, "PFS %s`%s' failed '%s'", h2_opt->pfs_cmd_name,
357                             image, strerror(error));
358                 TIMER_RESULTS(start, "hammer2_pfs_create");
359                 break;
360         case HAMMER2IOC_PFS_DELETE:
361                 printf("PFS %s `%s'\n", h2_opt->pfs_cmd_name, image);
362                 TIMER_START(start);
363                 error = hammer2_pfs_delete(vroot, h2_opt->pfs_name);
364                 if (error)
365                         errx(1, "PFS %s`%s' failed '%s'", h2_opt->pfs_cmd_name,
366                             image, strerror(error));
367                 TIMER_RESULTS(start, "hammer2_pfs_delete");
368                 break;
369         case HAMMER2IOC_PFS_SNAPSHOT:
370                 printf("PFS %s `%s'\n", h2_opt->pfs_cmd_name, image);
371                 TIMER_START(start);
372                 error = hammer2_pfs_snapshot(vroot, h2_opt->pfs_name,
373                     h2_opt->mount_label);
374                 if (error)
375                         errx(1, "PFS %s`%s' failed '%s'", h2_opt->pfs_cmd_name,
376                             image, strerror(error));
377                 TIMER_RESULTS(start, "hammer2_pfs_snapshot");
378                 break;
379         case HAMMER2IOC_INODE_GET:
380                 printf("inode %s `%s'\n", h2_opt->inode_cmd_name, image);
381                 TIMER_START(start);
382                 error = hammer2_inode_getx(vroot, h2_opt->inode_path);
383                 if (error)
384                         errx(1, "inode %s `%s' failed '%s'",
385                             h2_opt->inode_cmd_name, image, strerror(error));
386                 TIMER_RESULTS(start, "hammer2_inode_getx");
387                 break;
388         case HAMMER2IOC_INODE_SET:
389                 printf("inode %s `%s'\n", h2_opt->inode_cmd_name, image);
390                 TIMER_START(start);
391                 if (!strcmp(h2_opt->inode_cmd_name, "setcheck")) {
392                         error = hammer2_inode_setcheck(vroot,
393                             h2_opt->inode_path);
394                         if (error)
395                                 errx(1, "inode %s `%s' failed '%s'",
396                                     h2_opt->inode_cmd_name, image,
397                                     strerror(error));
398                 } else if (!strcmp(h2_opt->inode_cmd_name, "setcomp")) {
399                         error = hammer2_inode_setcomp(vroot,
400                             h2_opt->inode_path);
401                         if (error)
402                                 errx(1, "inode %s `%s' failed '%s'",
403                                     h2_opt->inode_cmd_name, image,
404                                     strerror(error));
405                 } else {
406                         assert(0);
407                 }
408                 TIMER_RESULTS(start, "hammer2_inode_setx");
409                 break;
410         case HAMMER2IOC_BULKFREE_SCAN:
411                 printf("bulkfree `%s'\n", image);
412                 TIMER_START(start);
413                 error = hammer2_bulkfree(vroot);
414                 if (error)
415                         errx(1, "bulkfree `%s' failed '%s'", image,
416                             strerror(error));
417                 TIMER_RESULTS(start, "hammer2_bulkfree");
418                 break;
419         case HAMMER2IOC_DESTROY:
420                 TIMER_START(start);
421                 if (strlen(h2_opt->destroy_path)) {
422                         printf("destroy `%s' in `%s'\n",
423                             h2_opt->destroy_path, image);
424                         error = hammer2_destroy_path(vroot,
425                             h2_opt->destroy_path);
426                         if (error)
427                                 errx(1, "destroy `%s' in `%s' failed '%s'",
428                                     h2_opt->destroy_path, image,
429                                     strerror(error));
430                 } else {
431                         printf("destroy %lld in `%s'\n",
432                             (long long)h2_opt->destroy_inum, image);
433                         error = hammer2_destroy_inum(vroot,
434                             h2_opt->destroy_inum);
435                         if (error)
436                                 errx(1, "destroy %lld in `%s' failed '%s'",
437                                     (long long)h2_opt->destroy_inum, image,
438                                     strerror(error));
439                 }
440                 TIMER_RESULTS(start, "hammer2_destroy");
441                 break;
442         case HAMMER2IOC_GROWFS:
443                 printf("growfs `%s'\n", image);
444                 TIMER_START(start);
445                 error = hammer2_growfs(vroot, h2_opt->image_size);
446                 if (error)
447                         errx(1, "growfs `%s' failed '%s'", image,
448                             strerror(error));
449                 TIMER_RESULTS(start, "hammer2_growfs");
450                 break;
451         default:
452                 printf("populating `%s'\n", image);
453                 TIMER_START(start);
454                 if (hammer2_populate_dir(vroot, dir, root, root, fsopts, 0))
455                         errx(1, "image file `%s' not populated", image);
456                 TIMER_RESULTS(start, "hammer2_populate_dir");
457                 break;
458         }
459
460         /* unmount image */
461         error = hammer2_vfs_unmount(&mp, 0);
462         if (error)
463                 errx(1, "failed to unmount, error %d", error);
464
465         /* check leaked resource */
466         if (vnode_count)
467                 printf("XXX %lld vnode left\n", (long long)vnode_count);
468         if (hammer2_chain_allocs)
469                 printf("XXX %ld chain left\n", hammer2_chain_allocs);
470         bcleanup();
471
472         /* vfs uninit */
473         error = hammer2_vfs_uninit();
474         if (error)
475                 errx(1, "failed to vfs uninit, error %d", error);
476
477         if (close(fsopts->fd) == -1)
478                 err(1, "closing `%s'", image);
479         fsopts->fd = -1;
480
481         printf("image `%s' complete\n", image);
482 }
483
484 /* end of public functions */
485
486 static void
487 hammer2_parse_pfs_opts(const char *buf, fsinfo_t *fsopts)
488 {
489         hammer2_makefs_options_t *h2_opt = fsopts->fs_specific;
490         char *o, *p;
491         size_t n;
492
493         o = p = strdup(buf);
494         p = strchr(p, ':');
495         if (p != NULL) {
496                 *p++ = 0;
497                 n = strlen(p);
498         } else {
499                 n = 0;
500         }
501
502         if (!strcmp(o, "get") || !strcmp(o, "list")) {
503                 h2_opt->ioctl_cmd = HAMMER2IOC_PFS_GET;
504         } else if (!strcmp(o, "lookup")) {
505                 if (n == 0 || n > NAME_MAX)
506                         errx(1, "invalid PFS name \"%s\"", p);
507                 h2_opt->ioctl_cmd = HAMMER2IOC_PFS_LOOKUP;
508         } else if (!strcmp(o, "create")) {
509                 if (n == 0 || n > NAME_MAX)
510                         errx(1, "invalid PFS name \"%s\"", p);
511                 h2_opt->ioctl_cmd = HAMMER2IOC_PFS_CREATE;
512         } else if (!strcmp(o, "delete")) {
513                 if (n == 0 || n > NAME_MAX)
514                         errx(1, "invalid PFS name \"%s\"", p);
515                 h2_opt->ioctl_cmd = HAMMER2IOC_PFS_DELETE;
516         } else if (!strcmp(o, "snapshot")) {
517                 if (n > NAME_MAX)
518                         errx(1, "invalid PFS name \"%s\"", p);
519                 h2_opt->ioctl_cmd = HAMMER2IOC_PFS_SNAPSHOT;
520         } else {
521                 errx(1, "invalid PFS command \"%s\"", o);
522         }
523
524         strlcpy(h2_opt->pfs_cmd_name, o, sizeof(h2_opt->pfs_cmd_name));
525         if (n > 0)
526                 strlcpy(h2_opt->pfs_name, p, sizeof(h2_opt->pfs_name));
527
528         free(o);
529 }
530
531 static void
532 hammer2_parse_inode_opts(const char *buf, fsinfo_t *fsopts)
533 {
534         hammer2_makefs_options_t *h2_opt = fsopts->fs_specific;
535         char *o, *p;
536         size_t n;
537
538         o = p = strdup(buf);
539         p = strchr(p, ':');
540         if (p != NULL) {
541                 *p++ = 0;
542                 n = strlen(p);
543         } else {
544                 n = 0;
545         }
546
547         if (!strcmp(o, "get")) {
548                 if (n == 0 || n > PATH_MAX)
549                         errx(1, "invalid file path \"%s\"", p);
550                 h2_opt->ioctl_cmd = HAMMER2IOC_INODE_GET;
551         } else if (!strcmp(o, "setcheck")) {
552                 if (n == 0 || n > PATH_MAX - 10)
553                         errx(1, "invalid argument \"%s\"", p);
554                 h2_opt->ioctl_cmd = HAMMER2IOC_INODE_SET;
555         } else if (!strcmp(o, "setcomp")) {
556                 if (n == 0 || n > PATH_MAX - 10)
557                         errx(1, "invalid argument \"%s\"", p);
558                 h2_opt->ioctl_cmd = HAMMER2IOC_INODE_SET;
559         } else {
560                 errx(1, "invalid inode command \"%s\"", o);
561         }
562
563         strlcpy(h2_opt->inode_cmd_name, o, sizeof(h2_opt->inode_cmd_name));
564         if (n > 0)
565                 strlcpy(h2_opt->inode_path, p, sizeof(h2_opt->inode_path));
566
567         free(o);
568 }
569
570 static hammer2_off_t
571 hammer2_image_size(fsinfo_t *fsopts)
572 {
573         hammer2_makefs_options_t *h2_opt = fsopts->fs_specific;
574         hammer2_off_t image_size, used_size = 0;
575         int num_level1, delta_num_level1;
576
577         /* use 4 volume headers by default */
578         num_level1 = h2_opt->num_volhdr * 2; /* default 4 x 2 */
579         assert(num_level1 != 0);
580         assert(num_level1 <= 8);
581
582         /* add 4MiB segment for each level1 */
583         used_size += HAMMER2_ZONE_SEG64 * num_level1;
584
585         /* add boot/aux area, but exact size unknown at this point */
586         used_size += HAMMER2_BOOT_NOM_BYTES + HAMMER2_AUX_NOM_BYTES;
587
588         /* add data size */
589         used_size += fsopts->size;
590
591         /* XXX add extra level1 for meta data and indirect blocks */
592         used_size += HAMMER2_FREEMAP_LEVEL1_SIZE;
593
594         /* XXX add extra level1 for safety */
595         if (used_size > HAMMER2_FREEMAP_LEVEL1_SIZE * 10)
596                 used_size += HAMMER2_FREEMAP_LEVEL1_SIZE;
597
598         /* use 8GiB image size by default */
599         image_size = HAMMER2_FREEMAP_LEVEL1_SIZE * num_level1;
600         printf("trying default image size %s\n", sizetostr(image_size));
601
602         /* adjust if image size isn't large enough */
603         if (used_size > image_size) {
604                 /* determine extra level1 needed */
605                 delta_num_level1 = howmany(used_size - image_size,
606                     HAMMER2_FREEMAP_LEVEL1_SIZE);
607
608                 /* adjust used size with 4MiB segment for each extra level1 */
609                 used_size += HAMMER2_ZONE_SEG64 * delta_num_level1;
610
611                 /* adjust image size with extra level1 */
612                 image_size += HAMMER2_FREEMAP_LEVEL1_SIZE * delta_num_level1;
613                 printf("trying adjusted image size %s\n",
614                     sizetostr(image_size));
615
616                 if (used_size > image_size)
617                         errx(1, "invalid used_size %lld > image_size %lld",
618                             (long long)used_size, (long long)image_size);
619         }
620
621         return image_size;
622 }
623
624 static const char *
625 hammer2_label_name(int label_type)
626 {
627         switch (label_type) {
628         case HAMMER2_LABEL_NONE:
629                 return "NONE";
630         case HAMMER2_LABEL_BOOT:
631                 return "BOOT";
632         case HAMMER2_LABEL_ROOT:
633                 return "ROOT";
634         case HAMMER2_LABEL_DATA:
635                 return "DATA";
636         default:
637                 assert(0);
638         }
639         return NULL;
640 }
641
642 static void
643 hammer2_validate(const char *dir, fsnode *root, fsinfo_t *fsopts)
644 {
645         hammer2_makefs_options_t *h2_opt = fsopts->fs_specific;
646         hammer2_mkfs_options_t *opt = &h2_opt->mkfs_options;
647         hammer2_off_t image_size = 0, minsize, maxsize;
648         const char *s;
649
650         /* ioctl commands could have NULL dir / root */
651         assert(fsopts != NULL);
652
653         if (debug & DEBUG_FS_VALIDATE) {
654                 APRINTF("before defaults set:\n");
655                 hammer2_dump_fsinfo(fsopts);
656         }
657
658         /* makefs only supports "DATA" for default PFS label */
659         if (!h2_opt->label_specified) {
660                 opt->DefaultLabelType = HAMMER2_LABEL_DATA;
661                 s = hammer2_label_name(opt->DefaultLabelType);
662                 printf("using default label \"%s\"\n", s);
663         }
664
665         /* set default mount PFS label */
666         if (!strcmp(h2_opt->mount_label, "")) {
667                 s = hammer2_label_name(HAMMER2_LABEL_DATA);
668                 strlcpy(h2_opt->mount_label, s, sizeof(h2_opt->mount_label));
669                 printf("using default mount label \"%s\"\n", s);
670         }
671
672         /* set default number of volume headers */
673         if (!h2_opt->num_volhdr) {
674                 h2_opt->num_volhdr = HAMMER2_NUM_VOLHDRS;
675                 printf("using default %d volume headers\n", h2_opt->num_volhdr);
676         }
677
678         /* done if ioctl commands */
679         if (h2_opt->ioctl_cmd) {
680                 if (h2_opt->ioctl_cmd == HAMMER2IOC_GROWFS)
681                         goto ignore_size_dir;
682                 else
683                         goto done;
684         }
685
686         /* calculate data size */
687         if (fsopts->size != 0)
688                 fsopts->size = 0; /* shouldn't reach here to begin with */
689         if (root == NULL)
690                 errx(1, "fsnode tree not constructed");
691         hammer2_size_dir(root, fsopts);
692         printf("estimated data size %s from %lld inode\n",
693             sizetostr(fsopts->size), (long long)fsopts->inodes);
694
695         /* determine image size from data size */
696         image_size = hammer2_image_size(fsopts);
697         assert((image_size & HAMMER2_FREEMAP_LEVEL1_MASK) == 0);
698 ignore_size_dir:
699         minsize = roundup(fsopts->minsize, HAMMER2_FREEMAP_LEVEL1_SIZE);
700         maxsize = roundup(fsopts->maxsize, HAMMER2_FREEMAP_LEVEL1_SIZE);
701         if (image_size < minsize)
702                 image_size = minsize;
703         else if (maxsize > 0 && image_size > maxsize)
704                 errx(1, "`%s' size of %lld is larger than the maxsize of %lld",
705                     dir, (long long)image_size, (long long)maxsize);
706
707         assert((image_size & HAMMER2_FREEMAP_LEVEL1_MASK) == 0);
708         h2_opt->image_size = image_size;
709         printf("using %s image size\n", sizetostr(h2_opt->image_size));
710 done:
711         if (debug & DEBUG_FS_VALIDATE) {
712                 APRINTF("after defaults set:\n");
713                 hammer2_dump_fsinfo(fsopts);
714         }
715 }
716
717 static void
718 hammer2_dump_fsinfo(fsinfo_t *fsopts)
719 {
720         hammer2_makefs_options_t *h2_opt = fsopts->fs_specific;
721         hammer2_mkfs_options_t *opt = &h2_opt->mkfs_options;
722         int i;
723         char *s;
724
725         assert(fsopts != NULL);
726
727         APRINTF("fsinfo_t at %p\n", fsopts);
728
729         printf("\tinodes %lld\n", (long long)fsopts->inodes);
730         printf("\tsize %lld, minsize %lld, maxsize %lld\n",
731             (long long)fsopts->size,
732             (long long)fsopts->minsize,
733             (long long)fsopts->maxsize);
734
735         printf("\thammer2_debug 0x%x\n", hammer2_debug);
736
737         printf("\tlabel_specified %d\n", h2_opt->label_specified);
738         printf("\tmount_label \"%s\"\n", h2_opt->mount_label);
739         printf("\tnum_volhdr %d\n", h2_opt->num_volhdr);
740         printf("\tioctl_cmd %ld\n", h2_opt->ioctl_cmd);
741         printf("\temergency_mode %d\n", h2_opt->emergency_mode);
742         printf("\tpfs_cmd_name \"%s\"\n", h2_opt->pfs_cmd_name);
743         printf("\tpfs_name \"%s\"\n", h2_opt->pfs_name);
744         printf("\tinode_cmd_name \"%s\"\n", h2_opt->inode_cmd_name);
745         printf("\tinode_path \"%s\"\n", h2_opt->inode_path);
746         printf("\tdestroy_path \"%s\"\n", h2_opt->destroy_path);
747         printf("\tdestroy_inum %lld\n", (long long)h2_opt->destroy_inum);
748         printf("\timage_size 0x%llx\n", (long long)h2_opt->image_size);
749
750         printf("\tHammer2Version %d\n", opt->Hammer2Version);
751         printf("\tBootAreaSize 0x%jx\n", opt->BootAreaSize);
752         printf("\tAuxAreaSize 0x%jx\n", opt->AuxAreaSize);
753         printf("\tNLabels %d\n", opt->NLabels);
754         printf("\tCompType %d\n", opt->CompType);
755         printf("\tCheckType %d\n", opt->CheckType);
756         printf("\tDefaultLabelType %d\n", opt->DefaultLabelType);
757         printf("\tDebugOpt %d\n", opt->DebugOpt);
758
759         s = NULL;
760         hammer2_uuid_to_str(&opt->Hammer2_FSType, &s);
761         printf("\tHammer2_FSType \"%s\"\n", s);
762         s = NULL;
763         hammer2_uuid_to_str(&opt->Hammer2_VolFSID, &s);
764         printf("\tHammer2_VolFSID \"%s\"\n", s);
765         s = NULL;
766         hammer2_uuid_to_str(&opt->Hammer2_SupCLID, &s);
767         printf("\tHammer2_SupCLID \"%s\"\n", s);
768         s = NULL;
769         hammer2_uuid_to_str(&opt->Hammer2_SupFSID, &s);
770         printf("\tHammer2_SupFSID \"%s\"\n", s);
771
772         for (i = 0; i < opt->NLabels; i++) {
773                 printf("\tLabel[%d] \"%s\"\n", i, opt->Label[i]);
774                 s = NULL;
775                 hammer2_uuid_to_str(&opt->Hammer2_PfsCLID[i], &s);
776                 printf("\t Hammer2_PfsCLID[%d] \"%s\"\n", i, s);
777                 s = NULL;
778                 hammer2_uuid_to_str(&opt->Hammer2_PfsFSID[i], &s);
779                 printf("\t Hammer2_PfsFSID[%d] \"%s\"\n", i, s);
780         }
781
782         free(s);
783 }
784
785 static int
786 hammer2_create_image(const char *image, fsinfo_t *fsopts)
787 {
788         hammer2_makefs_options_t *h2_opt = fsopts->fs_specific;
789         hammer2_mkfs_options_t *opt = &h2_opt->mkfs_options;
790         char *av[] = { (char *)image, }; /* XXX support multi-volumes */
791         char *buf;
792         int i, bufsize, oflags;
793         off_t bufrem;
794
795         assert(image != NULL);
796         assert(fsopts != NULL);
797
798         /* create image */
799         oflags = O_RDWR | O_CREAT;
800         if (fsopts->offset == 0)
801                 oflags |= O_TRUNC;
802         if ((fsopts->fd = open(image, oflags, 0666)) == -1) {
803                 warn("can't open `%s' for writing", image);
804                 return -1;
805         }
806
807         /* zero image */
808         bufsize = HAMMER2_PBUFSIZE;
809         bufrem = h2_opt->image_size;
810         if (fsopts->sparse) {
811                 if (ftruncate(fsopts->fd, bufrem) == -1) {
812                         warn("sparse option disabled");
813                         fsopts->sparse = 0;
814                 }
815         }
816         if (fsopts->sparse) {
817                 /* File truncated at bufrem. Remaining is 0 */
818                 bufrem = 0;
819                 buf = NULL;
820         } else {
821                 if (debug & DEBUG_FS_CREATE_IMAGE)
822                         APRINTF("zero-ing image `%s', %lld sectors, "
823                             "using %d byte chunks\n",
824                             image, (long long)bufrem, bufsize);
825                 buf = ecalloc(1, bufsize);
826         }
827
828         if (fsopts->offset != 0) {
829                 if (lseek(fsopts->fd, fsopts->offset, SEEK_SET) == -1) {
830                         warn("can't seek");
831                         free(buf);
832                         return -1;
833                 }
834         }
835
836         while (bufrem > 0) {
837                 i = write(fsopts->fd, buf, MIN(bufsize, bufrem));
838                 if (i == -1) {
839                         warn("zeroing image, %lld bytes to go",
840                             (long long)bufrem);
841                         free(buf);
842                         return -1;
843                 }
844                 bufrem -= i;
845         }
846         if (buf)
847                 free(buf);
848
849         /* make the file system */
850         if (debug & DEBUG_FS_CREATE_IMAGE)
851                 APRINTF("calling mkfs(\"%s\", ...)\n", image);
852         hammer2_mkfs(1, av, opt); /* success if returned */
853
854         return fsopts->fd;
855 }
856
857 static off_t
858 hammer2_phys_size(off_t size)
859 {
860         off_t radix_size, phys_size = 0;
861         int i;
862
863         if (size > HAMMER2_PBUFSIZE) {
864                 phys_size += rounddown(size, HAMMER2_PBUFSIZE);
865                 size = size % HAMMER2_PBUFSIZE;
866         }
867
868         for (i = HAMMER2_RADIX_MIN; i <= HAMMER2_RADIX_MAX; i++) {
869                 radix_size = 1UL << i;
870                 if (radix_size >= size) {
871                         phys_size += radix_size;
872                         break;
873                 }
874         }
875
876         return phys_size;
877 }
878
879 /* calculate data size */
880 static void
881 hammer2_size_dir(fsnode *root, fsinfo_t *fsopts)
882 {
883         fsnode *node;
884
885         assert(fsopts != NULL);
886
887         if (debug & DEBUG_FS_SIZE_DIR)
888                 APRINTF("entry: bytes %lld inodes %lld\n",
889                     (long long)fsopts->size, (long long)fsopts->inodes);
890
891         for (node = root; node != NULL; node = node->next) {
892                 if (node == root) { /* we're at "." */
893                         assert(strcmp(node->name, ".") == 0);
894                 } else if ((node->inode->flags & FI_SIZED) == 0) {
895                         /* don't count duplicate names */
896                         node->inode->flags |= FI_SIZED;
897                         if (debug & DEBUG_FS_SIZE_DIR_NODE)
898                                 APRINTF("`%s' size %lld\n",
899                                     node->name,
900                                     (long long)node->inode->st.st_size);
901                         fsopts->inodes++;
902                         fsopts->size += sizeof(hammer2_inode_data_t);
903                         if (node->type == S_IFREG) {
904                                 size_t st_size = node->inode->st.st_size;
905                                 if (st_size > HAMMER2_EMBEDDED_BYTES)
906                                         fsopts->size += hammer2_phys_size(st_size);
907                         } else if (node->type == S_IFLNK) {
908                                 size_t nlen = strlen(node->symlink);
909                                 if (nlen > HAMMER2_EMBEDDED_BYTES)
910                                         fsopts->size += hammer2_phys_size(nlen);
911                         }
912                 }
913                 if (node->type == S_IFDIR)
914                         hammer2_size_dir(node->child, fsopts);
915         }
916
917         if (debug & DEBUG_FS_SIZE_DIR)
918                 APRINTF("exit: size %lld inodes %lld\n",
919                     (long long)fsopts->size, (long long)fsopts->inodes);
920 }
921
922 static void
923 hammer2_print(const struct m_vnode *dvp, const struct m_vnode *vp,
924     const fsnode *node, int depth, const char *msg)
925 {
926         if (debug & DEBUG_FS_POPULATE) {
927                 if (1) {
928                         int indent = depth * 2;
929                         char *type;
930                         if (S_ISDIR(node->type))
931                                 type = "dir";
932                         else if (S_ISREG(node->type))
933                                 type = "reg";
934                         else if (S_ISLNK(node->type))
935                                 type = "lnk";
936                         else if (S_ISFIFO(node->type))
937                                 type = "fifo";
938                         else
939                                 type = "???";
940                         printf("%*.*s", indent, indent, "");
941                         printf("dvp=%p/%d vp=%p/%d \"%s\" %s %s\n",
942                             dvp, dvp ? VTOI(dvp)->refs : 0,
943                             vp, vp ? VTOI(vp)->refs : 0,
944                             node->name, type, msg);
945                 } else {
946                         char type;
947                         if (S_ISDIR(node->type))
948                                 type = 'd';
949                         else if (S_ISREG(node->type))
950                                 type = 'r';
951                         else if (S_ISLNK(node->type))
952                                 type = 'l';
953                         else if (S_ISFIFO(node->type))
954                                 type = 'f';
955                         else
956                                 type = '?';
957                         printf("%c", type);
958                         fflush(stdout);
959                 }
960         }
961 }
962
963 static int
964 hammer2_populate_dir(struct m_vnode *dvp, const char *dir, fsnode *root,
965     fsnode *parent, fsinfo_t *fsopts, int depth)
966 {
967         fsnode *cur;
968         struct m_vnode *vp;
969         struct stat st;
970         char f[MAXPATHLEN];
971         const char *path;
972         int hardlink;
973         int error;
974
975         assert(dvp != NULL);
976         assert(dir != NULL);
977         assert(root != NULL);
978         assert(parent != NULL);
979         assert(fsopts != NULL);
980
981         /* assert root directory */
982         assert(S_ISDIR(root->type));
983         assert(!strcmp(root->name, "."));
984         assert(!root->child);
985         assert(!root->parent || root->parent->child == root);
986
987         hammer2_print(dvp, NULL, root, depth, "enter");
988         if (stat(dir, &st) == -1)
989                 err(1, "no such path %s", dir);
990         if (!S_ISDIR(st.st_mode))
991                 errx(1, "no such dir %s", dir);
992
993         for (cur = root->next; cur != NULL; cur = cur->next) {
994                 /* global variable for HAMMER2 vnops */
995                 hammer2_curnode = cur;
996
997                 /* construct source path */
998                 if (cur->contents) {
999                         path = cur->contents;
1000                 } else {
1001                         if (S_ISDIR(cur->type)) {
1002                                 /* this should be same as root/path/name */
1003                                 if (snprintf(f, sizeof(f), "%s/%s",
1004                                     dir, cur->name) >= (int)sizeof(f))
1005                                         errx(1, "path %s too long", f);
1006                         } else {
1007                                 if (snprintf(f, sizeof(f), "%s/%s/%s",
1008                                     cur->root, cur->path, cur->name) >= (int)sizeof(f))
1009                                         errx(1, "path %s too long", f);
1010                         }
1011                         path = f;
1012                 }
1013                 if (S_ISLNK(cur->type)) {
1014                         if (lstat(path, &st) == -1)
1015                                 err(1, "no such symlink %s", path);
1016                 } else {
1017                         if (stat(path, &st) == -1)
1018                                 err(1, "no such path %s", path);
1019                 }
1020
1021                 /* update node state */
1022                 if ((cur->inode->flags & FI_ALLOCATED) == 0) {
1023                         cur->inode->flags |= FI_ALLOCATED;
1024                         if (cur != root)
1025                                 cur->parent = parent;
1026                 }
1027
1028                 /* detect hardlink */
1029                 if (cur->inode->flags & FI_WRITTEN) {
1030                         assert(!S_ISDIR(cur->type));
1031                         hardlink = 1;
1032                 } else {
1033                         hardlink = 0;
1034                 }
1035                 cur->inode->flags |= FI_WRITTEN;
1036
1037                 /* make sure it doesn't exist yet */
1038                 vp = NULL;
1039                 error = hammer2_nresolve(dvp, &vp, cur->name,
1040                     strlen(cur->name));
1041                 if (!error)
1042                         errx(1, "hammer2_nresolve(\"%s\") already exists",
1043                             cur->name);
1044                 hammer2_print(dvp, vp, cur, depth, "nresolve");
1045
1046                 /* if directory, mkdir and recurse */
1047                 if (S_ISDIR(cur->type)) {
1048                         assert(cur->child);
1049
1050                         vp = NULL;
1051                         error = hammer2_nmkdir(dvp, &vp, cur->name,
1052                             strlen(cur->name), cur->inode->st.st_mode);
1053                         if (error)
1054                                 errx(1, "hammer2_nmkdir(\"%s\") failed: %s",
1055                                     cur->name, strerror(error));
1056                         assert(vp);
1057                         hammer2_print(dvp, vp, cur, depth, "nmkdir");
1058
1059                         error = hammer2_populate_dir(vp, path, cur->child, cur,
1060                             fsopts, depth + 1);
1061                         if (error)
1062                                 errx(1, "failed to populate %s: %s",
1063                                     path, strerror(error));
1064                         cur->inode->param = vp;
1065                         continue;
1066                 }
1067
1068                 /* if regular file, creat and write its data */
1069                 if (S_ISREG(cur->type) && !hardlink) {
1070                         assert(cur->child == NULL);
1071
1072                         vp = NULL;
1073                         error = hammer2_ncreate(dvp, &vp, cur->name,
1074                             strlen(cur->name), cur->inode->st.st_mode);
1075                         if (error)
1076                                 errx(1, "hammer2_ncreate(\"%s\") failed: %s",
1077                                     cur->name, strerror(error));
1078                         assert(vp);
1079                         hammer2_print(dvp, vp, cur, depth, "ncreate");
1080
1081                         error = hammer2_write_file(vp, path, cur);
1082                         if (error)
1083                                 errx(1, "hammer2_write_file(\"%s\") failed: %s",
1084                                     path, strerror(error));
1085                         cur->inode->param = vp;
1086                         continue;
1087                 }
1088
1089                 /* if symlink, create a symlink against target */
1090                 if (S_ISLNK(cur->type)) {
1091                         assert(cur->child == NULL);
1092
1093                         vp = NULL;
1094                         error = hammer2_nsymlink(dvp, &vp, cur->name,
1095                             strlen(cur->name), cur->symlink,
1096                             cur->inode->st.st_mode);
1097                         if (error)
1098                                 errx(1, "hammer2_nsymlink(\"%s\") failed: %s",
1099                                     cur->name, strerror(error));
1100                         assert(vp);
1101                         hammer2_print(dvp, vp, cur, depth, "nsymlink");
1102                         cur->inode->param = vp;
1103                         continue;
1104                 }
1105
1106                 /* if fifo, create a fifo */
1107                 if (S_ISFIFO(cur->type) && !hardlink) {
1108                         assert(cur->child == NULL);
1109
1110                         vp = NULL;
1111                         error = hammer2_nmknod(dvp, &vp, cur->name,
1112                             strlen(cur->name), VFIFO, cur->inode->st.st_mode);
1113                         if (error)
1114                                 errx(1, "hammer2_nmknod(\"%s\") failed: %s",
1115                                     cur->name, strerror(error));
1116                         assert(vp);
1117                         hammer2_print(dvp, vp, cur, depth, "nmknod");
1118                         cur->inode->param = vp;
1119                         continue;
1120                 }
1121
1122                 /* if hardlink, creat a hardlink */
1123                 if ((S_ISREG(cur->type) || S_ISFIFO(cur->type)) && hardlink) {
1124                         char buf[64];
1125                         assert(cur->child == NULL);
1126
1127                         /* source vnode must not be NULL */
1128                         vp = cur->inode->param;
1129                         assert(vp);
1130                         /* currently these conditions must be true */
1131                         assert(vp->v_data);
1132                         assert(vp->v_type == VREG || vp->v_type == VFIFO);
1133                         assert(vp->v_logical);
1134                         assert(!vp->v_vflushed);
1135                         assert(vp->v_malloced);
1136                         assert(VTOI(vp)->refs > 0);
1137
1138                         error = hammer2_nlink(dvp, vp, cur->name,
1139                             strlen(cur->name));
1140                         if (error)
1141                                 errx(1, "hammer2_nlink(\"%s\") failed: %s",
1142                                     cur->name, strerror(error));
1143                         snprintf(buf, sizeof(buf), "nlink=%lld",
1144                             (long long)VTOI(vp)->meta.nlinks);
1145                         hammer2_print(dvp, vp, cur, depth, buf);
1146                         continue;
1147                 }
1148
1149                 /* other types are unsupported */
1150                 printf("ignore %s 0%o\n", path, cur->type);
1151         }
1152
1153         return 0;
1154 }
1155
1156 static int
1157 hammer2_write_file(struct m_vnode *vp, const char *path, fsnode *node)
1158 {
1159         struct stat *st = &node->inode->st;
1160         size_t nsize, bufsize;
1161         off_t offset;
1162         int fd, error;
1163         char *p;
1164
1165         nsize = st->st_size;
1166         if (nsize == 0)
1167                 return 0;
1168         /* check nsize vs maximum file size */
1169
1170         fd = open(path, O_RDONLY);
1171         if (fd < 0)
1172                 err(1, "failed to open %s", path);
1173
1174         p = mmap(0, nsize, PROT_READ, MAP_FILE|MAP_PRIVATE, fd, 0);
1175         if (p == MAP_FAILED)
1176                 err(1, "failed to mmap %s", path);
1177         close(fd);
1178
1179         for (offset = 0; offset < nsize; ) {
1180                 bufsize = MIN(nsize - offset, HAMMER2_PBUFSIZE);
1181                 assert(bufsize <= HAMMER2_PBUFSIZE);
1182                 error = hammer2_write(vp, p + offset, bufsize, offset);
1183                 if (error)
1184                         errx(1, "failed to write to %s vnode: %s",
1185                             path, strerror(error));
1186                 offset += bufsize;
1187                 if (bufsize == HAMMER2_PBUFSIZE)
1188                         assert((offset & (HAMMER2_PBUFSIZE - 1)) == 0);
1189         }
1190         munmap(p, nsize);
1191
1192         return 0;
1193 }
1194
1195 static int
1196 trim_char(char *p, char c)
1197 {
1198         char *o, tmp[PATH_MAX];
1199         bool prev_was_c;
1200         size_t n;
1201         int i;
1202
1203         strlcpy(tmp, p, sizeof(tmp));
1204         if (strncmp(tmp, p, sizeof(tmp)))
1205                 return ENOSPC;
1206
1207         /* trim consecutive */
1208         prev_was_c = false;
1209         o = p;
1210         n = strlen(p);
1211
1212         for (i = 0; i < n; i++) {
1213                 if (tmp[i] == c) {
1214                         if (!prev_was_c)
1215                                 *p++ = tmp[i];
1216                         prev_was_c = true;
1217                 } else {
1218                         *p++ = tmp[i];
1219                         prev_was_c = false;
1220                 }
1221         }
1222         *p = 0;
1223         assert(strlen(p) <= strlen(tmp));
1224
1225         /* assert no consecutive */
1226         prev_was_c = false;
1227         p = o;
1228         n = strlen(p);
1229
1230         for (i = 0; i < n; i++) {
1231                 if (p[i] == c) {
1232                         assert(!prev_was_c);
1233                         prev_was_c = true;
1234                 } else {
1235                         prev_was_c = false;
1236                 }
1237         }
1238
1239         /* trim leading */
1240         if (*p == c)
1241                 memmove(p, p + 1, strlen(p + 1) + 1);
1242         assert(*p != '/');
1243
1244         /* trim trailing */
1245         p += strlen(p);
1246         p--;
1247         if (*p == c)
1248                 *p = 0;
1249         assert(p[strlen(p) - 1] != '/');
1250
1251         return 0;
1252 }
1253
1254 static int
1255 trim_slash(char *p)
1256 {
1257         return trim_char(p, '/');
1258 }
1259
1260 static int
1261 hammer2_version_get(struct m_vnode *vp)
1262 {
1263         hammer2_dev_t *hmp;
1264
1265         hmp = VTOI(vp)->pmp->pfs_hmps[0];
1266         if (hmp == NULL)
1267                 return EINVAL;
1268
1269         printf("version: %d\n", hmp->voldata.version);
1270
1271         return 0;
1272 }
1273
1274 struct pfs_entry {
1275         TAILQ_ENTRY(pfs_entry) entry;
1276         char name[NAME_MAX+1];
1277         char s[NAME_MAX+1];
1278 };
1279
1280 static int
1281 hammer2_pfs_get(struct m_vnode *vp)
1282 {
1283         hammer2_ioc_pfs_t pfs;
1284         TAILQ_HEAD(, pfs_entry) head;
1285         struct pfs_entry *p, *e;
1286         char *pfs_id_str;
1287         const char *type_str;
1288         int error;
1289
1290         bzero(&pfs, sizeof(pfs));
1291         TAILQ_INIT(&head);
1292
1293         while ((pfs.name_key = pfs.name_next) != (hammer2_key_t)-1) {
1294                 error = hammer2_ioctl_pfs_get(VTOI(vp), &pfs);
1295                 if (error)
1296                         return error;
1297
1298                 pfs_id_str = NULL;
1299                 hammer2_uuid_to_str(&pfs.pfs_clid, &pfs_id_str);
1300
1301                 if (pfs.pfs_type == HAMMER2_PFSTYPE_MASTER) {
1302                         if (pfs.pfs_subtype == HAMMER2_PFSSUBTYPE_NONE)
1303                                 type_str = "MASTER";
1304                         else
1305                                 type_str = hammer2_pfssubtype_to_str(
1306                                     pfs.pfs_subtype);
1307                 } else {
1308                         type_str = hammer2_pfstype_to_str(pfs.pfs_type);
1309                 }
1310                 e = ecalloc(1, sizeof(*e));
1311                 snprintf(e->name, sizeof(e->name), "%s", pfs.name);
1312                 snprintf(e->s, sizeof(e->s), "%-11s %s", type_str, pfs_id_str);
1313                 free(pfs_id_str);
1314
1315                 p = TAILQ_FIRST(&head);
1316                 while (p) {
1317                         if (strcmp(e->name, p->name) <= 0) {
1318                                 TAILQ_INSERT_BEFORE(p, e, entry);
1319                                 break;
1320                         }
1321                         p = TAILQ_NEXT(p, entry);
1322                 }
1323                 if (!p)
1324                         TAILQ_INSERT_TAIL(&head, e, entry);
1325         }
1326
1327         printf("Type        "
1328             "ClusterId (pfs_clid)                 "
1329             "Label\n");
1330         while ((p = TAILQ_FIRST(&head)) != NULL) {
1331                 printf("%s %s\n", p->s, p->name);
1332                 TAILQ_REMOVE(&head, p, entry);
1333                 free(p);
1334         }
1335
1336         return 0;
1337 }
1338
1339 static int
1340 hammer2_pfs_lookup(struct m_vnode *vp, const char *pfs_name)
1341 {
1342         hammer2_ioc_pfs_t pfs;
1343         char *pfs_id_str;
1344         int error;
1345
1346         bzero(&pfs, sizeof(pfs));
1347         strlcpy(pfs.name, pfs_name, sizeof(pfs.name));
1348
1349         error = hammer2_ioctl_pfs_lookup(VTOI(vp), &pfs);
1350         if (error == 0) {
1351                 printf("name: %s\n", pfs.name);
1352                 printf("type: %s\n", hammer2_pfstype_to_str(pfs.pfs_type));
1353                 printf("subtype: %s\n",
1354                     hammer2_pfssubtype_to_str(pfs.pfs_subtype));
1355
1356                 pfs_id_str = NULL;
1357                 hammer2_uuid_to_str(&pfs.pfs_fsid, &pfs_id_str);
1358                 printf("fsid: %s\n", pfs_id_str);
1359                 free(pfs_id_str);
1360
1361                 pfs_id_str = NULL;
1362                 hammer2_uuid_to_str(&pfs.pfs_clid, &pfs_id_str);
1363                 printf("clid: %s\n", pfs_id_str);
1364                 free(pfs_id_str);
1365         }
1366
1367         return error;
1368 }
1369
1370 static int
1371 hammer2_pfs_create(struct m_vnode *vp, const char *pfs_name)
1372 {
1373         hammer2_ioc_pfs_t pfs;
1374         int error;
1375
1376         bzero(&pfs, sizeof(pfs));
1377         strlcpy(pfs.name, pfs_name, sizeof(pfs.name));
1378         pfs.pfs_type = HAMMER2_PFSTYPE_MASTER;
1379         uuid_create(&pfs.pfs_clid, NULL);
1380         uuid_create(&pfs.pfs_fsid, NULL);
1381
1382         error = hammer2_ioctl_pfs_create(VTOI(vp), &pfs);
1383         if (error == EEXIST)
1384                 fprintf(stderr,
1385                     "NOTE: Typically the same name is "
1386                     "used for cluster elements on "
1387                     "different mounts,\n"
1388                     "      but cluster elements on the "
1389                     "same mount require unique names.\n"
1390                     "hammer2: pfs_create(%s): already present\n",
1391                     pfs_name);
1392
1393         return error;
1394 }
1395
1396 static int
1397 hammer2_pfs_delete(struct m_vnode *vp, const char *pfs_name)
1398 {
1399         hammer2_ioc_pfs_t pfs;
1400
1401         bzero(&pfs, sizeof(pfs));
1402         strlcpy(pfs.name, pfs_name, sizeof(pfs.name));
1403
1404         return hammer2_ioctl_pfs_delete(VTOI(vp), &pfs);
1405 }
1406
1407 static int
1408 hammer2_pfs_snapshot(struct m_vnode *vp, const char *pfs_name,
1409     const char *mount_label)
1410 {
1411         hammer2_ioc_pfs_t pfs;
1412         struct tm *tp;
1413         time_t t;
1414
1415         bzero(&pfs, sizeof(pfs));
1416         strlcpy(pfs.name, pfs_name, sizeof(pfs.name));
1417
1418         if (strlen(pfs.name) == 0) {
1419                 time(&t);
1420                 tp = localtime(&t);
1421                 snprintf(pfs.name, sizeof(pfs.name),
1422                     "%s.%04d%02d%02d.%02d%02d%02d",
1423                     mount_label,
1424                     tp->tm_year + 1900,
1425                     tp->tm_mon + 1,
1426                     tp->tm_mday,
1427                     tp->tm_hour,
1428                     tp->tm_min,
1429                     tp->tm_sec);
1430         }
1431
1432         return hammer2_ioctl_pfs_snapshot(VTOI(vp), &pfs);
1433 }
1434
1435 static int
1436 hammer2_inode_getx(struct m_vnode *dvp, const char *f)
1437 {
1438         hammer2_ioc_inode_t inode;
1439         hammer2_inode_t *ip;
1440         hammer2_inode_meta_t *meta;
1441         struct m_vnode *vp;
1442         char *o, *p, *name, *str = NULL;
1443         int error;
1444         uuid_t uuid;
1445
1446         assert(strlen(f) > 0);
1447         o = p = name = strdup(f);
1448
1449         error = trim_slash(p);
1450         if (error)
1451                 return error;
1452         if (strlen(p) == 0)
1453                 return EINVAL;
1454
1455         while ((p = strchr(p, '/')) != NULL) {
1456                 *p++ = 0; /* NULL terminate name */
1457                 vp = NULL;
1458                 error = hammer2_nresolve(dvp, &vp, name, strlen(name));
1459                 if (error)
1460                         return error;
1461
1462                 ip = VTOI(vp);
1463                 assert(ip->meta.type == HAMMER2_OBJTYPE_DIRECTORY);
1464
1465                 dvp = vp;
1466                 name = p;
1467         }
1468
1469         error = hammer2_nresolve(dvp, &vp, name, strlen(name));
1470         if (error)
1471                 return error;
1472
1473         bzero(&inode, sizeof(inode));
1474         error = hammer2_ioctl_inode_get(VTOI(vp), &inode);
1475         if (error)
1476                 return error;
1477
1478         meta = &inode.ip_data.meta;
1479         printf("--------------------\n");
1480         printf("flags = 0x%x\n", inode.flags);
1481         printf("data_count = %ju\n", (uintmax_t)inode.data_count);
1482         printf("inode_count = %ju\n", (uintmax_t)inode.inode_count);
1483         printf("--------------------\n");
1484         printf("version = %u\n", meta->version);
1485         printf("pfs_subtype = %u (%s)\n", meta->pfs_subtype,
1486             hammer2_pfssubtype_to_str(meta->pfs_subtype));
1487         printf("uflags = 0x%x\n", (unsigned int)meta->uflags);
1488         printf("rmajor = %u\n", meta->rmajor);
1489         printf("rminor = %u\n", meta->rminor);
1490         printf("ctime = %s\n", hammer2_time64_to_str(meta->ctime, &str));
1491         printf("mtime = %s\n", hammer2_time64_to_str(meta->mtime, &str));
1492         printf("atime = %s\n", hammer2_time64_to_str(meta->atime, &str));
1493         printf("btime = %s\n", hammer2_time64_to_str(meta->btime, &str));
1494         uuid = meta->uid;
1495         printf("uid = %s\n", hammer2_uuid_to_str(&uuid, &str));
1496         uuid = meta->gid;
1497         printf("gid = %s\n", hammer2_uuid_to_str(&uuid, &str));
1498         printf("type = %u (%s)\n", meta->type,
1499             hammer2_iptype_to_str(meta->type));
1500         printf("op_flags = 0x%x\n", meta->op_flags);
1501         printf("cap_flags = 0x%x\n", meta->cap_flags);
1502         printf("mode = 0%o\n", meta->mode);
1503         printf("inum = 0x%jx\n", (uintmax_t)meta->inum);
1504         printf("size = %ju\n", (uintmax_t)meta->size);
1505         printf("nlinks = %ju\n", (uintmax_t)meta->nlinks);
1506         printf("iparent = 0x%jx\n", (uintmax_t)meta->iparent);
1507         printf("name_key = 0x%jx\n", (uintmax_t)meta->name_key);
1508         printf("name_len = %u\n", meta->name_len);
1509         printf("ncopies = %u\n", meta->ncopies);
1510         printf("comp_algo = %u\n", meta->comp_algo);
1511         printf("target_type = %u\n", meta->target_type);
1512         printf("check_algo = %u\n", meta->check_algo);
1513         printf("pfs_nmasters = %u\n", meta->pfs_nmasters);
1514         printf("pfs_type = %u (%s)\n", meta->pfs_type,
1515             hammer2_pfstype_to_str(meta->pfs_type));
1516         printf("pfs_inum = 0x%jx\n", (uintmax_t)meta->pfs_inum);
1517         uuid = meta->pfs_clid;
1518         printf("pfs_clid = %s\n", hammer2_uuid_to_str(&uuid, &str));
1519         uuid = meta->pfs_fsid;
1520         printf("pfs_fsid = %s\n", hammer2_uuid_to_str(&uuid, &str));
1521         printf("data_quota = 0x%jx\n", (uintmax_t)meta->data_quota);
1522         printf("inode_quota = 0x%jx\n", (uintmax_t)meta->inode_quota);
1523         printf("pfs_lsnap_tid = 0x%jx\n", (uintmax_t)meta->pfs_lsnap_tid);
1524         printf("decrypt_check = 0x%jx\n", (uintmax_t)meta->decrypt_check);
1525         printf("--------------------\n");
1526
1527         free(o);
1528
1529         return error;
1530 }
1531
1532 static int
1533 hammer2_inode_setcheck(struct m_vnode *dvp, const char *f)
1534 {
1535         hammer2_ioc_inode_t inode;
1536         hammer2_inode_t *ip;
1537         struct m_vnode *vp;
1538         char *o, *p, *name, *check_algo_str;
1539         const char *checks[] = { "none", "disabled", "crc32", "xxhash64",
1540             "sha192", };
1541         int check_algo_idx, error;
1542         uint8_t check_algo;
1543
1544         assert(strlen(f) > 0);
1545         o = p = strdup(f);
1546
1547         p = strrchr(p, ':');
1548         if (p == NULL)
1549                 return EINVAL;
1550
1551         *p++ = 0; /* NULL terminate path */
1552         check_algo_str = p;
1553         name = p = o;
1554
1555         error = trim_slash(p);
1556         if (error)
1557                 return error;
1558         if (strlen(p) == 0 || strlen(check_algo_str) == 0)
1559                 return EINVAL;
1560
1561         /* convert check_algo_str to check_algo_idx */
1562         check_algo_idx = nitems(checks);
1563         while (--check_algo_idx >= 0)
1564                 if (strcasecmp(check_algo_str, checks[check_algo_idx]) == 0)
1565                         break;
1566         if (check_algo_idx < 0) {
1567                 if (strcasecmp(check_algo_str, "default") == 0) {
1568                         check_algo_str = "xxhash64";
1569                         check_algo_idx = HAMMER2_CHECK_XXHASH64;
1570                 } else if (strcasecmp(check_algo_str, "disabled") == 0) {
1571                         check_algo_str = "disabled";
1572                         check_algo_idx = HAMMER2_CHECK_DISABLED;
1573                 } else {
1574                         printf("invalid check_algo_str: %s\n", check_algo_str);
1575                         return EINVAL;
1576                 }
1577         }
1578         check_algo = HAMMER2_ENC_ALGO(check_algo_idx);
1579         printf("change %s to algo %d (%s)\n", p, check_algo, check_algo_str);
1580
1581         while ((p = strchr(p, '/')) != NULL) {
1582                 *p++ = 0; /* NULL terminate name */
1583                 vp = NULL;
1584                 error = hammer2_nresolve(dvp, &vp, name, strlen(name));
1585                 if (error)
1586                         return error;
1587
1588                 ip = VTOI(vp);
1589                 assert(ip->meta.type == HAMMER2_OBJTYPE_DIRECTORY);
1590
1591                 dvp = vp;
1592                 name = p;
1593         }
1594
1595         error = hammer2_nresolve(dvp, &vp, name, strlen(name));
1596         if (error)
1597                 return error;
1598         ip = VTOI(vp);
1599
1600         bzero(&inode, sizeof(inode));
1601         error = hammer2_ioctl_inode_get(ip, &inode);
1602         if (error)
1603                 return error;
1604
1605         inode.flags |= HAMMER2IOC_INODE_FLAG_CHECK;
1606         inode.ip_data.meta.check_algo = check_algo;
1607         error = hammer2_ioctl_inode_set(ip, &inode);
1608         if (error)
1609                 return error;
1610
1611         free(o);
1612
1613         return error;
1614 }
1615
1616 static int
1617 hammer2_inode_setcomp(struct m_vnode *dvp, const char *f)
1618 {
1619         hammer2_ioc_inode_t inode;
1620         hammer2_inode_t *ip;
1621         struct m_vnode *vp;
1622         char *o, *p, *name, *comp_algo_str, *comp_level_str;
1623         const char *comps[] = { "none", "autozero", "lz4", "zlib", };
1624         int comp_algo_idx, comp_level_idx, error;
1625         uint8_t comp_algo, comp_level;
1626
1627         assert(strlen(f) > 0);
1628         o = p = strdup(f);
1629
1630         p = strrchr(p, ':');
1631         if (p == NULL)
1632                 return EINVAL;
1633
1634         *p++ = 0; /* NULL terminate comp_algo_str */
1635         comp_level_str = p;
1636         p = o;
1637
1638         p = strrchr(p, ':');
1639         if (p == NULL) {
1640                 /* comp_level_str not specified */
1641                 comp_algo_str = comp_level_str;
1642                 comp_level_str = NULL;
1643         } else {
1644                 *p++ = 0; /* NULL terminate path */
1645                 comp_algo_str = p;
1646         }
1647         name = p = o;
1648
1649         error = trim_slash(p);
1650         if (error)
1651                 return error;
1652         if (strlen(p) == 0 || strlen(comp_algo_str) == 0)
1653                 return EINVAL;
1654
1655         /* convert comp_algo_str to comp_algo_idx */
1656         comp_algo_idx = nitems(comps);
1657         while (--comp_algo_idx >= 0)
1658                 if (strcasecmp(comp_algo_str, comps[comp_algo_idx]) == 0)
1659                         break;
1660         if (comp_algo_idx < 0) {
1661                 if (strcasecmp(comp_algo_str, "default") == 0) {
1662                         comp_algo_str = "lz4";
1663                         comp_algo_idx = HAMMER2_COMP_LZ4;
1664                 } else if (strcasecmp(comp_algo_str, "disabled") == 0) {
1665                         comp_algo_str = "autozero";
1666                         comp_algo_idx = HAMMER2_COMP_AUTOZERO;
1667                 } else {
1668                         printf("invalid comp_algo_str: %s\n", comp_algo_str);
1669                         return EINVAL;
1670                 }
1671         }
1672         comp_algo = HAMMER2_ENC_ALGO(comp_algo_idx);
1673
1674         /* convert comp_level_str to comp_level_idx */
1675         if (comp_level_str == NULL) {
1676                 comp_level_idx = 0;
1677         } else if (isdigit((int)comp_level_str[0])) {
1678                 comp_level_idx = strtol(comp_level_str, NULL, 0);
1679         } else if (strcasecmp(comp_level_str, "default") == 0) {
1680                 comp_level_idx = 0;
1681         } else {
1682                 printf("invalid comp_level_str: %s\n", comp_level_str);
1683                 return EINVAL;
1684         }
1685         if (comp_level_idx) {
1686                 switch (comp_algo) {
1687                 case HAMMER2_COMP_ZLIB:
1688                         if (comp_level_idx < 6 || comp_level_idx > 9) {
1689                                 printf("unsupported comp_level %d for %s\n",
1690                                     comp_level_idx, comp_algo_str);
1691                                 return EINVAL;
1692                         }
1693                         break;
1694                 default:
1695                         printf("unsupported comp_level %d for %s\n",
1696                             comp_level_idx, comp_algo_str);
1697                         return EINVAL;
1698                 }
1699         }
1700         comp_level = HAMMER2_ENC_LEVEL(comp_level_idx);
1701         printf("change %s to algo %d (%s) level %d\n",
1702             p, comp_algo, comp_algo_str, comp_level_idx);
1703
1704         while ((p = strchr(p, '/')) != NULL) {
1705                 *p++ = 0; /* NULL terminate name */
1706                 vp = NULL;
1707                 error = hammer2_nresolve(dvp, &vp, name, strlen(name));
1708                 if (error)
1709                         return error;
1710
1711                 ip = VTOI(vp);
1712                 assert(ip->meta.type == HAMMER2_OBJTYPE_DIRECTORY);
1713
1714                 dvp = vp;
1715                 name = p;
1716         }
1717
1718         error = hammer2_nresolve(dvp, &vp, name, strlen(name));
1719         if (error)
1720                 return error;
1721         ip = VTOI(vp);
1722
1723         bzero(&inode, sizeof(inode));
1724         error = hammer2_ioctl_inode_get(ip, &inode);
1725         if (error)
1726                 return error;
1727
1728         inode.flags |= HAMMER2IOC_INODE_FLAG_COMP;
1729         inode.ip_data.meta.comp_algo = comp_algo | comp_level;
1730         error = hammer2_ioctl_inode_set(ip, &inode);
1731         if (error)
1732                 return error;
1733
1734         free(o);
1735
1736         return error;
1737 }
1738
1739 static int
1740 hammer2_bulkfree(struct m_vnode *vp)
1741 {
1742         hammer2_ioc_bulkfree_t bfi;
1743         size_t usermem;
1744         size_t usermem_size = sizeof(usermem);
1745
1746         bzero(&bfi, sizeof(bfi));
1747         usermem = 0;
1748         if (sysctlbyname("hw.usermem", &usermem, &usermem_size, NULL, 0) == 0)
1749                 bfi.size = usermem / 16;
1750         else
1751                 bfi.size = 0;
1752         if (bfi.size < 8192 * 1024)
1753                 bfi.size = 8192 * 1024;
1754
1755         return hammer2_ioctl_bulkfree_scan(VTOI(vp), &bfi);
1756 }
1757
1758 static int
1759 hammer2_destroy_path(struct m_vnode *dvp, const char *f)
1760 {
1761         hammer2_ioc_destroy_t destroy;
1762         hammer2_inode_t *ip;
1763         struct m_vnode *vp;
1764         char *o, *p, *name;
1765         int error;
1766
1767         assert(strlen(f) > 0);
1768         o = p = name = strdup(f);
1769
1770         error = trim_slash(p);
1771         if (error)
1772                 return error;
1773         if (strlen(p) == 0)
1774                 return EINVAL;
1775
1776         while ((p = strchr(p, '/')) != NULL) {
1777                 *p++ = 0; /* NULL terminate name */
1778                 vp = NULL;
1779                 error = hammer2_nresolve(dvp, &vp, name, strlen(name));
1780                 if (error)
1781                         return error;
1782
1783                 ip = VTOI(vp);
1784                 assert(ip->meta.type == HAMMER2_OBJTYPE_DIRECTORY);
1785
1786                 dvp = vp;
1787                 name = p;
1788         }
1789
1790         /* XXX When does (or why does not) ioctl modify this inode ? */
1791         hammer2_inode_modify(VTOI(dvp));
1792
1793         bzero(&destroy, sizeof(destroy));
1794         destroy.cmd = HAMMER2_DELETE_FILE;
1795         snprintf(destroy.path, sizeof(destroy.path), "%s", name);
1796
1797         printf("%s\t", f);
1798         fflush(stdout);
1799
1800         error = hammer2_ioctl_destroy(VTOI(dvp), &destroy);
1801         if (error)
1802                 printf("%s\n", strerror(error));
1803         else
1804                 printf("ok\n");
1805         free(o);
1806
1807         return error;
1808 }
1809
1810 static int
1811 hammer2_destroy_inum(struct m_vnode *vp, hammer2_tid_t inum)
1812 {
1813         hammer2_ioc_destroy_t destroy;
1814         int error;
1815
1816         bzero(&destroy, sizeof(destroy));
1817         destroy.cmd = HAMMER2_DELETE_INUM;
1818         destroy.inum = inum;
1819
1820         printf("%jd\t", (intmax_t)destroy.inum);
1821         fflush(stdout);
1822
1823         error = hammer2_ioctl_destroy(VTOI(vp), &destroy);
1824         if (error)
1825                 printf("%s\n", strerror(error));
1826         else
1827                 printf("ok\n");
1828
1829         return error;
1830 }
1831
1832 static int
1833 hammer2_growfs(struct m_vnode *vp, hammer2_off_t size)
1834 {
1835         hammer2_ioc_growfs_t growfs;
1836         int error;
1837
1838         bzero(&growfs, sizeof(growfs));
1839         growfs.size = size;
1840
1841         error = hammer2_ioctl_growfs(VTOI(vp), &growfs, NULL);
1842         if (!error) {
1843                 if (growfs.modified)
1844                         printf("grown to %016jx\n", (intmax_t)growfs.size);
1845                 else
1846                         printf("no size change - %016jx\n",
1847                             (intmax_t)growfs.size);
1848         }
1849
1850         return error;
1851 }
1852
1853 static void
1854 assert_trim_slash(const char *input, const char *expected)
1855 {
1856         char tmp[PATH_MAX];
1857         int error;
1858
1859         strlcpy(tmp, input, sizeof(tmp));
1860         error = trim_slash(tmp);
1861         if (error)
1862                 errx(1, "input \"%s\" error %d", input, error);
1863
1864         if (strncmp(tmp, expected, sizeof(tmp)))
1865                 errx(1, "input \"%s\" result \"%s\" vs expected \"%s\"",
1866                     input, tmp, expected);
1867 }
1868
1869 static void
1870 unittest_trim_slash(void)
1871 {
1872         assert_trim_slash("", "");
1873         assert_trim_slash("/", "");
1874         assert_trim_slash("//", "");
1875         assert_trim_slash("///", "");
1876
1877         assert_trim_slash("makefs", "makefs");
1878         assert_trim_slash("/makefs", "makefs");
1879         assert_trim_slash("//makefs", "makefs");
1880         assert_trim_slash("makefs/", "makefs");
1881         assert_trim_slash("makefs//", "makefs");
1882         assert_trim_slash("/makefs/", "makefs");
1883         assert_trim_slash("//makefs//", "makefs");
1884
1885         assert_trim_slash("sys/vfs/hammer2", "sys/vfs/hammer2");
1886         assert_trim_slash("/sys/vfs/hammer2", "sys/vfs/hammer2");
1887         assert_trim_slash("//sys/vfs/hammer2", "sys/vfs/hammer2");
1888         assert_trim_slash("///sys/vfs/hammer2", "sys/vfs/hammer2");
1889         assert_trim_slash("sys/vfs/hammer2/", "sys/vfs/hammer2");
1890         assert_trim_slash("sys/vfs/hammer2//", "sys/vfs/hammer2");
1891         assert_trim_slash("sys/vfs/hammer2///", "sys/vfs/hammer2");
1892         assert_trim_slash("/sys/vfs/hammer2/", "sys/vfs/hammer2");
1893         assert_trim_slash("//sys//vfs//hammer2//", "sys/vfs/hammer2");
1894         assert_trim_slash("///sys///vfs///hammer2///", "sys/vfs/hammer2");
1895
1896         APRINTF("success\n");
1897 }