hammer2.c revision 1.7 1 /* $NetBSD: hammer2.c,v 1.7 2021/01/10 12:38:40 tkusumi Exp $ */
2
3 /*-
4 * Copyright (c) 2017-2019 The DragonFly Project
5 * Copyright (c) 2017-2019 Tomohiro Kusumi <tkusumi (at) 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.7 2021/01/10 12:38:40 tkusumi 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 = 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