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