1 /*
2 * Copyright (c) 2026 Netflix, Inc. Written by Warner Losh
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 *
6 * Derived from memdisk_uefi.c
7 * Copyright 2025 Richard Russo
8 * SPDX-License-Identifier: BSD-2-Clause-Patent
9 */
10
11 #include "loader_efi.h"
12 #include <bootstrap.h>
13 #include <dev_net.h>
14 #include <efilib.h>
15 #include <Protocol/RamDisk.h>
16 #include "decompress.h"
17 #include <ipxe_download.h>
18 #include <sys/_param.h>
19
20 #define ULL(x) ((unsigned long long)(x))
21 #define DOWNLOAD_BUFSIZE (64 * 1024)
22
23 static EFI_GUID ipxeGuid = IPXE_DOWNLOAD_PROTOCOL_GUID;
24 static EFI_GUID ramdiskGuid = EFI_RAM_DISK_PROTOCOL_GUID;
25 static EFI_GUID virtual_disk_guid = EFI_VIRTUAL_DISK_GUID;
26 static EFI_GUID virtual_cd_guid = EFI_VIRTUAL_CD_GUID;
27
28 static IPXE_DOWNLOAD_PROTOCOL *ipxe_download;
29 static EFI_RAM_DISK_PROTOCOL *ram_disk;
30
31 static bool
download_cancel_requested(void)32 download_cancel_requested(void)
33 {
34 int c;
35
36 if (!ischar())
37 return (false);
38 c = getchar();
39 return (c == '\033');
40 }
41
42 struct dl_state;
43 typedef struct dl_state dl_state;
44
45 static struct dl_state
46 {
47 bool in_progress;
48 size_t size;
49 EFI_STATUS status;
50 decomp_state *dctx;
51 bool complete;
52 } dl;
53
54 static void
download_cleanup(dl_state * ctx)55 download_cleanup(dl_state *ctx)
56 {
57 if (ctx->dctx)
58 decomp_fini(ctx->dctx, true);
59 ctx->in_progress = false;
60 }
61
62 static EFI_STATUS
download_chunk(dl_state * ctx,void * buffer,size_t length,size_t offset)63 download_chunk(dl_state *ctx, void *buffer, size_t length, size_t offset)
64 {
65 decomp_state *dctx = ctx->dctx;
66 enum step_return sr;
67
68 if (offset == 0 && length == 0) {
69 printf("Starting the download\n");
70 return (EFI_SUCCESS);
71 }
72
73 /*
74 * Make a note of the size when we're hinted about it.
75 */
76 if (length == 0) {
77 printf("We know we will download %llu bytes\n", ULL(offset));
78 ctx->size = offset;
79 ctx->status = EFI_SUCCESS;
80 return (EFI_SUCCESS);
81 }
82
83 /*
84 * Peek into the first chunk to see the format of the data.
85 */
86 if (offset == 0) {
87 dctx = decomp_init(buffer, length, ctx->size);
88 if (dctx == NULL) {
89 ctx->in_progress = false;
90 ctx->status = EFI_VOLUME_CORRUPTED;
91 return (ctx->status);
92 }
93 ctx->dctx = dctx;
94 }
95
96 sr = decomp_step(dctx, buffer, length, offset);
97 if (sr == err) {
98 printf("Error on download\n");
99 return (EFI_VOLUME_CORRUPTED);
100 }
101 ctx->complete = (sr == done);
102
103 unsigned long long sofar = offset + length;
104 #define MB 1000000
105 if (sofar / MB != offset / MB) {
106 if (ctx->size)
107 printf("%dMB / %dMB (%d%%)\r",
108 (int)(sofar / MB),
109 (int)(ctx->size / MB),
110 (int)(100 * sofar / ctx->size));
111 else
112 printf("%dMB\r", (int)(sofar / MB));
113 }
114 return (EFI_SUCCESS);
115 }
116
117 static EFI_STATUS EFIAPI
download_data(IN VOID * Context,IN VOID * Buffer,IN UINTN BufferLength,IN UINTN FileOffset)118 download_data(IN VOID *Context, IN VOID *Buffer, IN UINTN BufferLength,
119 IN UINTN FileOffset)
120 {
121
122 return (download_chunk(Context, Buffer, BufferLength, FileOffset));
123 }
124
125 static void EFIAPI
download_finish(IN VOID * Context,IN EFI_STATUS Status)126 download_finish(IN VOID *Context, IN EFI_STATUS Status)
127 {
128 dl_state *ctx = Context;
129
130 ctx->in_progress = false;
131 ctx->status = Status;
132 if (ctx->dctx != NULL && !EFI_ERROR(Status))
133 decomp_fini(ctx->dctx, false);
134 }
135
136 static int
fallback_to_md(EFI_PHYSICAL_ADDRESS pa,size_t len)137 fallback_to_md(EFI_PHYSICAL_ADDRESS pa, size_t len)
138 {
139 #ifdef LOADER_MD_SUPPORT
140 int unit;
141
142 unit = md_register((void *)(uintptr_t)pa, len, MD_FLAG_KERNEL);
143 if (unit < 0) {
144 printf("Could not register downloaded image as an md: %s\n",
145 strerror(errno));
146 return (errno);
147 }
148 setenv("uefi_ignore_boot_mgr", "true", 1);
149 return (0);
150 #else
151 printf("No memory disk support in this loader\n");
152 return (EOPNOTSUPP);
153 #endif
154 }
155
156 int
download_md_image(const char * url)157 download_md_image(const char *url)
158 {
159 struct stat sb;
160 dl_state ctx;
161 uint8_t *buf;
162 size_t offset;
163 ssize_t nread;
164 int error, fd;
165
166 fd = open(url, O_RDONLY);
167 if (fd < 0)
168 return (errno);
169 if (fstat(fd, &sb) != 0) {
170 error = errno;
171 goto out_close;
172 }
173 if (sb.st_size <= 0 || (uintmax_t)sb.st_size > SIZE_MAX) {
174 error = EINVAL;
175 goto out_close;
176 }
177
178 buf = malloc(DOWNLOAD_BUFSIZE);
179 if (buf == NULL) {
180 error = ENOMEM;
181 goto out_close;
182 }
183 memset(&ctx, 0, sizeof(ctx));
184 ctx.size = sb.st_size;
185 offset = 0;
186 printf("Press Esc to cancel.\n");
187 while ((nread = read(fd, buf, DOWNLOAD_BUFSIZE)) > 0) {
188 if (download_cancel_requested()) {
189 printf("\nDownload cancelled.\n");
190 error = ECANCELED;
191 goto out_decomp;
192 }
193 if (EFI_ERROR(download_chunk(&ctx, buf, nread, offset))) {
194 error = EIO;
195 goto out_decomp;
196 }
197 offset += nread;
198 }
199 if (nread < 0) {
200 error = errno;
201 goto out_decomp;
202 }
203 if (offset != ctx.size || !ctx.complete) {
204 error = EIO;
205 goto out_decomp;
206 }
207
208 decomp_fini(ctx.dctx, false);
209 error = fallback_to_md(decomp_buffer(ctx.dctx),
210 decomp_buffer_length(ctx.dctx));
211 goto out_free;
212
213 out_decomp:
214 if (ctx.dctx != NULL)
215 decomp_fini(ctx.dctx, true);
216 out_free:
217 free(buf);
218 out_close:
219 close(fd);
220 return (error);
221 }
222
223 void
maybe_download_initmd(void)224 maybe_download_initmd(void)
225 {
226 #ifdef LOADER_NET_SUPPORT
227 struct devdesc dev;
228 const char *url;
229 int error;
230
231 if (efi_find_handle(&efinet_dev, 0) == NULL)
232 return;
233
234 memset(&dev, 0, sizeof(dev));
235 dev.d_dev = &efinet_dev;
236 dev.d_unit = 0;
237 error = net_configure(&dev);
238 if (error != 0) {
239 printf("Could not configure net0 for initmd: %s\n",
240 strerror(error));
241 return;
242 }
243
244 url = getenv("dhcp.initmd");
245 if (url == NULL || *url == '\0')
246 return;
247
248 printf("Downloading initmd from %s\n", url);
249 error = download_md_image(url);
250 if (error != 0 && error != ECANCELED)
251 printf("Could not download initmd: %s\n", strerror(error));
252 #endif
253 }
254
255 static void
do_download_ramdisk(CHAR8 * url,bool is_disk)256 do_download_ramdisk(CHAR8 *url, bool is_disk)
257 {
258 EFI_STATUS Status;
259 EFI_GUID disk_type = is_disk ? virtual_disk_guid : virtual_cd_guid;
260 EFI_DEVICE_PATH_PROTOCOL *ram_disk_path;
261 IPXE_DOWNLOAD_FILE token;
262 dl_state *ctx = &dl;
263 int error;
264
265 printf("Downloading %s as a %s\n", url, is_disk ? "disk" : "cd");
266 printf("Press Esc to cancel.\n");
267 memset(ctx, 0, sizeof(*ctx));
268 ctx->in_progress = true;
269 Status = ipxe_download->Start(ipxe_download, url, download_data, download_finish,
270 &dl, &token);
271 if (EFI_ERROR(Status)) {
272 printf("Couldn't start download %u\n", (unsigned)Status);
273 download_cleanup(ctx);
274 return;
275 }
276 while (ctx->in_progress) {
277 ipxe_download->Poll(ipxe_download);
278 if (!ctx->in_progress)
279 break;
280 if (download_cancel_requested()) {
281 printf("\nCancelling download...\n");
282 Status = ipxe_download->Abort(ipxe_download, token,
283 EFI_ABORTED);
284 if (EFI_ERROR(Status)) {
285 printf("Could not cancel download %u\n",
286 (unsigned)Status);
287 }
288 }
289 }
290 if (ctx->status == EFI_ABORTED) {
291 printf("Download cancelled.\n");
292 download_cleanup(ctx);
293 return;
294 }
295 if (EFI_ERROR(ctx->status)) {
296 printf("Download had error %u\n", (unsigned)ctx->status);
297 download_cleanup(ctx);
298 return;
299 }
300 if (ctx->size == 0) {
301 printf("Nothing downloaded\n");
302 download_cleanup(ctx);
303 return;
304 }
305
306 printf("\nDownloaded %llu bytes, actual size %llu -- registering ramdisk\n",
307 ULL(ctx->size), ULL(decomp_buffer_length(ctx->dctx)));
308
309 /* ram_disk will be NULL if this fails */
310 BS->LocateProtocol(&ramdiskGuid, NULL, (void**)&ram_disk);
311
312 /*
313 * If there's no ram_disk protocol installed in this firmware, do the
314 * next best thing by saving a pointer and using that later.
315 */
316 if (ram_disk == NULL) {
317 printf("No RamDisk protocol, falling back to md image\n");
318 error = fallback_to_md(decomp_buffer(ctx->dctx), decomp_buffer_length(ctx->dctx));
319 if (error) {
320 printf("Failed to register as an MD device\n");
321 download_cleanup(ctx);
322 }
323 return;
324 }
325
326 /*
327 * Register the RamDisk with UEFI. This registers it so the rest of the
328 * boot loader can see it as a block device.
329 */
330 Status = ram_disk->Register(decomp_buffer(ctx->dctx), decomp_buffer_length(ctx->dctx),
331 &disk_type, NULL, &ram_disk_path);
332 if (EFI_ERROR(Status)) {
333 printf("failed to register ram disk %u, falling back to md image\n", (unsigned)Status);
334 error = fallback_to_md(decomp_buffer(ctx->dctx), decomp_buffer_length(ctx->dctx));
335 if (error) {
336 printf("Failed to register as an MD device\n");
337 download_cleanup(ctx);
338 }
339 return;
340 }
341
342 CHAR16 *text = efi_devpath_name(ram_disk_path);
343 if (text != NULL) {
344 CHAR8 uefi_path[1024];
345 printf("Installed RAM disk as %S\n", text);
346
347 cpy16to8(text, uefi_path, sizeof(uefi_path));
348 setenv("uefi_ignore_boot_mgr", "true", 1);
349 setenv("uefi_rootdev", uefi_path, 1);
350 efi_free_devpath_name(text);
351 } else {
352 printf("Installed RAM disk to unknown device type\n");
353 }
354 }
355
356 /*
357 * Scan the command line for memdisk=url or memcd=url. Do nothing if that's not
358 * present, otherwise try to download that image. Returns true when we've tried
359 * to download an image, whether successful or not.
360 *
361 * Open Question: Do we want some way to chain boot into the /boot/loader.efi or
362 * \efi\boot\bootXXXXX.efi inside the ram disk we load? If so, how do we keep
363 * from infinite chainbooting? Also, I don't understand the load it but don't save
364 * it option...
365 */
366 bool
maybe_download_ramdisk(int argc,CHAR16 ** argv)367 maybe_download_ramdisk(int argc, CHAR16 **argv)
368 {
369 char var[256];
370 EFI_STATUS status;
371
372 status = BS->LocateProtocol(&ipxeGuid, NULL, (void **)&ipxe_download);
373 if (EFI_ERROR(status)) {
374 ipxe_download = NULL;
375 return (false);
376 }
377
378 for (int i = 0; i < argc; i++) {
379 cpy16to8(argv[i], var, sizeof(var));
380 if (strncmp(var, "memdisk=", 8) == 0) {
381 do_download_ramdisk(var + 8, true);
382 return (true);
383 }
384 if (strncmp(var, "memcd=", 6) == 0) {
385 do_download_ramdisk(var + 6, false);
386 return (true);
387 }
388 }
389 return (false);
390 }
391