d455cd5ac3
A bug in the EFI HTTP driver of TianoCore EDK2 causes memory corruption when an http instance that uses tls is reconfigured, leading to a crash. Work around this by forcing a new http instance for each request instead of reconfiguring the existing one. The upstream bug report is https://bugzilla.tianocore.org/show_bug.cgi?id=1917 Submitted by: bcran Reviewed By: imp, kevans, tsoome MFC after: 1 week Differential Revision: https://reviews.freebsd.org/D21281
801 lines
20 KiB
C
801 lines
20 KiB
C
/*-
|
|
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
|
|
*
|
|
* Copyright (c) 2019 Intel Corporation
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*
|
|
* $FreeBSD$
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <netinet/in.h>
|
|
#include <netinet/in_systm.h>
|
|
|
|
#include <stand.h>
|
|
#include <bootstrap.h>
|
|
#include <net.h>
|
|
|
|
#include <efi.h>
|
|
#include <efilib.h>
|
|
#include <efiprot.h>
|
|
#include <Protocol/Http.h>
|
|
#include <Protocol/Ip4Config2.h>
|
|
#include <Protocol/ServiceBinding.h>
|
|
|
|
/* Poll timeout in milliseconds */
|
|
static const int EFIHTTP_POLL_TIMEOUT = 300000;
|
|
|
|
static EFI_GUID http_guid = EFI_HTTP_PROTOCOL_GUID;
|
|
static EFI_GUID httpsb_guid = EFI_HTTP_SERVICE_BINDING_PROTOCOL_GUID;
|
|
static EFI_GUID ip4config2_guid = EFI_IP4_CONFIG2_PROTOCOL_GUID;
|
|
|
|
static bool efihttp_init_done = false;
|
|
|
|
static int efihttp_dev_init(void);
|
|
static int efihttp_dev_strategy(void *devdata, int rw, daddr_t blk, size_t size,
|
|
char *buf, size_t *rsize);
|
|
static int efihttp_dev_open(struct open_file *f, ...);
|
|
static int efihttp_dev_close(struct open_file *f);
|
|
|
|
static int efihttp_fs_open(const char *path, struct open_file *f);
|
|
static int efihttp_fs_close(struct open_file *f);
|
|
static int efihttp_fs_read(struct open_file *f, void *buf, size_t size,
|
|
size_t *resid);
|
|
static int efihttp_fs_write(struct open_file *f, const void *buf, size_t size,
|
|
size_t *resid);
|
|
static off_t efihttp_fs_seek(struct open_file *f, off_t offset, int where);
|
|
static int efihttp_fs_stat(struct open_file *f, struct stat *sb);
|
|
static int efihttp_fs_readdir(struct open_file *f, struct dirent *d);
|
|
|
|
struct open_efihttp {
|
|
EFI_HTTP_PROTOCOL *http;
|
|
EFI_HANDLE http_handle;
|
|
EFI_HANDLE dev_handle;
|
|
char *uri_base;
|
|
};
|
|
|
|
struct file_efihttp {
|
|
ssize_t size;
|
|
off_t offset;
|
|
char *path;
|
|
bool is_dir;
|
|
};
|
|
|
|
struct devsw efihttp_dev = {
|
|
.dv_name = "http",
|
|
.dv_type = DEVT_NET,
|
|
.dv_init = efihttp_dev_init,
|
|
.dv_strategy = efihttp_dev_strategy,
|
|
.dv_open = efihttp_dev_open,
|
|
.dv_close = efihttp_dev_close,
|
|
.dv_ioctl = noioctl,
|
|
.dv_print = NULL,
|
|
.dv_cleanup = NULL,
|
|
};
|
|
|
|
struct fs_ops efihttp_fsops = {
|
|
.fs_name = "efihttp",
|
|
.fo_open = efihttp_fs_open,
|
|
.fo_close = efihttp_fs_close,
|
|
.fo_read = efihttp_fs_read,
|
|
.fo_write = efihttp_fs_write,
|
|
.fo_seek = efihttp_fs_seek,
|
|
.fo_stat = efihttp_fs_stat,
|
|
.fo_readdir = efihttp_fs_readdir,
|
|
};
|
|
|
|
static void EFIAPI
|
|
notify(EFI_EVENT event __unused, void *context)
|
|
{
|
|
bool *b;
|
|
|
|
b = (bool *)context;
|
|
*b = true;
|
|
}
|
|
|
|
static int
|
|
setup_ipv4_config2(EFI_HANDLE handle, MAC_ADDR_DEVICE_PATH *mac,
|
|
IPv4_DEVICE_PATH *ipv4, DNS_DEVICE_PATH *dns)
|
|
{
|
|
EFI_IP4_CONFIG2_PROTOCOL *ip4config2;
|
|
EFI_STATUS status;
|
|
|
|
status = BS->OpenProtocol(handle, &ip4config2_guid,
|
|
(void **)&ip4config2, IH, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
|
|
if (EFI_ERROR(status))
|
|
return (efi_status_to_errno(status));
|
|
if (ipv4 != NULL) {
|
|
if (mac != NULL) {
|
|
setenv("boot.netif.hwaddr",
|
|
ether_sprintf((u_char *)mac->MacAddress.Addr), 1);
|
|
}
|
|
setenv("boot.netif.ip",
|
|
inet_ntoa(*(struct in_addr *)ipv4->LocalIpAddress.Addr), 1);
|
|
setenv("boot.netif.netmask",
|
|
intoa(*(n_long *)ipv4->SubnetMask.Addr), 1);
|
|
setenv("boot.netif.gateway",
|
|
inet_ntoa(*(struct in_addr *)ipv4->GatewayIpAddress.Addr),
|
|
1);
|
|
status = ip4config2->SetData(ip4config2,
|
|
Ip4Config2DataTypePolicy, sizeof(EFI_IP4_CONFIG2_POLICY),
|
|
&(EFI_IP4_CONFIG2_POLICY) { Ip4Config2PolicyStatic });
|
|
if (EFI_ERROR(status))
|
|
return (efi_status_to_errno(status));
|
|
|
|
status = ip4config2->SetData(ip4config2,
|
|
Ip4Config2DataTypeManualAddress,
|
|
sizeof(EFI_IP4_CONFIG2_MANUAL_ADDRESS),
|
|
&(EFI_IP4_CONFIG2_MANUAL_ADDRESS) {
|
|
.Address = ipv4->LocalIpAddress,
|
|
.SubnetMask = ipv4->SubnetMask });
|
|
if (EFI_ERROR(status))
|
|
return (efi_status_to_errno(status));
|
|
|
|
if (ipv4->GatewayIpAddress.Addr[0] != 0) {
|
|
status = ip4config2->SetData(ip4config2,
|
|
Ip4Config2DataTypeGateway, sizeof(EFI_IPv4_ADDRESS),
|
|
&ipv4->GatewayIpAddress);
|
|
if (EFI_ERROR(status))
|
|
return (efi_status_to_errno(status));
|
|
}
|
|
|
|
if (dns != NULL) {
|
|
status = ip4config2->SetData(ip4config2,
|
|
Ip4Config2DataTypeDnsServer,
|
|
sizeof(EFI_IPv4_ADDRESS), &dns->DnsServerIp);
|
|
if (EFI_ERROR(status))
|
|
return (efi_status_to_errno(status));
|
|
}
|
|
} else {
|
|
status = ip4config2->SetData(ip4config2,
|
|
Ip4Config2DataTypePolicy, sizeof(EFI_IP4_CONFIG2_POLICY),
|
|
&(EFI_IP4_CONFIG2_POLICY) { Ip4Config2PolicyDhcp });
|
|
if (EFI_ERROR(status))
|
|
return (efi_status_to_errno(status));
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
efihttp_dev_init(void)
|
|
{
|
|
EFI_DEVICE_PATH *imgpath, *devpath;
|
|
URI_DEVICE_PATH *uri;
|
|
EFI_HANDLE handle;
|
|
EFI_STATUS status;
|
|
int err;
|
|
bool found_http;
|
|
|
|
imgpath = efi_lookup_image_devpath(IH);
|
|
if (imgpath == NULL)
|
|
return (ENXIO);
|
|
devpath = imgpath;
|
|
found_http = false;
|
|
for (; !IsDevicePathEnd(devpath);
|
|
devpath = NextDevicePathNode(devpath)) {
|
|
if (DevicePathType(devpath) != MESSAGING_DEVICE_PATH ||
|
|
DevicePathSubType(devpath) != MSG_URI_DP)
|
|
continue;
|
|
uri = (URI_DEVICE_PATH *)devpath;
|
|
if (strncmp("http", (const char *)uri->Uri, 4) == 0)
|
|
found_http = true;
|
|
}
|
|
if (!found_http)
|
|
return (ENXIO);
|
|
|
|
status = BS->LocateDevicePath(&httpsb_guid, &imgpath, &handle);
|
|
if (EFI_ERROR(status))
|
|
return (efi_status_to_errno(status));
|
|
|
|
err = efi_register_handles(&efihttp_dev, &handle, NULL, 1);
|
|
if (!err)
|
|
efihttp_init_done = true;
|
|
|
|
return (err);
|
|
}
|
|
|
|
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)
|
|
{
|
|
return (EIO);
|
|
}
|
|
|
|
static int
|
|
efihttp_dev_open(struct open_file *f, ...)
|
|
{
|
|
EFI_HTTP_CONFIG_DATA config;
|
|
EFI_HTTPv4_ACCESS_POINT config_access;
|
|
DNS_DEVICE_PATH *dns;
|
|
EFI_DEVICE_PATH *devpath, *imgpath;
|
|
EFI_SERVICE_BINDING_PROTOCOL *sb;
|
|
IPv4_DEVICE_PATH *ipv4;
|
|
MAC_ADDR_DEVICE_PATH *mac;
|
|
URI_DEVICE_PATH *uri;
|
|
struct devdesc *dev;
|
|
struct open_efihttp *oh;
|
|
char *c;
|
|
EFI_HANDLE handle;
|
|
EFI_STATUS status;
|
|
int err, len;
|
|
|
|
if (!efihttp_init_done)
|
|
return (ENXIO);
|
|
|
|
imgpath = efi_lookup_image_devpath(IH);
|
|
if (imgpath == NULL)
|
|
return (ENXIO);
|
|
devpath = imgpath;
|
|
status = BS->LocateDevicePath(&httpsb_guid, &devpath, &handle);
|
|
if (EFI_ERROR(status))
|
|
return (efi_status_to_errno(status));
|
|
mac = NULL;
|
|
ipv4 = NULL;
|
|
dns = NULL;
|
|
uri = NULL;
|
|
for (; !IsDevicePathEnd(imgpath);
|
|
imgpath = NextDevicePathNode(imgpath)) {
|
|
if (DevicePathType(imgpath) != MESSAGING_DEVICE_PATH)
|
|
continue;
|
|
switch (DevicePathSubType(imgpath)) {
|
|
case MSG_MAC_ADDR_DP:
|
|
mac = (MAC_ADDR_DEVICE_PATH *)imgpath;
|
|
break;
|
|
case MSG_IPv4_DP:
|
|
ipv4 = (IPv4_DEVICE_PATH *)imgpath;
|
|
break;
|
|
case MSG_DNS_DP:
|
|
dns = (DNS_DEVICE_PATH *)imgpath;
|
|
break;
|
|
case MSG_URI_DP:
|
|
uri = (URI_DEVICE_PATH *)imgpath;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (uri == NULL)
|
|
return (ENXIO);
|
|
|
|
err = setup_ipv4_config2(handle, mac, ipv4, dns);
|
|
if (err)
|
|
return (err);
|
|
|
|
oh = calloc(1, sizeof(struct open_efihttp));
|
|
if (!oh)
|
|
return (ENOMEM);
|
|
oh->dev_handle = handle;
|
|
dev = (struct devdesc *)f->f_devdata;
|
|
dev->d_opendata = oh;
|
|
|
|
status = BS->OpenProtocol(handle, &httpsb_guid, (void **)&sb, IH, NULL,
|
|
EFI_OPEN_PROTOCOL_GET_PROTOCOL);
|
|
if (EFI_ERROR(status)) {
|
|
err = efi_status_to_errno(status);
|
|
goto end;
|
|
}
|
|
|
|
status = sb->CreateChild(sb, &oh->http_handle);
|
|
if (EFI_ERROR(status)) {
|
|
err = efi_status_to_errno(status);
|
|
goto end;
|
|
}
|
|
|
|
status = BS->OpenProtocol(oh->http_handle, &http_guid,
|
|
(void **)&oh->http, IH, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
|
|
if (EFI_ERROR(status)) {
|
|
sb->DestroyChild(sb, oh->http_handle);
|
|
err = efi_status_to_errno(status);
|
|
goto end;
|
|
}
|
|
|
|
config.HttpVersion = HttpVersion11;
|
|
config.TimeOutMillisec = 0;
|
|
config.LocalAddressIsIPv6 = FALSE;
|
|
config.AccessPoint.IPv4Node = &config_access;
|
|
config_access.UseDefaultAddress = TRUE;
|
|
config_access.LocalPort = 0;
|
|
status = oh->http->Configure(oh->http, &config);
|
|
if (EFI_ERROR(status)) {
|
|
sb->DestroyChild(sb, oh->http_handle);
|
|
err = efi_status_to_errno(status);
|
|
goto end;
|
|
}
|
|
|
|
/*
|
|
* Here we make attempt to construct a "base" URI by stripping
|
|
* the last two path components from the loaded URI under the
|
|
* assumption that it is something like:
|
|
*
|
|
* http://127.0.0.1/foo/boot/loader.efi
|
|
*
|
|
* hoping to arriving at:
|
|
*
|
|
* http://127.0.0.1/foo/
|
|
*/
|
|
len = DevicePathNodeLength(&uri->Header) - sizeof(URI_DEVICE_PATH);
|
|
oh->uri_base = malloc(len + 1);
|
|
if (oh->uri_base == NULL) {
|
|
err = ENOMEM;
|
|
goto end;
|
|
}
|
|
strncpy(oh->uri_base, (const char *)uri->Uri, len);
|
|
oh->uri_base[len] = '\0';
|
|
c = strrchr(oh->uri_base, '/');
|
|
if (c != NULL)
|
|
*c = '\0';
|
|
c = strrchr(oh->uri_base, '/');
|
|
if (c != NULL && *(c + 1) != '\0')
|
|
*(c + 1) = '\0';
|
|
|
|
err = 0;
|
|
end:
|
|
if (err != 0) {
|
|
free(dev->d_opendata);
|
|
dev->d_opendata = NULL;
|
|
}
|
|
return (err);
|
|
}
|
|
|
|
static int
|
|
efihttp_dev_close(struct open_file *f)
|
|
{
|
|
EFI_SERVICE_BINDING_PROTOCOL *sb;
|
|
struct devdesc *dev;
|
|
struct open_efihttp *oh;
|
|
EFI_STATUS status;
|
|
|
|
dev = (struct devdesc *)f->f_devdata;
|
|
oh = (struct open_efihttp *)dev->d_opendata;
|
|
status = BS->OpenProtocol(oh->dev_handle, &httpsb_guid, (void **)&sb,
|
|
IH, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
|
|
if (EFI_ERROR(status))
|
|
return (efi_status_to_errno(status));
|
|
sb->DestroyChild(sb, oh->http_handle);
|
|
free(oh->uri_base);
|
|
free(oh);
|
|
dev->d_opendata = NULL;
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
_efihttp_fs_open(const char *path, struct open_file *f)
|
|
{
|
|
EFI_HTTP_CONFIG_DATA config;
|
|
EFI_HTTPv4_ACCESS_POINT config_access;
|
|
EFI_HTTP_TOKEN token;
|
|
EFI_HTTP_MESSAGE message;
|
|
EFI_HTTP_REQUEST_DATA request;
|
|
EFI_HTTP_RESPONSE_DATA response;
|
|
EFI_HTTP_HEADER headers[3];
|
|
char *host, *hostp;
|
|
char *c;
|
|
struct devdesc *dev;
|
|
struct open_efihttp *oh;
|
|
struct file_efihttp *fh;
|
|
EFI_STATUS status;
|
|
UINTN i;
|
|
int polltime;
|
|
bool done;
|
|
|
|
dev = (struct devdesc *)f->f_devdata;
|
|
oh = (struct open_efihttp *)dev->d_opendata;
|
|
fh = calloc(1, sizeof(struct file_efihttp));
|
|
if (fh == NULL)
|
|
return (ENOMEM);
|
|
f->f_fsdata = fh;
|
|
fh->path = strdup(path);
|
|
|
|
/*
|
|
* Reset the HTTP state.
|
|
*
|
|
* EDK II's persistent HTTP connection handling is graceless,
|
|
* assuming that all connections are persistent regardless of
|
|
* any Connection: header or HTTP version reported by the
|
|
* server, and failing to send requests when a more sane
|
|
* implementation would seem to be just reestablishing the
|
|
* closed connection.
|
|
*
|
|
* In the hopes of having some robustness, we indicate to the
|
|
* server that we will close the connection by using a
|
|
* Connection: close header. And then here we manually
|
|
* unconfigure and reconfigure the http instance to force the
|
|
* connection closed.
|
|
*/
|
|
memset(&config, 0, sizeof(config));
|
|
memset(&config_access, 0, sizeof(config_access));
|
|
config.AccessPoint.IPv4Node = &config_access;
|
|
status = oh->http->GetModeData(oh->http, &config);
|
|
if (EFI_ERROR(status))
|
|
return (efi_status_to_errno(status));
|
|
status = oh->http->Configure(oh->http, NULL);
|
|
if (EFI_ERROR(status))
|
|
return (efi_status_to_errno(status));
|
|
status = oh->http->Configure(oh->http, &config);
|
|
if (EFI_ERROR(status))
|
|
return (efi_status_to_errno(status));
|
|
|
|
/* Send the read request */
|
|
done = false;
|
|
status = BS->CreateEvent(EVT_NOTIFY_SIGNAL, TPL_CALLBACK, notify,
|
|
&done, &token.Event);
|
|
if (EFI_ERROR(status))
|
|
return (efi_status_to_errno(status));
|
|
|
|
/* extract the host portion of the URL */
|
|
host = strdup(oh->uri_base);
|
|
if (host == NULL)
|
|
return (ENOMEM);
|
|
hostp = host;
|
|
/* Remove the protocol scheme */
|
|
c = strchr(host, '/');
|
|
if (c != NULL && *(c + 1) == '/')
|
|
hostp = (c + 2);
|
|
|
|
/* Remove any path information */
|
|
c = strchr(hostp, '/');
|
|
if (c != NULL)
|
|
*c = '\0';
|
|
|
|
token.Status = EFI_NOT_READY;
|
|
token.Message = &message;
|
|
message.Data.Request = &request;
|
|
message.HeaderCount = 3;
|
|
message.Headers = headers;
|
|
message.BodyLength = 0;
|
|
message.Body = NULL;
|
|
request.Method = HttpMethodGet;
|
|
request.Url = calloc(strlen(oh->uri_base) + strlen(path) + 1, 2);
|
|
headers[0].FieldName = (CHAR8 *)"Host";
|
|
headers[0].FieldValue = (CHAR8 *)hostp;
|
|
headers[1].FieldName = (CHAR8 *)"Connection";
|
|
headers[1].FieldValue = (CHAR8 *)"close";
|
|
headers[2].FieldName = (CHAR8 *)"Accept";
|
|
headers[2].FieldValue = (CHAR8 *)"*/*";
|
|
cpy8to16(oh->uri_base, request.Url, strlen(oh->uri_base));
|
|
cpy8to16(path, request.Url + strlen(oh->uri_base), strlen(path));
|
|
status = oh->http->Request(oh->http, &token);
|
|
free(request.Url);
|
|
free(host);
|
|
if (EFI_ERROR(status)) {
|
|
BS->CloseEvent(token.Event);
|
|
return (efi_status_to_errno(status));
|
|
}
|
|
|
|
polltime = 0;
|
|
while (!done && polltime < EFIHTTP_POLL_TIMEOUT) {
|
|
status = oh->http->Poll(oh->http);
|
|
if (EFI_ERROR(status))
|
|
break;
|
|
|
|
if (!done) {
|
|
delay(100 * 1000);
|
|
polltime += 100;
|
|
}
|
|
}
|
|
BS->CloseEvent(token.Event);
|
|
if (EFI_ERROR(token.Status))
|
|
return (efi_status_to_errno(token.Status));
|
|
|
|
/* Wait for the read response */
|
|
done = false;
|
|
status = BS->CreateEvent(EVT_NOTIFY_SIGNAL, TPL_CALLBACK, notify,
|
|
&done, &token.Event);
|
|
if (EFI_ERROR(status))
|
|
return (efi_status_to_errno(status));
|
|
token.Status = EFI_NOT_READY;
|
|
token.Message = &message;
|
|
message.Data.Response = &response;
|
|
message.HeaderCount = 0;
|
|
message.Headers = NULL;
|
|
message.BodyLength = 0;
|
|
message.Body = NULL;
|
|
response.StatusCode = HTTP_STATUS_UNSUPPORTED_STATUS;
|
|
status = oh->http->Response(oh->http, &token);
|
|
if (EFI_ERROR(status)) {
|
|
BS->CloseEvent(token.Event);
|
|
return (efi_status_to_errno(status));
|
|
}
|
|
|
|
polltime = 0;
|
|
while (!done && polltime < EFIHTTP_POLL_TIMEOUT) {
|
|
status = oh->http->Poll(oh->http);
|
|
if (EFI_ERROR(status))
|
|
break;
|
|
|
|
if (!done) {
|
|
delay(100 * 1000);
|
|
polltime += 100;
|
|
}
|
|
}
|
|
BS->CloseEvent(token.Event);
|
|
if (EFI_ERROR(token.Status)) {
|
|
BS->FreePool(message.Headers);
|
|
return (efi_status_to_errno(token.Status));
|
|
}
|
|
if (response.StatusCode != HTTP_STATUS_200_OK) {
|
|
BS->FreePool(message.Headers);
|
|
return (EIO);
|
|
}
|
|
fh->size = 0;
|
|
fh->is_dir = false;
|
|
for (i = 0; i < message.HeaderCount; i++) {
|
|
if (strcasecmp((const char *)message.Headers[i].FieldName,
|
|
"Content-Length") == 0)
|
|
fh->size = strtoul((const char *)
|
|
message.Headers[i].FieldValue, NULL, 10);
|
|
else if (strcasecmp((const char *)message.Headers[i].FieldName,
|
|
"Content-type") == 0) {
|
|
if (strncmp((const char *)message.Headers[i].FieldValue,
|
|
"text/html", 9) == 0)
|
|
fh->is_dir = true;
|
|
}
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
efihttp_fs_open(const char *path, struct open_file *f)
|
|
{
|
|
char *path_slash;
|
|
int err;
|
|
|
|
if (!efihttp_init_done)
|
|
return (ENXIO);
|
|
/*
|
|
* If any path fails to open, try with a trailing slash in
|
|
* case it's a directory.
|
|
*/
|
|
err = _efihttp_fs_open(path, f);
|
|
if (err != 0) {
|
|
/*
|
|
* Work around a bug in the EFI HTTP implementation which
|
|
* causes a crash if the http instance isn't torn down
|
|
* between requests.
|
|
* See https://bugzilla.tianocore.org/show_bug.cgi?id=1917
|
|
*/
|
|
efihttp_dev_close(f);
|
|
efihttp_dev_open(f);
|
|
path_slash = malloc(strlen(path) + 2);
|
|
if (path_slash == NULL)
|
|
return (ENOMEM);
|
|
strcpy(path_slash, path);
|
|
strcat(path_slash, "/");
|
|
err = _efihttp_fs_open(path_slash, f);
|
|
free(path_slash);
|
|
}
|
|
return (err);
|
|
}
|
|
|
|
static int
|
|
efihttp_fs_close(struct open_file *f __unused)
|
|
{
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
_efihttp_fs_read(struct open_file *f, void *buf, size_t size, size_t *resid)
|
|
{
|
|
EFI_HTTP_TOKEN token;
|
|
EFI_HTTP_MESSAGE message;
|
|
EFI_STATUS status;
|
|
struct devdesc *dev;
|
|
struct open_efihttp *oh;
|
|
struct file_efihttp *fh;
|
|
bool done;
|
|
int polltime;
|
|
|
|
fh = (struct file_efihttp *)f->f_fsdata;
|
|
|
|
if (fh->size > 0 && fh->offset >= fh->size) {
|
|
if (resid != NULL)
|
|
*resid = size;
|
|
|
|
return 0;
|
|
}
|
|
|
|
dev = (struct devdesc *)f->f_devdata;
|
|
oh = (struct open_efihttp *)dev->d_opendata;
|
|
done = false;
|
|
status = BS->CreateEvent(EVT_NOTIFY_SIGNAL, TPL_CALLBACK, notify,
|
|
&done, &token.Event);
|
|
if (EFI_ERROR(status)) {
|
|
return (efi_status_to_errno(status));
|
|
}
|
|
token.Status = EFI_NOT_READY;
|
|
token.Message = &message;
|
|
message.Data.Request = NULL;
|
|
message.HeaderCount = 0;
|
|
message.Headers = NULL;
|
|
message.BodyLength = size;
|
|
message.Body = buf;
|
|
status = oh->http->Response(oh->http, &token);
|
|
if (status == EFI_CONNECTION_FIN) {
|
|
if (resid)
|
|
*resid = size;
|
|
return (0);
|
|
} else if (EFI_ERROR(status)) {
|
|
BS->CloseEvent(token.Event);
|
|
return (efi_status_to_errno(status));
|
|
}
|
|
polltime = 0;
|
|
while (!done && polltime < EFIHTTP_POLL_TIMEOUT) {
|
|
status = oh->http->Poll(oh->http);
|
|
if (EFI_ERROR(status))
|
|
break;
|
|
|
|
if (!done) {
|
|
delay(100 * 1000);
|
|
polltime += 100;
|
|
}
|
|
}
|
|
BS->CloseEvent(token.Event);
|
|
if (token.Status == EFI_CONNECTION_FIN) {
|
|
if (resid)
|
|
*resid = size;
|
|
return (0);
|
|
} else if (EFI_ERROR(token.Status))
|
|
return (efi_status_to_errno(token.Status));
|
|
if (resid)
|
|
*resid = size - message.BodyLength;
|
|
fh->offset += message.BodyLength;
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
efihttp_fs_read(struct open_file *f, void *buf, size_t size, size_t *resid)
|
|
{
|
|
size_t res;
|
|
int err = 0;
|
|
|
|
while (size > 0) {
|
|
err = _efihttp_fs_read(f, buf, size, &res);
|
|
if (err != 0 || res == size)
|
|
goto end;
|
|
buf += (size - res);
|
|
size = res;
|
|
}
|
|
end:
|
|
if (resid)
|
|
*resid = size;
|
|
return (err);
|
|
}
|
|
|
|
static int
|
|
efihttp_fs_write(struct open_file *f __unused, const void *buf __unused,
|
|
size_t size __unused, size_t *resid __unused)
|
|
{
|
|
return (EIO);
|
|
}
|
|
|
|
static off_t
|
|
efihttp_fs_seek(struct open_file *f, off_t offset, int where)
|
|
{
|
|
struct file_efihttp *fh;
|
|
char *path;
|
|
void *buf;
|
|
size_t res, res2;
|
|
int err;
|
|
|
|
fh = (struct file_efihttp *)f->f_fsdata;
|
|
if (where == SEEK_SET && fh->offset == offset)
|
|
return (0);
|
|
if (where == SEEK_SET && fh->offset < offset) {
|
|
buf = malloc(1500);
|
|
if (buf == NULL)
|
|
return (ENOMEM);
|
|
res = offset - fh->offset;
|
|
while (res > 0) {
|
|
err = _efihttp_fs_read(f, buf, min(1500, res), &res2);
|
|
if (err != 0) {
|
|
free(buf);
|
|
return (err);
|
|
}
|
|
res -= min(1500, res) - res2;
|
|
}
|
|
free(buf);
|
|
return (0);
|
|
} else if (where == SEEK_SET) {
|
|
path = fh->path;
|
|
fh->path = NULL;
|
|
efihttp_fs_close(f);
|
|
/*
|
|
* Work around a bug in the EFI HTTP implementation which
|
|
* causes a crash if the http instance isn't torn down
|
|
* between requests.
|
|
* See https://bugzilla.tianocore.org/show_bug.cgi?id=1917
|
|
*/
|
|
efihttp_dev_close(f);
|
|
efihttp_dev_open(f);
|
|
err = efihttp_fs_open(path, f);
|
|
free(path);
|
|
if (err != 0)
|
|
return (err);
|
|
return efihttp_fs_seek(f, offset, where);
|
|
}
|
|
return (EIO);
|
|
}
|
|
|
|
static int
|
|
efihttp_fs_stat(struct open_file *f, struct stat *sb)
|
|
{
|
|
struct file_efihttp *fh;
|
|
|
|
fh = (struct file_efihttp *)f->f_fsdata;
|
|
memset(sb, 0, sizeof(*sb));
|
|
sb->st_nlink = 1;
|
|
sb->st_mode = 0777 | (fh->is_dir ? S_IFDIR : S_IFREG);
|
|
sb->st_size = fh->size;
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
efihttp_fs_readdir(struct open_file *f, struct dirent *d)
|
|
{
|
|
static char *dirbuf = NULL, *db2, *cursor;
|
|
static int dirbuf_len = 0;
|
|
char *end;
|
|
struct file_efihttp *fh;
|
|
|
|
fh = (struct file_efihttp *)f->f_fsdata;
|
|
if (dirbuf_len < fh->size) {
|
|
db2 = realloc(dirbuf, fh->size);
|
|
if (db2 == NULL) {
|
|
free(dirbuf);
|
|
return (ENOMEM);
|
|
} else
|
|
dirbuf = db2;
|
|
|
|
dirbuf_len = fh->size;
|
|
}
|
|
|
|
if (fh->offset != fh->size) {
|
|
efihttp_fs_seek(f, 0, SEEK_SET);
|
|
efihttp_fs_read(f, dirbuf, dirbuf_len, NULL);
|
|
cursor = dirbuf;
|
|
}
|
|
|
|
cursor = strstr(cursor, "<a href=\"");
|
|
if (cursor == NULL)
|
|
return (ENOENT);
|
|
cursor += 9;
|
|
end = strchr(cursor, '"');
|
|
if (*(end - 1) == '/') {
|
|
end--;
|
|
d->d_type = DT_DIR;
|
|
} else
|
|
d->d_type = DT_REG;
|
|
memcpy(d->d_name, cursor, end - cursor);
|
|
d->d_name[end - cursor] = '\0';
|
|
|
|
return (0);
|
|
}
|