498 lines
11 KiB
C
498 lines
11 KiB
C
/* grubberize.c - The elastic binding between grub and standalone EFI */
|
|
/*
|
|
* Copyright © 2014 Pete Batard <pete@akeo.ie>
|
|
* Based on GRUB -- GRand Unified Bootloader
|
|
* Copyright © 2001-2014 Free Software Foundation, Inc.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <grub/err.h>
|
|
#include <grub/misc.h>
|
|
#include <grub/disk.h>
|
|
#include <grub/fs.h>
|
|
#include <grub/dl.h>
|
|
#include <grub/file.h>
|
|
|
|
#include "driver.h"
|
|
|
|
/* The file system list should only ever contain one element */
|
|
grub_fs_t grub_fs_list = NULL;
|
|
|
|
/* Keep track of the mounted filesystems */
|
|
LIST_ENTRY FsListHead;
|
|
|
|
grub_file_filter_t grub_file_filters_all[GRUB_FILE_FILTER_MAX];
|
|
grub_file_filter_t grub_file_filters_enabled[GRUB_FILE_FILTER_MAX];
|
|
|
|
extern EFI_STATUS GrubErrToEFIStatus(grub_err_t err);
|
|
|
|
grub_err_t
|
|
grub_device_close_2(grub_device_t device);
|
|
|
|
|
|
/* Don't care about refcounts for a standalone EFI FS driver */
|
|
int
|
|
grub_dl_ref(grub_dl_t mod) {
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
grub_dl_unref(grub_dl_t mod) {
|
|
return 0;
|
|
};
|
|
|
|
/* The following 3 calls are copied verbatim from the GRUB kernel */
|
|
grub_ssize_t
|
|
grub_file_read (grub_file_t file, void *buf, grub_size_t len)
|
|
{
|
|
grub_ssize_t res;
|
|
grub_disk_read_hook_t read_hook;
|
|
void *read_hook_data;
|
|
|
|
if (file->offset > file->size)
|
|
{
|
|
grub_error (GRUB_ERR_OUT_OF_RANGE,
|
|
N_("attempt to read past the end of file"));
|
|
return -1;
|
|
}
|
|
|
|
if (len == 0)
|
|
return 0;
|
|
|
|
if (len > file->size - file->offset)
|
|
len = file->size - file->offset;
|
|
|
|
/* Prevent an overflow. */
|
|
if ((grub_ssize_t) len < 0)
|
|
len >>= 1;
|
|
|
|
if (len == 0)
|
|
return 0;
|
|
read_hook = file->read_hook;
|
|
read_hook_data = file->read_hook_data;
|
|
if (!file->read_hook)
|
|
{
|
|
file->read_hook = grub_file_progress_hook;
|
|
file->read_hook_data = file;
|
|
file->progress_offset = file->offset;
|
|
}
|
|
res = (file->fs->read) (file, buf, len);
|
|
file->read_hook = read_hook;
|
|
file->read_hook_data = read_hook_data;
|
|
if (res > 0)
|
|
file->offset += res;
|
|
|
|
return res;
|
|
}
|
|
|
|
grub_err_t
|
|
grub_file_close(grub_file_t file)
|
|
{
|
|
if (file->fs->close)
|
|
(file->fs->close) (file);
|
|
|
|
if (file->device)
|
|
grub_device_close_2 (file->device);
|
|
grub_free (file->name);
|
|
grub_free (file);
|
|
return grub_errno;
|
|
}
|
|
|
|
grub_off_t
|
|
grub_file_seek(grub_file_t file, grub_off_t offset)
|
|
{
|
|
grub_off_t old;
|
|
|
|
if (offset > file->size)
|
|
{
|
|
grub_error (GRUB_ERR_OUT_OF_RANGE,
|
|
N_("attempt to seek outside of the file"));
|
|
return -1;
|
|
}
|
|
|
|
old = file->offset;
|
|
file->offset = offset;
|
|
|
|
return old;
|
|
}
|
|
/*
|
|
int
|
|
grub_device_iterate(grub_device_iterate_hook_t hook, void *hook_data)
|
|
{
|
|
PrintError(L"grub_device_iterate() called\n");
|
|
return 0;
|
|
}
|
|
*/
|
|
grub_disk_read_hook_t grub_file_progress_hook = NULL;
|
|
|
|
grub_err_t
|
|
grub_disk_read(grub_disk_t disk, grub_disk_addr_t sector,
|
|
grub_off_t offset, grub_size_t size, void *buf)
|
|
{
|
|
EFI_STATUS Status;
|
|
EFI_FS* FileSystem = (EFI_FS *) disk->data;
|
|
EFI_BLOCK_IO_MEDIA *Media;
|
|
|
|
// ASSERT(FileSystem != NULL);
|
|
// ASSERT(FileSystem->DiskIo != NULL);
|
|
// ASSERT(FileSystem->BlockIo != NULL);
|
|
if ((FileSystem == NULL) ||
|
|
(FileSystem->DiskIo == NULL) ||
|
|
(FileSystem->BlockIo == NULL)) {
|
|
return GRUB_ERR_BAD_ARGUMENT;
|
|
}
|
|
|
|
if (FileSystem->BlockIo2 != NULL)
|
|
{
|
|
Media = FileSystem->BlockIo2->Media;
|
|
} else {
|
|
Media = FileSystem->BlockIo->Media;
|
|
}
|
|
/* NB: We could get the actual blocksize through FileSystem->BlockIo->Media->BlockSize
|
|
* but GRUB uses the fixed GRUB_DISK_SECTOR_SIZE, so we follow suit
|
|
*/
|
|
if (FileSystem->DiskIo2 != NULL)
|
|
{
|
|
Status = FileSystem->DiskIo2->ReadDiskEx(FileSystem->DiskIo2, Media->MediaId,
|
|
sector * GRUB_DISK_SECTOR_SIZE + offset, &(FileSystem->DiskIo2Token), size, buf);
|
|
} else {
|
|
Status = FileSystem->DiskIo->ReadDisk(FileSystem->DiskIo, Media->MediaId,
|
|
sector * GRUB_DISK_SECTOR_SIZE + offset, size, buf);
|
|
}
|
|
|
|
if (EFI_ERROR(Status)) {
|
|
PrintStatusError(Status, L"Could not read block at address %08x", sector);
|
|
return GRUB_ERR_READ_ERROR;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
grub_uint64_t
|
|
grub_disk_get_size (grub_disk_t disk)
|
|
{
|
|
EFI_FS* FileSystem = (EFI_FS *) disk->data;
|
|
|
|
// ASSERT(FileSystem != NULL);
|
|
// ASSERT(FileSystem->BlockIo != NULL);
|
|
if ((FileSystem == NULL) ||
|
|
(FileSystem->BlockIo == NULL)) {
|
|
return GRUB_ERR_BAD_ARGUMENT;
|
|
}
|
|
|
|
|
|
if (FileSystem->BlockIo2 != NULL)
|
|
{
|
|
return (FileSystem->BlockIo2->Media->LastBlock + 1) *
|
|
FileSystem->BlockIo2->Media->BlockSize;
|
|
}
|
|
|
|
return (FileSystem->BlockIo->Media->LastBlock + 1) *
|
|
FileSystem->BlockIo->Media->BlockSize;
|
|
}
|
|
|
|
grub_device_t
|
|
grub_device_open_2(const char *name)
|
|
{
|
|
CHAR16 *Name = Utf8ToUtf16Alloc((CHAR8 *) name);
|
|
struct grub_device* device;
|
|
EFI_FS *FileSystem;
|
|
|
|
// ASSERT(name != NULL);
|
|
|
|
if (Name == NULL) {
|
|
if (LogLevel > FS_LOGLEVEL_ERROR)
|
|
grub_printf("Could not convert device '%s' to UTF-16\n", name);
|
|
return NULL;
|
|
}
|
|
for (FileSystem = (EFI_FS *) FsListHead.ForwardLink; FileSystem != (EFI_FS *) &FsListHead;
|
|
FileSystem = (EFI_FS *) FileSystem->ForwardLink) {
|
|
if (StrCmp(FileSystem->DevicePathString, Name) == 0)
|
|
break;
|
|
}
|
|
|
|
if (Name != NULL)
|
|
{
|
|
FreePool(Name);
|
|
Name = NULL;
|
|
}
|
|
|
|
if (FileSystem == (EFI_FS *) &FsListHead)
|
|
return NULL;
|
|
|
|
device = grub_zalloc(sizeof(struct grub_device));
|
|
if (device == NULL)
|
|
return NULL;
|
|
device->disk = grub_zalloc(sizeof(struct grub_disk));
|
|
if (device->disk == NULL) {
|
|
grub_free(device);
|
|
return NULL;
|
|
}
|
|
/* The private disk data is a pointer back to our EFI_FS */
|
|
device->disk->data = (void *) FileSystem;
|
|
/* Ideally, we'd fill the other disk data, such as total_sectors, name
|
|
* and so on, but since we're doing the actual disk access through EFI
|
|
* DiskIO rather than GRUB's disk.c, this doesn't seem to be needed.
|
|
*/
|
|
|
|
return device;
|
|
}
|
|
|
|
grub_err_t
|
|
grub_device_close_2(grub_device_t device)
|
|
{
|
|
// ASSERT(device != NULL);
|
|
if (device != NULL) {
|
|
grub_free(device->disk);
|
|
grub_free(device);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
EFI_STATUS
|
|
GrubDeviceInit(EFI_FS *FileSystem)
|
|
{
|
|
CHAR8 *name = Utf16ToUtf8Alloc(FileSystem->DevicePathString);
|
|
|
|
if (name == NULL)
|
|
return EFI_OUT_OF_RESOURCES;
|
|
|
|
/* Insert this filesystem in our list */
|
|
InsertTailList(&FsListHead, (LIST_ENTRY *) FileSystem);
|
|
|
|
FileSystem->GrubDevice = (VOID *) grub_device_open_2((const char *) name);
|
|
|
|
if (name != NULL)
|
|
{
|
|
FreePool(name);
|
|
name = NULL;
|
|
}
|
|
|
|
if (FileSystem->GrubDevice == NULL) {
|
|
RemoveEntryList((LIST_ENTRY *)FileSystem);
|
|
return EFI_NOT_FOUND;
|
|
}
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
EFI_STATUS
|
|
GrubDeviceExit(EFI_FS *FileSystem)
|
|
{
|
|
grub_device_close_2((grub_device_t) FileSystem->GrubDevice);
|
|
RemoveEntryList((LIST_ENTRY *)FileSystem);
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
EFI_STATUS
|
|
GrubCreateFile(EFI_GRUB_FILE **File, EFI_FS *FileSystem)
|
|
{
|
|
EFI_GRUB_FILE *NewFile;
|
|
grub_file_t f;
|
|
|
|
NewFile = AllocateZeroPool(sizeof(*NewFile));
|
|
if (NewFile == NULL)
|
|
return EFI_OUT_OF_RESOURCES;
|
|
NewFile->GrubFile = AllocateZeroPool(sizeof(*f));
|
|
if (NewFile->GrubFile == NULL) {
|
|
if (NewFile != NULL)
|
|
{
|
|
FreePool(NewFile);
|
|
NewFile = NULL;
|
|
}
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
/* Initialize the attributes */
|
|
NewFile->FileSystem = FileSystem;
|
|
CopyMem(&NewFile->EfiFile, &FileSystem->RootFile->EfiFile, sizeof(EFI_FILE));
|
|
|
|
f = (grub_file_t) NewFile->GrubFile;
|
|
f->device = (grub_device_t) FileSystem->GrubDevice;
|
|
f->fs = grub_fs_list;
|
|
|
|
*File = NewFile;
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
VOID
|
|
GrubDestroyFile(EFI_GRUB_FILE *File)
|
|
{
|
|
if (File->GrubFile != NULL)
|
|
{
|
|
FreePool(File->GrubFile);
|
|
File->GrubFile = NULL;
|
|
}
|
|
|
|
if (File != NULL)
|
|
{
|
|
FreePool(File);
|
|
File = NULL;
|
|
}
|
|
}
|
|
|
|
UINT64
|
|
GrubGetFileSize(EFI_GRUB_FILE *File)
|
|
{
|
|
grub_file_t f = (grub_file_t) File->GrubFile;
|
|
return (UINT64) f->size;
|
|
}
|
|
|
|
UINT64
|
|
GrubGetFileOffset(EFI_GRUB_FILE *File)
|
|
{
|
|
grub_file_t f = (grub_file_t) File->GrubFile;
|
|
return (UINT64) f->offset;
|
|
}
|
|
|
|
VOID GrubSetFileOffset(EFI_GRUB_FILE *File, UINT64 Offset)
|
|
{
|
|
grub_file_t f = (grub_file_t) File->GrubFile;
|
|
f->offset = (grub_off_t) Offset;
|
|
}
|
|
|
|
/*
|
|
* The following provides an EFI interface for each basic GRUB fs call
|
|
*/
|
|
EFI_STATUS
|
|
GrubDir(EFI_GRUB_FILE *File, const CHAR8 *path,
|
|
GRUB_DIRHOOK Hook, VOID *HookData)
|
|
{
|
|
grub_fs_t p = grub_fs_list;
|
|
grub_file_t f = (grub_file_t) File->GrubFile;
|
|
grub_err_t rc;
|
|
|
|
grub_errno = 0;
|
|
rc = p->dir(f->device, path, (grub_fs_dir_hook_t) Hook, HookData);
|
|
return GrubErrToEFIStatus(rc);
|
|
}
|
|
|
|
EFI_STATUS
|
|
GrubOpen(EFI_GRUB_FILE *File)
|
|
{
|
|
grub_fs_t p = grub_fs_list;
|
|
grub_file_t f = (grub_file_t) File->GrubFile;
|
|
grub_err_t rc;
|
|
|
|
grub_errno = 0;
|
|
rc = p->open(f, File->path);
|
|
return GrubErrToEFIStatus(rc);
|
|
}
|
|
|
|
VOID
|
|
GrubClose(EFI_GRUB_FILE *File)
|
|
{
|
|
grub_fs_t p = grub_fs_list;
|
|
grub_file_t f = (grub_file_t) File->GrubFile;
|
|
|
|
grub_errno = 0;
|
|
p->close(f);
|
|
}
|
|
|
|
EFI_STATUS
|
|
GrubRead(EFI_GRUB_FILE *File, VOID *Data, UINTN *Len)
|
|
{
|
|
grub_fs_t p = grub_fs_list;
|
|
grub_file_t f = (grub_file_t) File->GrubFile;
|
|
grub_ssize_t len;
|
|
INTN Remaining;
|
|
|
|
/* GRUB may return an error if we request more data than available */
|
|
Remaining = f->size - f->offset;
|
|
|
|
if ( Remaining < 0 ) {
|
|
*Len = 0;
|
|
}else if (*Len > (UINTN)Remaining) {
|
|
*Len = Remaining;
|
|
}
|
|
|
|
len = p->read(f, (char *) Data, *Len);
|
|
|
|
if (len < 0) {
|
|
*Len = 0;
|
|
return GrubErrToEFIStatus(grub_errno);
|
|
}
|
|
|
|
/* You'd think that GRUB read() would increase the offset... */
|
|
f->offset += len;
|
|
*Len = len;
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
EFI_STATUS
|
|
GrubLabel(EFI_GRUB_FILE *File, CHAR8 **label)
|
|
{
|
|
grub_fs_t p = grub_fs_list;
|
|
grub_file_t f = (grub_file_t) File->GrubFile;
|
|
grub_err_t rc;
|
|
|
|
grub_errno = 0;
|
|
rc = p->label(f->device, (char **) label);
|
|
return GrubErrToEFIStatus(rc);
|
|
}
|
|
|
|
/* Helper for GrubFSProbe. */
|
|
static int
|
|
probe_dummy_iter (const char *filename __attribute__ ((unused)),
|
|
const struct grub_dirhook_info *info __attribute__ ((unused)),
|
|
void *data __attribute__ ((unused)))
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
BOOLEAN
|
|
GrubFSProbe(EFI_FS *FileSystem)
|
|
{
|
|
grub_fs_t p = grub_fs_list;
|
|
grub_device_t device = (grub_device_t) FileSystem->GrubDevice;
|
|
|
|
if ((p == NULL) || (device->disk == NULL)) {
|
|
PrintError(L"GrubFSProbe: uninitialized variables\n");
|
|
return FALSE;
|
|
}
|
|
|
|
grub_errno = 0;
|
|
(p->dir)(device, "/", probe_dummy_iter, NULL);
|
|
if (grub_errno != 0) {
|
|
if (LogLevel >= FS_LOGLEVEL_INFO)
|
|
grub_print_error(); /* NB: this call will reset grub_errno */
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
CHAR16 *
|
|
GrubGetUuid(EFI_FS* FileSystem)
|
|
{
|
|
EFI_STATUS Status;
|
|
grub_fs_t p = grub_fs_list;
|
|
grub_device_t device = (grub_device_t) FileSystem->GrubDevice;
|
|
static CHAR16 Uuid[36];
|
|
char* uuid;
|
|
|
|
if (p->uuid(device, &uuid) || (uuid == NULL))
|
|
return NULL;
|
|
|
|
Status = Utf8ToUtf16NoAlloc(uuid, Uuid, ARRAYSIZE(Uuid));
|
|
if (EFI_ERROR(Status)) {
|
|
PrintStatusError(Status, L"Could not convert UUID to UTF-16");
|
|
return NULL;
|
|
}
|
|
|
|
return Uuid;
|
|
}
|