1 /* $NetBSD: hammer2.c,v 1.8 2021/01/10 13:44:57 martin 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.8 2021/01/10 13:44:57 martin 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 = (size_t)((hammer2_off_t)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