xref: /dragonfly/usr.sbin/makefs/hammer2.c (revision 89a3eb16)
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 bool
1261 is_supported_link(const char *s)
1262 {
1263 	/* absolute path can't be supported */
1264 	if (strlen(s) >= 1 && strncmp(s, "/", 1) == 0)
1265 		return false;
1266 
1267 	/* XXX ".." is currently unsupported */
1268 	if (strlen(s) >= 3 && strncmp(s, "../", 3) == 0)
1269 		return false;
1270 
1271 	return true;
1272 }
1273 
1274 static int
1275 hammer2_version_get(struct m_vnode *vp)
1276 {
1277 	hammer2_dev_t *hmp;
1278 
1279 	hmp = VTOI(vp)->pmp->pfs_hmps[0];
1280 	if (hmp == NULL)
1281 		return EINVAL;
1282 
1283 	printf("version: %d\n", hmp->voldata.version);
1284 
1285 	return 0;
1286 }
1287 
1288 struct pfs_entry {
1289 	TAILQ_ENTRY(pfs_entry) entry;
1290 	char name[NAME_MAX+1];
1291 	char s[NAME_MAX+1];
1292 };
1293 
1294 static int
1295 hammer2_pfs_get(struct m_vnode *vp)
1296 {
1297 	hammer2_ioc_pfs_t pfs;
1298 	TAILQ_HEAD(, pfs_entry) head;
1299 	struct pfs_entry *p, *e;
1300 	char *pfs_id_str;
1301 	const char *type_str;
1302 	int error;
1303 
1304 	bzero(&pfs, sizeof(pfs));
1305 	TAILQ_INIT(&head);
1306 
1307 	while ((pfs.name_key = pfs.name_next) != (hammer2_key_t)-1) {
1308 		error = hammer2_ioctl_pfs_get(VTOI(vp), &pfs);
1309 		if (error)
1310 			return error;
1311 
1312 		pfs_id_str = NULL;
1313 		hammer2_uuid_to_str(&pfs.pfs_clid, &pfs_id_str);
1314 
1315 		if (pfs.pfs_type == HAMMER2_PFSTYPE_MASTER) {
1316 			if (pfs.pfs_subtype == HAMMER2_PFSSUBTYPE_NONE)
1317 				type_str = "MASTER";
1318 			else
1319 				type_str = hammer2_pfssubtype_to_str(
1320 				    pfs.pfs_subtype);
1321 		} else {
1322 			type_str = hammer2_pfstype_to_str(pfs.pfs_type);
1323 		}
1324 		e = ecalloc(1, sizeof(*e));
1325 		snprintf(e->name, sizeof(e->name), "%s", pfs.name);
1326 		snprintf(e->s, sizeof(e->s), "%-11s %s", type_str, pfs_id_str);
1327 		free(pfs_id_str);
1328 
1329 		p = TAILQ_FIRST(&head);
1330 		while (p) {
1331 			if (strcmp(e->name, p->name) <= 0) {
1332 				TAILQ_INSERT_BEFORE(p, e, entry);
1333 				break;
1334 			}
1335 			p = TAILQ_NEXT(p, entry);
1336 		}
1337 		if (!p)
1338 			TAILQ_INSERT_TAIL(&head, e, entry);
1339 	}
1340 
1341 	printf("Type        "
1342 	    "ClusterId (pfs_clid)                 "
1343 	    "Label\n");
1344 	while ((p = TAILQ_FIRST(&head)) != NULL) {
1345 		printf("%s %s\n", p->s, p->name);
1346 		TAILQ_REMOVE(&head, p, entry);
1347 		free(p);
1348 	}
1349 
1350 	return 0;
1351 }
1352 
1353 static int
1354 hammer2_pfs_lookup(struct m_vnode *vp, const char *pfs_name)
1355 {
1356 	hammer2_ioc_pfs_t pfs;
1357 	char *pfs_id_str;
1358 	int error;
1359 
1360 	bzero(&pfs, sizeof(pfs));
1361 	strlcpy(pfs.name, pfs_name, sizeof(pfs.name));
1362 
1363 	error = hammer2_ioctl_pfs_lookup(VTOI(vp), &pfs);
1364 	if (error == 0) {
1365 		printf("name: %s\n", pfs.name);
1366 		printf("type: %s\n", hammer2_pfstype_to_str(pfs.pfs_type));
1367 		printf("subtype: %s\n",
1368 		    hammer2_pfssubtype_to_str(pfs.pfs_subtype));
1369 
1370 		pfs_id_str = NULL;
1371 		hammer2_uuid_to_str(&pfs.pfs_fsid, &pfs_id_str);
1372 		printf("fsid: %s\n", pfs_id_str);
1373 		free(pfs_id_str);
1374 
1375 		pfs_id_str = NULL;
1376 		hammer2_uuid_to_str(&pfs.pfs_clid, &pfs_id_str);
1377 		printf("clid: %s\n", pfs_id_str);
1378 		free(pfs_id_str);
1379 	}
1380 
1381 	return error;
1382 }
1383 
1384 static int
1385 hammer2_pfs_create(struct m_vnode *vp, const char *pfs_name)
1386 {
1387 	hammer2_ioc_pfs_t pfs;
1388 	int error;
1389 
1390 	bzero(&pfs, sizeof(pfs));
1391 	strlcpy(pfs.name, pfs_name, sizeof(pfs.name));
1392 	pfs.pfs_type = HAMMER2_PFSTYPE_MASTER;
1393 	uuid_create(&pfs.pfs_clid, NULL);
1394 	uuid_create(&pfs.pfs_fsid, NULL);
1395 
1396 	error = hammer2_ioctl_pfs_create(VTOI(vp), &pfs);
1397 	if (error == EEXIST)
1398 		fprintf(stderr,
1399 		    "NOTE: Typically the same name is "
1400 		    "used for cluster elements on "
1401 		    "different mounts,\n"
1402 		    "      but cluster elements on the "
1403 		    "same mount require unique names.\n"
1404 		    "hammer2: pfs_create(%s): already present\n",
1405 		    pfs_name);
1406 
1407 	return error;
1408 }
1409 
1410 static int
1411 hammer2_pfs_delete(struct m_vnode *vp, const char *pfs_name)
1412 {
1413 	hammer2_ioc_pfs_t pfs;
1414 
1415 	bzero(&pfs, sizeof(pfs));
1416 	strlcpy(pfs.name, pfs_name, sizeof(pfs.name));
1417 
1418 	return hammer2_ioctl_pfs_delete(VTOI(vp), &pfs);
1419 }
1420 
1421 static int
1422 hammer2_pfs_snapshot(struct m_vnode *vp, const char *pfs_name,
1423     const char *mount_label)
1424 {
1425 	hammer2_ioc_pfs_t pfs;
1426 	struct tm *tp;
1427 	time_t t;
1428 
1429 	bzero(&pfs, sizeof(pfs));
1430 	strlcpy(pfs.name, pfs_name, sizeof(pfs.name));
1431 
1432 	if (strlen(pfs.name) == 0) {
1433 		time(&t);
1434 		tp = localtime(&t);
1435 		snprintf(pfs.name, sizeof(pfs.name),
1436 		    "%s.%04d%02d%02d.%02d%02d%02d",
1437 		    mount_label,
1438 		    tp->tm_year + 1900,
1439 		    tp->tm_mon + 1,
1440 		    tp->tm_mday,
1441 		    tp->tm_hour,
1442 		    tp->tm_min,
1443 		    tp->tm_sec);
1444 	}
1445 
1446 	return hammer2_ioctl_pfs_snapshot(VTOI(vp), &pfs);
1447 }
1448 
1449 static int
1450 hammer2_inode_getx(struct m_vnode *dvp, const char *f)
1451 {
1452 	hammer2_ioc_inode_t inode;
1453 	hammer2_inode_t *ip;
1454 	hammer2_inode_meta_t *meta;
1455 	struct m_vnode *vp;
1456 	char *o, *p, *name, *str = NULL;
1457 	char tmp[PATH_MAX];
1458 	int error;
1459 	uuid_t uuid;
1460 
1461 	assert(strlen(f) > 0);
1462 	o = p = name = strdup(f);
1463 
1464 	error = trim_slash(p);
1465 	if (error)
1466 		return error;
1467 	if (strlen(p) == 0)
1468 		return EINVAL;
1469 
1470 	while ((p = strchr(p, '/')) != NULL) {
1471 		*p++ = 0; /* NULL terminate name */
1472 		if (!strcmp(name, ".")) {
1473 			name = p;
1474 			continue;
1475 		}
1476 		vp = NULL;
1477 		error = hammer2_nresolve(dvp, &vp, name, strlen(name));
1478 		if (error)
1479 			return error;
1480 
1481 		ip = VTOI(vp);
1482 		switch (ip->meta.type) {
1483 		case HAMMER2_OBJTYPE_DIRECTORY:
1484 			break;
1485 		case HAMMER2_OBJTYPE_SOFTLINK:
1486 			bzero(tmp, sizeof(tmp));
1487 			error = hammer2_readlink(vp, tmp, sizeof(tmp));
1488 			if (error)
1489 				return error;
1490 			if (!is_supported_link(tmp))
1491 				return EINVAL;
1492 			strlcat(tmp, "/", sizeof(tmp));
1493 			strlcat(tmp, p, sizeof(tmp));
1494 			error = trim_slash(tmp);
1495 			if (error)
1496 				return error;
1497 			p = name = tmp;
1498 			continue;
1499 		default:
1500 			return EINVAL;
1501 		}
1502 
1503 		dvp = vp;
1504 		name = p;
1505 	}
1506 
1507 	error = hammer2_nresolve(dvp, &vp, name, strlen(name));
1508 	if (error)
1509 		return error;
1510 
1511 	bzero(&inode, sizeof(inode));
1512 	error = hammer2_ioctl_inode_get(VTOI(vp), &inode);
1513 	if (error)
1514 		return error;
1515 
1516 	meta = &inode.ip_data.meta;
1517 	printf("--------------------\n");
1518 	printf("flags = 0x%x\n", inode.flags);
1519 	printf("data_count = %ju\n", (uintmax_t)inode.data_count);
1520 	printf("inode_count = %ju\n", (uintmax_t)inode.inode_count);
1521 	printf("--------------------\n");
1522 	printf("version = %u\n", meta->version);
1523 	printf("pfs_subtype = %u (%s)\n", meta->pfs_subtype,
1524 	    hammer2_pfssubtype_to_str(meta->pfs_subtype));
1525 	printf("uflags = 0x%x\n", (unsigned int)meta->uflags);
1526 	printf("rmajor = %u\n", meta->rmajor);
1527 	printf("rminor = %u\n", meta->rminor);
1528 	printf("ctime = %s\n", hammer2_time64_to_str(meta->ctime, &str));
1529 	printf("mtime = %s\n", hammer2_time64_to_str(meta->mtime, &str));
1530 	printf("atime = %s\n", hammer2_time64_to_str(meta->atime, &str));
1531 	printf("btime = %s\n", hammer2_time64_to_str(meta->btime, &str));
1532 	uuid = meta->uid;
1533 	printf("uid = %s\n", hammer2_uuid_to_str(&uuid, &str));
1534 	uuid = meta->gid;
1535 	printf("gid = %s\n", hammer2_uuid_to_str(&uuid, &str));
1536 	printf("type = %u (%s)\n", meta->type,
1537 	    hammer2_iptype_to_str(meta->type));
1538 	printf("op_flags = 0x%x\n", meta->op_flags);
1539 	printf("cap_flags = 0x%x\n", meta->cap_flags);
1540 	printf("mode = 0%o\n", meta->mode);
1541 	printf("inum = 0x%jx\n", (uintmax_t)meta->inum);
1542 	printf("size = %ju\n", (uintmax_t)meta->size);
1543 	printf("nlinks = %ju\n", (uintmax_t)meta->nlinks);
1544 	printf("iparent = 0x%jx\n", (uintmax_t)meta->iparent);
1545 	printf("name_key = 0x%jx\n", (uintmax_t)meta->name_key);
1546 	printf("name_len = %u\n", meta->name_len);
1547 	printf("ncopies = %u\n", meta->ncopies);
1548 	printf("comp_algo = %u\n", meta->comp_algo);
1549 	printf("target_type = %u\n", meta->target_type);
1550 	printf("check_algo = %u\n", meta->check_algo);
1551 	printf("pfs_nmasters = %u\n", meta->pfs_nmasters);
1552 	printf("pfs_type = %u (%s)\n", meta->pfs_type,
1553 	    hammer2_pfstype_to_str(meta->pfs_type));
1554 	printf("pfs_inum = 0x%jx\n", (uintmax_t)meta->pfs_inum);
1555 	uuid = meta->pfs_clid;
1556 	printf("pfs_clid = %s\n", hammer2_uuid_to_str(&uuid, &str));
1557 	uuid = meta->pfs_fsid;
1558 	printf("pfs_fsid = %s\n", hammer2_uuid_to_str(&uuid, &str));
1559 	printf("data_quota = 0x%jx\n", (uintmax_t)meta->data_quota);
1560 	printf("inode_quota = 0x%jx\n", (uintmax_t)meta->inode_quota);
1561 	printf("pfs_lsnap_tid = 0x%jx\n", (uintmax_t)meta->pfs_lsnap_tid);
1562 	printf("decrypt_check = 0x%jx\n", (uintmax_t)meta->decrypt_check);
1563 	printf("--------------------\n");
1564 
1565 	free(o);
1566 
1567 	return error;
1568 }
1569 
1570 static int
1571 hammer2_inode_setcheck(struct m_vnode *dvp, const char *f)
1572 {
1573 	hammer2_ioc_inode_t inode;
1574 	hammer2_inode_t *ip;
1575 	struct m_vnode *vp;
1576 	char *o, *p, *name, *check_algo_str;
1577 	char tmp[PATH_MAX];
1578 	const char *checks[] = { "none", "disabled", "crc32", "xxhash64",
1579 	    "sha192", };
1580 	int check_algo_idx, error;
1581 	uint8_t check_algo;
1582 
1583 	assert(strlen(f) > 0);
1584 	o = p = strdup(f);
1585 
1586 	p = strrchr(p, ':');
1587 	if (p == NULL)
1588 		return EINVAL;
1589 
1590 	*p++ = 0; /* NULL terminate path */
1591 	check_algo_str = p;
1592 	name = p = o;
1593 
1594 	error = trim_slash(p);
1595 	if (error)
1596 		return error;
1597 	if (strlen(p) == 0 || strlen(check_algo_str) == 0)
1598 		return EINVAL;
1599 
1600 	/* convert check_algo_str to check_algo_idx */
1601 	check_algo_idx = nitems(checks);
1602 	while (--check_algo_idx >= 0)
1603 		if (strcasecmp(check_algo_str, checks[check_algo_idx]) == 0)
1604 			break;
1605 	if (check_algo_idx < 0) {
1606 		if (strcasecmp(check_algo_str, "default") == 0) {
1607 			check_algo_str = "xxhash64";
1608 			check_algo_idx = HAMMER2_CHECK_XXHASH64;
1609 		} else if (strcasecmp(check_algo_str, "disabled") == 0) {
1610 			check_algo_str = "disabled";
1611 			check_algo_idx = HAMMER2_CHECK_DISABLED;
1612 		} else {
1613 			printf("invalid check_algo_str: %s\n", check_algo_str);
1614 			return EINVAL;
1615 		}
1616 	}
1617 	check_algo = HAMMER2_ENC_ALGO(check_algo_idx);
1618 	printf("change %s to algo %d (%s)\n", p, check_algo, check_algo_str);
1619 
1620 	while ((p = strchr(p, '/')) != NULL) {
1621 		*p++ = 0; /* NULL terminate name */
1622 		if (!strcmp(name, ".")) {
1623 			name = p;
1624 			continue;
1625 		}
1626 		vp = NULL;
1627 		error = hammer2_nresolve(dvp, &vp, name, strlen(name));
1628 		if (error)
1629 			return error;
1630 
1631 		ip = VTOI(vp);
1632 		switch (ip->meta.type) {
1633 		case HAMMER2_OBJTYPE_DIRECTORY:
1634 			break;
1635 		case HAMMER2_OBJTYPE_SOFTLINK:
1636 			bzero(tmp, sizeof(tmp));
1637 			error = hammer2_readlink(vp, tmp, sizeof(tmp));
1638 			if (error)
1639 				return error;
1640 			if (!is_supported_link(tmp))
1641 				return EINVAL;
1642 			strlcat(tmp, "/", sizeof(tmp));
1643 			strlcat(tmp, p, sizeof(tmp));
1644 			error = trim_slash(tmp);
1645 			if (error)
1646 				return error;
1647 			p = name = tmp;
1648 			continue;
1649 		default:
1650 			return EINVAL;
1651 		}
1652 
1653 		dvp = vp;
1654 		name = p;
1655 	}
1656 
1657 	error = hammer2_nresolve(dvp, &vp, name, strlen(name));
1658 	if (error)
1659 		return error;
1660 	ip = VTOI(vp);
1661 
1662 	bzero(&inode, sizeof(inode));
1663 	error = hammer2_ioctl_inode_get(ip, &inode);
1664 	if (error)
1665 		return error;
1666 
1667 	inode.flags |= HAMMER2IOC_INODE_FLAG_CHECK;
1668 	inode.ip_data.meta.check_algo = check_algo;
1669 	error = hammer2_ioctl_inode_set(ip, &inode);
1670 	if (error)
1671 		return error;
1672 
1673 	free(o);
1674 
1675 	return error;
1676 }
1677 
1678 static int
1679 hammer2_inode_setcomp(struct m_vnode *dvp, const char *f)
1680 {
1681 	hammer2_ioc_inode_t inode;
1682 	hammer2_inode_t *ip;
1683 	struct m_vnode *vp;
1684 	char *o, *p, *name, *comp_algo_str, *comp_level_str;
1685 	char tmp[PATH_MAX];
1686 	const char *comps[] = { "none", "autozero", "lz4", "zlib", };
1687 	int comp_algo_idx, comp_level_idx, error;
1688 	uint8_t comp_algo, comp_level;
1689 
1690 	assert(strlen(f) > 0);
1691 	o = p = strdup(f);
1692 
1693 	p = strrchr(p, ':');
1694 	if (p == NULL)
1695 		return EINVAL;
1696 
1697 	*p++ = 0; /* NULL terminate comp_algo_str */
1698 	comp_level_str = p;
1699 	p = o;
1700 
1701 	p = strrchr(p, ':');
1702 	if (p == NULL) {
1703 		/* comp_level_str not specified */
1704 		comp_algo_str = comp_level_str;
1705 		comp_level_str = NULL;
1706 	} else {
1707 		*p++ = 0; /* NULL terminate path */
1708 		comp_algo_str = p;
1709 	}
1710 	name = p = o;
1711 
1712 	error = trim_slash(p);
1713 	if (error)
1714 		return error;
1715 	if (strlen(p) == 0 || strlen(comp_algo_str) == 0)
1716 		return EINVAL;
1717 
1718 	/* convert comp_algo_str to comp_algo_idx */
1719 	comp_algo_idx = nitems(comps);
1720 	while (--comp_algo_idx >= 0)
1721 		if (strcasecmp(comp_algo_str, comps[comp_algo_idx]) == 0)
1722 			break;
1723 	if (comp_algo_idx < 0) {
1724 		if (strcasecmp(comp_algo_str, "default") == 0) {
1725 			comp_algo_str = "lz4";
1726 			comp_algo_idx = HAMMER2_COMP_LZ4;
1727 		} else if (strcasecmp(comp_algo_str, "disabled") == 0) {
1728 			comp_algo_str = "autozero";
1729 			comp_algo_idx = HAMMER2_COMP_AUTOZERO;
1730 		} else {
1731 			printf("invalid comp_algo_str: %s\n", comp_algo_str);
1732 			return EINVAL;
1733 		}
1734 	}
1735 	comp_algo = HAMMER2_ENC_ALGO(comp_algo_idx);
1736 
1737 	/* convert comp_level_str to comp_level_idx */
1738 	if (comp_level_str == NULL) {
1739 		comp_level_idx = 0;
1740 	} else if (isdigit((int)comp_level_str[0])) {
1741 		comp_level_idx = strtol(comp_level_str, NULL, 0);
1742 	} else if (strcasecmp(comp_level_str, "default") == 0) {
1743 		comp_level_idx = 0;
1744 	} else {
1745 		printf("invalid comp_level_str: %s\n", comp_level_str);
1746 		return EINVAL;
1747 	}
1748 	if (comp_level_idx) {
1749 		switch (comp_algo) {
1750 		case HAMMER2_COMP_ZLIB:
1751 			if (comp_level_idx < 6 || comp_level_idx > 9) {
1752 				printf("unsupported comp_level %d for %s\n",
1753 				    comp_level_idx, comp_algo_str);
1754 				return EINVAL;
1755 			}
1756 			break;
1757 		default:
1758 			printf("unsupported comp_level %d for %s\n",
1759 			    comp_level_idx, comp_algo_str);
1760 			return EINVAL;
1761 		}
1762 	}
1763 	comp_level = HAMMER2_ENC_LEVEL(comp_level_idx);
1764 	printf("change %s to algo %d (%s) level %d\n",
1765 	    p, comp_algo, comp_algo_str, comp_level_idx);
1766 
1767 	while ((p = strchr(p, '/')) != NULL) {
1768 		*p++ = 0; /* NULL terminate name */
1769 		if (!strcmp(name, ".")) {
1770 			name = p;
1771 			continue;
1772 		}
1773 		vp = NULL;
1774 		error = hammer2_nresolve(dvp, &vp, name, strlen(name));
1775 		if (error)
1776 			return error;
1777 
1778 		ip = VTOI(vp);
1779 		switch (ip->meta.type) {
1780 		case HAMMER2_OBJTYPE_DIRECTORY:
1781 			break;
1782 		case HAMMER2_OBJTYPE_SOFTLINK:
1783 			bzero(tmp, sizeof(tmp));
1784 			error = hammer2_readlink(vp, tmp, sizeof(tmp));
1785 			if (error)
1786 				return error;
1787 			if (!is_supported_link(tmp))
1788 				return EINVAL;
1789 			strlcat(tmp, "/", sizeof(tmp));
1790 			strlcat(tmp, p, sizeof(tmp));
1791 			error = trim_slash(tmp);
1792 			if (error)
1793 				return error;
1794 			p = name = tmp;
1795 			continue;
1796 		default:
1797 			return EINVAL;
1798 		}
1799 
1800 		dvp = vp;
1801 		name = p;
1802 	}
1803 
1804 	error = hammer2_nresolve(dvp, &vp, name, strlen(name));
1805 	if (error)
1806 		return error;
1807 	ip = VTOI(vp);
1808 
1809 	bzero(&inode, sizeof(inode));
1810 	error = hammer2_ioctl_inode_get(ip, &inode);
1811 	if (error)
1812 		return error;
1813 
1814 	inode.flags |= HAMMER2IOC_INODE_FLAG_COMP;
1815 	inode.ip_data.meta.comp_algo = comp_algo | comp_level;
1816 	error = hammer2_ioctl_inode_set(ip, &inode);
1817 	if (error)
1818 		return error;
1819 
1820 	free(o);
1821 
1822 	return error;
1823 }
1824 
1825 static int
1826 hammer2_bulkfree(struct m_vnode *vp)
1827 {
1828 	hammer2_ioc_bulkfree_t bfi;
1829 	size_t usermem;
1830 	size_t usermem_size = sizeof(usermem);
1831 
1832 	bzero(&bfi, sizeof(bfi));
1833 	usermem = 0;
1834 	if (sysctlbyname("hw.usermem", &usermem, &usermem_size, NULL, 0) == 0)
1835 		bfi.size = usermem / 16;
1836 	else
1837 		bfi.size = 0;
1838 	if (bfi.size < 8192 * 1024)
1839 		bfi.size = 8192 * 1024;
1840 
1841 	return hammer2_ioctl_bulkfree_scan(VTOI(vp), &bfi);
1842 }
1843 
1844 static int
1845 hammer2_destroy_path(struct m_vnode *dvp, const char *f)
1846 {
1847 	hammer2_ioc_destroy_t destroy;
1848 	hammer2_inode_t *ip;
1849 	struct m_vnode *vp;
1850 	char *o, *p, *name;
1851 	char tmp[PATH_MAX];
1852 	int error;
1853 
1854 	assert(strlen(f) > 0);
1855 	o = p = name = strdup(f);
1856 
1857 	error = trim_slash(p);
1858 	if (error)
1859 		return error;
1860 	if (strlen(p) == 0)
1861 		return EINVAL;
1862 
1863 	while ((p = strchr(p, '/')) != NULL) {
1864 		*p++ = 0; /* NULL terminate name */
1865 		if (!strcmp(name, ".")) {
1866 			name = p;
1867 			continue;
1868 		}
1869 		vp = NULL;
1870 		error = hammer2_nresolve(dvp, &vp, name, strlen(name));
1871 		if (error)
1872 			return error;
1873 
1874 		ip = VTOI(vp);
1875 		switch (ip->meta.type) {
1876 		case HAMMER2_OBJTYPE_DIRECTORY:
1877 			break;
1878 		case HAMMER2_OBJTYPE_SOFTLINK:
1879 			bzero(tmp, sizeof(tmp));
1880 			error = hammer2_readlink(vp, tmp, sizeof(tmp));
1881 			if (error)
1882 				return error;
1883 			if (!is_supported_link(tmp))
1884 				return EINVAL;
1885 			strlcat(tmp, "/", sizeof(tmp));
1886 			strlcat(tmp, p, sizeof(tmp));
1887 			error = trim_slash(tmp);
1888 			if (error)
1889 				return error;
1890 			p = name = tmp;
1891 			continue;
1892 		default:
1893 			return EINVAL;
1894 		}
1895 
1896 		dvp = vp;
1897 		name = p;
1898 	}
1899 
1900 	/* XXX When does (or why does not) ioctl modify this inode ? */
1901 	hammer2_inode_modify(VTOI(dvp));
1902 
1903 	bzero(&destroy, sizeof(destroy));
1904 	destroy.cmd = HAMMER2_DELETE_FILE;
1905 	snprintf(destroy.path, sizeof(destroy.path), "%s", name);
1906 
1907 	printf("%s\t", f);
1908 	fflush(stdout);
1909 
1910 	error = hammer2_ioctl_destroy(VTOI(dvp), &destroy);
1911 	if (error)
1912 		printf("%s\n", strerror(error));
1913 	else
1914 		printf("ok\n");
1915 	free(o);
1916 
1917 	return error;
1918 }
1919 
1920 static int
1921 hammer2_destroy_inum(struct m_vnode *vp, hammer2_tid_t inum)
1922 {
1923 	hammer2_ioc_destroy_t destroy;
1924 	int error;
1925 
1926 	bzero(&destroy, sizeof(destroy));
1927 	destroy.cmd = HAMMER2_DELETE_INUM;
1928 	destroy.inum = inum;
1929 
1930 	printf("%jd\t", (intmax_t)destroy.inum);
1931 	fflush(stdout);
1932 
1933 	error = hammer2_ioctl_destroy(VTOI(vp), &destroy);
1934 	if (error)
1935 		printf("%s\n", strerror(error));
1936 	else
1937 		printf("ok\n");
1938 
1939 	return error;
1940 }
1941 
1942 static int
1943 hammer2_growfs(struct m_vnode *vp, hammer2_off_t size)
1944 {
1945 	hammer2_ioc_growfs_t growfs;
1946 	int error;
1947 
1948 	bzero(&growfs, sizeof(growfs));
1949 	growfs.size = size;
1950 
1951 	error = hammer2_ioctl_growfs(VTOI(vp), &growfs, NULL);
1952 	if (!error) {
1953 		if (growfs.modified)
1954 			printf("grown to %016jx\n", (intmax_t)growfs.size);
1955 		else
1956 			printf("no size change - %016jx\n",
1957 			    (intmax_t)growfs.size);
1958 	}
1959 
1960 	return error;
1961 }
1962 
1963 static void
1964 assert_trim_slash(const char *input, const char *expected)
1965 {
1966 	char tmp[PATH_MAX];
1967 	int error;
1968 
1969 	strlcpy(tmp, input, sizeof(tmp));
1970 	error = trim_slash(tmp);
1971 	if (error)
1972 		errx(1, "input \"%s\" error %d", input, error);
1973 
1974 	if (strncmp(tmp, expected, sizeof(tmp)))
1975 		errx(1, "input \"%s\" result \"%s\" vs expected \"%s\"",
1976 		    input, tmp, expected);
1977 }
1978 
1979 static void
1980 unittest_trim_slash(void)
1981 {
1982 	assert_trim_slash("", "");
1983 	assert_trim_slash("/", "");
1984 	assert_trim_slash("//", "");
1985 	assert_trim_slash("///", "");
1986 
1987 	assert_trim_slash("makefs", "makefs");
1988 	assert_trim_slash("/makefs", "makefs");
1989 	assert_trim_slash("//makefs", "makefs");
1990 	assert_trim_slash("makefs/", "makefs");
1991 	assert_trim_slash("makefs//", "makefs");
1992 	assert_trim_slash("/makefs/", "makefs");
1993 	assert_trim_slash("//makefs//", "makefs");
1994 
1995 	assert_trim_slash("sys/vfs/hammer2", "sys/vfs/hammer2");
1996 	assert_trim_slash("/sys/vfs/hammer2", "sys/vfs/hammer2");
1997 	assert_trim_slash("//sys/vfs/hammer2", "sys/vfs/hammer2");
1998 	assert_trim_slash("///sys/vfs/hammer2", "sys/vfs/hammer2");
1999 	assert_trim_slash("sys/vfs/hammer2/", "sys/vfs/hammer2");
2000 	assert_trim_slash("sys/vfs/hammer2//", "sys/vfs/hammer2");
2001 	assert_trim_slash("sys/vfs/hammer2///", "sys/vfs/hammer2");
2002 	assert_trim_slash("/sys/vfs/hammer2/", "sys/vfs/hammer2");
2003 	assert_trim_slash("//sys//vfs//hammer2//", "sys/vfs/hammer2");
2004 	assert_trim_slash("///sys///vfs///hammer2///", "sys/vfs/hammer2");
2005 
2006 	APRINTF("success\n");
2007 }
2008