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