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