| /* |
| * 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 privileges 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 are available when running in BIOS mode")); |
| |
| distrib8_len = grub_strlen (efi_distributor); |
| distributor16 = xcalloc (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 < 0) |
| continue; /* FIXME Should we abort on error? */ |
| 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) |
| continue; /* FIXME Should we abort on error? */ |
| if (current_len == -1) |
| { |
| if (!have_order_num) |
| { |
| order_num = i; |
| have_order_num = 1; |
| grub_util_info ("Creating new entry at Boot%04x", |
| order_num); |
| } |
| continue; |
| } |
| 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 a free BootNNNN slot")); |
| 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")); |
| } |