1 /* $OpenBSD: gpt.c,v 1.11 2016/03/28 16:55:09 mestre Exp $ */ 2 /* 3 * Copyright (c) 2015 Markus Muller <mmu@grummel.net> 4 * Copyright (c) 2015 Kenneth R Westerback <krw@openbsd.org> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 #include <sys/param.h> /* DEV_BSIZE */ 20 #include <sys/disklabel.h> 21 #include <sys/dkio.h> 22 #include <sys/ioctl.h> 23 24 #include <errno.h> 25 #include <stdio.h> 26 #include <stdlib.h> 27 #include <string.h> 28 #include <unistd.h> 29 #include <uuid.h> 30 31 #include "disk.h" 32 #include "misc.h" 33 #include "part.h" 34 #include "gpt.h" 35 36 #ifdef DEBUG 37 #define DPRINTF(x...) printf(x) 38 #else 39 #define DPRINTF(x...) 40 #endif 41 42 struct gpt_header gh; 43 struct gpt_partition gp[NGPTPARTITIONS]; 44 45 int 46 GPT_get_header(off_t where) 47 { 48 char *secbuf; 49 uint64_t partlastlba; 50 int partspersec; 51 uint32_t orig_gh_csum, new_gh_csum; 52 53 secbuf = DISK_readsector(where); 54 if (secbuf == 0) 55 return (1); 56 57 memcpy(&gh, secbuf, sizeof(struct gpt_header)); 58 free(secbuf); 59 60 if (letoh64(gh.gh_sig) != GPTSIGNATURE) { 61 DPRINTF("gpt signature: expected 0x%llx, got 0x%llx\n", 62 GPTSIGNATURE, letoh64(gh.gh_sig)); 63 return (1); 64 } 65 66 if (letoh32(gh.gh_rev) != GPTREVISION) { 67 DPRINTF("gpt revision: expected 0x%x, got 0x%x\n", 68 GPTREVISION, letoh32(gh.gh_rev)); 69 return (1); 70 } 71 72 if (letoh64(gh.gh_lba_self) != where) { 73 DPRINTF("gpt self lba: expected %lld, got %llu\n", 74 (long long)where, letoh64(gh.gh_lba_self)); 75 return (1); 76 } 77 78 if (letoh32(gh.gh_size) != GPTMINHDRSIZE) { 79 DPRINTF("gpt header size: expected %u, got %u\n", 80 GPTMINHDRSIZE, letoh32(gh.gh_size)); 81 return (1); 82 } 83 84 if (letoh32(gh.gh_part_size) != GPTMINPARTSIZE) { 85 DPRINTF("gpt partition size: expected %u, got %u\n", 86 GPTMINPARTSIZE, letoh32(gh.gh_part_size)); 87 return (1); 88 } 89 90 if (letoh32(gh.gh_part_num) > NGPTPARTITIONS) { 91 DPRINTF("gpt partition count: expected <= %u, got %u\n", 92 NGPTPARTITIONS, letoh32(gh.gh_part_num)); 93 return (1); 94 } 95 96 orig_gh_csum = gh.gh_csum; 97 gh.gh_csum = 0; 98 new_gh_csum = crc32((unsigned char *)&gh, letoh32(gh.gh_size)); 99 gh.gh_csum = orig_gh_csum; 100 if (letoh32(orig_gh_csum) != new_gh_csum) { 101 DPRINTF("gpt header checksum: expected 0x%x, got 0x%x\n", 102 orig_gh_csum, new_gh_csum); 103 return (1); 104 } 105 106 if (letoh64(gh.gh_lba_end) >= DL_GETDSIZE(&dl)) { 107 DPRINTF("gpt last usable LBA: expected < %lld, got %llu\n", 108 DL_GETDSIZE(&dl), letoh64(gh.gh_lba_end)); 109 return (1); 110 } 111 112 if (letoh64(gh.gh_lba_start) >= letoh64(gh.gh_lba_end)) { 113 DPRINTF("gpt first usable LBA: expected < %llu, got %llu\n", 114 letoh64(gh.gh_lba_start), letoh64(gh.gh_lba_start)); 115 return (1); 116 } 117 118 if (letoh64(gh.gh_part_lba) <= letoh64(gh.gh_lba_end) && 119 letoh64(gh.gh_part_lba) >= letoh64(gh.gh_lba_start)) { 120 DPRINTF("gpt partition table start lba: expected < %llu or " 121 "> %llu, got %llu\n", letoh64(gh.gh_lba_start), 122 letoh64(gh.gh_lba_end), letoh64(gh.gh_part_lba)); 123 return (1); 124 } 125 126 partspersec = dl.d_secsize / letoh32(gh.gh_part_size); 127 partlastlba = letoh64(gh.gh_part_lba) + 128 ((letoh32(gh.gh_part_num) + partspersec - 1) / partspersec) - 1; 129 if (partlastlba <= letoh64(gh.gh_lba_end) && 130 partlastlba >= letoh64(gh.gh_lba_start)) { 131 DPRINTF("gpt partition table last LBA: expected < %llu or " 132 "> %llu, got %llu\n", letoh64(gh.gh_lba_start), 133 letoh64(gh.gh_lba_end), partlastlba); 134 return (1); 135 } 136 137 /* 138 * Other possible paranoia checks: 139 * 1) partition table starts before primary gpt lba. 140 * 2) partition table extends into lowest partition. 141 * 3) alt partition table starts before gh_lba_end. 142 */ 143 return (0); 144 } 145 146 int 147 GPT_get_partition_table(off_t where) 148 { 149 ssize_t len; 150 off_t off; 151 int secs; 152 uint32_t checksum, partspersec; 153 154 DPRINTF("gpt partition table being read from LBA %llu\n", 155 letoh64(gh.gh_part_lba)); 156 157 partspersec = dl.d_secsize / letoh32(gh.gh_part_size); 158 if (partspersec * letoh32(gh.gh_part_size) != dl.d_secsize) { 159 DPRINTF("gpt partition table entry invalid size. %u\n", 160 letoh32(gh.gh_part_size)); 161 return (1); 162 } 163 secs = (letoh32(gh.gh_part_num) + partspersec - 1) / partspersec; 164 165 memset(&gp, 0, sizeof(gp)); 166 167 where *= dl.d_secsize; 168 off = lseek(disk.fd, where, SEEK_SET); 169 if (off == -1) { 170 DPRINTF("seek to gpt partition table @ sector %llu failed\n", 171 (unsigned long long)where / dl.d_secsize); 172 return (1); 173 } 174 len = read(disk.fd, &gp, secs * dl.d_secsize); 175 if (len == -1 || len != secs * dl.d_secsize) { 176 DPRINTF("gpt partition table read failed.\n"); 177 return (1); 178 } 179 180 checksum = crc32((unsigned char *)&gp, letoh32(gh.gh_part_num) * 181 letoh32(gh.gh_part_size)); 182 if (checksum != letoh32(gh.gh_part_csum)) { 183 DPRINTF("gpt partition table checksum: expected %x, got %x\n", 184 checksum, letoh32(gh.gh_part_csum)); 185 return (1); 186 } 187 188 return (0); 189 } 190 191 void 192 GPT_get_gpt(int which) 193 { 194 int privalid, altvalid; 195 196 /* 197 * primary header && primary partition table || 198 * alt header && alt partition table 199 */ 200 privalid = GPT_get_header(GPTSECTOR); 201 if (privalid == 0) 202 privalid = GPT_get_partition_table(gh.gh_part_lba); 203 if (which == 1 || (which == 0 && privalid == 0)) 204 return; 205 206 /* No valid GPT found. Zap any artifacts. */ 207 memset(&gh, 0, sizeof(gh)); 208 memset(&gp, 0, sizeof(gp)); 209 210 altvalid = GPT_get_header(DL_GETDSIZE(&dl) - 1); 211 if (altvalid == 0) 212 altvalid = GPT_get_partition_table(gh.gh_part_lba); 213 if (which == 2 || altvalid == 0) 214 return; 215 216 /* No valid GPT found. Zap any artifacts. */ 217 memset(&gh, 0, sizeof(gh)); 218 memset(&gp, 0, sizeof(gp)); 219 } 220 221 void 222 GPT_print(char *units, int verbosity) 223 { 224 const int secsize = unit_types[SECTORS].conversion; 225 struct uuid guid; 226 char *guidstr = NULL; 227 double size; 228 int i, u, status; 229 230 u = unit_lookup(units); 231 size = ((double)DL_GETDSIZE(&dl) * secsize) / unit_types[u].conversion; 232 printf("Disk: %s Usable LBA: %llu to %llu [%.0f ", 233 disk.name, letoh64(gh.gh_lba_start), letoh64(gh.gh_lba_end), size); 234 235 if (u == SECTORS && secsize != DEV_BSIZE) 236 printf("%d-byte ", secsize); 237 printf("%s]\n", unit_types[u].lname); 238 239 if (verbosity) { 240 printf("GUID: "); 241 uuid_dec_le(&gh.gh_guid, &guid); 242 uuid_to_string(&guid, &guidstr, &status); 243 if (status == uuid_s_ok) 244 printf("%s\n", guidstr); 245 else 246 printf("<invalid header GUID>\n"); 247 free(guidstr); 248 } 249 250 GPT_print_parthdr(verbosity); 251 for (i = 0; i < letoh32(gh.gh_part_num); i++) { 252 if (uuid_is_nil(&gp[i].gp_type, NULL)) 253 continue; 254 GPT_print_part(i, units, verbosity); 255 } 256 257 } 258 259 void 260 GPT_print_parthdr(int verbosity) 261 { 262 printf(" #: type " 263 " [ start: size ]\n"); 264 if (verbosity) 265 printf(" guid name\n"); 266 printf("--------------------------------------------------------" 267 "----------------\n"); 268 } 269 270 void 271 GPT_print_part(int n, char *units, int verbosity) 272 { 273 struct uuid guid; 274 const int secsize = unit_types[SECTORS].conversion; 275 struct gpt_partition *partn = &gp[n]; 276 char *guidstr = NULL; 277 double size; 278 int u, status; 279 280 uuid_dec_le(&partn->gp_type, &guid); 281 u = unit_lookup(units); 282 size = letoh64(partn->gp_lba_end) - letoh64(partn->gp_lba_start) + 1; 283 size = (size * secsize) / unit_types[u].conversion; 284 printf("%c%3d: %-36s [%12lld: %12.0f%s]\n", 285 (letoh64(partn->gp_attrs) & GPTDOSACTIVE)?'*':' ', n, 286 PRT_uuid_to_typename(&guid), letoh64(partn->gp_lba_start), 287 size, unit_types[u].abbr); 288 289 if (verbosity) { 290 uuid_dec_le(&partn->gp_guid, &guid); 291 uuid_to_string(&guid, &guidstr, &status); 292 if (status != uuid_s_ok) 293 printf(" <invalid partition guid> "); 294 else 295 printf(" %-36s ", guidstr); 296 printf("%-36s\n", utf16le_to_string(partn->gp_name)); 297 free(guidstr); 298 } 299 } 300 301 int 302 GPT_init(void) 303 { 304 extern u_int32_t b_arg; 305 const int secsize = unit_types[SECTORS].conversion; 306 struct uuid guid; 307 int needed; 308 uint32_t status; 309 const uint8_t gpt_uuid_efi_system[] = GPT_UUID_EFI_SYSTEM; 310 const uint8_t gpt_uuid_openbsd[] = GPT_UUID_OPENBSD; 311 312 memset(&gh, 0, sizeof(gh)); 313 memset(&gp, 0, sizeof(gp)); 314 315 needed = sizeof(gp) / secsize + 2; 316 /* Start on 64 sector boundary */ 317 if (needed % 64) 318 needed += (64 - (needed % 64)); 319 320 gh.gh_sig = htole64(GPTSIGNATURE); 321 gh.gh_rev = htole32(GPTREVISION); 322 gh.gh_size = htole32(GPTMINHDRSIZE); 323 gh.gh_csum = 0; 324 gh.gh_rsvd = 0; 325 gh.gh_lba_self = htole64(1); 326 gh.gh_lba_alt = htole64(DL_GETDSIZE(&dl) - 1); 327 gh.gh_lba_start = htole64(needed); 328 gh.gh_lba_end = htole64(DL_GETDSIZE(&dl) - needed); 329 gh.gh_part_lba = htole64(2); 330 gh.gh_part_num = htole32(NGPTPARTITIONS); 331 gh.gh_part_size = htole32(GPTMINPARTSIZE); 332 333 uuid_create(&guid, &status); 334 if (status != uuid_s_ok) 335 return (1); 336 uuid_enc_le(&gh.gh_guid, &guid); 337 338 #if defined(__i386__) || defined(__amd64__) 339 if (b_arg > 0) { 340 /* Add an EFI system partition on i386/amd64. */ 341 uuid_dec_be(gpt_uuid_efi_system, &guid); 342 uuid_enc_le(&gp[1].gp_type, &guid); 343 uuid_create(&guid, &status); 344 if (status != uuid_s_ok) 345 return (1); 346 uuid_enc_le(&gp[1].gp_guid, &guid); 347 gp[1].gp_lba_start = gh.gh_lba_start; 348 gp[1].gp_lba_end = htole64(letoh64(gh.gh_lba_start)+b_arg - 1); 349 memcpy(gp[1].gp_name, string_to_utf16le("EFI System Area"), 350 sizeof(gp[1].gp_name)); 351 } 352 #endif 353 uuid_dec_be(gpt_uuid_openbsd, &guid); 354 uuid_enc_le(&gp[3].gp_type, &guid); 355 uuid_create(&guid, &status); 356 if (status != uuid_s_ok) 357 return (1); 358 uuid_enc_le(&gp[3].gp_guid, &guid); 359 gp[3].gp_lba_start = gh.gh_lba_start; 360 #if defined(__i386__) || defined(__amd64__) 361 if (b_arg > 0) { 362 gp[3].gp_lba_start = htole64(letoh64(gp[3].gp_lba_start) + 363 b_arg); 364 if (letoh64(gp[3].gp_lba_start) % 64) 365 gp[3].gp_lba_start = 366 htole64(letoh64(gp[3].gp_lba_start) + 367 (64 - letoh64(gp[3].gp_lba_start) % 64)); 368 } 369 #endif 370 gp[3].gp_lba_end = gh.gh_lba_end; 371 memcpy(gp[3].gp_name, string_to_utf16le("OpenBSD Area"), 372 sizeof(gp[3].gp_name)); 373 374 gh.gh_part_csum = crc32((unsigned char *)&gp, sizeof(gp)); 375 gh.gh_csum = crc32((unsigned char *)&gh, sizeof(gh)); 376 377 return 0; 378 } 379 380 int 381 GPT_write(void) 382 { 383 char *secbuf; 384 const int secsize = unit_types[SECTORS].conversion; 385 ssize_t len; 386 off_t off; 387 u_int64_t altgh, altgp; 388 389 /* Assume we always write full-size partition table. XXX */ 390 altgh = DL_GETDSIZE(&dl) - 1; 391 altgp = DL_GETDSIZE(&dl) - 1 - (sizeof(gp) / secsize); 392 393 /* 394 * Place the new GPT header at the start of sectors 1 and 395 * DL_GETDSIZE(lp)-1 and write the sectors back. 396 */ 397 gh.gh_lba_self = htole64(1); 398 gh.gh_lba_alt = htole64(altgh); 399 gh.gh_part_lba = htole64(2); 400 gh.gh_part_csum = crc32((unsigned char *)&gp, sizeof(gp)); 401 gh.gh_csum = 0; 402 gh.gh_csum = crc32((unsigned char *)&gh, letoh32(gh.gh_size)); 403 404 secbuf = DISK_readsector(1); 405 if (secbuf == NULL) 406 return (-1); 407 408 memcpy(secbuf, &gh, sizeof(gh)); 409 DISK_writesector(secbuf, 1); 410 free(secbuf); 411 412 gh.gh_lba_self = htole64(altgh); 413 gh.gh_lba_alt = htole64(1); 414 gh.gh_part_lba = htole64(altgp); 415 gh.gh_csum = 0; 416 gh.gh_csum = crc32((unsigned char *)&gh, letoh32(gh.gh_size)); 417 418 secbuf = DISK_readsector(altgh); 419 if (secbuf == NULL) 420 return (-1); 421 422 memcpy(secbuf, &gh, sizeof(gh)); 423 DISK_writesector(secbuf, altgh); 424 free(secbuf); 425 426 /* 427 * Write partition table after primary header 428 * (i.e. at sector 1) and before alt header 429 * (i.e. ending in sector before alt header. 430 * XXX ALWAYS NGPTPARTITIONS! 431 * XXX ASSUME gp is multiple of sector size! 432 */ 433 off = lseek(disk.fd, secsize * 2, SEEK_SET); 434 if (off == secsize * 2) 435 len = write(disk.fd, &gp, sizeof(gp)); 436 else 437 len = -1; 438 if (len == -1 || len != sizeof(gp)) { 439 errno = EIO; 440 return (-1); 441 } 442 443 off = lseek(disk.fd, secsize * altgp, SEEK_SET); 444 if (off == secsize * altgp) 445 len = write(disk.fd, &gp, sizeof(gp)); 446 else 447 len = -1; 448 449 if (len == -1 || len != sizeof(gp)) { 450 errno = EIO; 451 return (-1); 452 } 453 454 /* Refresh in-kernel disklabel from the updated disk information. */ 455 ioctl(disk.fd, DIOCRLDINFO, 0); 456 457 return (0); 458 } 459