xref: /dragonfly/usr.sbin/fstyp/hammer2.c (revision 5ca0a96d)
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 				assert(0);
220 		}
221 		break;
222 	case HAMMER2_BREF_TYPE_INDIRECT:
223 		bscan = &media->npdata[0];
224 		bcount = bytes / sizeof(hammer2_blockref_t);
225 		break;
226 	default:
227 		bscan = NULL;
228 		bcount = 0;
229 		break;
230 	}
231 
232 	for (i = 0; i < bcount; ++i) {
233 		if (bscan[i].type != HAMMER2_BREF_TYPE_EMPTY) {
234 			if (find_pfs(fp, &bscan[i], pfs, res) == -1) {
235 				free(media);
236 				return (-1);
237 			}
238 		}
239 	}
240 	free(media);
241 
242 	return (0);
243 }
244 
245 static char*
246 extract_device_name(const char *devpath)
247 {
248 	char *p, *head;
249 
250 	if (!devpath)
251 		return (NULL);
252 
253 	p = strdup(devpath);
254 	head = p;
255 
256 	p = strchr(p, '@');
257 	if (p)
258 		*p = 0;
259 
260 	p = strrchr(head, '/');
261 	if (p) {
262 		p++;
263 		if (*p == 0) {
264 			free(head);
265 			return (NULL);
266 		}
267 		p = strdup(p);
268 		free(head);
269 		return (p);
270 	}
271 
272 	return (head);
273 }
274 
275 static int
276 read_label(FILE *fp, char *label, size_t size, const char *devpath)
277 {
278 	hammer2_blockref_t broot, best, *bref;
279 	hammer2_media_data_t *vols[HAMMER2_NUM_VOLHDRS], *media;
280 	size_t bytes;
281 	bool res = false;
282 	int i, best_i, error = 1;
283 	const char *pfs;
284 	char *devname;
285 
286 	best_i = -1;
287 	memset(vols, 0, sizeof(vols));
288 	memset(&best, 0, sizeof(best));
289 
290 	for (i = 0; i < HAMMER2_NUM_VOLHDRS; i++) {
291 		if (i * HAMMER2_ZONE_BYTES64 >= get_file_size(fp))
292 			break;
293 		memset(&broot, 0, sizeof(broot));
294 		broot.type = HAMMER2_BREF_TYPE_VOLUME;
295 		broot.data_off = (i * HAMMER2_ZONE_BYTES64) | HAMMER2_PBUFRADIX;
296 		vols[i] = (void*)read_voldata(fp, i);
297 		if (vols[i] == NULL) {
298 			warnx("hammer2: failed to read volume data");
299 			goto fail;
300 		}
301 		broot.mirror_tid = vols[i]->voldata.mirror_tid;
302 		if (best_i < 0 || best.mirror_tid < broot.mirror_tid) {
303 			best_i = i;
304 			best = broot;
305 		}
306 	}
307 
308 	bref = &vols[best_i]->voldata.sroot_blockset.blockref[0];
309 	if (bref->type != HAMMER2_BREF_TYPE_INODE) {
310 		/* Don't print error as devpath could be non-root volume. */
311 		goto fail;
312 	}
313 
314 	media = read_media(fp, bref, &bytes);
315 	if (media == NULL) {
316 		goto fail;
317 	}
318 
319 	/*
320 	 * fstyp_function in DragonFly takes an additional devpath argument
321 	 * which doesn't exist in FreeBSD and NetBSD.
322 	 */
323 #ifdef HAS_DEVPATH
324 	pfs = strchr(devpath, '@');
325 	if (!pfs) {
326 		assert(strlen(devpath));
327 		switch (devpath[strlen(devpath) - 1]) {
328 		case 'a':
329 			pfs = "BOOT";
330 			break;
331 		case 'd':
332 			pfs = "ROOT";
333 			break;
334 		default:
335 			pfs = "DATA";
336 			break;
337 		}
338 	} else
339 		pfs++;
340 
341 	if (strlen(pfs) > HAMMER2_INODE_MAXNAME) {
342 		goto fail;
343 	}
344 	devname = extract_device_name(devpath);
345 #else
346 	pfs = "";
347 	devname = extract_device_name(NULL);
348 	assert(!devname);
349 #endif
350 
351 	/* Add device name to help support multiple autofs -media mounts. */
352 	if (find_pfs(fp, bref, pfs, &res) == 0 && res) {
353 		if (devname)
354 			snprintf(label, size, "%s_%s", pfs, devname);
355 		else
356 			strlcpy(label, pfs, size);
357 	} else {
358 		memset(label, 0, size);
359 		memcpy(label, media->ipdata.filename,
360 		    sizeof(media->ipdata.filename));
361 		if (devname) {
362 			strlcat(label, "_", size);
363 			strlcat(label, devname, size);
364 		}
365 	}
366 	if (devname)
367 		free(devname);
368 	free(media);
369 	error = 0;
370 fail:
371 	for (i = 0; i < HAMMER2_NUM_VOLHDRS; i++)
372 		free(vols[i]);
373 
374 	return (error);
375 }
376 
377 int
378 fstyp_hammer2(FILE *fp, char *label, size_t size, const char *devpath)
379 {
380 	hammer2_volume_data_t *voldata = read_voldata(fp, 0);
381 	int error = 1;
382 
383 	if (voldata->volu_id != HAMMER2_ROOT_VOLUME)
384 		goto fail;
385 	if (voldata->nvolumes != 0)
386 		goto fail;
387 	if (test_voldata(fp))
388 		goto fail;
389 
390 	error = read_label(fp, label, size, devpath);
391 fail:
392 	free(voldata);
393 	return (error);
394 }
395 
396 static int
397 __fsvtyp_hammer2(const char *blkdevs, char *label, size_t size, int partial)
398 {
399 	hammer2_volume_data_t *voldata = NULL;
400 	FILE *fp = NULL;
401 	char *dup = NULL, *target_label = NULL, *p, *volpath, *rootvolpath;
402 	char x[HAMMER2_MAX_VOLUMES];
403 	int i, volid, error = 1;
404 
405 	if (!blkdevs)
406 		goto fail;
407 
408 	memset(x, 0, sizeof(x));
409 	p = dup = strdup(blkdevs);
410 	if ((p = strchr(p, '@')) != NULL) {
411 		*p++ = '\0';
412 		target_label = p;
413 	}
414 	p = dup;
415 
416 	volpath = NULL;
417 	rootvolpath = NULL;
418 	volid = -1;
419 	while (p) {
420 		volpath = p;
421 		if ((p = strchr(p, ':')) != NULL)
422 			*p++ = '\0';
423 		if ((fp = fopen(volpath, "r")) == NULL) {
424 			warnx("hammer2: failed to open %s", volpath);
425 			goto fail;
426 		}
427 		if (test_voldata(fp))
428 			break;
429 		voldata = read_voldata(fp, 0);
430 		fclose(fp);
431 		if (voldata == NULL) {
432 			warnx("hammer2: failed to read volume data");
433 			goto fail;
434 		}
435 		volid = voldata->volu_id;
436 		free(voldata);
437 		voldata = NULL;
438 		assert(volid >= 0);
439 		assert(volid < HAMMER2_MAX_VOLUMES);
440 		x[volid]++;
441 		if (volid == HAMMER2_ROOT_VOLUME)
442 			rootvolpath = volpath;
443 	}
444 
445 	/* If no rootvolpath, proceed only if partial mode with volpath. */
446 	if (rootvolpath)
447 		volpath = rootvolpath;
448 	else if (!partial || !volpath)
449 		goto fail;
450 	if ((fp = fopen(volpath, "r")) == NULL) {
451 		warnx("hammer2: failed to open %s", volpath);
452 		goto fail;
453 	}
454 	voldata = read_voldata(fp, 0);
455 	if (voldata == NULL) {
456 		warnx("hammer2: failed to read volume data");
457 		goto fail;
458 	}
459 
460 	if (volid == -1)
461 		goto fail;
462 	if (partial)
463 		goto success;
464 
465 	for (i = 0; i < HAMMER2_MAX_VOLUMES; i++)
466 		if (x[i] > 1)
467 			goto fail;
468 	for (i = 0; i < HAMMER2_MAX_VOLUMES; i++)
469 		if (x[i] == 0)
470 			break;
471 	if (voldata->nvolumes != i)
472 		goto fail;
473 	for (; i < HAMMER2_MAX_VOLUMES; i++)
474 		if (x[i] != 0)
475 			goto fail;
476 success:
477 	/* Reconstruct @label format path using only root volume. */
478 	if (target_label) {
479 		int siz = strlen(volpath) + strlen(target_label) + 2;
480 		p = calloc(1, siz);
481 		snprintf(p, siz, "%s@%s", volpath, target_label);
482 		volpath = p;
483 	}
484 	error = read_label(fp, label, size, volpath);
485 	if (target_label)
486 		free(p);
487 	/* If in partial mode, read label but ignore error. */
488 	if (partial)
489 		error = 0;
490 fail:
491 	if (fp)
492 		fclose(fp);
493 	free(voldata);
494 	free(dup);
495 	return (error);
496 }
497 
498 int
499 fsvtyp_hammer2(const char *blkdevs, char *label, size_t size)
500 {
501 	return (__fsvtyp_hammer2(blkdevs, label, size, 0));
502 }
503 
504 int
505 fsvtyp_hammer2_partial(const char *blkdevs, char *label, size_t size)
506 {
507 	return (__fsvtyp_hammer2(blkdevs, label, size, 1));
508 }
509