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