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