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