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