417 lines
12 KiB
C
417 lines
12 KiB
C
/*
|
|
* GRUB -- GRand Unified Bootloader
|
|
* Copyright (C) 2013 Free Software Foundation, Inc.
|
|
*
|
|
* GRUB 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.
|
|
*
|
|
* GRUB 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 GRUB. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <config-util.h>
|
|
|
|
#include <windows.h>
|
|
#include <grub/util/install.h>
|
|
#include <grub/util/misc.h>
|
|
#include <grub/efi/api.h>
|
|
#include <grub/charset.h>
|
|
#include <grub/gpt_partition.h>
|
|
|
|
#define GRUB_EFI_GLOBAL_VARIABLE_GUID_WINDOWS_STR L"{8be4df61-93ca-11d2-aa0d-00e098032b8c}"
|
|
|
|
static enum { PLAT_UNK, PLAT_BIOS, PLAT_EFI } platform;
|
|
static DWORD (WINAPI * func_GetFirmwareEnvironmentVariableW) (LPCWSTR lpName,
|
|
LPCWSTR lpGuid,
|
|
PVOID pBuffer,
|
|
DWORD nSize);
|
|
static BOOL (WINAPI * func_SetFirmwareEnvironmentVariableW) (LPCWSTR lpName,
|
|
LPCWSTR lpGuid,
|
|
PVOID pBuffer,
|
|
DWORD nSize);
|
|
static void (WINAPI * func_GetNativeSystemInfo) (LPSYSTEM_INFO lpSystemInfo);
|
|
|
|
static int
|
|
get_efi_privilegies (void)
|
|
{
|
|
int ret = 1;
|
|
HANDLE hSelf;
|
|
TOKEN_PRIVILEGES tkp;
|
|
|
|
if (!OpenProcessToken (GetCurrentProcess(),
|
|
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hSelf))
|
|
return 0;
|
|
|
|
LookupPrivilegeValue (NULL, SE_SYSTEM_ENVIRONMENT_NAME,
|
|
&tkp.Privileges[0].Luid);
|
|
tkp.PrivilegeCount = 1;
|
|
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
|
|
if (!AdjustTokenPrivileges (hSelf, FALSE, &tkp, 0, NULL, 0))
|
|
ret = 0;
|
|
if (GetLastError () != ERROR_SUCCESS)
|
|
ret = 0;
|
|
CloseHandle (hSelf);
|
|
return 1;
|
|
}
|
|
|
|
static void
|
|
get_platform (void)
|
|
{
|
|
HMODULE kernel32;
|
|
char buffer[256];
|
|
|
|
if (platform != PLAT_UNK)
|
|
return;
|
|
|
|
kernel32 = GetModuleHandle(TEXT("kernel32.dll"));
|
|
if (!kernel32)
|
|
{
|
|
platform = PLAT_BIOS;
|
|
return;
|
|
}
|
|
|
|
func_GetFirmwareEnvironmentVariableW = (void *)
|
|
GetProcAddress (kernel32, "GetFirmwareEnvironmentVariableW");
|
|
func_SetFirmwareEnvironmentVariableW = (void *)
|
|
GetProcAddress (kernel32, "SetFirmwareEnvironmentVariableW");
|
|
func_GetNativeSystemInfo = (void *)
|
|
GetProcAddress (kernel32, "GetNativeSystemInfo");
|
|
if (!func_GetNativeSystemInfo)
|
|
func_GetNativeSystemInfo = GetSystemInfo;
|
|
if (!func_GetFirmwareEnvironmentVariableW
|
|
|| !func_SetFirmwareEnvironmentVariableW)
|
|
{
|
|
platform = PLAT_BIOS;
|
|
return;
|
|
}
|
|
|
|
if (!get_efi_privilegies ())
|
|
{
|
|
grub_util_warn ("Insufficient privilegies to access firmware, assuming BIOS");
|
|
platform = PLAT_BIOS;
|
|
}
|
|
|
|
if (!func_GetFirmwareEnvironmentVariableW (L"BootOrder", GRUB_EFI_GLOBAL_VARIABLE_GUID_WINDOWS_STR,
|
|
buffer, sizeof (buffer))
|
|
&& GetLastError () != ERROR_INVALID_FUNCTION)
|
|
{
|
|
platform = PLAT_BIOS;
|
|
return;
|
|
}
|
|
platform = PLAT_EFI;
|
|
return;
|
|
}
|
|
|
|
const char *
|
|
grub_install_get_default_x86_platform (void)
|
|
{
|
|
SYSTEM_INFO si;
|
|
|
|
get_platform ();
|
|
if (platform != PLAT_EFI)
|
|
return "i386-pc";
|
|
|
|
/* EFI */
|
|
/* Assume 64-bit in case of failure. */
|
|
si.wProcessorArchitecture = PROCESSOR_ARCHITECTURE_AMD64;
|
|
func_GetNativeSystemInfo (&si);
|
|
if (si.wProcessorArchitecture != PROCESSOR_ARCHITECTURE_INTEL)
|
|
return "x86_64-efi";
|
|
else
|
|
return "i386-efi";
|
|
}
|
|
|
|
static void *
|
|
get_efi_variable (const wchar_t *varname, ssize_t *len)
|
|
{
|
|
void *ret = NULL;
|
|
size_t alloc_size = 256, read_size;
|
|
get_platform ();
|
|
while (1)
|
|
{
|
|
DWORD err;
|
|
ret = xmalloc (alloc_size);
|
|
read_size = func_GetFirmwareEnvironmentVariableW (varname, GRUB_EFI_GLOBAL_VARIABLE_GUID_WINDOWS_STR,
|
|
ret, alloc_size);
|
|
err = GetLastError ();
|
|
if (read_size)
|
|
{
|
|
*len = read_size;
|
|
return ret;
|
|
}
|
|
if (err == ERROR_INSUFFICIENT_BUFFER
|
|
&& alloc_size * 2 != 0)
|
|
{
|
|
alloc_size *= 2;
|
|
free (ret);
|
|
continue;
|
|
}
|
|
if (err == ERROR_ENVVAR_NOT_FOUND)
|
|
{
|
|
*len = -1;
|
|
return NULL;
|
|
}
|
|
*len = -2;
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
static void
|
|
set_efi_variable (const wchar_t *varname, void *in, grub_size_t len)
|
|
{
|
|
get_platform ();
|
|
func_SetFirmwareEnvironmentVariableW (varname, GRUB_EFI_GLOBAL_VARIABLE_GUID_WINDOWS_STR,
|
|
in, len);
|
|
}
|
|
|
|
static char
|
|
bin2hex (int v)
|
|
{
|
|
if (v < 10)
|
|
return '0' + v;
|
|
return 'A' + v - 10;
|
|
}
|
|
|
|
static void *
|
|
get_efi_variable_bootn (grub_uint16_t n, ssize_t *len)
|
|
{
|
|
wchar_t varname[20] = L"Boot0000";
|
|
varname[7] = bin2hex (n & 0xf);
|
|
varname[6] = bin2hex ((n >> 4) & 0xf);
|
|
varname[5] = bin2hex ((n >> 8) & 0xf);
|
|
varname[4] = bin2hex ((n >> 12) & 0xf);
|
|
return get_efi_variable (varname, len);
|
|
}
|
|
|
|
static void
|
|
set_efi_variable_bootn (grub_uint16_t n, void *in, grub_size_t len)
|
|
{
|
|
wchar_t varname[20] = L"Boot0000";
|
|
varname[7] = bin2hex (n & 0xf);
|
|
varname[6] = bin2hex ((n >> 4) & 0xf);
|
|
varname[5] = bin2hex ((n >> 8) & 0xf);
|
|
varname[4] = bin2hex ((n >> 12) & 0xf);
|
|
set_efi_variable (varname, in, len);
|
|
}
|
|
|
|
void
|
|
grub_install_register_efi (grub_device_t efidir_grub_dev,
|
|
const char *efifile_path,
|
|
const char *efi_distributor)
|
|
{
|
|
grub_uint16_t *boot_order, *new_boot_order;
|
|
grub_uint16_t *distributor16;
|
|
grub_uint8_t *entry;
|
|
grub_size_t distrib8_len, distrib16_len, path16_len, path8_len;
|
|
ssize_t boot_order_len, new_boot_order_len;
|
|
grub_uint16_t order_num = 0;
|
|
int have_order_num = 0;
|
|
grub_size_t max_path_length;
|
|
grub_uint8_t *path;
|
|
void *pathptr;
|
|
struct grub_efi_hard_drive_device_path *hddp;
|
|
struct grub_efi_file_path_device_path *filep;
|
|
struct grub_efi_device_path *endp;
|
|
|
|
get_platform ();
|
|
if (platform != PLAT_EFI)
|
|
grub_util_error ("%s", "no EFI routines when running in BIOS mode");
|
|
|
|
distrib8_len = grub_strlen (efi_distributor);
|
|
distributor16 = xmalloc ((distrib8_len + 1) * GRUB_MAX_UTF16_PER_UTF8
|
|
* sizeof (grub_uint16_t));
|
|
distrib16_len = grub_utf8_to_utf16 (distributor16, distrib8_len * GRUB_MAX_UTF16_PER_UTF8,
|
|
(const grub_uint8_t *) efi_distributor,
|
|
distrib8_len, 0);
|
|
distributor16[distrib16_len] = 0;
|
|
|
|
/* Windows doesn't allow to list variables so first look for bootorder to
|
|
find if there is an entry from the same distributor. If not try sequentially
|
|
until we find same distributor or empty spot. */
|
|
boot_order = get_efi_variable (L"BootOrder", &boot_order_len);
|
|
if (boot_order_len < -1)
|
|
grub_util_error ("%s", "unexpected EFI error");
|
|
if (boot_order_len > 0)
|
|
{
|
|
size_t i;
|
|
for (i = 0; i < boot_order_len / 2; i++)
|
|
{
|
|
void *current = NULL;
|
|
ssize_t current_len;
|
|
current = get_efi_variable_bootn (i, ¤t_len);
|
|
if (current_len < (distrib16_len + 1) * sizeof (grub_uint16_t)
|
|
+ 6)
|
|
{
|
|
grub_free (current);
|
|
continue;
|
|
}
|
|
if (grub_memcmp ((grub_uint16_t *) current + 3,
|
|
distributor16,
|
|
(distrib16_len + 1) * sizeof (grub_uint16_t)) != 0)
|
|
{
|
|
grub_free (current);
|
|
continue;
|
|
}
|
|
order_num = i;
|
|
have_order_num = 1;
|
|
grub_util_info ("Found matching distributor at Boot%04x",
|
|
order_num);
|
|
grub_free (current);
|
|
break;
|
|
}
|
|
}
|
|
if (!have_order_num)
|
|
{
|
|
size_t i;
|
|
for (i = 0; i < 0x10000; i++)
|
|
{
|
|
void *current = NULL;
|
|
ssize_t current_len;
|
|
current = get_efi_variable_bootn (i, ¤t_len);
|
|
if (current_len == -1)
|
|
{
|
|
order_num = i;
|
|
have_order_num = 1;
|
|
grub_util_info ("Creating new entry at Boot%04x",
|
|
order_num);
|
|
break;
|
|
}
|
|
if (current_len < (distrib16_len + 1) * sizeof (grub_uint16_t)
|
|
+ 6)
|
|
{
|
|
grub_free (current);
|
|
continue;
|
|
}
|
|
if (grub_memcmp ((grub_uint16_t *) current + 3,
|
|
distributor16,
|
|
(distrib16_len + 1) * sizeof (grub_uint16_t)) != 0)
|
|
{
|
|
grub_free (current);
|
|
continue;
|
|
}
|
|
order_num = i;
|
|
have_order_num = 1;
|
|
grub_util_info ("Found matching distributor at Boot%04x",
|
|
order_num);
|
|
grub_free (current);
|
|
break;
|
|
}
|
|
}
|
|
if (!have_order_num)
|
|
grub_util_error ("%s", "Couldn't find free BootNNNN spot");
|
|
path8_len = grub_strlen (efifile_path);
|
|
max_path_length = sizeof (*hddp) + sizeof (*filep) + (path8_len * GRUB_MAX_UTF16_PER_UTF8 + 1) * sizeof (grub_uint16_t) + sizeof (*endp);
|
|
entry = xmalloc (6 + (distrib16_len + 1) * sizeof (grub_uint16_t) + max_path_length);
|
|
/* attributes: active. */
|
|
entry[0] = 1;
|
|
entry[1] = 0;
|
|
entry[2] = 0;
|
|
entry[3] = 0;
|
|
grub_memcpy (entry + 6,
|
|
distributor16,
|
|
(distrib16_len + 1) * sizeof (grub_uint16_t));
|
|
|
|
path = entry + 6 + (distrib16_len + 1) * sizeof (grub_uint16_t);
|
|
pathptr = path;
|
|
|
|
hddp = pathptr;
|
|
grub_memset (hddp, 0, sizeof (*hddp));
|
|
hddp->header.type = GRUB_EFI_MEDIA_DEVICE_PATH_TYPE;
|
|
hddp->header.subtype = GRUB_EFI_HARD_DRIVE_DEVICE_PATH_SUBTYPE;
|
|
hddp->header.length = sizeof (*hddp);
|
|
hddp->partition_number = efidir_grub_dev->disk->partition ? efidir_grub_dev->disk->partition->number + 1 : 1;
|
|
if (efidir_grub_dev->disk->partition
|
|
&& grub_strcmp (efidir_grub_dev->disk->partition->partmap->name, "msdos") == 0)
|
|
{
|
|
grub_partition_t p;
|
|
|
|
p = efidir_grub_dev->disk->partition;
|
|
efidir_grub_dev->disk->partition = p->parent;
|
|
if (grub_disk_read (efidir_grub_dev->disk, 0, 440,
|
|
4, hddp->partition_signature))
|
|
grub_util_error ("%s", grub_errmsg);
|
|
efidir_grub_dev->disk->partition = p;
|
|
|
|
hddp->partmap_type = 1;
|
|
hddp->signature_type = 1;
|
|
}
|
|
else if (efidir_grub_dev->disk->partition
|
|
&& grub_strcmp (efidir_grub_dev->disk->partition->partmap->name, "gpt") == 0)
|
|
{
|
|
struct grub_gpt_partentry gptdata;
|
|
grub_partition_t p;
|
|
|
|
p = efidir_grub_dev->disk->partition;
|
|
efidir_grub_dev->disk->partition = p->parent;
|
|
if (grub_disk_read (efidir_grub_dev->disk,
|
|
p->offset, p->index,
|
|
sizeof (gptdata), &gptdata))
|
|
grub_util_error ("%s", grub_errmsg);
|
|
efidir_grub_dev->disk->partition = p;
|
|
grub_memcpy (hddp->partition_signature,
|
|
gptdata.guid, 16);
|
|
|
|
hddp->partmap_type = 2;
|
|
hddp->signature_type = 2;
|
|
}
|
|
|
|
hddp->partition_start = grub_partition_get_start (efidir_grub_dev->disk->partition)
|
|
<< (efidir_grub_dev->disk->log_sector_size - GRUB_DISK_SECTOR_BITS);
|
|
hddp->partition_size = grub_disk_get_size (efidir_grub_dev->disk)
|
|
<< (efidir_grub_dev->disk->log_sector_size - GRUB_DISK_SECTOR_BITS);
|
|
|
|
pathptr = hddp + 1;
|
|
filep = pathptr;
|
|
filep->header.type = GRUB_EFI_MEDIA_DEVICE_PATH_TYPE;
|
|
filep->header.subtype = GRUB_EFI_FILE_PATH_DEVICE_PATH_SUBTYPE;
|
|
|
|
path16_len = grub_utf8_to_utf16 (filep->path_name,
|
|
path8_len * GRUB_MAX_UTF16_PER_UTF8,
|
|
(const grub_uint8_t *) efifile_path,
|
|
path8_len, 0);
|
|
filep->path_name[path16_len] = 0;
|
|
filep->header.length = sizeof (*filep) + (path16_len + 1) * sizeof (grub_uint16_t);
|
|
pathptr = &filep->path_name[path16_len + 1];
|
|
endp = pathptr;
|
|
endp->type = GRUB_EFI_END_DEVICE_PATH_TYPE;
|
|
endp->subtype = GRUB_EFI_END_ENTIRE_DEVICE_PATH_SUBTYPE;
|
|
endp->length = sizeof (*endp);
|
|
pathptr = endp + 1;
|
|
|
|
entry[4] = (grub_uint8_t *) pathptr - path;
|
|
entry[5] = ((grub_uint8_t *) pathptr - path) >> 8;
|
|
|
|
new_boot_order = xmalloc ((boot_order_len > 0 ? boot_order_len : 0) + 2);
|
|
new_boot_order[0] = order_num;
|
|
new_boot_order_len = 1;
|
|
{
|
|
ssize_t i;
|
|
for (i = 0; i < boot_order_len / 2; i++)
|
|
if (boot_order[i] != order_num)
|
|
new_boot_order[new_boot_order_len++] = boot_order[i];
|
|
}
|
|
|
|
set_efi_variable_bootn (order_num, entry, (grub_uint8_t *) pathptr - entry);
|
|
set_efi_variable (L"BootOrder", new_boot_order, new_boot_order_len * sizeof (grub_uint16_t));
|
|
}
|
|
|
|
void
|
|
grub_install_register_ieee1275 (int is_prep, const char *install_device,
|
|
int partno, const char *relpath)
|
|
{
|
|
grub_util_error ("%s", "no IEEE1275 routines are available for your platform");
|
|
}
|
|
|
|
void
|
|
grub_install_sgi_setup (const char *install_device,
|
|
const char *imgfile, const char *destname)
|
|
{
|
|
grub_util_error ("%s", "no SGI routines are available for your platform");
|
|
}
|