xref: /freebsd/stand/efi/libefi/efihttp.c (revision 7c43148a974877188a930e4078a164f83da8e652)
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 */
45 static const int EFIHTTP_POLL_TIMEOUT = 300000;
46 
47 static EFI_GUID http_guid = EFI_HTTP_PROTOCOL_GUID;
48 static EFI_GUID httpsb_guid = EFI_HTTP_SERVICE_BINDING_PROTOCOL_GUID;
49 static EFI_GUID ip4config2_guid = EFI_IP4_CONFIG2_PROTOCOL_GUID;
50 
51 static bool efihttp_init_done = false;
52 
53 static int efihttp_dev_init(void);
54 static int efihttp_dev_strategy(void *devdata, int rw, daddr_t blk, size_t size,
55     char *buf, size_t *rsize);
56 static int efihttp_dev_open(struct open_file *f, ...);
57 static int efihttp_dev_close(struct open_file *f);
58 
59 static int efihttp_fs_open(const char *path, struct open_file *f);
60 static int efihttp_fs_close(struct open_file *f);
61 static int efihttp_fs_read(struct open_file *f, void *buf, size_t size,
62     size_t *resid);
63 static int efihttp_fs_write(struct open_file *f, const void *buf, size_t size,
64     size_t *resid);
65 static off_t efihttp_fs_seek(struct open_file *f, off_t offset, int where);
66 static int efihttp_fs_stat(struct open_file *f, struct stat *sb);
67 static int efihttp_fs_readdir(struct open_file *f, struct dirent *d);
68 
69 struct open_efihttp {
70 	EFI_HTTP_PROTOCOL *http;
71 	EFI_HANDLE	http_handle;
72 	EFI_HANDLE	dev_handle;
73 	char		*uri_base;
74 };
75 
76 struct file_efihttp {
77 	ssize_t		size;
78 	off_t		offset;
79 	char		*path;
80 	bool		is_dir;
81 };
82 
83 struct 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 
95 struct 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 
106 static void EFIAPI
notify(EFI_EVENT event __unused,void * context)107 notify(EFI_EVENT event __unused, void *context)
108 {
109 	bool *b;
110 
111 	b = (bool *)context;
112 	*b = true;
113 }
114 
115 static int
setup_ipv4_config2(EFI_HANDLE handle,MAC_ADDR_DEVICE_PATH * mac,IPv4_DEVICE_PATH * ipv4,DNS_DEVICE_PATH * dns)116 setup_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 
179 static int
efihttp_dev_init(void)180 efihttp_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 
217 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)218 efihttp_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 
225 static int
efihttp_dev_open(struct open_file * f,...)226 efihttp_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;
354 end:
355 	if (err != 0) {
356 		free(dev->d_opendata);
357 		dev->d_opendata = NULL;
358 	}
359 	return (err);
360 }
361 
362 static int
efihttp_dev_close(struct open_file * f)363 efihttp_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 
383 static int
_efihttp_fs_open(const char * path,struct open_file * f)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 
560 static int
efihttp_fs_open(const char * path,struct open_file * f)561 efihttp_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 
593 static int
efihttp_fs_close(struct open_file * f __unused)594 efihttp_fs_close(struct open_file *f __unused)
595 {
596 	return (0);
597 }
598 
599 static int
_efihttp_fs_read(struct open_file * f,void * buf,size_t size,size_t * resid)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 
668 static int
efihttp_fs_read(struct open_file * f,void * buf,size_t size,size_t * resid)669 efihttp_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 	}
681 end:
682 	if (resid)
683 		*resid = size;
684 	return (err);
685 }
686 
687 static int
efihttp_fs_write(struct open_file * f __unused,const void * buf __unused,size_t size __unused,size_t * resid __unused)688 efihttp_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 
694 static off_t
efihttp_fs_seek(struct open_file * f,off_t offset,int where)695 efihttp_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 
742 static int
efihttp_fs_stat(struct open_file * f,struct stat * sb)743 efihttp_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 
755 static int
efihttp_fs_readdir(struct open_file * f,struct dirent * d)756 efihttp_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