xref: /dragonfly/sbin/hammer2/ondisk.c (revision 7d3e9a5b)
1 /*
2  * Copyright (c) 2020 Tomohiro Kusumi <tkusumi@netbsd.org>
3  * Copyright (c) 2020 The DragonFly Project
4  * All rights reserved.
5  *
6  * This code is derived from software contributed to The DragonFly Project
7  * by Matthew Dillon <dillon@dragonflybsd.org>
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  *
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in
17  *    the documentation and/or other materials provided with the
18  *    distribution.
19  * 3. Neither the name of The DragonFly Project nor the names of its
20  *    contributors may be used to endorse or promote products derived
21  *    from this software without specific, prior written permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
26  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
27  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
28  * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
29  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
30  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
31  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
32  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
33  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34  * SUCH DAMAGE.
35  */
36 
37 #include <sys/types.h>
38 #include <sys/stat.h>
39 #include <unistd.h>
40 #include <fcntl.h>
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <fstab.h>
45 #include <assert.h>
46 #include <errno.h>
47 #include <err.h>
48 
49 #include <vfs/hammer2/hammer2_disk.h>
50 
51 #include "hammer2_subs.h"
52 
53 static hammer2_ondisk_t fso;
54 static int hammer2_volumes_initialized;
55 
56 static void
57 hammer2_init_volume(hammer2_volume_t *vol)
58 {
59 	vol->fd = -1;
60 	vol->id = -1;
61 	vol->offset = (hammer2_off_t)-1;
62 	vol->size = (hammer2_off_t)-1;
63 }
64 
65 void
66 hammer2_init_ondisk(hammer2_ondisk_t *fsp)
67 {
68 	int i;
69 
70 	bzero(fsp, sizeof(*fsp));
71 	fsp->version = -1;
72 	for (i = 0; i < HAMMER2_MAX_VOLUMES; ++i)
73 		hammer2_init_volume(&fsp->volumes[i]);
74 }
75 
76 void
77 hammer2_install_volume(hammer2_volume_t *vol, int fd, int id, const char *path,
78 		       hammer2_off_t offset, hammer2_off_t size)
79 {
80 	bzero(vol, sizeof(*vol));
81 	vol->fd = fd;
82 	vol->id = id;
83 	vol->path = strdup(path);
84 	vol->offset = offset;
85 	vol->size = size;
86 }
87 
88 void
89 hammer2_uninstall_volume(hammer2_volume_t *vol)
90 {
91 	fsync(vol->fd);
92 	close(vol->fd);
93 	free(vol->path);
94 	hammer2_init_volume(vol);
95 }
96 
97 /*
98  * Locate a valid volume header.  If any of the four volume headers is good,
99  * we have a valid volume header and choose the best one based on mirror_tid.
100  */
101 static int
102 hammer2_read_volume_header(int fd, const char *path,
103 			   hammer2_volume_data_t *voldata)
104 {
105 	hammer2_volume_data_t vd;
106 	hammer2_tid_t mirror_tid = -1;
107 	hammer2_off_t size = check_volume(fd);
108 	hammer2_crc32_t crc0, crc1;
109 	const char *p;
110 	int i, zone = -1;
111 	ssize_t ret;
112 
113 	for (i = 0; i < HAMMER2_NUM_VOLHDRS; ++i) {
114 		if (i * HAMMER2_ZONE_BYTES64 >= size)
115 			break;
116 		if (lseek(fd, i * HAMMER2_ZONE_BYTES64, SEEK_SET) == -1)
117 			break;
118 		ret = read(fd, &vd, HAMMER2_PBUFSIZE);
119 		if (ret == -1) {
120 			fprintf(stderr, "%s #%d: read %s\n",
121 				path, i, strerror(errno));
122 			continue;
123 		}
124 		if (ret != HAMMER2_PBUFSIZE) {
125 			fprintf(stderr, "%s #%d: read %s\n",
126 				path, i, strerror(errno));
127 			continue;
128 		}
129 
130 		p = (const char*)&vd;
131 		/* verify volume header magic */
132 		if ((vd.magic != HAMMER2_VOLUME_ID_HBO) &&
133 		    (vd.magic != HAMMER2_VOLUME_ID_ABO)) {
134 			fprintf(stderr, "%s #%d: bad magic\n", path, i);
135 			continue;
136 		}
137 
138 		if (vd.magic == HAMMER2_VOLUME_ID_ABO) {
139 			/* XXX: Reversed-endianness filesystem */
140 			fprintf(stderr,
141 				"%s #%d: reverse-endian filesystem detected",
142 				path, i);
143 			continue;
144 		}
145 
146 		/* verify volume header CRC's */
147 		crc0 = vd.icrc_sects[HAMMER2_VOL_ICRC_SECT0];
148 		crc1 = hammer2_icrc32(p + HAMMER2_VOLUME_ICRC0_OFF,
149 				      HAMMER2_VOLUME_ICRC0_SIZE);
150 		if (crc0 != crc1) {
151 			fprintf(stderr,
152 				"%s #%d: volume header crc mismatch "
153 				"sect0 %08x/%08x\n",
154 				path, i, crc0, crc1);
155 			continue;
156 		}
157 
158 		crc0 = vd.icrc_sects[HAMMER2_VOL_ICRC_SECT1];
159 		crc1 = hammer2_icrc32(p + HAMMER2_VOLUME_ICRC1_OFF,
160 				      HAMMER2_VOLUME_ICRC1_SIZE);
161 		if (crc0 != crc1) {
162 			fprintf(stderr,
163 				"%s #%d: volume header crc mismatch "
164 				"sect1 %08x/%08x",
165 				path, i, crc0, crc1);
166 			continue;
167 		}
168 
169 		crc0 = vd.icrc_volheader;
170 		crc1 = hammer2_icrc32(p + HAMMER2_VOLUME_ICRCVH_OFF,
171 				      HAMMER2_VOLUME_ICRCVH_SIZE);
172 		if (crc0 != crc1) {
173 			fprintf(stderr,
174 				"%s #%d: volume header crc mismatch "
175 				"vh %08x/%08x",
176 				path, i, crc0, crc1);
177 			continue;
178 		}
179 		if (zone == -1 || mirror_tid < vd.mirror_tid) {
180 			bcopy(&vd, voldata, sizeof(vd));
181 			mirror_tid = vd.mirror_tid;
182 			zone = i;
183 		}
184 	}
185 	return(zone);
186 }
187 
188 static void
189 hammer2_err_uuid_mismatch(uuid_t *uuid1, uuid_t *uuid2, const char *id)
190 {
191 	char *p1 = NULL, *p2 = NULL;
192 
193 	hammer2_uuid_to_str(uuid1, &p1);
194 	hammer2_uuid_to_str(uuid2, &p2);
195 
196 	errx(1, "%s uuid mismatch %s vs %s", id, p1, p2);
197 
198 	free(p1);
199 	free(p2);
200 }
201 
202 static void
203 hammer2_add_volume(const char *path, int rdonly)
204 {
205 	hammer2_volume_data_t voldata;
206 	hammer2_volume_t *vol;
207 	struct stat st;
208 	int fd, i;
209 	uuid_t uuid;
210 
211 	fd = open(path, rdonly ? O_RDONLY : O_RDWR);
212 	if (fd == -1)
213 		err(1, "open");
214 
215 	if (fstat(fd, &st) == -1)
216 		err(1, "fstat");
217 	if (!S_ISCHR(st.st_mode) && !S_ISREG(st.st_mode))
218 		errx(1, "Unsupported file type");
219 
220 	if (hammer2_read_volume_header(fd, path, &voldata) >= 0) {
221 		i = voldata.volu_id;
222 		if (i < 0 || i >= HAMMER2_MAX_VOLUMES)
223 			errx(1, "%s has bad volume id %d", path, i);
224 		vol = &fso.volumes[i];
225 		if (vol->id != -1)
226 			errx(1, "%s already initialized", path);
227 		/* all headers must have the same version, nvolumes and uuid */
228 		if (!fso.nvolumes) {
229 			fso.version = voldata.version;
230 			fso.nvolumes = voldata.nvolumes;
231 			fso.fsid = voldata.fsid;
232 			fso.fstype = voldata.fstype;
233 		} else {
234 			if (fso.version != (int)voldata.version)
235 				errx(1, "Volume version mismatch %d vs %d",
236 				     fso.version, (int)voldata.version);
237 			if (fso.nvolumes != voldata.nvolumes)
238 				errx(1, "Volume count mismatch %d vs %d",
239 				     fso.nvolumes, voldata.nvolumes);
240 			uuid = voldata.fsid;
241 			if (!uuid_equal(&fso.fsid, &uuid, NULL))
242 				hammer2_err_uuid_mismatch(&fso.fsid,
243 							  &uuid,
244 							  "fsid");
245 			uuid = voldata.fstype;
246 			if (!uuid_equal(&fso.fstype, &uuid, NULL))
247 				hammer2_err_uuid_mismatch(&fso.fstype,
248 							  &uuid,
249 							  "fstype");
250 		}
251 		/* all per-volume tests passed */
252 		hammer2_install_volume(vol, fd, i, path,
253 				       voldata.volu_loff[i], voldata.volu_size);
254 		fso.total_size += vol->size;
255 	} else {
256 		errx(1, "No valid volume headers found!");
257 	}
258 }
259 
260 static void
261 hammer2_verify_volumes_common(const hammer2_ondisk_t *fsp)
262 {
263 	const hammer2_volume_t *vol;
264 	hammer2_off_t size;
265 	struct stat *st;
266 	const char *path;
267 	int i, j, nvolumes = 0;
268 
269 	if (fsp->version == -1)
270 		errx(1, "Bad volume version %d", fsp->version);
271 
272 	/* check initialized volume count */
273 	for (i = 0; i < HAMMER2_MAX_VOLUMES; ++i) {
274 		vol = &fsp->volumes[i];
275 		if (vol->id != -1)
276 			nvolumes++;
277 	}
278 
279 	/* fsp->nvolumes hasn't been verified yet, use nvolumes */
280 	st = calloc(nvolumes, sizeof(*st));
281 
282 	for (i = 0; i < HAMMER2_MAX_VOLUMES; ++i) {
283 		vol = &fsp->volumes[i];
284 		if (vol->id == -1)
285 			continue;
286 		path = vol->path;
287 		/* check volumes are unique */
288 		if (stat(path, &st[i]) != 0)
289 			errx(1, "Failed to stat %s", path);
290 		if (fstat(vol->fd, &st[i]) != 0)
291 			errx(1, "Failed to fstat %d", vol->fd);
292 		for (j = 0; j < i; ++j) {
293 			if ((st[i].st_ino == st[j].st_ino) &&
294 			    (st[i].st_dev == st[j].st_dev))
295 				errx(1, "%s specified more than once", path);
296 		}
297 		/* check volume fields are initialized */
298 		if (vol->fd == -1)
299 			errx(1, "%s has bad fd %d", path, vol->fd);
300 		if (vol->offset == (hammer2_off_t)-1)
301 			errx(1, "%s has bad offset 0x%016jx", path,
302 			     (intmax_t)vol->offset);
303 		if (vol->size == (hammer2_off_t)-1)
304 			errx(1, "%s has bad size 0x%016jx", path,
305 			     (intmax_t)vol->size);
306 		/* check volume size vs block device size */
307 		size = check_volume(vol->fd);
308 		printf("checkvolu header %d %016jx/%016jx\n", i, vol->size, size);
309 		if (vol->size > size)
310 			errx(1, "%s's size 0x%016jx exceeds device size 0x%016jx",
311 			     path, (intmax_t)vol->size, size);
312 	}
313 	free(st);
314 }
315 
316 static void
317 hammer2_verify_volumes_1(hammer2_ondisk_t *fsp,
318 			 const hammer2_volume_data_t *rootvoldata)
319 {
320 	const hammer2_volume_t *vol;
321 	hammer2_off_t off;
322 	const char *path;
323 	int i, nvolumes = 0;
324 
325 	/* check initialized volume count */
326 	for (i = 0; i < HAMMER2_MAX_VOLUMES; ++i) {
327 		vol = &fsp->volumes[i];
328 		if (vol->id != -1)
329 			nvolumes++;
330 	}
331 	if (nvolumes != 1)
332 		errx(1, "Only 1 volume supported");
333 	if (fsp->nvolumes)
334 		errx(1, "Volume count %d must be 0", fsp->nvolumes);
335 	fsp->nvolumes = nvolumes; /* adjust with actual count */
336 
337 	/* check volume header */
338 	if (rootvoldata) {
339 		if (rootvoldata->volu_id)
340 			errx(1, "Volume id %d must be 0", rootvoldata->volu_id);
341 		if (rootvoldata->nvolumes)
342 			errx(1, "Volume count %d must be 0",
343 			     rootvoldata->nvolumes);
344 		if (rootvoldata->total_size)
345 			errx(1, "Total size 0x%016jx must be 0",
346 			     (intmax_t)rootvoldata->total_size);
347 		for (i = 0; i < HAMMER2_MAX_VOLUMES; ++i) {
348 			off = rootvoldata->volu_loff[i];
349 			if (off)
350 				errx(1, "Volume offset[%d] 0x%016jx must be 0",
351 				     i, (intmax_t)off);
352 		}
353 	}
354 
355 	/* check volume */
356 	vol = &fsp->volumes[0];
357 	path = vol->path;
358 	if (vol->id)
359 		errx(1, "%s has non zero id %d", path, vol->id);
360 	if (vol->offset)
361 		errx(1, "%s has non zero offset 0x%016jx", path,
362 		     (intmax_t)vol->offset);
363 	if (vol->size & HAMMER2_VOLUME_ALIGNMASK64)
364 		errx(1, "%s's size is not 0x%016jx aligned", path,
365 		     (intmax_t)HAMMER2_VOLUME_ALIGN);
366 }
367 
368 static void
369 hammer2_verify_volumes_2(const hammer2_ondisk_t *fsp,
370 			 const hammer2_volume_data_t *rootvoldata)
371 {
372 	const hammer2_volume_t *vol;
373 	hammer2_off_t off;
374 	const char *path;
375 	int i, nvolumes = 0;
376 
377 	/* check initialized volume count */
378 	for (i = 0; i < HAMMER2_MAX_VOLUMES; ++i) {
379 		vol = &fsp->volumes[i];
380 		if (vol->id != -1)
381 			nvolumes++;
382 	}
383 	if (fsp->nvolumes != nvolumes)
384 		errx(1, "Volume count mismatch %d vs %d",
385 		     fsp->nvolumes, nvolumes);
386 
387 	/* check volume header */
388 	if (rootvoldata) {
389 		if (rootvoldata->volu_id != HAMMER2_ROOT_VOLUME)
390 			errx(1, "Volume id %d must be %d",
391 			     rootvoldata->volu_id, HAMMER2_ROOT_VOLUME);
392 		if (rootvoldata->nvolumes != fso.nvolumes)
393 			errx(1, "Volume header requires %d devices, %d specified",
394 			     rootvoldata->nvolumes, fso.nvolumes);
395 		if (rootvoldata->total_size != fso.total_size)
396 			errx(1, "Total size 0x%016jx does not equal sum of "
397 			     "volumes 0x%016jx",
398 			     rootvoldata->total_size, fso.total_size);
399 		for (i = 0; i < nvolumes; ++i) {
400 			off = rootvoldata->volu_loff[i];
401 			if (off == (hammer2_off_t)-1)
402 				errx(1, "Volume offset[%d] 0x%016jx must not be -1",
403 				     i, (intmax_t)off);
404 		}
405 		for (i = nvolumes; i < HAMMER2_MAX_VOLUMES; ++i) {
406 			off = rootvoldata->volu_loff[i];
407 			if (off != (hammer2_off_t)-1)
408 				errx(1, "Volume offset[%d] 0x%016jx must be -1",
409 				     i, (intmax_t)off);
410 		}
411 	}
412 
413 	/* check volumes */
414 	for (i = 0; i < HAMMER2_MAX_VOLUMES; ++i) {
415 		vol = &fsp->volumes[i];
416 		if (vol->id == -1)
417 			continue;
418 		path = vol->path;
419 		/* check offset */
420 		if (vol->offset & HAMMER2_FREEMAP_LEVEL1_MASK)
421 			errx(1, "%s's offset 0x%016jx not 0x%016jx aligned",
422 			     path, (intmax_t)vol->offset,
423 			     HAMMER2_FREEMAP_LEVEL1_SIZE);
424 		/* check vs previous volume */
425 		if (i) {
426 			if (vol->id != (vol-1)->id + 1)
427 				errx(1, "%s has inconsistent id %d", path,
428 				     vol->id);
429 			if (vol->offset != (vol-1)->offset + (vol-1)->size)
430 				errx(1, "%s has inconsistent offset 0x%016jx",
431 				     path, (intmax_t)vol->offset);
432 		} else { /* first */
433 			if (vol->offset)
434 				errx(1, "%s has non zero offset 0x%016jx", path,
435 				     (intmax_t)vol->offset);
436 		}
437 		/* check size for non-last and last volumes */
438 		if (i != fsp->nvolumes - 1) {
439 			if (vol->size < HAMMER2_FREEMAP_LEVEL1_SIZE)
440 				errx(1, "%s's size must be >= 0x%016jx", path,
441 				     (intmax_t)HAMMER2_FREEMAP_LEVEL1_SIZE);
442 			if (vol->size & HAMMER2_FREEMAP_LEVEL1_MASK)
443 				errx(1, "%s's size is not 0x%016jx aligned",
444 				     path,
445 				     (intmax_t)HAMMER2_FREEMAP_LEVEL1_SIZE);
446 		} else { /* last */
447 			if (vol->size & HAMMER2_VOLUME_ALIGNMASK64)
448 				errx(1, "%s's size is not 0x%016jx aligned",
449 				     path, (intmax_t)HAMMER2_VOLUME_ALIGN);
450 		}
451 	}
452 }
453 
454 void
455 hammer2_verify_volumes(hammer2_ondisk_t *fsp,
456 		       const hammer2_volume_data_t *rootvoldata)
457 {
458 	hammer2_verify_volumes_common(fsp);
459 	if (fsp->version >= HAMMER2_VOL_VERSION_MULTI_VOLUMES)
460 		hammer2_verify_volumes_2(fsp, rootvoldata);
461 	else
462 		hammer2_verify_volumes_1(fsp, rootvoldata);
463 	assert(fsp->nvolumes > 0);
464 }
465 
466 void
467 hammer2_print_volumes(const hammer2_ondisk_t *fsp)
468 {
469 	const hammer2_volume_t *vol;
470 	int i, n, w = 0;
471 
472 	for (i = 0; i < fsp->nvolumes; ++i) {
473 		vol = &fsp->volumes[i];
474 		n = (int)strlen(vol->path);
475 		if (n > w)
476 			w = n;
477 	}
478 
479 	printf("total    %-*.*s 0x%016jx 0x%016jx\n",
480 		w, w, "", (intmax_t)0, (intmax_t)fsp->total_size);
481 
482 	for (i = 0; i < fsp->nvolumes; ++i) {
483 		vol = &fsp->volumes[i];
484 		printf("volume%-2d %-*.*s 0x%016jx 0x%016jx%s\n",
485 		       vol->id, w, w, vol->path, (intmax_t)vol->offset,
486 		       (intmax_t)vol->size,
487 		       (vol->id == HAMMER2_ROOT_VOLUME ?
488 		       " (root volume)" : ""));
489 	}
490 }
491 
492 void
493 hammer2_init_volumes(const char *blkdevs, int rdonly)
494 {
495 	hammer2_volume_data_t *rootvoldata;
496 	char *p, *devpath;
497 
498 	if (hammer2_volumes_initialized)
499 		errx(1, "Already initialized");
500 	if (!blkdevs)
501 		errx(1, "NULL blkdevs");
502 
503 	hammer2_init_ondisk(&fso);
504 	p = strdup(blkdevs);
505 	while ((devpath = p) != NULL) {
506 		if ((p = strchr(p, ':')) != NULL)
507 			*p++ = 0;
508 		devpath = getdevpath(devpath, 0);
509 		if (strchr(devpath, ':'))
510 			hammer2_init_volumes(devpath, rdonly);
511 		else
512 			hammer2_add_volume(devpath, rdonly);
513 		free(devpath);
514 	}
515 	free(p);
516 	hammer2_volumes_initialized = 1;
517 
518 	rootvoldata = hammer2_read_root_volume_header();
519 	hammer2_verify_volumes(&fso, rootvoldata);
520 	free(rootvoldata);
521 }
522 
523 void
524 hammer2_cleanup_volumes(void)
525 {
526 	hammer2_volume_t *vol;
527 	int i;
528 
529 	for (i = 0; i < HAMMER2_MAX_VOLUMES; ++i) {
530 		vol = &fso.volumes[i];
531 		if (vol->id == -1)
532 			continue;
533 		hammer2_uninstall_volume(vol);
534 	}
535 	hammer2_volumes_initialized = 0;
536 }
537 
538 typedef void (*callback)(const hammer2_volume_t*, void *data);
539 
540 static int
541 hammer2_get_volume_attr(hammer2_off_t offset, callback fn, void *data)
542 {
543 	hammer2_volume_t *vol;
544 	int i;
545 
546 	assert(hammer2_volumes_initialized == 1);
547 	offset &= ~HAMMER2_OFF_MASK_RADIX;
548 
549 	/* do binary search if users really use this many supported volumes */
550 	for (i = 0; i < fso.nvolumes; ++i) {
551 		vol = &fso.volumes[i];
552 		if ((offset >= vol->offset) &&
553 		    (offset < vol->offset + vol->size)) {
554 			fn(vol, data);
555 			return(0);
556 		}
557 	}
558 
559 	return(-1);
560 }
561 
562 /* fd */
563 static void
564 hammer2_volume_fd_cb(const hammer2_volume_t *vol, void *data)
565 {
566 	*(int*)data = vol->fd;
567 }
568 
569 int
570 hammer2_get_volume_fd(hammer2_off_t offset)
571 {
572 	int ret = 0;
573 
574 	if (hammer2_get_volume_attr(offset, hammer2_volume_fd_cb, &ret) < 0)
575 		return(-1);
576 	return(ret);
577 }
578 
579 int
580 hammer2_get_root_volume_fd(void)
581 {
582 	return(hammer2_get_volume_fd(0));
583 }
584 
585 /* id */
586 static void
587 hammer2_volume_id_cb(const hammer2_volume_t *vol, void *data)
588 {
589 	*(int*)data = vol->id;
590 }
591 
592 int
593 hammer2_get_volume_id(hammer2_off_t offset)
594 {
595 	int ret = 0;
596 
597 	if (hammer2_get_volume_attr(offset, hammer2_volume_id_cb, &ret) < 0)
598 		return(-1);
599 	return(ret);
600 }
601 
602 int
603 hammer2_get_root_volume_id(void)
604 {
605 	return(hammer2_get_volume_id(0));
606 }
607 
608 /* path */
609 static void
610 hammer2_volume_path_cb(const hammer2_volume_t *vol, void *data)
611 {
612 	*(const char**)data = vol->path;
613 }
614 
615 const char *
616 hammer2_get_volume_path(hammer2_off_t offset)
617 {
618 	const char *ret = NULL;
619 
620 	if (hammer2_get_volume_attr(offset, hammer2_volume_path_cb, &ret) < 0)
621 		return(NULL);
622 	return(ret);
623 }
624 
625 const char *
626 hammer2_get_root_volume_path(void)
627 {
628 	return(hammer2_get_volume_path(0));
629 }
630 
631 /* offset */
632 static void
633 hammer2_volume_offset_cb(const hammer2_volume_t *vol, void *data)
634 {
635 	*(hammer2_off_t*)data = vol->offset;
636 }
637 
638 hammer2_off_t
639 hammer2_get_volume_offset(hammer2_off_t offset)
640 {
641 	hammer2_off_t ret = 0;
642 
643 	if (hammer2_get_volume_attr(offset, hammer2_volume_offset_cb, &ret) < 0)
644 		return(-1);
645 	return(ret);
646 }
647 
648 hammer2_off_t
649 hammer2_get_root_volume_offset(void)
650 {
651 	return(hammer2_get_volume_offset(0));
652 }
653 
654 /* size */
655 static void
656 hammer2_volume_size_cb(const hammer2_volume_t *vol, void *data)
657 {
658 	*(hammer2_off_t*)data = vol->size;
659 }
660 
661 hammer2_off_t
662 hammer2_get_volume_size(hammer2_off_t offset)
663 {
664 	hammer2_off_t ret = 0;
665 
666 	if (hammer2_get_volume_attr(offset, hammer2_volume_size_cb, &ret) < 0)
667 		return(-1);
668 	return(ret);
669 }
670 
671 hammer2_off_t
672 hammer2_get_root_volume_size(void)
673 {
674 	return(hammer2_get_volume_size(0));
675 }
676 
677 /* total size */
678 hammer2_off_t
679 hammer2_get_total_size(void)
680 {
681 	return(fso.total_size);
682 }
683 
684 hammer2_volume_data_t*
685 hammer2_read_root_volume_header(void)
686 {
687 	hammer2_volume_data_t *voldata;
688 	int fd = hammer2_get_root_volume_fd();
689 	const char *path = hammer2_get_root_volume_path();
690 
691 	if (fd == -1)
692 		return(NULL);
693 
694 	voldata = calloc(1, sizeof(*voldata));
695 	if (hammer2_read_volume_header(fd, path, voldata) >= 0)
696 		return(voldata);
697 	else
698 		errx(1, "Failed to read volume header");
699 }
700