xref: /netbsd-src/usr.sbin/fstyp/hammer2.c (revision 82d56013d7b633d116a93943de88e08335357a7c)
1 /*        $NetBSD: hammer2.c,v 1.8 2021/01/10 13:44:57 martin 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.8 2021/01/10 13:44:57 martin 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 = (size_t)((hammer2_off_t)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