xref: /freebsd/stand/efi/libefi/efihttp.c (revision d7d962ead0b6e5e8a39202d0590022082bf5bfb6)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3  *
4  * Copyright (c) 2019 Intel Corporation
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  *
27  * $FreeBSD$
28  */
29 
30 #include <sys/cdefs.h>
31 __FBSDID("$FreeBSD$");
32 
33 #include <sys/types.h>
34 
35 #include <netinet/in.h>
36 #include <netinet/in_systm.h>
37 
38 #include <stand.h>
39 #include <bootstrap.h>
40 #include <net.h>
41 
42 #include <efi.h>
43 #include <efilib.h>
44 #include <efiprot.h>
45 #include <Protocol/Http.h>
46 #include <Protocol/Ip4Config2.h>
47 #include <Protocol/ServiceBinding.h>
48 
49 /* Poll timeout in milliseconds */
50 static const int EFIHTTP_POLL_TIMEOUT = 300000;
51 
52 static EFI_GUID http_guid = EFI_HTTP_PROTOCOL_GUID;
53 static EFI_GUID httpsb_guid = EFI_HTTP_SERVICE_BINDING_PROTOCOL_GUID;
54 static EFI_GUID ip4config2_guid = EFI_IP4_CONFIG2_PROTOCOL_GUID;
55 
56 static bool efihttp_init_done = false;
57 
58 static int efihttp_dev_init(void);
59 static int efihttp_dev_strategy(void *devdata, int rw, daddr_t blk, size_t size,
60     char *buf, size_t *rsize);
61 static int efihttp_dev_open(struct open_file *f, ...);
62 static int efihttp_dev_close(struct open_file *f);
63 
64 static int efihttp_fs_open(const char *path, struct open_file *f);
65 static int efihttp_fs_close(struct open_file *f);
66 static int efihttp_fs_read(struct open_file *f, void *buf, size_t size,
67     size_t *resid);
68 static int efihttp_fs_write(struct open_file *f, const void *buf, size_t size,
69     size_t *resid);
70 static off_t efihttp_fs_seek(struct open_file *f, off_t offset, int where);
71 static int efihttp_fs_stat(struct open_file *f, struct stat *sb);
72 static int efihttp_fs_readdir(struct open_file *f, struct dirent *d);
73 
74 struct open_efihttp {
75 	EFI_HTTP_PROTOCOL *http;
76 	EFI_HANDLE	http_handle;
77 	EFI_HANDLE	dev_handle;
78 	char		*uri_base;
79 };
80 
81 struct file_efihttp {
82 	ssize_t		size;
83 	off_t		offset;
84 	char		*path;
85 	bool		is_dir;
86 };
87 
88 struct devsw efihttp_dev = {
89 	.dv_name =	"http",
90 	.dv_type =	DEVT_NET,
91 	.dv_init =	efihttp_dev_init,
92 	.dv_strategy =	efihttp_dev_strategy,
93 	.dv_open =	efihttp_dev_open,
94 	.dv_close =	efihttp_dev_close,
95 	.dv_ioctl =	noioctl,
96 	.dv_print =	NULL,
97 	.dv_cleanup =	NULL,
98 };
99 
100 struct fs_ops efihttp_fsops = {
101 	.fs_name =	"efihttp",
102 	.fo_open =	efihttp_fs_open,
103 	.fo_close =	efihttp_fs_close,
104 	.fo_read =	efihttp_fs_read,
105 	.fo_write =	efihttp_fs_write,
106 	.fo_seek =	efihttp_fs_seek,
107 	.fo_stat =	efihttp_fs_stat,
108 	.fo_readdir =	efihttp_fs_readdir,
109 };
110 
111 static void EFIAPI
112 notify(EFI_EVENT event __unused, void *context)
113 {
114 	bool *b;
115 
116 	b = (bool *)context;
117 	*b = true;
118 }
119 
120 static int
121 setup_ipv4_config2(EFI_HANDLE handle, MAC_ADDR_DEVICE_PATH *mac,
122     IPv4_DEVICE_PATH *ipv4, DNS_DEVICE_PATH *dns)
123 {
124 	EFI_IP4_CONFIG2_PROTOCOL *ip4config2;
125 	EFI_STATUS status;
126 
127 	status = BS->OpenProtocol(handle, &ip4config2_guid,
128 	    (void **)&ip4config2, IH, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
129 	if (EFI_ERROR(status))
130 		return (efi_status_to_errno(status));
131 	if (ipv4 != NULL) {
132 		if (mac != NULL) {
133 			setenv("boot.netif.hwaddr",
134 			    ether_sprintf((u_char *)mac->MacAddress.Addr), 1);
135 		}
136 		setenv("boot.netif.ip",
137 		    inet_ntoa(*(struct in_addr *)ipv4->LocalIpAddress.Addr), 1);
138 		setenv("boot.netif.netmask",
139 		    intoa(*(n_long *)ipv4->SubnetMask.Addr), 1);
140 		setenv("boot.netif.gateway",
141 		    inet_ntoa(*(struct in_addr *)ipv4->GatewayIpAddress.Addr),
142 		    1);
143 		status = ip4config2->SetData(ip4config2,
144 		    Ip4Config2DataTypePolicy, sizeof(EFI_IP4_CONFIG2_POLICY),
145 		    &(EFI_IP4_CONFIG2_POLICY) { Ip4Config2PolicyStatic });
146 		if (EFI_ERROR(status))
147 			return (efi_status_to_errno(status));
148 
149 		status = ip4config2->SetData(ip4config2,
150 		    Ip4Config2DataTypeManualAddress,
151 		    sizeof(EFI_IP4_CONFIG2_MANUAL_ADDRESS),
152 		    &(EFI_IP4_CONFIG2_MANUAL_ADDRESS) {
153 			.Address = ipv4->LocalIpAddress,
154 			.SubnetMask = ipv4->SubnetMask });
155 		if (EFI_ERROR(status))
156 			return (efi_status_to_errno(status));
157 
158 		if (ipv4->GatewayIpAddress.Addr[0] != 0) {
159 			status = ip4config2->SetData(ip4config2,
160 			    Ip4Config2DataTypeGateway, sizeof(EFI_IPv4_ADDRESS),
161 			    &ipv4->GatewayIpAddress);
162 			if (EFI_ERROR(status))
163 				return (efi_status_to_errno(status));
164 		}
165 
166 		if (dns != NULL) {
167 			status = ip4config2->SetData(ip4config2,
168 			    Ip4Config2DataTypeDnsServer,
169 			    sizeof(EFI_IPv4_ADDRESS), &dns->DnsServerIp);
170 			if (EFI_ERROR(status))
171 				return (efi_status_to_errno(status));
172 		}
173 	} else {
174 		status = ip4config2->SetData(ip4config2,
175 		    Ip4Config2DataTypePolicy, sizeof(EFI_IP4_CONFIG2_POLICY),
176 		    &(EFI_IP4_CONFIG2_POLICY) { Ip4Config2PolicyDhcp });
177 		if (EFI_ERROR(status))
178 			return (efi_status_to_errno(status));
179 	}
180 
181 	return (0);
182 }
183 
184 static int
185 efihttp_dev_init(void)
186 {
187 	EFI_DEVICE_PATH *imgpath, *devpath;
188 	URI_DEVICE_PATH *uri;
189 	EFI_HANDLE handle;
190 	EFI_STATUS status;
191 	int err;
192 	bool found_http;
193 
194 	imgpath = efi_lookup_image_devpath(IH);
195 	if (imgpath == NULL)
196 		return (ENXIO);
197 	devpath = imgpath;
198 	found_http = false;
199 	for (; !IsDevicePathEnd(devpath);
200 	    devpath = NextDevicePathNode(devpath)) {
201 		if (DevicePathType(devpath) != MESSAGING_DEVICE_PATH ||
202 		    DevicePathSubType(devpath) != MSG_URI_DP)
203 			continue;
204 		uri = (URI_DEVICE_PATH *)devpath;
205 		if (strncmp("http", (const char *)uri->Uri, 4) == 0)
206 			found_http = true;
207 	}
208 	if (!found_http)
209 		return (ENXIO);
210 
211 	status = BS->LocateDevicePath(&httpsb_guid, &imgpath, &handle);
212 	if (EFI_ERROR(status))
213 		return (efi_status_to_errno(status));
214 
215 	err = efi_register_handles(&efihttp_dev, &handle, NULL, 1);
216 	if (!err)
217 		efihttp_init_done = true;
218 
219 	return (err);
220 }
221 
222 static int
223 efihttp_dev_strategy(void *devdata __unused, int rw __unused,
224     daddr_t blk __unused, size_t size __unused, char *buf __unused,
225     size_t *rsize __unused)
226 {
227 	return (EIO);
228 }
229 
230 static int
231 efihttp_dev_open(struct open_file *f, ...)
232 {
233 	EFI_HTTP_CONFIG_DATA config;
234 	EFI_HTTPv4_ACCESS_POINT config_access;
235 	DNS_DEVICE_PATH *dns;
236 	EFI_DEVICE_PATH *devpath, *imgpath;
237 	EFI_SERVICE_BINDING_PROTOCOL *sb;
238 	IPv4_DEVICE_PATH *ipv4;
239 	MAC_ADDR_DEVICE_PATH *mac;
240 	URI_DEVICE_PATH *uri;
241 	struct devdesc *dev;
242 	struct open_efihttp *oh;
243 	char *c;
244 	EFI_HANDLE handle;
245 	EFI_STATUS status;
246 	int err, len;
247 
248 	if (!efihttp_init_done)
249 		return (ENXIO);
250 
251 	imgpath = efi_lookup_image_devpath(IH);
252 	if (imgpath == NULL)
253 		return (ENXIO);
254 	devpath = imgpath;
255 	status = BS->LocateDevicePath(&httpsb_guid, &devpath, &handle);
256 	if (EFI_ERROR(status))
257 		return (efi_status_to_errno(status));
258 	mac = NULL;
259 	ipv4 = NULL;
260 	dns = NULL;
261 	uri = NULL;
262 	for (; !IsDevicePathEnd(imgpath);
263 	    imgpath = NextDevicePathNode(imgpath)) {
264 		if (DevicePathType(imgpath) != MESSAGING_DEVICE_PATH)
265 			continue;
266 		switch (DevicePathSubType(imgpath)) {
267 		case MSG_MAC_ADDR_DP:
268 			mac = (MAC_ADDR_DEVICE_PATH *)imgpath;
269 			break;
270 		case MSG_IPv4_DP:
271 			ipv4 = (IPv4_DEVICE_PATH *)imgpath;
272 			break;
273 		case MSG_DNS_DP:
274 			dns = (DNS_DEVICE_PATH *)imgpath;
275 			break;
276 		case MSG_URI_DP:
277 			uri = (URI_DEVICE_PATH *)imgpath;
278 			break;
279 		default:
280 			break;
281 		}
282 	}
283 
284 	if (uri == NULL)
285 		return (ENXIO);
286 
287 	err = setup_ipv4_config2(handle, mac, ipv4, dns);
288 	if (err)
289 		return (err);
290 
291 	oh = calloc(1, sizeof(struct open_efihttp));
292 	if (!oh)
293 		return (ENOMEM);
294 	oh->dev_handle = handle;
295 	dev = (struct devdesc *)f->f_devdata;
296 	dev->d_opendata = oh;
297 
298 	status = BS->OpenProtocol(handle, &httpsb_guid, (void **)&sb, IH, NULL,
299 	    EFI_OPEN_PROTOCOL_GET_PROTOCOL);
300 	if (EFI_ERROR(status)) {
301 		err = efi_status_to_errno(status);
302 		goto end;
303 	}
304 
305 	status = sb->CreateChild(sb, &oh->http_handle);
306 	if (EFI_ERROR(status)) {
307 		err = efi_status_to_errno(status);
308 		goto end;
309 	}
310 
311 	status = BS->OpenProtocol(oh->http_handle, &http_guid,
312 	    (void **)&oh->http, IH, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
313 	if (EFI_ERROR(status)) {
314 		sb->DestroyChild(sb, oh->http_handle);
315 		err = efi_status_to_errno(status);
316 		goto end;
317 	}
318 
319 	config.HttpVersion = HttpVersion11;
320 	config.TimeOutMillisec = 0;
321 	config.LocalAddressIsIPv6 = FALSE;
322 	config.AccessPoint.IPv4Node = &config_access;
323 	config_access.UseDefaultAddress = TRUE;
324 	config_access.LocalPort = 0;
325 	status = oh->http->Configure(oh->http, &config);
326 	if (EFI_ERROR(status)) {
327 		sb->DestroyChild(sb, oh->http_handle);
328 		err = efi_status_to_errno(status);
329 		goto end;
330 	}
331 
332 	/*
333 	 * Here we make attempt to construct a "base" URI by stripping
334 	 * the last two path components from the loaded URI under the
335 	 * assumption that it is something like:
336 	 *
337 	 * http://127.0.0.1/foo/boot/loader.efi
338 	 *
339 	 * hoping to arriving at:
340 	 *
341 	 * http://127.0.0.1/foo/
342 	 */
343 	len = DevicePathNodeLength(&uri->Header) - sizeof(URI_DEVICE_PATH);
344 	oh->uri_base = malloc(len + 1);
345 	if (oh->uri_base == NULL) {
346 		err = ENOMEM;
347 		goto end;
348 	}
349 	strncpy(oh->uri_base, (const char *)uri->Uri, len);
350 	oh->uri_base[len] = '\0';
351 	c = strrchr(oh->uri_base, '/');
352 	if (c != NULL)
353 		*c = '\0';
354 	c = strrchr(oh->uri_base, '/');
355 	if (c != NULL && *(c + 1) != '\0')
356 		*(c + 1) = '\0';
357 
358 	err = 0;
359 end:
360 	if (err != 0) {
361 		free(dev->d_opendata);
362 		dev->d_opendata = NULL;
363 	}
364 	return (err);
365 }
366 
367 static int
368 efihttp_dev_close(struct open_file *f)
369 {
370 	EFI_SERVICE_BINDING_PROTOCOL *sb;
371 	struct devdesc *dev;
372 	struct open_efihttp *oh;
373 	EFI_STATUS status;
374 
375 	dev = (struct devdesc *)f->f_devdata;
376 	oh = (struct open_efihttp *)dev->d_opendata;
377 	status = BS->OpenProtocol(oh->dev_handle, &httpsb_guid, (void **)&sb,
378 	    IH, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
379 	if (EFI_ERROR(status))
380 		return (efi_status_to_errno(status));
381 	sb->DestroyChild(sb, oh->http_handle);
382 	free(oh->uri_base);
383 	free(oh);
384 	dev->d_opendata = NULL;
385 	return (0);
386 }
387 
388 static int
389 _efihttp_fs_open(const char *path, struct open_file *f)
390 {
391 	EFI_HTTP_CONFIG_DATA config;
392 	EFI_HTTPv4_ACCESS_POINT config_access;
393 	EFI_HTTP_TOKEN token;
394 	EFI_HTTP_MESSAGE message;
395 	EFI_HTTP_REQUEST_DATA request;
396 	EFI_HTTP_RESPONSE_DATA response;
397 	EFI_HTTP_HEADER headers[3];
398 	char *host, *hostp;
399 	char *c;
400 	struct devdesc *dev;
401 	struct open_efihttp *oh;
402 	struct file_efihttp *fh;
403 	EFI_STATUS status;
404 	UINTN i;
405 	int polltime;
406 	bool done;
407 
408 	dev = (struct devdesc *)f->f_devdata;
409 	oh = (struct open_efihttp *)dev->d_opendata;
410 	fh = calloc(1, sizeof(struct file_efihttp));
411 	if (fh == NULL)
412 		return (ENOMEM);
413 	f->f_fsdata = fh;
414 	fh->path = strdup(path);
415 
416 	/*
417 	 * Reset the HTTP state.
418 	 *
419 	 * EDK II's persistent HTTP connection handling is graceless,
420 	 * assuming that all connections are persistent regardless of
421 	 * any Connection: header or HTTP version reported by the
422 	 * server, and failing to send requests when a more sane
423 	 * implementation would seem to be just reestablishing the
424 	 * closed connection.
425 	 *
426 	 * In the hopes of having some robustness, we indicate to the
427 	 * server that we will close the connection by using a
428 	 * Connection: close header. And then here we manually
429 	 * unconfigure and reconfigure the http instance to force the
430 	 * connection closed.
431 	 */
432 	memset(&config, 0, sizeof(config));
433 	memset(&config_access, 0, sizeof(config_access));
434 	config.AccessPoint.IPv4Node = &config_access;
435 	status = oh->http->GetModeData(oh->http, &config);
436 	if (EFI_ERROR(status))
437 		return (efi_status_to_errno(status));
438 	status = oh->http->Configure(oh->http, NULL);
439 	if (EFI_ERROR(status))
440 		return (efi_status_to_errno(status));
441 	status = oh->http->Configure(oh->http, &config);
442 	if (EFI_ERROR(status))
443 		return (efi_status_to_errno(status));
444 
445 	/* Send the read request */
446 	done = false;
447 	status = BS->CreateEvent(EVT_NOTIFY_SIGNAL, TPL_CALLBACK, notify,
448 	    &done, &token.Event);
449 	if (EFI_ERROR(status))
450 		return (efi_status_to_errno(status));
451 
452 	/* extract the host portion of the URL */
453 	host = strdup(oh->uri_base);
454 	if (host == NULL)
455 		return (ENOMEM);
456 	hostp = host;
457 	/* Remove the protocol scheme */
458 	c = strchr(host, '/');
459 	if (c != NULL && *(c + 1) == '/')
460 		hostp = (c + 2);
461 
462 	/* Remove any path information */
463 	c = strchr(hostp, '/');
464 	if (c != NULL)
465 		*c = '\0';
466 
467 	token.Status = EFI_NOT_READY;
468 	token.Message = &message;
469 	message.Data.Request = &request;
470 	message.HeaderCount = 3;
471 	message.Headers = headers;
472 	message.BodyLength = 0;
473 	message.Body = NULL;
474 	request.Method = HttpMethodGet;
475 	request.Url = calloc(strlen(oh->uri_base) + strlen(path) + 1, 2);
476 	headers[0].FieldName = (CHAR8 *)"Host";
477 	headers[0].FieldValue = (CHAR8 *)hostp;
478 	headers[1].FieldName = (CHAR8 *)"Connection";
479 	headers[1].FieldValue = (CHAR8 *)"close";
480 	headers[2].FieldName = (CHAR8 *)"Accept";
481 	headers[2].FieldValue = (CHAR8 *)"*/*";
482 	cpy8to16(oh->uri_base, request.Url, strlen(oh->uri_base));
483 	cpy8to16(path, request.Url + strlen(oh->uri_base), strlen(path));
484 	status = oh->http->Request(oh->http, &token);
485 	free(request.Url);
486 	free(host);
487 	if (EFI_ERROR(status)) {
488 		BS->CloseEvent(token.Event);
489 		return (efi_status_to_errno(status));
490 	}
491 
492 	polltime = 0;
493 	while (!done && polltime < EFIHTTP_POLL_TIMEOUT) {
494 		status = oh->http->Poll(oh->http);
495 		if (EFI_ERROR(status))
496 			break;
497 
498 		if (!done) {
499 			delay(100 * 1000);
500 			polltime += 100;
501 		}
502 	}
503 	BS->CloseEvent(token.Event);
504 	if (EFI_ERROR(token.Status))
505 		return (efi_status_to_errno(token.Status));
506 
507 	/* Wait for the read response */
508 	done = false;
509 	status = BS->CreateEvent(EVT_NOTIFY_SIGNAL, TPL_CALLBACK, notify,
510 	    &done, &token.Event);
511 	if (EFI_ERROR(status))
512 		return (efi_status_to_errno(status));
513 	token.Status = EFI_NOT_READY;
514 	token.Message = &message;
515 	message.Data.Response = &response;
516 	message.HeaderCount = 0;
517 	message.Headers = NULL;
518 	message.BodyLength = 0;
519 	message.Body = NULL;
520 	response.StatusCode = HTTP_STATUS_UNSUPPORTED_STATUS;
521 	status = oh->http->Response(oh->http, &token);
522 	if (EFI_ERROR(status)) {
523 		BS->CloseEvent(token.Event);
524 		return (efi_status_to_errno(status));
525 	}
526 
527 	polltime = 0;
528 	while (!done && polltime < EFIHTTP_POLL_TIMEOUT) {
529 		status = oh->http->Poll(oh->http);
530 		if (EFI_ERROR(status))
531 			break;
532 
533 		if (!done) {
534 			delay(100 * 1000);
535 			polltime += 100;
536 		}
537 	}
538 	BS->CloseEvent(token.Event);
539 	if (EFI_ERROR(token.Status)) {
540 		BS->FreePool(message.Headers);
541 		return (efi_status_to_errno(token.Status));
542 	}
543 	if (response.StatusCode != HTTP_STATUS_200_OK) {
544 		BS->FreePool(message.Headers);
545 		return (EIO);
546 	}
547 	fh->size = 0;
548 	fh->is_dir = false;
549 	for (i = 0; i < message.HeaderCount; i++) {
550 		if (strcasecmp((const char *)message.Headers[i].FieldName,
551 		    "Content-Length") == 0)
552 			fh->size = strtoul((const char *)
553 			    message.Headers[i].FieldValue, NULL, 10);
554 		else if (strcasecmp((const char *)message.Headers[i].FieldName,
555 		    "Content-type") == 0) {
556 			if (strncmp((const char *)message.Headers[i].FieldValue,
557 			    "text/html", 9) == 0)
558 				fh->is_dir = true;
559 		}
560 	}
561 
562 	return (0);
563 }
564 
565 static int
566 efihttp_fs_open(const char *path, struct open_file *f)
567 {
568 	char *path_slash;
569 	int err;
570 
571 	if (!efihttp_init_done)
572 		return (ENXIO);
573 	/*
574 	 * If any path fails to open, try with a trailing slash in
575 	 * case it's a directory.
576 	 */
577 	err = _efihttp_fs_open(path, f);
578 	if (err != 0) {
579 		/*
580 		 * Work around a bug in the EFI HTTP implementation which
581 		 * causes a crash if the http instance isn't torn down
582 		 * between requests.
583 		 * See https://bugzilla.tianocore.org/show_bug.cgi?id=1917
584 		 */
585 		efihttp_dev_close(f);
586 		efihttp_dev_open(f);
587 		path_slash = malloc(strlen(path) + 2);
588 		if (path_slash == NULL)
589 			return (ENOMEM);
590 		strcpy(path_slash, path);
591 		strcat(path_slash, "/");
592 		err = _efihttp_fs_open(path_slash, f);
593 		free(path_slash);
594 	}
595 	return (err);
596 }
597 
598 static int
599 efihttp_fs_close(struct open_file *f __unused)
600 {
601 	return (0);
602 }
603 
604 static int
605 _efihttp_fs_read(struct open_file *f, void *buf, size_t size, size_t *resid)
606 {
607 	EFI_HTTP_TOKEN token;
608 	EFI_HTTP_MESSAGE message;
609 	EFI_STATUS status;
610 	struct devdesc *dev;
611 	struct open_efihttp *oh;
612 	struct file_efihttp *fh;
613 	bool done;
614 	int polltime;
615 
616 	fh = (struct file_efihttp *)f->f_fsdata;
617 
618 	if (fh->size > 0 && fh->offset >= fh->size) {
619 		if (resid != NULL)
620 			*resid = size;
621 
622 		return 0;
623 	}
624 
625 	dev = (struct devdesc *)f->f_devdata;
626 	oh = (struct open_efihttp *)dev->d_opendata;
627 	done = false;
628 	status = BS->CreateEvent(EVT_NOTIFY_SIGNAL, TPL_CALLBACK, notify,
629 	    &done, &token.Event);
630 	if (EFI_ERROR(status)) {
631 		return (efi_status_to_errno(status));
632 	}
633 	token.Status = EFI_NOT_READY;
634 	token.Message = &message;
635 	message.Data.Request = NULL;
636 	message.HeaderCount = 0;
637 	message.Headers = NULL;
638 	message.BodyLength = size;
639 	message.Body = buf;
640 	status = oh->http->Response(oh->http, &token);
641 	if (status == EFI_CONNECTION_FIN) {
642 		if (resid)
643 			*resid = size;
644 		return (0);
645 	} else if (EFI_ERROR(status)) {
646 		BS->CloseEvent(token.Event);
647 		return (efi_status_to_errno(status));
648 	}
649 	polltime = 0;
650 	while (!done && polltime < EFIHTTP_POLL_TIMEOUT) {
651 		status = oh->http->Poll(oh->http);
652 		if (EFI_ERROR(status))
653 				break;
654 
655 		if (!done) {
656 			delay(100 * 1000);
657 			polltime += 100;
658 		}
659 	}
660 	BS->CloseEvent(token.Event);
661 	if (token.Status == EFI_CONNECTION_FIN) {
662 		if (resid)
663 			*resid = size;
664 		return (0);
665 	} else if (EFI_ERROR(token.Status))
666 		return (efi_status_to_errno(token.Status));
667 	if (resid)
668 		*resid = size - message.BodyLength;
669 	fh->offset += message.BodyLength;
670 	return (0);
671 }
672 
673 static int
674 efihttp_fs_read(struct open_file *f, void *buf, size_t size, size_t *resid)
675 {
676 	size_t res;
677 	int err = 0;
678 
679 	while (size > 0) {
680 		err = _efihttp_fs_read(f, buf, size, &res);
681 		if (err != 0 || res == size)
682 			goto end;
683 		buf += (size - res);
684 		size = res;
685 	}
686 end:
687 	if (resid)
688 		*resid = size;
689 	return (err);
690 }
691 
692 static int
693 efihttp_fs_write(struct open_file *f __unused, const void *buf __unused,
694     size_t size __unused, size_t *resid __unused)
695 {
696 	return (EIO);
697 }
698 
699 static off_t
700 efihttp_fs_seek(struct open_file *f, off_t offset, int where)
701 {
702 	struct file_efihttp *fh;
703 	char *path;
704 	void *buf;
705 	size_t res, res2;
706 	int err;
707 
708 	fh = (struct file_efihttp *)f->f_fsdata;
709 	if (where == SEEK_SET && fh->offset == offset)
710 		return (0);
711 	if (where == SEEK_SET && fh->offset < offset) {
712 		buf = malloc(1500);
713 		if (buf == NULL)
714 			return (ENOMEM);
715 		res = offset - fh->offset;
716 		while (res > 0) {
717 			err = _efihttp_fs_read(f, buf, min(1500, res), &res2);
718 			if (err != 0) {
719 				free(buf);
720 				return (err);
721 			}
722 			res -= min(1500, res) - res2;
723 		}
724 		free(buf);
725 		return (0);
726 	} else if (where == SEEK_SET) {
727 		path = fh->path;
728 		fh->path = NULL;
729 		efihttp_fs_close(f);
730 		/*
731 		 * Work around a bug in the EFI HTTP implementation which
732 		 * causes a crash if the http instance isn't torn down
733 		 * between requests.
734 		 * See https://bugzilla.tianocore.org/show_bug.cgi?id=1917
735 		 */
736 		efihttp_dev_close(f);
737 		efihttp_dev_open(f);
738 		err = efihttp_fs_open(path, f);
739 		free(path);
740 		if (err != 0)
741 			return (err);
742 		return efihttp_fs_seek(f, offset, where);
743 	}
744 	return (EIO);
745 }
746 
747 static int
748 efihttp_fs_stat(struct open_file *f, struct stat *sb)
749 {
750 	struct file_efihttp *fh;
751 
752 	fh = (struct file_efihttp *)f->f_fsdata;
753 	memset(sb, 0, sizeof(*sb));
754 	sb->st_nlink = 1;
755 	sb->st_mode = 0777 | (fh->is_dir ? S_IFDIR : S_IFREG);
756 	sb->st_size = fh->size;
757 	return (0);
758 }
759 
760 static int
761 efihttp_fs_readdir(struct open_file *f, struct dirent *d)
762 {
763 	static char *dirbuf = NULL, *db2, *cursor;
764 	static int dirbuf_len = 0;
765 	char *end;
766 	struct file_efihttp *fh;
767 
768 	fh = (struct file_efihttp *)f->f_fsdata;
769 	if (dirbuf_len < fh->size) {
770 		db2 = realloc(dirbuf, fh->size);
771 		if (db2 == NULL) {
772 			free(dirbuf);
773 			return (ENOMEM);
774 		} else
775 			dirbuf = db2;
776 
777 		dirbuf_len = fh->size;
778 	}
779 
780 	if (fh->offset != fh->size) {
781 		efihttp_fs_seek(f, 0, SEEK_SET);
782 		efihttp_fs_read(f, dirbuf, dirbuf_len, NULL);
783 		cursor = dirbuf;
784 	}
785 
786 	cursor = strstr(cursor, "<a href=\"");
787 	if (cursor == NULL)
788 		return (ENOENT);
789 	cursor += 9;
790 	end = strchr(cursor, '"');
791 	if (*(end - 1) == '/') {
792 		end--;
793 		d->d_type = DT_DIR;
794 	} else
795 		d->d_type = DT_REG;
796 	memcpy(d->d_name, cursor, end - cursor);
797 	d->d_name[end - cursor] = '\0';
798 
799 	return (0);
800 }
801