efi-boot-shim/lib/simple_file.c
Matthew Garrett 17857eb8b5 Port MokManager to Linux Foundation loader UI code
This is the first stage of porting the MokManager UI to the UI code used
by the Linux Foundation UEFI loader.
2013-09-26 11:57:59 -04:00

502 lines
11 KiB
C

/*
* Copyright 2012 <James.Bottomley@HansenPartnership.com>
*
* see COPYING file
*/
#include <efi.h>
#include <efilib.h>
#include <console.h>
#include <simple_file.h>
#include <efiauthenticated.h>
#include <execute.h> /* for generate_path() */
static EFI_GUID IMAGE_PROTOCOL = LOADED_IMAGE_PROTOCOL;
static EFI_GUID SIMPLE_FS_PROTOCOL = SIMPLE_FILE_SYSTEM_PROTOCOL;
static EFI_GUID FILE_INFO = EFI_FILE_INFO_ID;
static EFI_GUID FS_INFO = EFI_FILE_SYSTEM_INFO_ID;
EFI_STATUS
simple_file_open_by_handle(EFI_HANDLE device, CHAR16 *name, EFI_FILE **file, UINT64 mode)
{
EFI_STATUS efi_status;
EFI_FILE_IO_INTERFACE *drive;
EFI_FILE *root;
efi_status = uefi_call_wrapper(BS->HandleProtocol, 3, device,
&SIMPLE_FS_PROTOCOL, &drive);
if (efi_status != EFI_SUCCESS) {
Print(L"Unable to find simple file protocol (%d)\n", efi_status);
goto error;
}
efi_status = uefi_call_wrapper(drive->OpenVolume, 2, drive, &root);
if (efi_status != EFI_SUCCESS) {
Print(L"Failed to open drive volume (%d)\n", efi_status);
goto error;
}
efi_status = uefi_call_wrapper(root->Open, 5, root, file, name,
mode, 0);
error:
return efi_status;
}
EFI_STATUS
simple_file_open(EFI_HANDLE image, CHAR16 *name, EFI_FILE **file, UINT64 mode)
{
EFI_STATUS efi_status;
EFI_HANDLE device;
EFI_LOADED_IMAGE *li;
EFI_DEVICE_PATH *loadpath = NULL;
CHAR16 *PathName = NULL;
efi_status = uefi_call_wrapper(BS->HandleProtocol, 3, image,
&IMAGE_PROTOCOL, &li);
if (efi_status != EFI_SUCCESS)
return simple_file_open_by_handle(image, name, file, mode);
efi_status = generate_path(name, li, &loadpath, &PathName);
if (efi_status != EFI_SUCCESS) {
Print(L"Unable to generate load path for %s\n", name);
return efi_status;
}
device = li->DeviceHandle;
efi_status = simple_file_open_by_handle(device, PathName, file, mode);
FreePool(PathName);
FreePool(loadpath);
return efi_status;
}
EFI_STATUS
simple_dir_read_all_by_handle(EFI_HANDLE image, EFI_FILE *file, CHAR16* name, EFI_FILE_INFO **entries,
int *count)
{
EFI_STATUS status;
char buf[4096];
UINTN size = sizeof(buf);
EFI_FILE_INFO *fi = (void *)buf;
status = uefi_call_wrapper(file->GetInfo, 4, file, &FILE_INFO,
&size, fi);
if (status != EFI_SUCCESS) {
Print(L"Failed to get file info\n");
goto out;
}
if ((fi->Attribute & EFI_FILE_DIRECTORY) == 0) {
Print(L"Not a directory %s\n", name);
status = EFI_INVALID_PARAMETER;
goto out;
}
size = 0;
*count = 0;
for (;;) {
UINTN len = sizeof(buf);
status = uefi_call_wrapper(file->Read, 3, file, &len, buf);
if (status != EFI_SUCCESS || len == 0)
break;
(*count)++;
size += len;
}
uefi_call_wrapper(file->SetPosition, 2, file, 0);
char *ptr = AllocatePool(size);
*entries = (EFI_FILE_INFO *)ptr;
if (!*entries)
return EFI_OUT_OF_RESOURCES;
int i;
for (i = 0; i < *count; i++) {
int len = size;
uefi_call_wrapper(file->Read, 3, file, &len, ptr);
ptr += len;
size -= len;
}
status = EFI_SUCCESS;
out:
simple_file_close(file);
if (status != EFI_SUCCESS && *entries) {
FreePool(*entries);
*entries = NULL;
}
return status;
}
EFI_STATUS
simple_dir_read_all(EFI_HANDLE image, CHAR16 *name, EFI_FILE_INFO **entries,
int *count)
{
EFI_FILE *file;
EFI_STATUS status;
status = simple_file_open(image, name, &file, EFI_FILE_MODE_READ);
if (status != EFI_SUCCESS) {
Print(L"failed to open file %s: %d\n", name, status);
return status;
}
return simple_dir_read_all_by_handle(image, file, name, entries, count);
}
EFI_STATUS
simple_file_read_all(EFI_FILE *file, UINTN *size, void **buffer)
{
EFI_STATUS efi_status;
EFI_FILE_INFO *fi;
char buf[1024];
*size = sizeof(buf);
fi = (void *)buf;
efi_status = uefi_call_wrapper(file->GetInfo, 4, file, &FILE_INFO,
size, fi);
if (efi_status != EFI_SUCCESS) {
Print(L"Failed to get file info\n");
return efi_status;
}
*size = fi->FileSize;
*buffer = AllocatePool(*size);
if (!*buffer) {
Print(L"Failed to allocate buffer of size %d\n", *size);
return EFI_OUT_OF_RESOURCES;
}
efi_status = uefi_call_wrapper(file->Read, 3, file, size, *buffer);
return efi_status;
}
EFI_STATUS
simple_file_write_all(EFI_FILE *file, UINTN size, void *buffer)
{
EFI_STATUS efi_status;
efi_status = uefi_call_wrapper(file->Write, 3, file, &size, buffer);
return efi_status;
}
void
simple_file_close(EFI_FILE *file)
{
uefi_call_wrapper(file->Close, 1, file);
}
EFI_STATUS
simple_volume_selector(CHAR16 **title, CHAR16 **selected, EFI_HANDLE *h)
{
UINTN count, i;
EFI_HANDLE *vol_handles = NULL;
EFI_STATUS status;
CHAR16 **entries;
int val;
uefi_call_wrapper(BS->LocateHandleBuffer, 5, ByProtocol,
&SIMPLE_FS_PROTOCOL, NULL, &count, &vol_handles);
if (!count || !vol_handles)
return EFI_NOT_FOUND;
entries = AllocatePool(sizeof(CHAR16 *) * (count+1));
if (!entries)
return EFI_OUT_OF_RESOURCES;
for (i = 0; i < count; i++) {
char buf[4096];
UINTN size = sizeof(buf);
EFI_FILE_SYSTEM_INFO *fi = (void *)buf;
EFI_FILE *root;
CHAR16 *name;
EFI_FILE_IO_INTERFACE *drive;
status = uefi_call_wrapper(BS->HandleProtocol, 3,
vol_handles[i],
&SIMPLE_FS_PROTOCOL, &drive);
if (status != EFI_SUCCESS || !drive)
continue;
status = uefi_call_wrapper(drive->OpenVolume, 2, drive, &root);
if (status != EFI_SUCCESS)
continue;
status = uefi_call_wrapper(root->GetInfo, 4, root, &FS_INFO,
&size, fi);
if (status != EFI_SUCCESS)
continue;
name = fi->VolumeLabel;
if (!name || StrLen(name) == 0 || StrCmp(name, L" ") == 0)
name = DevicePathToStr(DevicePathFromHandle(vol_handles[i]));
entries[i] = AllocatePool((StrLen(name) + 2) * sizeof(CHAR16));
if (!entries[i])
break;
StrCpy(entries[i], name);
}
entries[i] = NULL;
val = console_select(title, entries, 0);
if (val >= 0) {
*selected = AllocatePool((StrLen(entries[val]) + 1) * sizeof(CHAR16));
if (*selected) {
StrCpy(*selected , entries[val]);
}
*h = vol_handles[val];
} else {
*selected = NULL;
*h = 0;
}
for (i = 0; i < count; i++) {
if (entries[i])
FreePool(entries[i]);
}
FreePool(entries);
FreePool(vol_handles);
return EFI_SUCCESS;
}
EFI_STATUS
simple_dir_filter(EFI_HANDLE image, CHAR16 *name, CHAR16 *filter,
CHAR16 ***result, int *count, EFI_FILE_INFO **entries)
{
EFI_STATUS status;
int tot, offs = StrLen(filter), i, c, filtercount = 1;
EFI_FILE_INFO *next;
void *ptr;
CHAR16 *newfilter = AllocatePool((StrLen(filter) + 1) * sizeof(CHAR16)),
**filterarr;
if (!newfilter)
return EFI_OUT_OF_RESOURCES;
/* just in case efi ever stops writeable strings */
StrCpy(newfilter, filter);
for (i = 0; i < offs; i++) {
if (filter[i] == '|')
filtercount++;
}
filterarr = AllocatePool(filtercount * sizeof(void *));
if (!filterarr)
return EFI_OUT_OF_RESOURCES;
c = 0;
filterarr[c++] = newfilter;
for (i = 0; i < offs; i++) {
if (filter[i] == '|') {
newfilter[i] = '\0';
filterarr[c++] = &newfilter[i+1];
}
}
*count = 0;
status = simple_dir_read_all(image, name, entries, &tot);
if (status != EFI_SUCCESS)
goto out;
ptr = next = *entries;
for (i = 0; i < tot; i++) {
int len = StrLen(next->FileName);
for (c = 0; c < filtercount; c++) {
offs = StrLen(filterarr[c]);
if (StrCmp(&next->FileName[len - offs], filterarr[c]) == 0
|| (next->Attribute & EFI_FILE_DIRECTORY)) {
(*count)++;
break;
}
}
ptr += OFFSET_OF(EFI_FILE_INFO, FileName) + (len + 1)*sizeof(CHAR16);
next = ptr;
}
if (*count)
*result = AllocatePool(((*count) + 1) * sizeof(void *));
else
*result = AllocatePool(2 * sizeof(void *));
*count = 0;
ptr = next = *entries;
for (i = 0; i < tot; i++) {
int len = StrLen(next->FileName);
if (StrCmp(next->FileName, L".") == 0)
/* ignore . directory */
goto next;
if (next->Attribute & EFI_FILE_DIRECTORY) {
(*result)[(*count)] = next->FileName;
(*result)[(*count)][len] = '/';
(*result)[(*count)++][len + 1] = '\0';
goto next;
}
for (c = 0; c < filtercount; c++) {
offs = StrLen(filterarr[c]);
if (StrCmp(&next->FileName[len - offs], filterarr[c]) == 0) {
(*result)[(*count)++] = next->FileName;
} else {
continue;
}
break;
}
next:
if (StrCmp(next->FileName, L"../") == 0) {
/* place .. directory first */
CHAR16 *tmp = (*result)[(*count) - 1];
(*result)[(*count) - 1] = (*result)[0];
(*result)[0] = tmp;
}
ptr += OFFSET_OF(EFI_FILE_INFO, FileName) + (len + 1)*sizeof(CHAR16);
next = ptr;
}
if (*count == 0) {
/* no entries at all ... can happen because top level dir has no . or .. */
(*result)[(*count)++] = L"./";
}
(*result)[*count] = NULL;
status = EFI_SUCCESS;
out:
if (status != EFI_SUCCESS) {
if (*entries)
FreePool(*entries);
*entries = NULL;
if (*result)
FreePool(*result);
*result = NULL;
}
return status;
}
void
simple_file_selector(EFI_HANDLE *im, CHAR16 **title, CHAR16 *name,
CHAR16 *filter, CHAR16 **result)
{
EFI_STATUS status;
CHAR16 **entries;
EFI_FILE_INFO *dmp;
int count, select, len;
CHAR16 *newname, *selected;
*result = NULL;
if (!name)
name = L"\\";
if (!filter)
filter = L"";
if (!*im) {
EFI_HANDLE h;
CHAR16 *volname;
simple_volume_selector(title, &volname, &h);
if (!volname)
return;
FreePool(volname);
*im = h;
}
newname = AllocatePool((StrLen(name) + 1)*sizeof(CHAR16));
if (!newname)
return;
StrCpy(newname, name);
name = newname;
redo:
status = simple_dir_filter(*im, name, filter, &entries, &count, &dmp);
if (status != EFI_SUCCESS)
goto out_free_name;
select = console_select(title, entries, 0);
if (select < 0)
/* ESC key */
goto out_free;
selected = entries[select];
FreePool(entries);
entries = NULL;
/* note that memory used by selected is valid until dmp is freed */
len = StrLen(selected);
if (selected[len - 1] == '/') {
CHAR16 *newname;
/* stay where we are */
if (StrCmp(selected, L"./") == 0) {
FreePool(dmp);
goto redo;
} else if (StrCmp(selected, L"../") == 0) {
int i;
i = StrLen(name) - 1;
for (i = StrLen(name); i > 0; --i) {
if (name[i] == '\\')
break;
}
if (i == 0)
i = 1;
if (StrCmp(name, L"\\") != 0
&& StrCmp(&name[i], L"..") != 0) {
name[i] = '\0';
FreePool(dmp);
goto redo;
}
}
newname = AllocatePool((StrLen(name) + len + 2)*sizeof(CHAR16));
if (!newname)
goto out_free;
StrCpy(newname, name);
if (name[StrLen(name) - 1] != '\\')
StrCat(newname, L"\\");
StrCat(newname, selected);
/* remove trailing / */
newname[StrLen(newname) - 1] = '\0';
FreePool(dmp);
FreePool(name);
name = newname;
goto redo;
}
*result = AllocatePool((StrLen(name) + len + 2)*sizeof(CHAR16));
if (*result) {
StrCpy(*result, name);
if (name[StrLen(name) - 1] != '\\')
StrCat(*result, L"\\");
StrCat(*result, selected);
}
out_free:
FreePool(dmp);
if (entries)
FreePool(entries);
out_free_name:
FreePool(name);
}