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