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