xref: /netbsd/usr.sbin/fstyp/hammer2.c (revision 620d5e2a)
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@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
get_file_size(FILE * fp)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 *
read_voldata(FILE * fp,int i)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
test_voldata(FILE * fp)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*
read_media(FILE * fp,const hammer2_blockref_t * bref,size_t * media_bytes)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
find_pfs(FILE * fp,const hammer2_blockref_t * bref,const char * pfs,bool * res)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*
extract_device_name(const char * devpath)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
read_label(FILE * fp,char * label,size_t size,const char * devpath)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
fstyp_hammer2(FILE * fp,char * label,size_t size)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
__fsvtyp_hammer2(const char * blkdevs,char * label,size_t size,int partial)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
fsvtyp_hammer2(const char * blkdevs,char * label,size_t size)516 fsvtyp_hammer2(const char *blkdevs, char *label, size_t size)
517 {
518 	return __fsvtyp_hammer2(blkdevs, label, size, 0);
519 }
520 
521 int
fsvtyp_hammer2_partial(const char * blkdevs,char * label,size_t size)522 fsvtyp_hammer2_partial(const char *blkdevs, char *label, size_t size)
523 {
524 	return __fsvtyp_hammer2(blkdevs, label, size, 1);
525 }
526