| 1 | #include <stdint.h> |
| 2 | #include <stddef.h> |
| 3 | #include <lib/uri.h> |
| 4 | #include <lib/misc.h> |
| 5 | #include <lib/part.h> |
| 6 | #include <lib/libc.h> |
| 7 | #include <lib/config.h> |
| 8 | #include <fs/file.h> |
| 9 | #include <mm/pmm.h> |
| 10 | #include <lib/print.h> |
| 11 | #include <pxe/tftp.h> |
| 12 | #include <menu.h> |
| 13 | #include <lib/getchar.h> |
| 14 | #include <crypt/blake2b.h> |
| 15 | #include <compress/gzip.h> |
| 16 | |
| 17 | // A URI takes the form of: resource(root):/path#hash |
| 18 | // The following function splits up a URI into its components. |
| 19 | // Note: Returns pointers into a static buffer. Callers must copy values |
| 20 | // if they need to persist across multiple uri_resolve() calls. |
| 21 | bool uri_resolve(char *uri, char **resource, char **root, char **path, char **hash) { |
| 22 | #define URI_BUF_SIZE 4096 |
| 23 | static char buf[URI_BUF_SIZE]; |
| 24 | |
| 25 | size_t length = strlen(uri) + 1; |
| 26 | if (length > URI_BUF_SIZE) { |
| 27 | panic(true, "uri_resolve: URI too long (max %u)", URI_BUF_SIZE - 1); |
| 28 | } |
| 29 | memcpy(buf, uri, length); |
| 30 | uri = buf; |
| 31 | |
| 32 | *resource = *root = *path = *hash = NULL; |
| 33 | |
| 34 | // Get resource |
| 35 | for (size_t i = 0; ; i++) { |
| 36 | if (strlen(uri + i) < 1) |
| 37 | return false; |
| 38 | |
| 39 | if (!strncmp(uri + i, "(", 1)) { |
| 40 | *resource = uri; |
| 41 | uri[i] = 0; |
| 42 | uri += i + 1; |
| 43 | break; |
| 44 | } |
| 45 | } |
| 46 | |
| 47 | // Get root |
| 48 | for (size_t i = 0; ; i++) { |
| 49 | if (strlen(uri + i) < 3) |
| 50 | return false; |
| 51 | |
| 52 | if (!strncmp(uri + i, "):/", 3)) { |
| 53 | *root = uri; |
| 54 | uri[i] = 0; |
| 55 | uri += i + 3; |
| 56 | break; |
| 57 | } |
| 58 | } |
| 59 | |
| 60 | // Get path |
| 61 | if (*uri == 0) |
| 62 | return false; |
| 63 | *path = uri; |
| 64 | |
| 65 | // Get hash |
| 66 | for (int i = (int)strlen(uri) - 1; i >= 0; i--) { |
| 67 | if (uri[i] != '#') { |
| 68 | continue; |
| 69 | } |
| 70 | |
| 71 | uri[i++] = 0; |
| 72 | |
| 73 | if (hash != NULL) { |
| 74 | *hash = uri + i; |
| 75 | } |
| 76 | |
| 77 | if (strlen(uri + i) != 128) { |
| 78 | panic(true, "Blake2b hash must be 128 characters long"); |
| 79 | return false; |
| 80 | } |
| 81 | |
| 82 | // Validate all 128 characters are valid hexadecimal |
| 83 | for (size_t j = 0; j < 128; j++) { |
| 84 | char c = uri[i + j]; |
| 85 | if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) { |
| 86 | panic(true, "Blake2b hash contains invalid character at position %d", (int)j); |
| 87 | return false; |
| 88 | } |
| 89 | } |
| 90 | |
| 91 | break; |
| 92 | } |
| 93 | |
| 94 | return true; |
| 95 | } |
| 96 | |
| 97 | static bool parse_bios_partition(char *loc, int *drive, int *partition) { |
| 98 | uint64_t val; |
| 99 | |
| 100 | for (size_t i = 0; ; i++) { |
| 101 | if (loc[i] == 0) |
| 102 | return false; |
| 103 | |
| 104 | if (loc[i] == ':') { |
| 105 | loc[i] = 0; |
| 106 | if (*loc == 0) { |
| 107 | panic(true, "Drive number cannot be omitted for hdd():/ and odd():/"); |
| 108 | } else { |
| 109 | val = strtoui(loc, NULL, 10); |
| 110 | if (val < 1 || val > 256) { |
| 111 | panic(true, "Drive number outside range 1-256"); |
| 112 | } |
| 113 | *drive = val; |
| 114 | } |
| 115 | loc += i + 1; |
| 116 | break; |
| 117 | } |
| 118 | } |
| 119 | |
| 120 | val = strtoui(loc, NULL, 10); |
| 121 | if (val > 256) { |
| 122 | panic(true, "Partition number outside range 0-256"); |
| 123 | } |
| 124 | *partition = val; |
| 125 | |
| 126 | return true; |
| 127 | } |
| 128 | |
| 129 | static struct file_handle *uri_hdd_dispatch(char *loc, char *path) { |
| 130 | int drive, partition; |
| 131 | |
| 132 | if (!parse_bios_partition(loc, &drive, &partition)) |
| 133 | return NULL; |
| 134 | |
| 135 | struct volume *volume = volume_get_by_coord(false, drive, partition); |
| 136 | |
| 137 | if (volume == NULL) |
| 138 | return NULL; |
| 139 | |
| 140 | return fopen(volume, path); |
| 141 | } |
| 142 | |
| 143 | static struct file_handle *uri_odd_dispatch(char *loc, char *path) { |
| 144 | int drive, partition; |
| 145 | |
| 146 | if (!parse_bios_partition(loc, &drive, &partition)) |
| 147 | return NULL; |
| 148 | |
| 149 | struct volume *volume = volume_get_by_coord(true, drive, partition); |
| 150 | |
| 151 | if (volume == NULL) |
| 152 | return NULL; |
| 153 | |
| 154 | return fopen(volume, path); |
| 155 | } |
| 156 | |
| 157 | static struct file_handle *uri_guid_dispatch(char *guid_str, char *path) { |
| 158 | struct guid guid; |
| 159 | if (!string_to_guid_be(&guid, guid_str)) |
| 160 | return NULL; |
| 161 | |
| 162 | struct volume *volume = volume_get_by_guid(&guid); |
| 163 | if (volume == NULL) { |
| 164 | if (!string_to_guid_mixed(&guid, guid_str)) |
| 165 | return NULL; |
| 166 | |
| 167 | volume = volume_get_by_guid(&guid); |
| 168 | if (volume == NULL) |
| 169 | return NULL; |
| 170 | } |
| 171 | |
| 172 | return fopen(volume, path); |
| 173 | } |
| 174 | |
| 175 | static struct file_handle *uri_fslabel_dispatch(char *fslabel, char *path) { |
| 176 | struct volume *volume = volume_get_by_fslabel(fslabel); |
| 177 | if (volume == NULL) { |
| 178 | return NULL; |
| 179 | } |
| 180 | |
| 181 | return fopen(volume, path); |
| 182 | } |
| 183 | |
| 184 | static struct file_handle *uri_tftp_dispatch(char *root, char *path) { |
| 185 | uint32_t ip; |
| 186 | if (!strcmp(root, "")) { |
| 187 | ip = 0; |
| 188 | } else { |
| 189 | if (inet_pton(root, &ip)) { |
| 190 | panic(true, "tftp: Invalid ipv4 address: %s", root); |
| 191 | } |
| 192 | } |
| 193 | |
| 194 | struct file_handle *ret; |
| 195 | if ((ret = tftp_open(boot_volume, root, path)) == NULL) { |
| 196 | return NULL; |
| 197 | } |
| 198 | |
| 199 | return ret; |
| 200 | } |
| 201 | |
| 202 | static struct file_handle *uri_boot_dispatch(char *s_part, char *path) { |
| 203 | if (boot_volume->pxe) |
| 204 | return uri_tftp_dispatch(s_part, path); |
| 205 | |
| 206 | int partition; |
| 207 | |
| 208 | if (s_part[0] != '\0') { |
| 209 | uint64_t val = strtoui(s_part, NULL, 10); |
| 210 | if (val > 256) { |
| 211 | panic(true, "Partition number outside range 0-256"); |
| 212 | } |
| 213 | partition = val; |
| 214 | } else { |
| 215 | partition = boot_volume->partition; |
| 216 | } |
| 217 | |
| 218 | struct volume *volume = volume_get_by_coord(boot_volume->is_optical, |
| 219 | boot_volume->index, partition); |
| 220 | if (volume == NULL) |
| 221 | return NULL; |
| 222 | |
| 223 | return fopen(volume, path); |
| 224 | } |
| 225 | |
| 226 | // Release a range of memory previously reserved with memmap_alloc_range. |
| 227 | // Works for both low and high addresses, unlike pmm_free which truncates |
| 228 | // on 32-bit builds. |
| 229 | static void uri_release_range(uint64_t addr, uint64_t count) { |
| 230 | count = ALIGN_UP(count, 4096, panic(false, "uri: alignment overflow")); |
| 231 | memmap_alloc_range(addr, count, MEMMAP_USABLE, 0, false, false, true); |
| 232 | } |
| 233 | |
| 234 | // Allocate `count` bytes via ext_mem_alloc_type_aligned_mode and return |
| 235 | // the physical address in *out_addr. When allow_high_mem is true on i386 |
| 236 | // and the allocator landed above 4 GiB, *out_low is set to NULL and the |
| 237 | // 64-bit address is stored in *out_addr. Otherwise *out_low points at the |
| 238 | // allocation and *out_addr == (uintptr_t)*out_low. |
| 239 | static void uri_alloc(uint64_t count, uint32_t type, bool allow_high_mem, |
| 240 | void **out_low, uint64_t *out_addr) { |
| 241 | void *ret = ext_mem_alloc_type_aligned_mode(count, type, 4096, allow_high_mem); |
| 242 | #if defined (__i386__) |
| 243 | if (allow_high_mem) { |
| 244 | uint64_t addr = *(uint64_t *)ret; |
| 245 | if (addr >= 0x100000000) { |
| 246 | *out_low = NULL; |
| 247 | *out_addr = addr; |
| 248 | return; |
| 249 | } |
| 250 | ret = (void *)(uintptr_t)addr; |
| 251 | } |
| 252 | #else |
| 253 | (void)allow_high_mem; |
| 254 | #endif |
| 255 | *out_low = ret; |
| 256 | *out_addr = (uintptr_t)ret; |
| 257 | } |
| 258 | |
| 259 | struct file_handle *uri_open(char *uri, uint32_t type, bool allow_high_mem |
| 260 | #if defined (__i386__) |
| 261 | , void (*memcpy_to_64)(uint64_t dst, void *src, size_t count) |
| 262 | , void (*memcpy_from_64)(void *dst, uint64_t src, size_t count) |
| 263 | #endif |
| 264 | ) { |
| 265 | #if defined (__i386__) |
| 266 | if (memcpy_to_64 == NULL || memcpy_from_64 == NULL) { |
| 267 | allow_high_mem = false; |
| 268 | } |
| 269 | #endif |
| 270 | |
| 271 | struct file_handle *raw; |
| 272 | |
| 273 | char *resource = NULL, *root = NULL, *path = NULL, *hash = NULL; |
| 274 | if (!uri_resolve(uri, &resource, &root, &path, &hash)) { |
| 275 | return NULL; |
| 276 | } |
| 277 | |
| 278 | if (resource == NULL) { |
| 279 | panic(true, "No resource specified for URI `%#`.", uri); |
| 280 | } |
| 281 | |
| 282 | bool gz_compressed = *resource == '$'; |
| 283 | if (gz_compressed) { |
| 284 | resource++; |
| 285 | } |
| 286 | |
| 287 | if (!strcmp(resource, "hdd")) { |
| 288 | raw = uri_hdd_dispatch(root, path); |
| 289 | } else if (!strcmp(resource, "odd")) { |
| 290 | raw = uri_odd_dispatch(root, path); |
| 291 | } else if (!strcmp(resource, "boot")) { |
| 292 | raw = uri_boot_dispatch(root, path); |
| 293 | } else if (!strcmp(resource, "guid")) { |
| 294 | raw = uri_guid_dispatch(root, path); |
| 295 | } else if (!strcmp(resource, "uuid")) { |
| 296 | raw = uri_guid_dispatch(root, path); |
| 297 | } else if (!strcmp(resource, "fslabel")) { |
| 298 | raw = uri_fslabel_dispatch(root, path); |
| 299 | } else if (!strcmp(resource, "tftp")) { |
| 300 | raw = uri_tftp_dispatch(root, path); |
| 301 | } else { |
| 302 | panic(true, "Resource `%s` not valid.", resource); |
| 303 | } |
| 304 | |
| 305 | if (raw == NULL) { |
| 306 | return NULL; |
| 307 | } |
| 308 | |
| 309 | if (secure_boot_active && hash == NULL) { |
| 310 | panic(true, "Secure Boot is active and URI `%#` has no associated hash!", uri); |
| 311 | } |
| 312 | |
| 313 | uint8_t hash_buf[BLAKE2B_OUT_BYTES]; |
| 314 | if (hash != NULL) { |
| 315 | for (size_t i = 0; i < sizeof(hash_buf); i++) { |
| 316 | hash_buf[i] = digit_to_int(hash[i * 2]) << 4 | digit_to_int(hash[i * 2 + 1]); |
| 317 | } |
| 318 | } |
| 319 | |
| 320 | // Snapshot metadata from raw before the close cascade frees its buffers. |
| 321 | struct volume *raw_vol = raw->vol; |
| 322 | size_t raw_path_len = raw->path_len; |
| 323 | char *raw_path_copy = NULL; |
| 324 | if (raw->path != NULL && raw_path_len > 0) { |
| 325 | raw_path_copy = ext_mem_alloc(raw_path_len); |
| 326 | memcpy(raw_path_copy, raw->path, raw_path_len); |
| 327 | } |
| 328 | #if defined (UEFI) |
| 329 | EFI_HANDLE raw_efi_part = raw->efi_part_handle; |
| 330 | #endif |
| 331 | bool raw_pxe = raw->pxe; |
| 332 | uint32_t raw_pxe_ip = raw->pxe_ip; |
| 333 | uint16_t raw_pxe_port = raw->pxe_port; |
| 334 | |
| 335 | // Build the filter chain: raw -> blake2b -> gzip. blake2b hashes on-disk |
| 336 | // (compressed) bytes. |
| 337 | struct file_handle *top = raw; |
| 338 | struct file_handle *hash_fh = NULL; |
| 339 | if (hash != NULL) { |
| 340 | hash_fh = blake2b_open(top); |
| 341 | top = hash_fh; |
| 342 | } |
| 343 | if (gz_compressed) { |
| 344 | top = gzip_open(top); |
| 345 | } |
| 346 | |
| 347 | // Drain the stream into a final allocation. |
| 348 | void *buf_low = NULL; |
| 349 | uint64_t buf_addr = 0; |
| 350 | uint64_t buf_cap = 0; |
| 351 | uint64_t buf_len = 0; |
| 352 | bool is_high = false; |
| 353 | |
| 354 | if (!gz_compressed) { |
| 355 | // Size is authoritative. Single up-front allocation, one copy. |
| 356 | uint64_t sz = top->size; |
| 357 | uri_alloc(sz, type, allow_high_mem, &buf_low, &buf_addr); |
| 358 | is_high = (buf_low == NULL); |
| 359 | |
| 360 | #if defined (__i386__) |
| 361 | if (is_high) { |
| 362 | // 1 MiB bounce loop, same as the old freadall_mode high path. |
| 363 | void *pool = ext_mem_alloc(0x100000); |
| 364 | for (uint64_t i = 0; i < sz; i += 0x100000) { |
| 365 | size_t chunk = sz - i < 0x100000 ? (size_t)(sz - i) : 0x100000; |
| 366 | uint64_t got = top->read(top, pool, i, chunk); |
| 367 | if (got != chunk) { |
| 368 | panic(false, "uri: short read from non-gzip stream"); |
| 369 | } |
| 370 | memcpy_to_64(buf_addr + i, pool, chunk); |
| 371 | } |
| 372 | pmm_free(pool, 0x100000); |
| 373 | } else |
| 374 | #endif |
| 375 | { |
| 376 | // In-place fill. |
| 377 | if (sz > 0) { |
| 378 | uint64_t got = top->read(top, buf_low, 0, sz); |
| 379 | if (got != sz) { |
| 380 | panic(false, "uri: short read from non-gzip stream"); |
| 381 | } |
| 382 | } |
| 383 | } |
| 384 | buf_len = sz; |
| 385 | } else { |
| 386 | // Size is unknown (UINT64_MAX from gzip_open). Stretchy vector. |
| 387 | // Initial capacity: 1 MiB, doubles on exhaustion. |
| 388 | buf_cap = 0x100000; |
| 389 | uri_alloc(buf_cap, type, allow_high_mem, &buf_low, &buf_addr); |
| 390 | is_high = (buf_low == NULL); |
| 391 | |
| 392 | #if defined (__i386__) |
| 393 | // High-path uses a 1 MiB bounce pool for both the read side and |
| 394 | // the grow-copy; reused across iterations. |
| 395 | void *pool = is_high ? ext_mem_alloc(0x100000) : NULL; |
| 396 | #endif |
| 397 | |
| 398 | for (;;) { |
| 399 | if (buf_len == buf_cap) { |
| 400 | // Grow: double up to 64 MiB, then add 64 MiB per step. |
| 401 | // Doubling past that wastes too much memory on large files. |
| 402 | uint64_t new_cap = buf_cap < 0x4000000 |
| 403 | ? buf_cap * 2 |
| 404 | : buf_cap + 0x4000000; |
| 405 | uint64_t delta = new_cap - buf_cap; |
| 406 | |
| 407 | // Try to extend in place by claiming the USABLE range |
| 408 | // immediately below the current buffer. The allocator is |
| 409 | // top-down, so above is already taken; below is the only |
| 410 | // direction that can be contiguous. On success we only |
| 411 | // pay delta extra bytes, not 2x peak. |
| 412 | if (buf_addr >= delta && |
| 413 | memmap_alloc_range(buf_addr - delta, delta, type, |
| 414 | MEMMAP_USABLE, false, false, false)) { |
| 415 | uint64_t base = buf_addr - delta; |
| 416 | // Move existing data down. dest < src, forward-safe. |
| 417 | #if defined (__i386__) |
| 418 | if (is_high) { |
| 419 | for (uint64_t off = 0; off < buf_len; off += 0x100000) { |
| 420 | size_t chunk = buf_len - off < 0x100000 ? (size_t)(buf_len - off) : 0x100000; |
| 421 | memcpy_from_64(pool, buf_addr + off, chunk); |
| 422 | memcpy_to_64(base + off, pool, chunk); |
| 423 | } |
| 424 | } else |
| 425 | #endif |
| 426 | { |
| 427 | memmove((void *)(uintptr_t)base, buf_low, buf_len); |
| 428 | buf_low = (void *)(uintptr_t)base; |
| 429 | } |
| 430 | buf_addr = base; |
| 431 | buf_cap = new_cap; |
| 432 | goto grew; |
| 433 | } |
| 434 | |
| 435 | void *new_low = NULL; |
| 436 | uint64_t new_addr = 0; |
| 437 | uri_alloc(new_cap, type, allow_high_mem, &new_low, &new_addr); |
| 438 | bool new_is_high = (new_low == NULL); |
| 439 | |
| 440 | #if defined (__i386__) |
| 441 | if (is_high && new_is_high) { |
| 442 | // 64-to-64: bounce via low pool in 1 MiB strides. |
| 443 | for (uint64_t off = 0; off < buf_len; off += 0x100000) { |
| 444 | size_t chunk = buf_len - off < 0x100000 ? (size_t)(buf_len - off) : 0x100000; |
| 445 | memcpy_from_64(pool, buf_addr + off, chunk); |
| 446 | memcpy_to_64(new_addr + off, pool, chunk); |
| 447 | } |
| 448 | } else if (is_high && !new_is_high) { |
| 449 | // Shouldn't happen: once we landed high we ask for high. |
| 450 | // Keep a defensive path: bounce via pool, then memcpy. |
| 451 | for (uint64_t off = 0; off < buf_len; off += 0x100000) { |
| 452 | size_t chunk = buf_len - off < 0x100000 ? (size_t)(buf_len - off) : 0x100000; |
| 453 | memcpy_from_64(pool, buf_addr + off, chunk); |
| 454 | memcpy((uint8_t *)new_low + off, pool, chunk); |
| 455 | } |
| 456 | } else if (!is_high && new_is_high) { |
| 457 | for (uint64_t off = 0; off < buf_len; off += 0x100000) { |
| 458 | size_t chunk = buf_len - off < 0x100000 ? (size_t)(buf_len - off) : 0x100000; |
| 459 | memcpy_to_64(new_addr + off, (uint8_t *)buf_low + off, chunk); |
| 460 | } |
| 461 | } else |
| 462 | #endif |
| 463 | { |
| 464 | (void)new_is_high; /* Silence unused warning on non-i386. */ |
| 465 | memcpy(new_low, buf_low, buf_len); |
| 466 | } |
| 467 | |
| 468 | // Release the old allocation. |
| 469 | uri_release_range(buf_addr, buf_cap); |
| 470 | |
| 471 | buf_low = new_low; |
| 472 | buf_addr = new_addr; |
| 473 | buf_cap = new_cap; |
| 474 | #if defined (__i386__) |
| 475 | if (is_high != new_is_high && new_is_high && pool == NULL) { |
| 476 | pool = ext_mem_alloc(0x100000); |
| 477 | } |
| 478 | is_high = new_is_high; |
| 479 | #endif |
| 480 | grew:; |
| 481 | } |
| 482 | |
| 483 | uint64_t want = buf_cap - buf_len; |
| 484 | if (want > 65536) want = 65536; |
| 485 | |
| 486 | uint64_t got; |
| 487 | #if defined (__i386__) |
| 488 | if (is_high) { |
| 489 | got = top->read(top, pool, buf_len, want); |
| 490 | if (got > 0) memcpy_to_64(buf_addr + buf_len, pool, got); |
| 491 | } else |
| 492 | #endif |
| 493 | { |
| 494 | got = top->read(top, (uint8_t *)buf_low + buf_len, buf_len, want); |
| 495 | } |
| 496 | if (got == 0) break; |
| 497 | buf_len += got; |
| 498 | } |
| 499 | |
| 500 | // Release the page-aligned tail past the actual data so we don't |
| 501 | // hand the OS up to 64 MiB of slack typed as `type`. Keep at least |
| 502 | // one page so the returned handle has a valid address. |
| 503 | uint64_t kept = ALIGN_UP(buf_len, 4096, panic(true, "uri: alignment overflow")); |
| 504 | if (kept == 0) kept = 4096; |
| 505 | if (kept < buf_cap) { |
| 506 | uri_release_range(buf_addr + kept, buf_cap - kept); |
| 507 | buf_cap = kept; |
| 508 | } |
| 509 | |
| 510 | #if defined (__i386__) |
| 511 | if (pool != NULL) pmm_free(pool, 0x100000); |
| 512 | #endif |
| 513 | } |
| 514 | |
| 515 | // Finalize hash check now that all compressed bytes have flowed through |
| 516 | // the filter. |
| 517 | if (hash_fh != NULL) { |
| 518 | if (!blake2b_check_hash(hash_fh, hash_buf)) { |
| 519 | if (hash_mismatch_panic) { |
| 520 | panic(true, "Blake2b hash for URI `%#` does not match!", uri); |
| 521 | } else { |
| 522 | print("WARNING: Blake2b hash for URI `%#` does not match!\n" |
| 523 | " Press Y to continue, press any other key to return to menu...", uri); |
| 524 | |
| 525 | char ch = getchar(); |
| 526 | if (ch != 'Y' && ch != 'y') { |
| 527 | menu(false); |
| 528 | } |
| 529 | print("\n"); |
| 530 | } |
| 531 | } |
| 532 | } |
| 533 | |
| 534 | // Close the filter chain. fclose cascades. |
| 535 | fclose(top); |
| 536 | |
| 537 | // Build the returned memfile. Fresh allocation so we never mutate any |
| 538 | // closed filter handle's state. |
| 539 | struct file_handle *out = ext_mem_alloc(sizeof(struct file_handle)); |
| 540 | out->is_memfile = true; |
| 541 | out->readall = true; |
| 542 | out->is_high_mem = is_high; |
| 543 | out->fd = is_high ? NULL : buf_low; |
| 544 | out->load_addr_64 = buf_addr; |
| 545 | out->size = buf_len; |
| 546 | out->vol = raw_vol; |
| 547 | out->path = raw_path_copy; |
| 548 | out->path_len = raw_path_copy != NULL ? raw_path_len : 0; |
| 549 | #if defined (UEFI) |
| 550 | out->efi_part_handle = raw_efi_part; |
| 551 | #endif |
| 552 | out->pxe = raw_pxe; |
| 553 | out->pxe_ip = raw_pxe_ip; |
| 554 | out->pxe_port = raw_pxe_port; |
| 555 | |
| 556 | return out; |
| 557 | } |