Home | History | Annotate | Line # | Download | only in fstyp
      1 /*        $NetBSD: hammer2.c,v 1.9 2021/12/02 14:26:42 christos Exp $      */
      2 
      3 /*-
      4  * Copyright (c) 2017-2019 The DragonFly Project
      5  * Copyright (c) 2017-2019 Tomohiro Kusumi <tkusumi (at) netbsd.org>
      6  * All rights reserved.
      7  *
      8  * Redistribution and use in source and binary forms, with or without
      9  * modification, are permitted provided that the following conditions
     10  * are met:
     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 the
     15  *    documentation and/or other materials provided with the distribution.
     16  *
     17  * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
     18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     20  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
     21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     27  * SUCH DAMAGE.
     28  */
     29 #include <sys/cdefs.h>
     30 __KERNEL_RCSID(0, "$NetBSD: hammer2.c,v 1.9 2021/12/02 14:26:42 christos Exp $");
     31 
     32 #include <sys/param.h>
     33 #include <sys/ioctl.h>
     34 #include <sys/disk.h>
     35 
     36 #include <stdio.h>
     37 #include <stdlib.h>
     38 #include <stdbool.h>
     39 #include <string.h>
     40 #include <err.h>
     41 #include <assert.h>
     42 #include <uuid.h>
     43 
     44 #include "fstyp.h"
     45 #include "hammer2_disk.h"
     46 
     47 static ssize_t
     48 get_file_size(FILE *fp)
     49 {
     50 	ssize_t siz;
     51 	struct dkwedge_info dkw;
     52 
     53 	if (ioctl(fileno(fp), DIOCGWEDGEINFO, &dkw) != -1) {
     54 		return (ssize_t)dkw.dkw_size * DEV_BSIZE;
     55 	}
     56 
     57 	if (fseek(fp, 0, SEEK_END) == -1) {
     58 		warnx("hammer2: failed to seek media end");
     59 		return -1;
     60 	}
     61 
     62 	siz = ftell(fp);
     63 	if (siz == -1) {
     64 		warnx("hammer2: failed to tell media end");
     65 		return -1;
     66 	}
     67 
     68 	return siz;
     69 }
     70 
     71 static hammer2_volume_data_t *
     72 read_voldata(FILE *fp, int i)
     73 {
     74 	if (i < 0 || i >= HAMMER2_NUM_VOLHDRS)
     75 		return NULL;
     76 
     77 	if ((hammer2_off_t)i * (hammer2_off_t)HAMMER2_ZONE_BYTES64 >= (hammer2_off_t)get_file_size(fp))
     78 		return NULL;
     79 
     80 	return read_buf(fp, (off_t)i * (off_t)HAMMER2_ZONE_BYTES64,
     81 	    sizeof(hammer2_volume_data_t));
     82 }
     83 
     84 static int
     85 test_voldata(FILE *fp)
     86 {
     87 	hammer2_volume_data_t *voldata;
     88 	int i;
     89 	static int count = 0;
     90 	static uuid_t fsid, fstype;
     91 
     92 	for (i = 0; i < HAMMER2_NUM_VOLHDRS; i++) {
     93 		if ((hammer2_off_t)i * (hammer2_off_t)HAMMER2_ZONE_BYTES64 >= (hammer2_off_t)get_file_size(fp))
     94 			break;
     95 		voldata = read_voldata(fp, i);
     96 		if (voldata == NULL) {
     97 			warnx("hammer2: failed to read volume data");
     98 			return 1;
     99 		}
    100 		if (voldata->magic != HAMMER2_VOLUME_ID_HBO &&
    101 		    voldata->magic != HAMMER2_VOLUME_ID_ABO) {
    102 			free(voldata);
    103 			return 1;
    104 		}
    105 		if (voldata->volu_id > HAMMER2_MAX_VOLUMES - 1) {
    106 			free(voldata);
    107 			return 1;
    108 		}
    109 		if (voldata->nvolumes > HAMMER2_MAX_VOLUMES) {
    110 			free(voldata);
    111 			return 1;
    112 		}
    113 
    114 		if (count == 0) {
    115 			count = voldata->nvolumes;
    116 			memcpy(&fsid, &voldata->fsid, sizeof(fsid));
    117 			memcpy(&fstype, &voldata->fstype, sizeof(fstype));
    118 		} else {
    119 			if (voldata->nvolumes != count) {
    120 				free(voldata);
    121 				return 1;
    122 			}
    123 			if (!uuid_equal(&fsid, &voldata->fsid, NULL)) {
    124 				free(voldata);
    125 				return 1;
    126 			}
    127 			if (!uuid_equal(&fstype, &voldata->fstype, NULL)) {
    128 				free(voldata);
    129 				return 1;
    130 			}
    131 		}
    132 		free(voldata);
    133 	}
    134 
    135 	return 0;
    136 }
    137 
    138 static hammer2_media_data_t*
    139 read_media(FILE *fp, const hammer2_blockref_t *bref, size_t *media_bytes)
    140 {
    141 	hammer2_media_data_t *media;
    142 	hammer2_off_t io_off, io_base;
    143 	size_t bytes, io_bytes, boff, fbytes;
    144 
    145 	bytes = (bref->data_off & HAMMER2_OFF_MASK_RADIX);
    146 	if (bytes)
    147 		bytes = (size_t)1 << bytes;
    148 	*media_bytes = bytes;
    149 
    150 	if (!bytes) {
    151 		warnx("hammer2: blockref has no data");
    152 		return NULL;
    153 	}
    154 
    155 	io_off = bref->data_off & ~HAMMER2_OFF_MASK_RADIX;
    156 	io_base = io_off & ~(hammer2_off_t)(HAMMER2_LBUFSIZE - 1);
    157 	boff = (size_t)((hammer2_off_t)io_off - io_base);
    158 
    159 	io_bytes = HAMMER2_LBUFSIZE;
    160 	while (io_bytes + boff < bytes)
    161 		io_bytes <<= 1;
    162 
    163 	if (io_bytes > sizeof(hammer2_media_data_t)) {
    164 		warnx("hammer2: invalid I/O bytes");
    165 		return NULL;
    166 	}
    167 
    168 	/*
    169 	 * XXX fp is currently always root volume, so read fails if io_base is
    170 	 * beyond root volume limit. Fail with a message before read_buf() then.
    171 	 */
    172 	fbytes = (size_t)get_file_size(fp);
    173 	if ((ssize_t)fbytes == -1) {
    174 		warnx("hammer2: failed to get media size");
    175 		return NULL;
    176 	}
    177 	if (io_base >= fbytes) {
    178 		warnx("hammer2: XXX read beyond HAMMER2 root volume limit unsupported");
    179 		return NULL;
    180 	}
    181 
    182 	if (fseeko(fp, (off_t)io_base, SEEK_SET) == -1) {
    183 		warnx("hammer2: failed to seek media");
    184 		return NULL;
    185 	}
    186 	media = read_buf(fp, (off_t)io_base, io_bytes);
    187 	if (media == NULL) {
    188 		warnx("hammer2: failed to read media");
    189 		return NULL;
    190 	}
    191 	if (boff)
    192 		memcpy(media, (char *)media + boff, bytes);
    193 
    194 	return media;
    195 }
    196 
    197 static int
    198 find_pfs(FILE *fp, const hammer2_blockref_t *bref, const char *pfs, bool *res)
    199 {
    200 	hammer2_media_data_t *media;
    201 	hammer2_inode_data_t ipdata;
    202 	hammer2_blockref_t *bscan;
    203 	size_t bytes;
    204 	int i, bcount;
    205 
    206 	media = read_media(fp, bref, &bytes);
    207 	if (media == NULL)
    208 		return -1;
    209 
    210 	switch (bref->type) {
    211 	case HAMMER2_BREF_TYPE_INODE:
    212 		ipdata = media->ipdata;
    213 		if (ipdata.meta.pfs_type == HAMMER2_PFSTYPE_SUPROOT) {
    214 			bscan = &ipdata.u.blockset.blockref[0];
    215 			bcount = HAMMER2_SET_COUNT;
    216 		} else {
    217 			bscan = NULL;
    218 			bcount = 0;
    219 			if (ipdata.meta.op_flags & HAMMER2_OPFLAG_PFSROOT) {
    220 				if (memchr(ipdata.filename, 0,
    221 				    sizeof(ipdata.filename))) {
    222 					if (!strcmp(
    223 					    (const char*)ipdata.filename, pfs))
    224 						*res = true;
    225 				} else {
    226 					if (strlen(pfs) > 0 &&
    227 					    !memcmp(ipdata.filename, pfs,
    228 					    strlen(pfs)))
    229 						*res = true;
    230 				}
    231 			} else {
    232 				free(media);
    233 				return -1;
    234 			}
    235 		}
    236 		break;
    237 	case HAMMER2_BREF_TYPE_INDIRECT:
    238 		bscan = &media->npdata[0];
    239 		bcount = (int)(bytes / sizeof(hammer2_blockref_t));
    240 		break;
    241 	default:
    242 		bscan = NULL;
    243 		bcount = 0;
    244 		break;
    245 	}
    246 
    247 	for (i = 0; i < bcount; ++i) {
    248 		if (bscan[i].type != HAMMER2_BREF_TYPE_EMPTY) {
    249 			if (find_pfs(fp, &bscan[i], pfs, res) == -1) {
    250 				free(media);
    251 				return -1;
    252 			}
    253 		}
    254 	}
    255 	free(media);
    256 
    257 	return 0;
    258 }
    259 
    260 static char*
    261 extract_device_name(const char *devpath)
    262 {
    263 	char *p, *head;
    264 
    265 	if (!devpath)
    266 		return NULL;
    267 
    268 	p = strdup(devpath);
    269 	head = p;
    270 
    271 	p = strchr(p, '@');
    272 	if (p)
    273 		*p = 0;
    274 
    275 	p = strrchr(head, '/');
    276 	if (p) {
    277 		p++;
    278 		if (*p == 0) {
    279 			free(head);
    280 			return NULL;
    281 		}
    282 		p = strdup(p);
    283 		free(head);
    284 		return p;
    285 	}
    286 
    287 	return head;
    288 }
    289 
    290 static int
    291 read_label(FILE *fp, char *label, size_t size, const char *devpath)
    292 {
    293 	hammer2_blockref_t broot, best, *bref;
    294 	hammer2_media_data_t *vols[HAMMER2_NUM_VOLHDRS], *media;
    295 	size_t bytes;
    296 	bool res = false;
    297 	int i, best_i, error = 1;
    298 	const char *pfs;
    299 	char *devname;
    300 
    301 	best_i = -1;
    302 	memset(vols, 0, sizeof(vols));
    303 	memset(&best, 0, sizeof(best));
    304 
    305 	for (i = 0; i < HAMMER2_NUM_VOLHDRS; i++) {
    306 		if ((hammer2_off_t)i * (hammer2_off_t)HAMMER2_ZONE_BYTES64 >= (hammer2_off_t)get_file_size(fp))
    307 			break;
    308 		memset(&broot, 0, sizeof(broot));
    309 		broot.type = HAMMER2_BREF_TYPE_VOLUME;
    310 		broot.data_off = ((hammer2_off_t)i * (hammer2_off_t)HAMMER2_ZONE_BYTES64) | HAMMER2_PBUFRADIX;
    311 		vols[i] = (void*)read_voldata(fp, i);
    312 		if (vols[i] == NULL) {
    313 			warnx("hammer2: failed to read volume data");
    314 			goto fail;
    315 		}
    316 		broot.mirror_tid = vols[i]->voldata.mirror_tid;
    317 		if (best_i < 0 || best.mirror_tid < broot.mirror_tid) {
    318 			best_i = i;
    319 			best = broot;
    320 		}
    321 	}
    322 
    323 	bref = &vols[best_i]->voldata.sroot_blockset.blockref[0];
    324 	if (bref->type != HAMMER2_BREF_TYPE_INODE) {
    325 		/* Don't print error as devpath could be non-root volume. */
    326 		goto fail;
    327 	}
    328 
    329 	media = read_media(fp, bref, &bytes);
    330 	if (media == NULL) {
    331 		goto fail;
    332 	}
    333 
    334 	/*
    335 	 * fstyp_function in DragonFly takes an additional devpath argument
    336 	 * which doesn't exist in FreeBSD and NetBSD.
    337 	 */
    338 #ifdef HAS_DEVPATH
    339 	pfs = strchr(devpath, '@');
    340 	if (!pfs) {
    341 		assert(strlen(devpath));
    342 		switch (devpath[strlen(devpath) - 1]) {
    343 		case 'a':
    344 			pfs = "BOOT";
    345 			break;
    346 		case 'd':
    347 			pfs = "ROOT";
    348 			break;
    349 		default:
    350 			pfs = "DATA";
    351 			break;
    352 		}
    353 	} else
    354 		pfs++;
    355 
    356 	if (strlen(pfs) > HAMMER2_INODE_MAXNAME) {
    357 		goto fail;
    358 	}
    359 	devname = extract_device_name(devpath);
    360 #else
    361 	pfs = "";
    362 	devname = extract_device_name(NULL);
    363 	assert(!devname);
    364 #endif
    365 
    366 	/* Add device name to help support multiple autofs -media mounts. */
    367 	if (find_pfs(fp, bref, pfs, &res) == 0 && res) {
    368 		if (devname)
    369 			snprintf(label, size, "%s_%s", pfs, devname);
    370 		else
    371 			strlcpy(label, pfs, size);
    372 	} else {
    373 		memset(label, 0, size);
    374 		memcpy(label, media->ipdata.filename,
    375 		    sizeof(media->ipdata.filename));
    376 		if (devname) {
    377 			strlcat(label, "_", size);
    378 			strlcat(label, devname, size);
    379 		}
    380 	}
    381 	if (devname)
    382 		free(devname);
    383 	free(media);
    384 	error = 0;
    385 fail:
    386 	for (i = 0; i < HAMMER2_NUM_VOLHDRS; i++)
    387 		free(vols[i]);
    388 
    389 	return error;
    390 }
    391 
    392 int
    393 fstyp_hammer2(FILE *fp, char *label, size_t size)
    394 {
    395 	hammer2_volume_data_t *voldata = read_voldata(fp, 0);
    396 	int error = 1;
    397 
    398 	if (voldata == NULL)
    399 		goto fail;
    400 	if (voldata->volu_id != HAMMER2_ROOT_VOLUME)
    401 		goto fail;
    402 	if (voldata->nvolumes != 0)
    403 		goto fail;
    404 	if (test_voldata(fp))
    405 		goto fail;
    406 
    407 	error = read_label(fp, label, size, NULL);
    408 fail:
    409 	free(voldata);
    410 	return error;
    411 }
    412 
    413 static int
    414 __fsvtyp_hammer2(const char *blkdevs, char *label, size_t size, int partial)
    415 {
    416 	hammer2_volume_data_t *voldata = NULL;
    417 	FILE *fp = NULL;
    418 	char *dup = NULL, *target_label = NULL, *p, *volpath, *rootvolpath;
    419 	char x[HAMMER2_MAX_VOLUMES];
    420 	int i, volid, error = 1;
    421 
    422 	if (!blkdevs)
    423 		goto fail;
    424 
    425 	memset(x, 0, sizeof(x));
    426 	p = dup = strdup(blkdevs);
    427 	if ((p = strchr(p, '@')) != NULL) {
    428 		*p++ = '\0';
    429 		target_label = p;
    430 	}
    431 	p = dup;
    432 
    433 	volpath = NULL;
    434 	rootvolpath = NULL;
    435 	volid = -1;
    436 	while (p) {
    437 		volpath = p;
    438 		if ((p = strchr(p, ':')) != NULL)
    439 			*p++ = '\0';
    440 		if ((fp = fopen(volpath, "r")) == NULL) {
    441 			warnx("hammer2: failed to open %s", volpath);
    442 			goto fail;
    443 		}
    444 		if (test_voldata(fp))
    445 			break;
    446 		voldata = read_voldata(fp, 0);
    447 		fclose(fp);
    448 		if (voldata == NULL) {
    449 			warnx("hammer2: failed to read volume data");
    450 			goto fail;
    451 		}
    452 		volid = voldata->volu_id;
    453 		free(voldata);
    454 		voldata = NULL;
    455 		if (volid < 0 || volid >= HAMMER2_MAX_VOLUMES)
    456 			goto fail;
    457 		x[volid]++;
    458 		if (volid == HAMMER2_ROOT_VOLUME)
    459 			rootvolpath = volpath;
    460 	}
    461 
    462 	/* If no rootvolpath, proceed only if partial mode with volpath. */
    463 	if (rootvolpath)
    464 		volpath = rootvolpath;
    465 	else if (!partial || !volpath)
    466 		goto fail;
    467 	if ((fp = fopen(volpath, "r")) == NULL) {
    468 		warnx("hammer2: failed to open %s", volpath);
    469 		goto fail;
    470 	}
    471 	voldata = read_voldata(fp, 0);
    472 	if (voldata == NULL) {
    473 		warnx("hammer2: failed to read volume data");
    474 		goto fail;
    475 	}
    476 
    477 	if (volid == -1)
    478 		goto fail;
    479 	if (partial)
    480 		goto success;
    481 
    482 	for (i = 0; i < HAMMER2_MAX_VOLUMES; i++)
    483 		if (x[i] > 1)
    484 			goto fail;
    485 	for (i = 0; i < HAMMER2_MAX_VOLUMES; i++)
    486 		if (x[i] == 0)
    487 			break;
    488 	if (voldata->nvolumes != i)
    489 		goto fail;
    490 	for (; i < HAMMER2_MAX_VOLUMES; i++)
    491 		if (x[i] != 0)
    492 			goto fail;
    493 success:
    494 	/* Reconstruct @label format path using only root volume. */
    495 	if (target_label) {
    496 		size_t siz = strlen(volpath) + strlen(target_label) + 2;
    497 		p = calloc(1, siz);
    498 		snprintf(p, siz, "%s@%s", volpath, target_label);
    499 		volpath = p;
    500 	}
    501 	error = read_label(fp, label, size, volpath);
    502 	if (target_label)
    503 		free(p);
    504 	/* If in partial mode, read label but ignore error. */
    505 	if (partial)
    506 		error = 0;
    507 fail:
    508 	if (fp)
    509 		fclose(fp);
    510 	free(voldata);
    511 	free(dup);
    512 	return error;
    513 }
    514 
    515 int
    516 fsvtyp_hammer2(const char *blkdevs, char *label, size_t size)
    517 {
    518 	return __fsvtyp_hammer2(blkdevs, label, size, 0);
    519 }
    520 
    521 int
    522 fsvtyp_hammer2_partial(const char *blkdevs, char *label, size_t size)
    523 {
    524 	return __fsvtyp_hammer2(blkdevs, label, size, 1);
    525 }
    526