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