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