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
47static ssize_t
48get_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
71static hammer2_volume_data_t *
72read_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
84static int
85test_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
138static hammer2_media_data_t*
139read_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
197static int
198find_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
260static char*
261extract_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
290static int
291read_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;
385fail:
386	for (i = 0; i < HAMMER2_NUM_VOLHDRS; i++)
387		free(vols[i]);
388
389	return error;
390}
391
392int
393fstyp_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);
408fail:
409	free(voldata);
410	return error;
411}
412
413static int
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;
493success:
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;
507fail:
508	if (fp)
509		fclose(fp);
510	free(voldata);
511	free(dup);
512	return error;
513}
514
515int
516fsvtyp_hammer2(const char *blkdevs, char *label, size_t size)
517{
518	return __fsvtyp_hammer2(blkdevs, label, size, 0);
519}
520
521int
522fsvtyp_hammer2_partial(const char *blkdevs, char *label, size_t size)
523{
524	return __fsvtyp_hammer2(blkdevs, label, size, 1);
525}
526