xref: /dragonfly/usr.sbin/makefs/hammer2.c (revision 17183580)
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 <unistd.h>
49 #include <fcntl.h>
50 #include <time.h>
51 #include <err.h>
52 #include <assert.h>
53 #include <util.h>
54 
55 #include "makefs.h"
56 #include "hammer2.h"
57 
58 #define APRINTF(X, ...)	\
59     printf("%s: " X, __func__, ## __VA_ARGS__)
60 
61 static void hammer2_dump_fsinfo(fsinfo_t *);
62 static int hammer2_create_image(const char *, fsinfo_t *);
63 static int hammer2_populate_dir(struct m_vnode *, const char *, fsnode *,
64     fsnode *, fsinfo_t *, int);
65 static void hammer2_validate(const char *, fsnode *, fsinfo_t *);
66 static void hammer2_size_dir(fsnode *, fsinfo_t *);
67 static int hammer2_write_file(struct m_vnode *, const char *, fsnode *);
68 static int hammer2_bulkfree(struct m_vnode *vp);
69 static int hammer2_destroy_path(struct m_vnode *vp, const char *f);
70 static int hammer2_destroy_inum(struct m_vnode *vp, hammer2_tid_t inum);
71 static int hammer2_growfs(struct m_vnode *vp, hammer2_off_t size);
72 
73 fsnode *hammer2_curnode;
74 
75 void
76 hammer2_prep_opts(fsinfo_t *fsopts)
77 {
78 	hammer2_makefs_options_t *h2_opt = ecalloc(1, sizeof(*h2_opt));
79 	hammer2_mkfs_options_t *opt = &h2_opt->mkfs_options;
80 
81 	const option_t hammer2_options[] = {
82 		/* newfs_hammer2(8) compatible options */
83 		{ 'b', "BootAreaSize", NULL, OPT_STRBUF, 0, 0, "boot area size" },
84 		{ 'r', "AuxAreaSize", NULL, OPT_STRBUF, 0, 0, "aux area size" },
85 		{ 'V', "Hammer2Version", NULL, OPT_STRBUF, 0, 0, "file system version" },
86 		{ 'L', "Label", NULL, OPT_STRBUF, 0, 0, "PFS label" },
87 		/* makefs(8) specific options */
88 		{ 'm', "MountLabel", NULL, OPT_STRBUF, 0, 0, "destination PFS label" },
89 		{ 'v', "NumVolhdr", &h2_opt->num_volhdr, OPT_INT32,
90 		    1, HAMMER2_NUM_VOLHDRS, "number of volume headers" },
91 		{ 'd', "Hammer2Debug", NULL, OPT_STRBUF, 0, 0, "debug tunable" },
92 		{ 'B', "Bulkfree", &h2_opt->bulkfree, OPT_BOOL, 0, 0, "offline bulkfree" },
93 		{ 'D', "Destroy", NULL, OPT_STRBUF, 0, 0, "offline destroy" },
94 		{ 'G', "Growfs", &h2_opt->growfs, OPT_BOOL, 0, 0, "offline growfs" },
95 		{ .name = NULL },
96 	};
97 
98 	hammer2_mkfs_init(opt);
99 
100 	/* make this tunable ? */
101 	assert(opt->CompType == HAMMER2_COMP_NEWFS_DEFAULT);
102 	assert(opt->CheckType == HAMMER2_CHECK_XXHASH64);
103 
104 	/* force debug mode for mkfs */
105 	opt->DebugOpt = 1;
106 
107 	fsopts->fs_specific = h2_opt;
108 	fsopts->fs_options = copy_opts(hammer2_options);
109 	fsopts->sectorsize = DEV_BSIZE;
110 }
111 
112 void
113 hammer2_cleanup_opts(fsinfo_t *fsopts)
114 {
115 	hammer2_makefs_options_t *h2_opt = fsopts->fs_specific;
116 	hammer2_mkfs_options_t *opt = &h2_opt->mkfs_options;
117 
118 	hammer2_mkfs_cleanup(opt);
119 
120 	free(h2_opt);
121 	free(fsopts->fs_options);
122 }
123 
124 int
125 hammer2_parse_opts(const char *option, fsinfo_t *fsopts)
126 {
127 	hammer2_makefs_options_t *h2_opt = fsopts->fs_specific;
128 	hammer2_mkfs_options_t *opt = &h2_opt->mkfs_options;
129 
130 	option_t *hammer2_options = fsopts->fs_options;
131 	char buf[1024]; /* > HAMMER2_INODE_MAXNAME */
132 	int i;
133 
134 	assert(option != NULL);
135 	assert(fsopts != NULL);
136 
137 	if (debug & DEBUG_FS_PARSE_OPTS)
138 		APRINTF("got `%s'\n", option);
139 
140 	i = set_option(hammer2_options, option, buf, sizeof(buf));
141 	if (i == -1)
142 		return 0;
143 
144 	if (hammer2_options[i].name == NULL)
145 		abort();
146 
147 	switch (hammer2_options[i].letter) {
148 	case 'b':
149 		opt->BootAreaSize = getsize(buf, HAMMER2_NEWFS_ALIGN,
150 		    HAMMER2_BOOT_MAX_BYTES, 2);
151 		break;
152 	case 'r':
153 		opt->AuxAreaSize = getsize(buf, HAMMER2_NEWFS_ALIGN,
154 		    HAMMER2_AUX_MAX_BYTES, 2);
155 		break;
156 	case 'V':
157 		opt->Hammer2Version = strtol(buf, NULL, 0);
158 		if (opt->Hammer2Version < HAMMER2_VOL_VERSION_MIN ||
159 		    opt->Hammer2Version >= HAMMER2_VOL_VERSION_WIP)
160 			errx(1, "I don't understand how to format "
161 			    "HAMMER2 version %d",
162 			    opt->Hammer2Version);
163 		break;
164 	case 'L':
165 		h2_opt->label_specified = 1;
166 		if (strcasecmp(buf, "none") == 0)
167 			break;
168 		if (opt->NLabels >= MAXLABELS)
169 			errx(1, "Limit of %d local labels", MAXLABELS - 1);
170 		if (strlen(buf) == 0)
171 			errx(1, "Volume label '%s' cannot be 0-length", buf);
172 		if (strlen(buf) >= HAMMER2_INODE_MAXNAME)
173 			errx(1, "Volume label '%s' is too long (%d chars max)",
174 			    buf, HAMMER2_INODE_MAXNAME - 1);
175 		opt->Label[opt->NLabels++] = strdup(buf);
176 		break;
177 	case 'm':
178 		if (strlen(buf) == 0)
179 			errx(1, "Volume label '%s' cannot be 0-length", buf);
180 		if (strlen(buf) >= HAMMER2_INODE_MAXNAME)
181 			errx(1, "Volume label '%s' is too long (%d chars max)",
182 			    buf, HAMMER2_INODE_MAXNAME - 1);
183 		strlcpy(h2_opt->mount_label, buf, sizeof(h2_opt->mount_label));
184 		break;
185 	case 'd':
186 		hammer2_debug = strtoll(buf, NULL, 0);
187 		break;
188 	case 'D':
189 		h2_opt->destroy = 1;
190 		if (buf[0] == '/') {
191 			strlcpy(h2_opt->destroy_path, buf + 1,
192 			    sizeof(h2_opt->destroy_path));
193 		} else if ((buf[0] == '0' && buf[1] == 'x') ||
194 		    (buf[0] >= '0' && buf[0] <= '9')) {
195 			h2_opt->destroy_inum = strtoull(buf, NULL, 0);
196 			if (errno)
197 				err(1, "strtoull");
198 		} else {
199 			errx(1, "Invalid destroy argument %s", buf);
200 		}
201 		break;
202 	default:
203 		break;
204 	}
205 
206 	return 1;
207 }
208 
209 void
210 hammer2_makefs(const char *image, const char *dir, fsnode *root,
211     fsinfo_t *fsopts)
212 {
213 	hammer2_makefs_options_t *h2_opt = fsopts->fs_specific;
214 	struct mount mp;
215 	struct hammer2_mount_info info;
216 	struct m_vnode devvp, *vroot;
217 	hammer2_inode_t *iroot;
218 	struct timeval start;
219 	int error;
220 
221 	/* ioctl commands could have NULL root */
222 	assert(image != NULL);
223 	assert(dir != NULL);
224 	assert(fsopts != NULL);
225 
226 	if (debug & DEBUG_FS_MAKEFS)
227 		APRINTF("image \"%s\" directory \"%s\" root %p\n",
228 		    image, dir, root);
229 
230 	/* validate tree and options */
231 	TIMER_START(start);
232 	hammer2_validate(dir, root, fsopts);
233 	TIMER_RESULTS(start, "hammer2_validate");
234 
235 	if (!h2_opt->bulkfree && !h2_opt->destroy && !h2_opt->growfs) {
236 		/* create image */
237 		TIMER_START(start);
238 		if (hammer2_create_image(image, fsopts) == -1)
239 			errx(1, "image file `%s' not created", image);
240 		TIMER_RESULTS(start, "hammer2_create_image");
241 	} else {
242 		/* open existing image */
243 		fsopts->fd = open(image, O_RDWR);
244 		if (fsopts->fd < 0)
245 			err(1, "failed to open %s", image);
246 	}
247 	assert(fsopts->fd > 0);
248 
249 	if (debug & DEBUG_FS_MAKEFS)
250 		putchar('\n');
251 
252 	/* vfs init */
253 	error = hammer2_vfs_init();
254 	if (error)
255 		errx(1, "failed to vfs init, error %d", error);
256 
257 	/* mount image */
258 	memset(&devvp, 0, sizeof(devvp));
259 	devvp.fs = fsopts;
260 	memset(&mp, 0, sizeof(mp));
261 	memset(&info, 0, sizeof(info));
262 	error = hammer2_vfs_mount(&devvp, &mp, h2_opt->mount_label, &info);
263 	if (error)
264 		errx(1, "failed to mount, error %d", error);
265 	assert(mp.mnt_data);
266 
267 	/* get root vnode */
268 	vroot = NULL;
269 	error = hammer2_vfs_root(&mp, &vroot);
270 	if (error)
271 		errx(1, "failed to get root vnode, error %d", error);
272 	assert(vroot);
273 
274 	iroot = VTOI(vroot);
275 	assert(iroot);
276 	printf("root inode inum %lld, mode 0%o, refs %d\n",
277 	    (long long)iroot->meta.inum, iroot->meta.mode, iroot->refs);
278 
279 	if (!h2_opt->bulkfree && !h2_opt->destroy && !h2_opt->growfs) {
280 		/* populate image */
281 		printf("populating `%s'\n", image);
282 		TIMER_START(start);
283 		if (hammer2_populate_dir(vroot, dir, root, root, fsopts, 0))
284 			errx(1, "image file `%s' not populated", image);
285 		TIMER_RESULTS(start, "hammer2_populate_dir");
286 	} else if (h2_opt->bulkfree) {
287 		/* bulkfree image */
288 		printf("bulkfree `%s'\n", image);
289 		TIMER_START(start);
290 		if (hammer2_bulkfree(vroot))
291 			errx(1, "bulkfree `%s' failed", image);
292 		TIMER_RESULTS(start, "hammer2_bulkfree");
293 	} else if (h2_opt->destroy) {
294 		/* destroy inode */
295 		TIMER_START(start);
296 		if (strlen(h2_opt->destroy_path)) {
297 			printf("destroy `%s' in `%s'\n",
298 			    h2_opt->destroy_path, image);
299 			if (hammer2_destroy_path(vroot, h2_opt->destroy_path))
300 				errx(1, "destroy `%s' in `%s' failed",
301 				    h2_opt->destroy_path, image);
302 		} else {
303 			printf("destroy %lld in `%s'\n",
304 			    (long long)h2_opt->destroy_inum, image);
305 			if (hammer2_destroy_inum(vroot, h2_opt->destroy_inum))
306 				errx(1, "destroy %lld in `%s' failed",
307 				    (long long)h2_opt->destroy_inum, image);
308 		}
309 		TIMER_RESULTS(start, "hammer2_destroy");
310 	} else if (h2_opt->growfs) {
311 		/* growfs image */
312 		printf("growfs `%s'\n", image);
313 		TIMER_START(start);
314 		if (hammer2_growfs(vroot, h2_opt->image_size))
315 			errx(1, "growfs `%s' failed", image);
316 		TIMER_RESULTS(start, "hammer2_growfs");
317 	}
318 
319 	/* unmount image */
320 	error = hammer2_vfs_unmount(&mp, 0);
321 	if (error)
322 		errx(1, "failed to unmount, error %d", error);
323 
324 	/* check leaked resource */
325 	if (vnode_count)
326 		printf("XXX %lld vnode left\n", (long long)vnode_count);
327 	if (hammer2_chain_allocs)
328 		printf("XXX %ld chain left\n", hammer2_chain_allocs);
329 	bcleanup();
330 
331 	/* vfs uninit */
332 	error = hammer2_vfs_uninit();
333 	if (error)
334 		errx(1, "failed to vfs uninit, error %d", error);
335 
336 	if (close(fsopts->fd) == -1)
337 		err(1, "closing `%s'", image);
338 	fsopts->fd = -1;
339 
340 	printf("image `%s' complete\n", image);
341 }
342 
343 /* end of public functions */
344 
345 static hammer2_off_t
346 hammer2_image_size(fsinfo_t *fsopts)
347 {
348 	hammer2_makefs_options_t *h2_opt = fsopts->fs_specific;
349 	hammer2_off_t image_size, used_size = 0;
350 	int num_level1, delta_num_level1;
351 
352 	/* use 4 volume headers by default */
353 	num_level1 = h2_opt->num_volhdr * 2; /* default 4 x 2 */
354 	assert(num_level1 != 0);
355 	assert(num_level1 <= 8);
356 
357 	/* add 4MiB segment for each level1 */
358 	used_size += HAMMER2_ZONE_SEG64 * num_level1;
359 
360 	/* add boot/aux area, but exact size unknown at this point */
361 	used_size += HAMMER2_BOOT_NOM_BYTES + HAMMER2_AUX_NOM_BYTES;
362 
363 	/* add data size */
364 	used_size += fsopts->size;
365 
366 	/* XXX add extra level1 for meta data and indirect blocks */
367 	used_size += HAMMER2_FREEMAP_LEVEL1_SIZE;
368 
369 	/* XXX add extra level1 for safety */
370 	if (used_size > HAMMER2_FREEMAP_LEVEL1_SIZE * 10)
371 		used_size += HAMMER2_FREEMAP_LEVEL1_SIZE;
372 
373 	/* use 8GiB image size by default */
374 	image_size = HAMMER2_FREEMAP_LEVEL1_SIZE * num_level1;
375 	printf("trying default image size %s\n", sizetostr(image_size));
376 
377 	/* adjust if image size isn't large enough */
378 	if (used_size > image_size) {
379 		/* determine extra level1 needed */
380 		delta_num_level1 = howmany(used_size - image_size,
381 		    HAMMER2_FREEMAP_LEVEL1_SIZE);
382 
383 		/* adjust used size with 4MiB segment for each extra level1 */
384 		used_size += HAMMER2_ZONE_SEG64 * delta_num_level1;
385 
386 		/* adjust image size with extra level1 */
387 		image_size += HAMMER2_FREEMAP_LEVEL1_SIZE * delta_num_level1;
388 		printf("trying adjusted image size %s\n",
389 		    sizetostr(image_size));
390 
391 		if (used_size > image_size)
392 			errx(1, "invalid used_size %lld > image_size %lld",
393 			    (long long)used_size, (long long)image_size);
394 	}
395 
396 	return image_size;
397 }
398 
399 static const char *
400 hammer2_label_name(int label_type)
401 {
402 	switch (label_type) {
403 	case HAMMER2_LABEL_NONE:
404 		return "NONE";
405 	case HAMMER2_LABEL_BOOT:
406 		return "BOOT";
407 	case HAMMER2_LABEL_ROOT:
408 		return "ROOT";
409 	case HAMMER2_LABEL_DATA:
410 		return "DATA";
411 	default:
412 		assert(0);
413 	}
414 	return NULL;
415 }
416 
417 static void
418 hammer2_validate(const char *dir, fsnode *root, fsinfo_t *fsopts)
419 {
420 	hammer2_makefs_options_t *h2_opt = fsopts->fs_specific;
421 	hammer2_mkfs_options_t *opt = &h2_opt->mkfs_options;
422 	hammer2_off_t image_size = 0, minsize, maxsize;
423 	const char *s;
424 
425 	assert(dir != NULL);
426 	assert(fsopts != NULL);
427 
428 	if (debug & DEBUG_FS_VALIDATE) {
429 		APRINTF("before defaults set:\n");
430 		hammer2_dump_fsinfo(fsopts);
431 	}
432 
433 	/* makefs only supports "DATA" for default PFS label */
434 	if (!h2_opt->label_specified) {
435 		opt->DefaultLabelType = HAMMER2_LABEL_DATA;
436 		s = hammer2_label_name(opt->DefaultLabelType);
437 		printf("using default label \"%s\"\n", s);
438 	}
439 
440 	/* set default mount PFS label */
441 	if (!strcmp(h2_opt->mount_label, "")) {
442 		s = hammer2_label_name(HAMMER2_LABEL_DATA);
443 		strlcpy(h2_opt->mount_label, s, sizeof(h2_opt->mount_label));
444 		printf("using default mount label \"%s\"\n", s);
445 	}
446 
447 	/* set default number of volume headers */
448 	if (!h2_opt->num_volhdr) {
449 		h2_opt->num_volhdr = HAMMER2_NUM_VOLHDRS;
450 		printf("using default %d volume headers\n", h2_opt->num_volhdr);
451 	}
452 
453 	/* done if ioctl commands */
454 	if (h2_opt->bulkfree || h2_opt->destroy)
455 		goto done;
456 	if (h2_opt->growfs)
457 		goto ignore_size_dir;
458 
459 	/* calculate data size */
460 	if (fsopts->size != 0)
461 		fsopts->size = 0; /* shouldn't reach here to begin with */
462 	if (root == NULL)
463 		errx(1, "fsnode tree not constructed");
464 	hammer2_size_dir(root, fsopts);
465 	printf("estimated data size %s from %lld inode\n",
466 	    sizetostr(fsopts->size), (long long)fsopts->inodes);
467 
468 	/* determine image size from data size */
469 	image_size = hammer2_image_size(fsopts);
470 	assert((image_size & HAMMER2_FREEMAP_LEVEL1_MASK) == 0);
471 ignore_size_dir:
472 	minsize = roundup(fsopts->minsize, HAMMER2_FREEMAP_LEVEL1_SIZE);
473 	maxsize = roundup(fsopts->maxsize, HAMMER2_FREEMAP_LEVEL1_SIZE);
474 	if (image_size < minsize)
475 		image_size = minsize;
476 	else if (maxsize > 0 && image_size > maxsize)
477 		errx(1, "`%s' size of %lld is larger than the maxsize of %lld",
478 		    dir, (long long)image_size, (long long)maxsize);
479 
480 	assert((image_size & HAMMER2_FREEMAP_LEVEL1_MASK) == 0);
481 	h2_opt->image_size = image_size;
482 	printf("using %s image size\n", sizetostr(h2_opt->image_size));
483 done:
484 	if (debug & DEBUG_FS_VALIDATE) {
485 		APRINTF("after defaults set:\n");
486 		hammer2_dump_fsinfo(fsopts);
487 	}
488 }
489 
490 static void
491 hammer2_dump_fsinfo(fsinfo_t *fsopts)
492 {
493 	hammer2_makefs_options_t *h2_opt = fsopts->fs_specific;
494 	hammer2_mkfs_options_t *opt = &h2_opt->mkfs_options;
495 	int i;
496 	char *s;
497 
498 	assert(fsopts != NULL);
499 
500 	APRINTF("fsinfo_t at %p\n", fsopts);
501 
502 	printf("\tinodes %lld\n", (long long)fsopts->inodes);
503 	printf("\tsize %lld, minsize %lld, maxsize %lld\n",
504 	    (long long)fsopts->size,
505 	    (long long)fsopts->minsize,
506 	    (long long)fsopts->maxsize);
507 
508 	printf("\thammer2_debug 0x%x\n", hammer2_debug);
509 
510 	printf("\tlabel_specified %d\n", h2_opt->label_specified);
511 	printf("\tmount_label \"%s\"\n", h2_opt->mount_label);
512 	printf("\tnum_volhdr %d\n", h2_opt->num_volhdr);
513 	printf("\tbulkfree %d\n", h2_opt->bulkfree);
514 	printf("\tdestroy %d\n", h2_opt->destroy);
515 	printf("\tdestroy_path \"%s\"\n", h2_opt->destroy_path);
516 	printf("\tdestroy_inum %lld\n", (long long)h2_opt->destroy_inum);
517 	printf("\tgrowfs %d\n", h2_opt->growfs);
518 	printf("\timage_size 0x%llx\n", (long long)h2_opt->image_size);
519 
520 	printf("\tHammer2Version %d\n", opt->Hammer2Version);
521 	printf("\tBootAreaSize 0x%jx\n", opt->BootAreaSize);
522 	printf("\tAuxAreaSize 0x%jx\n", opt->AuxAreaSize);
523 	printf("\tNLabels %d\n", opt->NLabels);
524 	printf("\tCompType %d\n", opt->CompType);
525 	printf("\tCheckType %d\n", opt->CheckType);
526 	printf("\tDefaultLabelType %d\n", opt->DefaultLabelType);
527 	printf("\tDebugOpt %d\n", opt->DebugOpt);
528 
529 	s = NULL;
530 	hammer2_uuid_to_str(&opt->Hammer2_FSType, &s);
531 	printf("\tHammer2_FSType \"%s\"\n", s);
532 	s = NULL;
533 	hammer2_uuid_to_str(&opt->Hammer2_VolFSID, &s);
534 	printf("\tHammer2_VolFSID \"%s\"\n", s);
535 	s = NULL;
536 	hammer2_uuid_to_str(&opt->Hammer2_SupCLID, &s);
537 	printf("\tHammer2_SupCLID \"%s\"\n", s);
538 	s = NULL;
539 	hammer2_uuid_to_str(&opt->Hammer2_SupFSID, &s);
540 	printf("\tHammer2_SupFSID \"%s\"\n", s);
541 
542 	for (i = 0; i < opt->NLabels; i++) {
543 		printf("\tLabel[%d] \"%s\"\n", i, opt->Label[i]);
544 		s = NULL;
545 		hammer2_uuid_to_str(&opt->Hammer2_PfsCLID[i], &s);
546 		printf("\t Hammer2_PfsCLID[%d] \"%s\"\n", i, s);
547 		s = NULL;
548 		hammer2_uuid_to_str(&opt->Hammer2_PfsFSID[i], &s);
549 		printf("\t Hammer2_PfsFSID[%d] \"%s\"\n", i, s);
550 	}
551 
552 	free(s);
553 }
554 
555 static int
556 hammer2_create_image(const char *image, fsinfo_t *fsopts)
557 {
558 	hammer2_makefs_options_t *h2_opt = fsopts->fs_specific;
559 	hammer2_mkfs_options_t *opt = &h2_opt->mkfs_options;
560 	char *av[] = { (char *)image, }; /* XXX support multi-volumes */
561 	char *buf;
562 	int i, bufsize, oflags;
563 	off_t bufrem;
564 
565 	assert(image != NULL);
566 	assert(fsopts != NULL);
567 
568 	/* create image */
569 	oflags = O_RDWR | O_CREAT;
570 	if (fsopts->offset == 0)
571 		oflags |= O_TRUNC;
572 	if ((fsopts->fd = open(image, oflags, 0666)) == -1) {
573 		warn("can't open `%s' for writing", image);
574 		return -1;
575 	}
576 
577 	/* zero image */
578 	bufsize = HAMMER2_PBUFSIZE;
579 	bufrem = h2_opt->image_size;
580 	if (fsopts->sparse) {
581 		if (ftruncate(fsopts->fd, bufrem) == -1) {
582 			warn("sparse option disabled");
583 			fsopts->sparse = 0;
584 		}
585 	}
586 	if (fsopts->sparse) {
587 		/* File truncated at bufrem. Remaining is 0 */
588 		bufrem = 0;
589 		buf = NULL;
590 	} else {
591 		if (debug & DEBUG_FS_CREATE_IMAGE)
592 			APRINTF("zero-ing image `%s', %lld sectors, "
593 			    "using %d byte chunks\n",
594 			    image, (long long)bufrem, bufsize);
595 		buf = ecalloc(1, bufsize);
596 	}
597 
598 	if (fsopts->offset != 0) {
599 		if (lseek(fsopts->fd, fsopts->offset, SEEK_SET) == -1) {
600 			warn("can't seek");
601 			free(buf);
602 			return -1;
603 		}
604 	}
605 
606 	while (bufrem > 0) {
607 		i = write(fsopts->fd, buf, MIN(bufsize, bufrem));
608 		if (i == -1) {
609 			warn("zeroing image, %lld bytes to go",
610 			    (long long)bufrem);
611 			free(buf);
612 			return -1;
613 		}
614 		bufrem -= i;
615 	}
616 	if (buf)
617 		free(buf);
618 
619 	/* make the file system */
620 	if (debug & DEBUG_FS_CREATE_IMAGE)
621 		APRINTF("calling mkfs(\"%s\", ...)\n", image);
622 	hammer2_mkfs(1, av, opt); /* success if returned */
623 
624 	return fsopts->fd;
625 }
626 
627 static off_t
628 hammer2_phys_size(off_t size)
629 {
630 	off_t radix_size, phys_size = 0;
631 	int i;
632 
633 	if (size > HAMMER2_PBUFSIZE) {
634 		phys_size += rounddown(size, HAMMER2_PBUFSIZE);
635 		size = size % HAMMER2_PBUFSIZE;
636 	}
637 
638 	for (i = HAMMER2_RADIX_MIN; i <= HAMMER2_RADIX_MAX; i++) {
639 		radix_size = 1UL << i;
640 		if (radix_size >= size) {
641 			phys_size += radix_size;
642 			break;
643 		}
644 	}
645 
646 	return phys_size;
647 }
648 
649 /* calculate data size */
650 static void
651 hammer2_size_dir(fsnode *root, fsinfo_t *fsopts)
652 {
653 	fsnode *node;
654 
655 	assert(fsopts != NULL);
656 
657 	if (debug & DEBUG_FS_SIZE_DIR)
658 		APRINTF("entry: bytes %lld inodes %lld\n",
659 		    (long long)fsopts->size, (long long)fsopts->inodes);
660 
661 	for (node = root; node != NULL; node = node->next) {
662 		if (node == root) { /* we're at "." */
663 			assert(strcmp(node->name, ".") == 0);
664 		} else if ((node->inode->flags & FI_SIZED) == 0) {
665 			/* don't count duplicate names */
666 			node->inode->flags |= FI_SIZED;
667 			if (debug & DEBUG_FS_SIZE_DIR_NODE)
668 				APRINTF("`%s' size %lld\n",
669 				    node->name,
670 				    (long long)node->inode->st.st_size);
671 			fsopts->inodes++;
672 			fsopts->size += sizeof(hammer2_inode_data_t);
673 			if (node->type == S_IFREG) {
674 				size_t st_size = node->inode->st.st_size;
675 				if (st_size > HAMMER2_EMBEDDED_BYTES)
676 					fsopts->size += hammer2_phys_size(st_size);
677 			} else if (node->type == S_IFLNK) {
678 				size_t nlen = strlen(node->symlink);
679 				if (nlen > HAMMER2_EMBEDDED_BYTES)
680 					fsopts->size += hammer2_phys_size(nlen);
681 			}
682 		}
683 		if (node->type == S_IFDIR)
684 			hammer2_size_dir(node->child, fsopts);
685 	}
686 
687 	if (debug & DEBUG_FS_SIZE_DIR)
688 		APRINTF("exit: size %lld inodes %lld\n",
689 		    (long long)fsopts->size, (long long)fsopts->inodes);
690 }
691 
692 static void
693 hammer2_print(const struct m_vnode *dvp, const struct m_vnode *vp,
694     const fsnode *node, int depth, const char *msg)
695 {
696 	if (debug & DEBUG_FS_POPULATE) {
697 		if (1) {
698 			int indent = depth * 2;
699 			char *type;
700 			if (S_ISDIR(node->type))
701 				type = "dir";
702 			else if (S_ISREG(node->type))
703 				type = "reg";
704 			else if (S_ISLNK(node->type))
705 				type = "lnk";
706 			else if (S_ISFIFO(node->type))
707 				type = "fifo";
708 			else
709 				type = "???";
710 			printf("%*.*s", indent, indent, "");
711 			printf("dvp=%p/%d vp=%p/%d \"%s\" %s %s\n",
712 			    dvp, dvp ? VTOI(dvp)->refs : 0,
713 			    vp, vp ? VTOI(vp)->refs : 0,
714 			    node->name, type, msg);
715 		} else {
716 			char type;
717 			if (S_ISDIR(node->type))
718 				type = 'd';
719 			else if (S_ISREG(node->type))
720 				type = 'r';
721 			else if (S_ISLNK(node->type))
722 				type = 'l';
723 			else if (S_ISFIFO(node->type))
724 				type = 'f';
725 			else
726 				type = '?';
727 			printf("%c", type);
728 			fflush(stdout);
729 		}
730 	}
731 }
732 
733 static int
734 hammer2_populate_dir(struct m_vnode *dvp, const char *dir, fsnode *root,
735     fsnode *parent, fsinfo_t *fsopts, int depth)
736 {
737 	fsnode *cur;
738 	struct m_vnode *vp;
739 	struct stat st;
740 	char f[MAXPATHLEN];
741 	const char *path;
742 	int hardlink;
743 	int error;
744 
745 	assert(dvp != NULL);
746 	assert(dir != NULL);
747 	assert(root != NULL);
748 	assert(parent != NULL);
749 	assert(fsopts != NULL);
750 
751 	/* assert root directory */
752 	assert(S_ISDIR(root->type));
753 	assert(!strcmp(root->name, "."));
754 	assert(!root->child);
755 	assert(!root->parent || root->parent->child == root);
756 
757 	hammer2_print(dvp, NULL, root, depth, "enter");
758 	if (stat(dir, &st) == -1)
759 		err(1, "no such path %s", dir);
760 	if (!S_ISDIR(st.st_mode))
761 		errx(1, "no such dir %s", dir);
762 
763 	for (cur = root->next; cur != NULL; cur = cur->next) {
764 		/* global variable for HAMMER2 vnops */
765 		hammer2_curnode = cur;
766 
767 		/* construct source path */
768 		if (cur->contents) {
769 			path = cur->contents;
770 		} else {
771 			if (S_ISDIR(cur->type)) {
772 				/* this should be same as root/path/name */
773 				if (snprintf(f, sizeof(f), "%s/%s",
774 				    dir, cur->name) >= (int)sizeof(f))
775 					errx(1, "path %s too long", f);
776 			} else {
777 				if (snprintf(f, sizeof(f), "%s/%s/%s",
778 				    cur->root, cur->path, cur->name) >= (int)sizeof(f))
779 					errx(1, "path %s too long", f);
780 			}
781 			path = f;
782 		}
783 		if (stat(path, &st) == -1)
784 			err(1, "no such file %s", f);
785 
786 		/* update node state */
787 		if ((cur->inode->flags & FI_ALLOCATED) == 0) {
788 			cur->inode->flags |= FI_ALLOCATED;
789 			if (cur != root)
790 				cur->parent = parent;
791 		}
792 
793 		/* detect hardlink */
794 		if (cur->inode->flags & FI_WRITTEN) {
795 			assert(!S_ISDIR(cur->type));
796 			hardlink = 1;
797 		} else {
798 			hardlink = 0;
799 		}
800 		cur->inode->flags |= FI_WRITTEN;
801 
802 		/* make sure it doesn't exist yet */
803 		vp = NULL;
804 		error = hammer2_nresolve(dvp, &vp, cur->name,
805 		    strlen(cur->name));
806 		if (!error)
807 			errx(1, "hammer2_nresolve(\"%s\") already exists",
808 			    cur->name);
809 		hammer2_print(dvp, vp, cur, depth, "nresolve");
810 
811 		/* if directory, mkdir and recurse */
812 		if (S_ISDIR(cur->type)) {
813 			assert(cur->child);
814 
815 			vp = NULL;
816 			error = hammer2_nmkdir(dvp, &vp, cur->name,
817 			    strlen(cur->name), cur->inode->st.st_mode);
818 			if (error)
819 				errx(1, "hammer2_nmkdir(\"%s\") failed: %s",
820 				    cur->name, strerror(error));
821 			assert(vp);
822 			hammer2_print(dvp, vp, cur, depth, "nmkdir");
823 
824 			error = hammer2_populate_dir(vp, path, cur->child, cur,
825 			    fsopts, depth + 1);
826 			if (error)
827 				errx(1, "failed to populate %s: %s",
828 				    path, strerror(error));
829 			cur->inode->param = vp;
830 			continue;
831 		}
832 
833 		/* if regular file, creat and write its data */
834 		if (S_ISREG(cur->type) && !hardlink) {
835 			assert(cur->child == NULL);
836 
837 			vp = NULL;
838 			error = hammer2_ncreate(dvp, &vp, cur->name,
839 			    strlen(cur->name), cur->inode->st.st_mode);
840 			if (error)
841 				errx(1, "hammer2_ncreate(\"%s\") failed: %s",
842 				    cur->name, strerror(error));
843 			assert(vp);
844 			hammer2_print(dvp, vp, cur, depth, "ncreate");
845 
846 			error = hammer2_write_file(vp, path, cur);
847 			if (error)
848 				errx(1, "hammer2_write_file(\"%s\") failed: %s",
849 				    path, strerror(error));
850 			cur->inode->param = vp;
851 			continue;
852 		}
853 
854 		/* if symlink, create a symlink against target */
855 		if (S_ISLNK(cur->type)) {
856 			assert(cur->child == NULL);
857 
858 			vp = NULL;
859 			error = hammer2_nsymlink(dvp, &vp, cur->name,
860 			    strlen(cur->name), cur->symlink,
861 			    cur->inode->st.st_mode);
862 			if (error)
863 				errx(1, "hammer2_nsymlink(\"%s\") failed: %s",
864 				    cur->name, strerror(error));
865 			assert(vp);
866 			hammer2_print(dvp, vp, cur, depth, "nsymlink");
867 			cur->inode->param = vp;
868 			continue;
869 		}
870 
871 		/* if fifo, create a fifo */
872 		if (S_ISFIFO(cur->type) && !hardlink) {
873 			assert(cur->child == NULL);
874 
875 			vp = NULL;
876 			error = hammer2_nmknod(dvp, &vp, cur->name,
877 			    strlen(cur->name), VFIFO, cur->inode->st.st_mode);
878 			if (error)
879 				errx(1, "hammer2_nmknod(\"%s\") failed: %s",
880 				    cur->name, strerror(error));
881 			assert(vp);
882 			hammer2_print(dvp, vp, cur, depth, "nmknod");
883 			cur->inode->param = vp;
884 			continue;
885 		}
886 
887 		/* if hardlink, creat a hardlink */
888 		if ((S_ISREG(cur->type) || S_ISFIFO(cur->type)) && hardlink) {
889 			char buf[64];
890 			assert(cur->child == NULL);
891 
892 			/* source vnode must not be NULL */
893 			vp = cur->inode->param;
894 			assert(vp);
895 			/* currently these conditions must be true */
896 			assert(vp->v_data);
897 			assert(vp->v_type == VREG || vp->v_type == VFIFO);
898 			assert(vp->v_logical);
899 			assert(!vp->v_vflushed);
900 			assert(vp->v_malloced);
901 			assert(VTOI(vp)->refs > 0);
902 
903 			error = hammer2_nlink(dvp, vp, cur->name,
904 			    strlen(cur->name));
905 			if (error)
906 				errx(1, "hammer2_nlink(\"%s\") failed: %s",
907 				    cur->name, strerror(error));
908 			snprintf(buf, sizeof(buf), "nlink=%lld",
909 			    (long long)VTOI(vp)->meta.nlinks);
910 			hammer2_print(dvp, vp, cur, depth, buf);
911 			continue;
912 		}
913 
914 		/* other types are unsupported */
915 		printf("ignore %s 0%o\n", path, cur->type);
916 	}
917 
918 	return 0;
919 }
920 
921 static int
922 hammer2_write_file(struct m_vnode *vp, const char *path, fsnode *node)
923 {
924 	struct stat *st = &node->inode->st;
925 	size_t nsize, bufsize;
926 	off_t offset;
927 	int fd, error;
928 	char *p;
929 
930 	nsize = st->st_size;
931 	if (nsize == 0)
932 		return 0;
933 	/* check nsize vs maximum file size */
934 
935 	fd = open(path, O_RDONLY);
936 	if (fd < 0)
937 		err(1, "failed to open %s", path);
938 
939 	p = mmap(0, nsize, PROT_READ, MAP_FILE|MAP_PRIVATE, fd, 0);
940 	if (p == MAP_FAILED)
941 		err(1, "failed to mmap %s", path);
942 	close(fd);
943 
944 	for (offset = 0; offset < nsize; ) {
945 		bufsize = MIN(nsize - offset, HAMMER2_PBUFSIZE);
946 		assert(bufsize <= HAMMER2_PBUFSIZE);
947 		error = hammer2_write(vp, p + offset, bufsize, offset);
948 		if (error)
949 			errx(1, "failed to write to %s vnode: %s",
950 			    path, strerror(error));
951 		offset += bufsize;
952 		if (bufsize == HAMMER2_PBUFSIZE)
953 			assert((offset & (HAMMER2_PBUFSIZE - 1)) == 0);
954 	}
955 	munmap(p, nsize);
956 
957 	return 0;
958 }
959 
960 static int
961 hammer2_bulkfree(struct m_vnode *vp)
962 {
963 	hammer2_ioc_bulkfree_t bfi;
964 	size_t usermem;
965 	size_t usermem_size = sizeof(usermem);
966 
967 	bzero(&bfi, sizeof(bfi));
968 	usermem = 0;
969 	if (sysctlbyname("hw.usermem", &usermem, &usermem_size, NULL, 0) == 0)
970 		bfi.size = usermem / 16;
971 	else
972 		bfi.size = 0;
973 	if (bfi.size < 8192 * 1024)
974 		bfi.size = 8192 * 1024;
975 
976 	return hammer2_ioctl_bulkfree_scan(VTOI(vp), &bfi);
977 }
978 
979 static int
980 hammer2_destroy_path(struct m_vnode *vp, const char *f)
981 {
982 	hammer2_ioc_destroy_t destroy;
983 	int error = 0;
984 
985 	bzero(&destroy, sizeof(destroy));
986 	destroy.cmd = HAMMER2_DELETE_FILE;
987 	snprintf(destroy.path, sizeof(destroy.path), "%s", f);
988 
989 	printf("%s\t", f);
990 	fflush(stdout);
991 
992 	error = hammer2_ioctl_destroy(VTOI(vp), &destroy);
993 	if (error)
994 		printf("%s\n", strerror(error));
995 	else
996 		printf("ok\n");
997 
998 	return error;
999 }
1000 
1001 static int
1002 hammer2_destroy_inum(struct m_vnode *vp, hammer2_tid_t inum)
1003 {
1004 	hammer2_ioc_destroy_t destroy;
1005 	int error = 0;
1006 
1007 	bzero(&destroy, sizeof(destroy));
1008 	destroy.cmd = HAMMER2_DELETE_INUM;
1009 	destroy.inum = inum;
1010 
1011 	printf("%jd\t", (intmax_t)destroy.inum);
1012 	fflush(stdout);
1013 
1014 	error = hammer2_ioctl_destroy(VTOI(vp), &destroy);
1015 	if (error)
1016 		printf("%s\n", strerror(error));
1017 	else
1018 		printf("ok\n");
1019 
1020 	return error;
1021 }
1022 
1023 static int
1024 hammer2_growfs(struct m_vnode *vp, hammer2_off_t size)
1025 {
1026 	hammer2_ioc_growfs_t growfs;
1027 	int error;
1028 
1029 	bzero(&growfs, sizeof(growfs));
1030 	growfs.size = size;
1031 
1032 	error = hammer2_ioctl_growfs(VTOI(vp), &growfs, NULL);
1033 	if (!error) {
1034 		if (growfs.modified)
1035 			printf("grown to %016jx\n", (intmax_t)growfs.size);
1036 		else
1037 			printf("no size change - %016jx\n",
1038 				(intmax_t)growfs.size);
1039 	}
1040 
1041 	return error;
1042 }
1043