mirror of
https://git.proxmox.com/git/grub2
synced 2025-07-21 15:01:22 +00:00

The code here is finished with the memory stored in name, but it only frees it if there curvalue is valid, while it could actually free it regardless. The fix is a simple relocation of the grub_free() to before the test of curvalue. Fixes: CID 96646 Signed-off-by: Darren Kenny <darren.kenny@oracle.com> Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
1540 lines
39 KiB
C
1540 lines
39 KiB
C
/* xnu.c - load xnu kernel. Thanks to Florian Idelberger for all the
|
|
time he spent testing this
|
|
*/
|
|
/*
|
|
* GRUB -- GRand Unified Bootloader
|
|
* Copyright (C) 2009 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 <grub/file.h>
|
|
#include <grub/xnu.h>
|
|
#include <grub/cpu/xnu.h>
|
|
#include <grub/mm.h>
|
|
#include <grub/dl.h>
|
|
#include <grub/loader.h>
|
|
#include <grub/machoload.h>
|
|
#include <grub/macho.h>
|
|
#include <grub/cpu/macho.h>
|
|
#include <grub/command.h>
|
|
#include <grub/misc.h>
|
|
#include <grub/extcmd.h>
|
|
#include <grub/env.h>
|
|
#include <grub/i18n.h>
|
|
#include <grub/verify.h>
|
|
#include <grub/safemath.h>
|
|
|
|
GRUB_MOD_LICENSE ("GPLv3+");
|
|
|
|
#if defined (__i386) && !defined (GRUB_MACHINE_EFI)
|
|
#include <grub/autoefi.h>
|
|
#endif
|
|
|
|
struct grub_xnu_devtree_key *grub_xnu_devtree_root = 0;
|
|
static int driverspackagenum = 0;
|
|
static int driversnum = 0;
|
|
int grub_xnu_is_64bit = 0;
|
|
int grub_xnu_darwin_version = 0;
|
|
|
|
grub_addr_t grub_xnu_heap_target_start = 0;
|
|
grub_size_t grub_xnu_heap_size = 0;
|
|
struct grub_relocator *grub_xnu_relocator;
|
|
|
|
static grub_err_t
|
|
grub_xnu_register_memory (const char *prefix, int *suffix,
|
|
grub_addr_t addr, grub_size_t size);
|
|
grub_err_t
|
|
grub_xnu_heap_malloc (int size, void **src, grub_addr_t *target)
|
|
{
|
|
grub_err_t err;
|
|
grub_relocator_chunk_t ch;
|
|
grub_addr_t tgt;
|
|
|
|
if (grub_add (grub_xnu_heap_target_start, grub_xnu_heap_size, &tgt))
|
|
return GRUB_ERR_OUT_OF_RANGE;
|
|
|
|
err = grub_relocator_alloc_chunk_addr (grub_xnu_relocator, &ch, tgt, size);
|
|
if (err)
|
|
return err;
|
|
|
|
*src = get_virtual_current_address (ch);
|
|
*target = tgt;
|
|
grub_xnu_heap_size += size;
|
|
grub_dprintf ("xnu", "val=%p\n", *src);
|
|
return GRUB_ERR_NONE;
|
|
}
|
|
|
|
/* Make sure next block of the heap will be aligned.
|
|
Please notice: aligned are pointers AFTER relocation
|
|
and not the current ones. */
|
|
grub_err_t
|
|
grub_xnu_align_heap (int align)
|
|
{
|
|
grub_xnu_heap_size
|
|
= ALIGN_UP (grub_xnu_heap_target_start+ grub_xnu_heap_size, align)
|
|
- grub_xnu_heap_target_start;
|
|
return GRUB_ERR_NONE;
|
|
}
|
|
|
|
/* Free subtree pointed by CUR. */
|
|
void
|
|
grub_xnu_free_devtree (struct grub_xnu_devtree_key *cur)
|
|
{
|
|
struct grub_xnu_devtree_key *d;
|
|
while (cur)
|
|
{
|
|
grub_free (cur->name);
|
|
if (cur->datasize == -1)
|
|
grub_xnu_free_devtree (cur->first_child);
|
|
else if (cur->data)
|
|
grub_free (cur->data);
|
|
d = cur->next;
|
|
grub_free (cur);
|
|
cur = d;
|
|
}
|
|
}
|
|
|
|
/* Compute the size of device tree in xnu format. */
|
|
static grub_size_t
|
|
grub_xnu_writetree_get_size (struct grub_xnu_devtree_key *start,
|
|
const char *name)
|
|
{
|
|
grub_size_t ret;
|
|
struct grub_xnu_devtree_key *cur;
|
|
|
|
/* Key header. */
|
|
ret = 2 * sizeof (grub_uint32_t);
|
|
|
|
/* "name" value. */
|
|
ret += 32 + sizeof (grub_uint32_t)
|
|
+ grub_strlen (name) + 4
|
|
- (grub_strlen (name) % 4);
|
|
|
|
for (cur = start; cur; cur = cur->next)
|
|
if (cur->datasize != -1)
|
|
{
|
|
int align_overhead;
|
|
|
|
align_overhead = 4 - (cur->datasize % 4);
|
|
if (align_overhead == 4)
|
|
align_overhead = 0;
|
|
ret += 32 + sizeof (grub_uint32_t) + cur->datasize + align_overhead;
|
|
}
|
|
else
|
|
ret += grub_xnu_writetree_get_size (cur->first_child, cur->name);
|
|
return ret;
|
|
}
|
|
|
|
/* Write devtree in XNU format at curptr assuming the head is named NAME.*/
|
|
static void *
|
|
grub_xnu_writetree_toheap_real (void *curptr,
|
|
struct grub_xnu_devtree_key *start,
|
|
const char *name)
|
|
{
|
|
struct grub_xnu_devtree_key *cur;
|
|
int nkeys = 0, nvals = 0;
|
|
for (cur = start; cur; cur = cur->next)
|
|
{
|
|
if (cur->datasize == -1)
|
|
nkeys++;
|
|
else
|
|
nvals++;
|
|
}
|
|
/* For the name. */
|
|
nvals++;
|
|
|
|
*((grub_uint32_t *) curptr) = nvals;
|
|
curptr = ((grub_uint32_t *) curptr) + 1;
|
|
*((grub_uint32_t *) curptr) = nkeys;
|
|
curptr = ((grub_uint32_t *) curptr) + 1;
|
|
|
|
/* First comes "name" value. */
|
|
grub_memset (curptr, 0, 32);
|
|
grub_memcpy (curptr, "name", 4);
|
|
curptr = ((grub_uint8_t *) curptr) + 32;
|
|
*((grub_uint32_t *)curptr) = grub_strlen (name) + 1;
|
|
curptr = ((grub_uint32_t *) curptr) + 1;
|
|
grub_memcpy (curptr, name, grub_strlen (name));
|
|
curptr = ((grub_uint8_t *) curptr) + grub_strlen (name);
|
|
grub_memset (curptr, 0, 4 - (grub_strlen (name) % 4));
|
|
curptr = ((grub_uint8_t *) curptr) + (4 - (grub_strlen (name) % 4));
|
|
|
|
/* Then the other values. */
|
|
for (cur = start; cur; cur = cur->next)
|
|
if (cur->datasize != -1)
|
|
{
|
|
int align_overhead;
|
|
|
|
align_overhead = 4 - (cur->datasize % 4);
|
|
if (align_overhead == 4)
|
|
align_overhead = 0;
|
|
grub_memset (curptr, 0, 32);
|
|
grub_strncpy (curptr, cur->name, 31);
|
|
curptr = ((grub_uint8_t *) curptr) + 32;
|
|
*((grub_uint32_t *) curptr) = cur->datasize;
|
|
curptr = ((grub_uint32_t *) curptr) + 1;
|
|
grub_memcpy (curptr, cur->data, cur->datasize);
|
|
curptr = ((grub_uint8_t *) curptr) + cur->datasize;
|
|
grub_memset (curptr, 0, align_overhead);
|
|
curptr = ((grub_uint8_t *) curptr) + align_overhead;
|
|
}
|
|
|
|
/* And then the keys. Recursively use this function. */
|
|
for (cur = start; cur; cur = cur->next)
|
|
if (cur->datasize == -1)
|
|
{
|
|
curptr = grub_xnu_writetree_toheap_real (curptr,
|
|
cur->first_child,
|
|
cur->name);
|
|
if (!curptr)
|
|
return 0;
|
|
}
|
|
return curptr;
|
|
}
|
|
|
|
grub_err_t
|
|
grub_xnu_writetree_toheap (grub_addr_t *target, grub_size_t *size)
|
|
{
|
|
struct grub_xnu_devtree_key *chosen;
|
|
struct grub_xnu_devtree_key *memorymap;
|
|
struct grub_xnu_devtree_key *driverkey;
|
|
struct grub_xnu_extdesc *extdesc;
|
|
grub_err_t err;
|
|
void *src;
|
|
|
|
err = grub_xnu_align_heap (GRUB_XNU_PAGESIZE);
|
|
if (err)
|
|
return err;
|
|
|
|
/* Device tree itself is in the memory map of device tree. */
|
|
/* Create a dummy value in memory-map. */
|
|
chosen = grub_xnu_create_key (&grub_xnu_devtree_root, "chosen");
|
|
if (! chosen)
|
|
return grub_errno;
|
|
memorymap = grub_xnu_create_key (&(chosen->first_child), "memory-map");
|
|
if (! memorymap)
|
|
return grub_errno;
|
|
|
|
driverkey = (struct grub_xnu_devtree_key *) grub_malloc (sizeof (*driverkey));
|
|
if (! driverkey)
|
|
return grub_errno;
|
|
driverkey->name = grub_strdup ("DeviceTree");
|
|
if (! driverkey->name)
|
|
return grub_errno;
|
|
driverkey->datasize = sizeof (*extdesc);
|
|
driverkey->next = memorymap->first_child;
|
|
memorymap->first_child = driverkey;
|
|
driverkey->data = extdesc
|
|
= (struct grub_xnu_extdesc *) grub_malloc (sizeof (*extdesc));
|
|
if (! driverkey->data)
|
|
return grub_errno;
|
|
|
|
/* Allocate the space based on the size with dummy value. */
|
|
*size = grub_xnu_writetree_get_size (grub_xnu_devtree_root, "/");
|
|
err = grub_xnu_heap_malloc (ALIGN_UP (*size + 1, GRUB_XNU_PAGESIZE),
|
|
&src, target);
|
|
if (err)
|
|
return err;
|
|
|
|
/* Put real data in the dummy. */
|
|
extdesc->addr = *target;
|
|
extdesc->size = (grub_uint32_t) *size;
|
|
|
|
/* Write the tree to heap. */
|
|
grub_xnu_writetree_toheap_real (src, grub_xnu_devtree_root, "/");
|
|
return GRUB_ERR_NONE;
|
|
}
|
|
|
|
/* Find a key or value in parent key. */
|
|
struct grub_xnu_devtree_key *
|
|
grub_xnu_find_key (struct grub_xnu_devtree_key *parent, const char *name)
|
|
{
|
|
struct grub_xnu_devtree_key *cur;
|
|
for (cur = parent; cur; cur = cur->next)
|
|
if (grub_strcmp (cur->name, name) == 0)
|
|
return cur;
|
|
return 0;
|
|
}
|
|
|
|
struct grub_xnu_devtree_key *
|
|
grub_xnu_create_key (struct grub_xnu_devtree_key **parent, const char *name)
|
|
{
|
|
struct grub_xnu_devtree_key *ret;
|
|
ret = grub_xnu_find_key (*parent, name);
|
|
if (ret)
|
|
return ret;
|
|
ret = (struct grub_xnu_devtree_key *) grub_zalloc (sizeof (*ret));
|
|
if (! ret)
|
|
return 0;
|
|
ret->name = grub_strdup (name);
|
|
if (! ret->name)
|
|
{
|
|
grub_free (ret);
|
|
return 0;
|
|
}
|
|
ret->datasize = -1;
|
|
ret->next = *parent;
|
|
*parent = ret;
|
|
return ret;
|
|
}
|
|
|
|
struct grub_xnu_devtree_key *
|
|
grub_xnu_create_value (struct grub_xnu_devtree_key **parent, const char *name)
|
|
{
|
|
struct grub_xnu_devtree_key *ret;
|
|
ret = grub_xnu_find_key (*parent, name);
|
|
if (ret)
|
|
{
|
|
if (ret->datasize == -1)
|
|
grub_xnu_free_devtree (ret->first_child);
|
|
else if (ret->datasize)
|
|
grub_free (ret->data);
|
|
ret->datasize = 0;
|
|
ret->data = 0;
|
|
return ret;
|
|
}
|
|
ret = (struct grub_xnu_devtree_key *) grub_zalloc (sizeof (*ret));
|
|
if (! ret)
|
|
return 0;
|
|
ret->name = grub_strdup (name);
|
|
if (! ret->name)
|
|
{
|
|
grub_free (ret);
|
|
return 0;
|
|
}
|
|
ret->next = *parent;
|
|
*parent = ret;
|
|
return ret;
|
|
}
|
|
|
|
static grub_err_t
|
|
grub_xnu_unload (void)
|
|
{
|
|
grub_cpu_xnu_unload ();
|
|
|
|
grub_xnu_free_devtree (grub_xnu_devtree_root);
|
|
grub_xnu_devtree_root = 0;
|
|
|
|
/* Free loaded image. */
|
|
driversnum = 0;
|
|
driverspackagenum = 0;
|
|
grub_relocator_unload (grub_xnu_relocator);
|
|
grub_xnu_relocator = NULL;
|
|
grub_xnu_heap_target_start = 0;
|
|
grub_xnu_heap_size = 0;
|
|
grub_xnu_unlock ();
|
|
return GRUB_ERR_NONE;
|
|
}
|
|
|
|
static grub_err_t
|
|
grub_cmd_xnu_kernel (grub_command_t cmd __attribute__ ((unused)),
|
|
int argc, char *args[])
|
|
{
|
|
grub_err_t err;
|
|
grub_macho_t macho;
|
|
grub_uint32_t startcode, endcode;
|
|
int i;
|
|
char *ptr;
|
|
void *loadaddr;
|
|
grub_addr_t loadaddr_target;
|
|
|
|
if (argc < 1)
|
|
return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
|
|
|
|
grub_xnu_unload ();
|
|
|
|
macho = grub_macho_open (args[0], GRUB_FILE_TYPE_XNU_KERNEL, 0);
|
|
if (! macho)
|
|
return grub_errno;
|
|
|
|
err = grub_macho_size32 (macho, &startcode, &endcode, GRUB_MACHO_NOBSS,
|
|
args[0]);
|
|
if (err)
|
|
{
|
|
grub_macho_close (macho);
|
|
grub_xnu_unload ();
|
|
return err;
|
|
}
|
|
|
|
grub_dprintf ("xnu", "endcode = %lx, startcode = %lx\n",
|
|
(unsigned long) endcode, (unsigned long) startcode);
|
|
|
|
grub_xnu_relocator = grub_relocator_new ();
|
|
if (!grub_xnu_relocator)
|
|
return grub_errno;
|
|
grub_xnu_heap_target_start = startcode;
|
|
err = grub_xnu_heap_malloc (endcode - startcode, &loadaddr,
|
|
&loadaddr_target);
|
|
|
|
if (err)
|
|
{
|
|
grub_macho_close (macho);
|
|
grub_xnu_unload ();
|
|
return err;
|
|
}
|
|
|
|
/* Load kernel. */
|
|
err = grub_macho_load32 (macho, args[0], (char *) loadaddr - startcode,
|
|
GRUB_MACHO_NOBSS, &grub_xnu_darwin_version);
|
|
if (err)
|
|
{
|
|
grub_macho_close (macho);
|
|
grub_xnu_unload ();
|
|
return err;
|
|
}
|
|
|
|
grub_xnu_entry_point = grub_macho_get_entry_point32 (macho, args[0]);
|
|
if (! grub_xnu_entry_point)
|
|
{
|
|
grub_macho_close (macho);
|
|
grub_xnu_unload ();
|
|
return grub_error (GRUB_ERR_BAD_OS, "couldn't find entry point");
|
|
}
|
|
|
|
grub_macho_close (macho);
|
|
|
|
err = grub_xnu_align_heap (GRUB_XNU_PAGESIZE);
|
|
if (err)
|
|
{
|
|
grub_xnu_unload ();
|
|
return err;
|
|
}
|
|
|
|
/* Copy parameters to kernel command line. */
|
|
ptr = grub_xnu_cmdline;
|
|
for (i = 1; i < argc; i++)
|
|
{
|
|
if (ptr + grub_strlen (args[i]) + 1
|
|
>= grub_xnu_cmdline + sizeof (grub_xnu_cmdline))
|
|
break;
|
|
grub_memcpy (ptr, args[i], grub_strlen (args[i]));
|
|
ptr += grub_strlen (args[i]);
|
|
*ptr = ' ';
|
|
ptr++;
|
|
}
|
|
|
|
/* Replace last space by '\0'. */
|
|
if (ptr != grub_xnu_cmdline)
|
|
*(ptr - 1) = 0;
|
|
|
|
err = grub_verify_string (grub_xnu_cmdline, GRUB_VERIFY_KERNEL_CMDLINE);
|
|
if (err)
|
|
return err;
|
|
|
|
#if defined (__i386) && !defined (GRUB_MACHINE_EFI)
|
|
err = grub_efiemu_autocore ();
|
|
if (err)
|
|
return err;
|
|
#endif
|
|
|
|
grub_loader_set (grub_xnu_boot, grub_xnu_unload, 0);
|
|
|
|
grub_xnu_lock ();
|
|
grub_xnu_is_64bit = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static grub_err_t
|
|
grub_cmd_xnu_kernel64 (grub_command_t cmd __attribute__ ((unused)),
|
|
int argc, char *args[])
|
|
{
|
|
grub_err_t err;
|
|
grub_macho_t macho;
|
|
grub_uint64_t startcode, endcode;
|
|
int i;
|
|
char *ptr;
|
|
void *loadaddr;
|
|
grub_addr_t loadaddr_target;
|
|
|
|
if (argc < 1)
|
|
return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
|
|
|
|
grub_xnu_unload ();
|
|
|
|
macho = grub_macho_open (args[0], GRUB_FILE_TYPE_XNU_KERNEL, 1);
|
|
if (! macho)
|
|
return grub_errno;
|
|
|
|
err = grub_macho_size64 (macho, &startcode, &endcode, GRUB_MACHO_NOBSS,
|
|
args[0]);
|
|
if (err)
|
|
{
|
|
grub_macho_close (macho);
|
|
grub_xnu_unload ();
|
|
return err;
|
|
}
|
|
|
|
startcode &= 0x0fffffff;
|
|
endcode &= 0x0fffffff;
|
|
|
|
grub_dprintf ("xnu", "endcode = %lx, startcode = %lx\n",
|
|
(unsigned long) endcode, (unsigned long) startcode);
|
|
|
|
grub_xnu_relocator = grub_relocator_new ();
|
|
if (!grub_xnu_relocator)
|
|
return grub_errno;
|
|
grub_xnu_heap_target_start = startcode;
|
|
err = grub_xnu_heap_malloc (endcode - startcode, &loadaddr,
|
|
&loadaddr_target);
|
|
|
|
if (err)
|
|
{
|
|
grub_macho_close (macho);
|
|
grub_xnu_unload ();
|
|
return err;
|
|
}
|
|
|
|
/* Load kernel. */
|
|
err = grub_macho_load64 (macho, args[0], (char *) loadaddr - startcode,
|
|
GRUB_MACHO_NOBSS, &grub_xnu_darwin_version);
|
|
if (err)
|
|
{
|
|
grub_macho_close (macho);
|
|
grub_xnu_unload ();
|
|
return err;
|
|
}
|
|
|
|
grub_xnu_entry_point = grub_macho_get_entry_point64 (macho, args[0])
|
|
& 0x0fffffff;
|
|
if (! grub_xnu_entry_point)
|
|
{
|
|
grub_macho_close (macho);
|
|
grub_xnu_unload ();
|
|
return grub_error (GRUB_ERR_BAD_OS, "couldn't find entry point");
|
|
}
|
|
|
|
grub_macho_close (macho);
|
|
|
|
err = grub_xnu_align_heap (GRUB_XNU_PAGESIZE);
|
|
if (err)
|
|
{
|
|
grub_xnu_unload ();
|
|
return err;
|
|
}
|
|
|
|
/* Copy parameters to kernel command line. */
|
|
ptr = grub_xnu_cmdline;
|
|
for (i = 1; i < argc; i++)
|
|
{
|
|
if (ptr + grub_strlen (args[i]) + 1
|
|
>= grub_xnu_cmdline + sizeof (grub_xnu_cmdline))
|
|
break;
|
|
grub_memcpy (ptr, args[i], grub_strlen (args[i]));
|
|
ptr += grub_strlen (args[i]);
|
|
*ptr = ' ';
|
|
ptr++;
|
|
}
|
|
|
|
/* Replace last space by '\0'. */
|
|
if (ptr != grub_xnu_cmdline)
|
|
*(ptr - 1) = 0;
|
|
|
|
err = grub_verify_string (grub_xnu_cmdline, GRUB_VERIFY_KERNEL_CMDLINE);
|
|
if (err)
|
|
return err;
|
|
|
|
#if defined (__i386) && !defined (GRUB_MACHINE_EFI)
|
|
err = grub_efiemu_autocore ();
|
|
if (err)
|
|
return err;
|
|
#endif
|
|
|
|
grub_loader_set (grub_xnu_boot, grub_xnu_unload, 0);
|
|
|
|
grub_xnu_lock ();
|
|
grub_xnu_is_64bit = 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Register a memory in a memory map under name PREFIXSUFFIX
|
|
and increment SUFFIX. */
|
|
static grub_err_t
|
|
grub_xnu_register_memory (const char *prefix, int *suffix,
|
|
grub_addr_t addr, grub_size_t size)
|
|
{
|
|
struct grub_xnu_devtree_key *chosen;
|
|
struct grub_xnu_devtree_key *memorymap;
|
|
struct grub_xnu_devtree_key *driverkey;
|
|
struct grub_xnu_extdesc *extdesc;
|
|
|
|
if (! grub_xnu_heap_size)
|
|
return grub_error (GRUB_ERR_BAD_OS, N_("you need to load the kernel first"));
|
|
|
|
chosen = grub_xnu_create_key (&grub_xnu_devtree_root, "chosen");
|
|
if (! chosen)
|
|
return grub_errno;
|
|
memorymap = grub_xnu_create_key (&(chosen->first_child), "memory-map");
|
|
if (! memorymap)
|
|
return grub_errno;
|
|
|
|
driverkey = (struct grub_xnu_devtree_key *) grub_malloc (sizeof (*driverkey));
|
|
if (! driverkey)
|
|
return grub_errno;
|
|
if (suffix)
|
|
driverkey->name = grub_xasprintf ("%s%d", prefix, (*suffix)++);
|
|
else
|
|
driverkey->name = grub_strdup (prefix);
|
|
if (!driverkey->name)
|
|
{
|
|
grub_free (driverkey);
|
|
return grub_errno;
|
|
}
|
|
driverkey->datasize = sizeof (*extdesc);
|
|
driverkey->next = memorymap->first_child;
|
|
driverkey->data = extdesc
|
|
= (struct grub_xnu_extdesc *) grub_malloc (sizeof (*extdesc));
|
|
if (! driverkey->data)
|
|
{
|
|
grub_free (driverkey->name);
|
|
grub_free (driverkey);
|
|
return grub_errno;
|
|
}
|
|
memorymap->first_child = driverkey;
|
|
extdesc->addr = addr;
|
|
extdesc->size = (grub_uint32_t) size;
|
|
return GRUB_ERR_NONE;
|
|
}
|
|
|
|
static inline char *
|
|
get_name_ptr (char *name)
|
|
{
|
|
char *p = name, *p2;
|
|
/* Skip Info.plist. */
|
|
p2 = grub_strrchr (p, '/');
|
|
if (!p2)
|
|
return name;
|
|
if (p2 == name)
|
|
return name + 1;
|
|
p = p2 - 1;
|
|
|
|
p2 = grub_strrchr (p, '/');
|
|
if (!p2)
|
|
return name;
|
|
if (p2 == name)
|
|
return name + 1;
|
|
if (grub_memcmp (p2, "/Contents/", sizeof ("/Contents/") - 1) != 0)
|
|
return p2 + 1;
|
|
|
|
p = p2 - 1;
|
|
|
|
p2 = grub_strrchr (p, '/');
|
|
if (!p2)
|
|
return name;
|
|
return p2 + 1;
|
|
}
|
|
|
|
/* Load .kext. */
|
|
static grub_err_t
|
|
grub_xnu_load_driver (char *infoplistname, grub_file_t binaryfile,
|
|
const char *filename)
|
|
{
|
|
grub_macho_t macho;
|
|
grub_err_t err;
|
|
grub_file_t infoplist;
|
|
struct grub_xnu_extheader *exthead;
|
|
int neededspace = sizeof (*exthead);
|
|
grub_uint8_t *buf;
|
|
void *buf0;
|
|
grub_addr_t buf_target;
|
|
grub_size_t infoplistsize = 0, machosize = 0;
|
|
char *name, *nameend;
|
|
int namelen;
|
|
|
|
name = get_name_ptr (infoplistname);
|
|
nameend = grub_strchr (name, '/');
|
|
|
|
if (nameend)
|
|
namelen = nameend - name;
|
|
else
|
|
namelen = grub_strlen (name);
|
|
|
|
neededspace += namelen + 1;
|
|
|
|
if (! grub_xnu_heap_size)
|
|
return grub_error (GRUB_ERR_BAD_OS, N_("you need to load the kernel first"));
|
|
|
|
/* Compute the needed space. */
|
|
if (binaryfile)
|
|
{
|
|
macho = grub_macho_file (binaryfile, filename, grub_xnu_is_64bit);
|
|
if (!macho)
|
|
grub_file_close (binaryfile);
|
|
else
|
|
{
|
|
if (grub_xnu_is_64bit)
|
|
machosize = grub_macho_filesize64 (macho);
|
|
else
|
|
machosize = grub_macho_filesize32 (macho);
|
|
}
|
|
neededspace += machosize;
|
|
}
|
|
else
|
|
macho = 0;
|
|
|
|
if (infoplistname)
|
|
infoplist = grub_file_open (infoplistname, GRUB_FILE_TYPE_XNU_INFO_PLIST);
|
|
else
|
|
infoplist = 0;
|
|
grub_errno = GRUB_ERR_NONE;
|
|
if (infoplist)
|
|
{
|
|
infoplistsize = grub_file_size (infoplist);
|
|
neededspace += infoplistsize + 1;
|
|
}
|
|
else
|
|
infoplistsize = 0;
|
|
|
|
/* Allocate the space. */
|
|
err = grub_xnu_align_heap (GRUB_XNU_PAGESIZE);
|
|
if (err)
|
|
goto fail;
|
|
err = grub_xnu_heap_malloc (neededspace, &buf0, &buf_target);
|
|
if (err)
|
|
goto fail;
|
|
buf = buf0;
|
|
|
|
exthead = (struct grub_xnu_extheader *) buf;
|
|
grub_memset (exthead, 0, sizeof (*exthead));
|
|
buf += sizeof (*exthead);
|
|
|
|
/* Load the binary. */
|
|
if (macho)
|
|
{
|
|
exthead->binaryaddr = buf_target + (buf - (grub_uint8_t *) buf0);
|
|
exthead->binarysize = machosize;
|
|
if (grub_xnu_is_64bit)
|
|
err = grub_macho_readfile64 (macho, filename, buf);
|
|
else
|
|
err = grub_macho_readfile32 (macho, filename, buf);
|
|
if (err)
|
|
goto fail;
|
|
grub_macho_close (macho);
|
|
buf += machosize;
|
|
}
|
|
grub_errno = GRUB_ERR_NONE;
|
|
|
|
/* Load the plist. */
|
|
if (infoplist)
|
|
{
|
|
exthead->infoplistaddr = buf_target + (buf - (grub_uint8_t *) buf0);
|
|
exthead->infoplistsize = infoplistsize + 1;
|
|
if (grub_file_read (infoplist, buf, infoplistsize)
|
|
!= (grub_ssize_t) (infoplistsize))
|
|
{
|
|
grub_file_close (infoplist);
|
|
if (!grub_errno)
|
|
grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"),
|
|
infoplistname);
|
|
return grub_errno;
|
|
}
|
|
grub_file_close (infoplist);
|
|
buf[infoplistsize] = 0;
|
|
buf += infoplistsize + 1;
|
|
}
|
|
grub_errno = GRUB_ERR_NONE;
|
|
|
|
exthead->nameaddr = (buf - (grub_uint8_t *) buf0) + buf_target;
|
|
exthead->namesize = namelen + 1;
|
|
grub_memcpy (buf, name, namelen);
|
|
buf[namelen] = 0;
|
|
buf += namelen + 1;
|
|
|
|
/* Announce to kernel */
|
|
return grub_xnu_register_memory ("Driver-", &driversnum, buf_target,
|
|
neededspace);
|
|
fail:
|
|
if (macho)
|
|
grub_macho_close (macho);
|
|
return err;
|
|
}
|
|
|
|
/* Load mkext. */
|
|
static grub_err_t
|
|
grub_cmd_xnu_mkext (grub_command_t cmd __attribute__ ((unused)),
|
|
int argc, char *args[])
|
|
{
|
|
grub_file_t file;
|
|
void *loadto;
|
|
grub_addr_t loadto_target;
|
|
grub_err_t err;
|
|
grub_off_t readoff = 0;
|
|
grub_ssize_t readlen = -1;
|
|
struct grub_macho_fat_header head;
|
|
struct grub_macho_fat_arch *archs;
|
|
int narchs, i;
|
|
|
|
if (argc != 1)
|
|
return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
|
|
|
|
if (! grub_xnu_heap_size)
|
|
return grub_error (GRUB_ERR_BAD_OS, N_("you need to load the kernel first"));
|
|
|
|
file = grub_file_open (args[0], GRUB_FILE_TYPE_XNU_MKEXT);
|
|
if (! file)
|
|
return grub_errno;
|
|
|
|
/* Sometimes caches are fat binary. Errgh. */
|
|
if (grub_file_read (file, &head, sizeof (head))
|
|
!= (grub_ssize_t) (sizeof (head)))
|
|
{
|
|
/* I don't know the internal structure of package but
|
|
can hardly imagine a valid package shorter than 20 bytes. */
|
|
grub_file_close (file);
|
|
if (!grub_errno)
|
|
grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), args[0]);
|
|
return grub_errno;
|
|
}
|
|
|
|
/* Find the corresponding architecture. */
|
|
if (grub_be_to_cpu32 (head.magic) == GRUB_MACHO_FAT_MAGIC)
|
|
{
|
|
narchs = grub_be_to_cpu32 (head.nfat_arch);
|
|
archs = grub_calloc (narchs, sizeof (struct grub_macho_fat_arch));
|
|
if (! archs)
|
|
{
|
|
grub_file_close (file);
|
|
return grub_errno;
|
|
|
|
}
|
|
if (grub_file_read (file, archs,
|
|
sizeof (struct grub_macho_fat_arch) * narchs)
|
|
!= (grub_ssize_t) sizeof(struct grub_macho_fat_arch) * narchs)
|
|
{
|
|
grub_free (archs);
|
|
if (!grub_errno)
|
|
grub_error (GRUB_ERR_READ_ERROR, N_("premature end of file %s"),
|
|
args[0]);
|
|
return grub_errno;
|
|
}
|
|
for (i = 0; i < narchs; i++)
|
|
{
|
|
if (!grub_xnu_is_64bit && GRUB_MACHO_CPUTYPE_IS_HOST32
|
|
(grub_be_to_cpu32 (archs[i].cputype)))
|
|
{
|
|
readoff = grub_be_to_cpu32 (archs[i].offset);
|
|
readlen = grub_be_to_cpu32 (archs[i].size);
|
|
}
|
|
if (grub_xnu_is_64bit && GRUB_MACHO_CPUTYPE_IS_HOST64
|
|
(grub_be_to_cpu32 (archs[i].cputype)))
|
|
{
|
|
readoff = grub_be_to_cpu32 (archs[i].offset);
|
|
readlen = grub_be_to_cpu32 (archs[i].size);
|
|
}
|
|
}
|
|
grub_free (archs);
|
|
}
|
|
else
|
|
{
|
|
/* It's a flat file. Some sane people still exist. */
|
|
readoff = 0;
|
|
readlen = grub_file_size (file);
|
|
}
|
|
|
|
if (readlen == -1)
|
|
{
|
|
grub_file_close (file);
|
|
return grub_error (GRUB_ERR_BAD_OS, "no suitable architecture is found");
|
|
}
|
|
|
|
/* Allocate space. */
|
|
err = grub_xnu_align_heap (GRUB_XNU_PAGESIZE);
|
|
if (err)
|
|
{
|
|
grub_file_close (file);
|
|
return err;
|
|
}
|
|
|
|
err = grub_xnu_heap_malloc (readlen, &loadto, &loadto_target);
|
|
if (err)
|
|
{
|
|
grub_file_close (file);
|
|
return err;
|
|
}
|
|
|
|
/* Read the file. */
|
|
grub_file_seek (file, readoff);
|
|
if (grub_file_read (file, loadto, readlen) != (grub_ssize_t) (readlen))
|
|
{
|
|
grub_file_close (file);
|
|
if (!grub_errno)
|
|
grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), args[0]);
|
|
return grub_errno;
|
|
}
|
|
grub_file_close (file);
|
|
|
|
/* Pass it to kernel. */
|
|
return grub_xnu_register_memory ("DriversPackage-", &driverspackagenum,
|
|
loadto_target, readlen);
|
|
}
|
|
|
|
static grub_err_t
|
|
grub_cmd_xnu_ramdisk (grub_command_t cmd __attribute__ ((unused)),
|
|
int argc, char *args[])
|
|
{
|
|
grub_file_t file;
|
|
void *loadto;
|
|
grub_addr_t loadto_target;
|
|
grub_err_t err;
|
|
grub_size_t size;
|
|
|
|
if (argc != 1)
|
|
return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
|
|
|
|
if (! grub_xnu_heap_size)
|
|
return grub_error (GRUB_ERR_BAD_OS, N_("you need to load the kernel first"));
|
|
|
|
file = grub_file_open (args[0], GRUB_FILE_TYPE_XNU_RAMDISK);
|
|
if (! file)
|
|
return grub_errno;
|
|
|
|
err = grub_xnu_align_heap (GRUB_XNU_PAGESIZE);
|
|
if (err)
|
|
return err;
|
|
|
|
size = grub_file_size (file);
|
|
|
|
err = grub_xnu_heap_malloc (size, &loadto, &loadto_target);
|
|
if (err)
|
|
return err;
|
|
if (grub_file_read (file, loadto, size) != (grub_ssize_t) (size))
|
|
{
|
|
grub_file_close (file);
|
|
if (!grub_errno)
|
|
grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), args[0]);
|
|
return grub_errno;
|
|
}
|
|
return grub_xnu_register_memory ("RAMDisk", 0, loadto_target, size);
|
|
}
|
|
|
|
/* Returns true if the kext should be loaded according to plist
|
|
and osbundlereq. Also fill BINNAME. */
|
|
static int
|
|
grub_xnu_check_os_bundle_required (char *plistname,
|
|
const char *osbundlereq,
|
|
char **binname)
|
|
{
|
|
grub_file_t file;
|
|
char *buf = 0, *tagstart = 0, *ptr1 = 0, *keyptr = 0;
|
|
char *stringptr = 0, *ptr2 = 0;
|
|
grub_size_t size;
|
|
int depth = 0;
|
|
int ret;
|
|
int osbundlekeyfound = 0, binnamekeyfound = 0;
|
|
if (binname)
|
|
*binname = 0;
|
|
|
|
file = grub_file_open (plistname, GRUB_FILE_TYPE_XNU_INFO_PLIST);
|
|
if (! file)
|
|
return 0;
|
|
|
|
size = grub_file_size (file);
|
|
buf = grub_malloc (size);
|
|
if (! buf)
|
|
{
|
|
grub_file_close (file);
|
|
return 0;
|
|
}
|
|
if (grub_file_read (file, buf, size) != (grub_ssize_t) (size))
|
|
{
|
|
grub_file_close (file);
|
|
if (!grub_errno)
|
|
grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), plistname);
|
|
return 0;
|
|
}
|
|
grub_file_close (file);
|
|
|
|
/* Set the return value for the case when no OSBundleRequired tag is found. */
|
|
if (osbundlereq)
|
|
ret = grub_strword (osbundlereq, "all") || grub_strword (osbundlereq, "-");
|
|
else
|
|
ret = 1;
|
|
|
|
/* Parse plist. It's quite dirty and inextensible but does its job. */
|
|
for (ptr1 = buf; ptr1 < buf + size; ptr1++)
|
|
switch (*ptr1)
|
|
{
|
|
case '<':
|
|
tagstart = ptr1;
|
|
*ptr1 = 0;
|
|
if (keyptr && depth == 4
|
|
&& grub_strcmp (keyptr, "OSBundleRequired") == 0)
|
|
osbundlekeyfound = 1;
|
|
if (keyptr && depth == 4 &&
|
|
grub_strcmp (keyptr, "CFBundleExecutable") == 0)
|
|
binnamekeyfound = 1;
|
|
if (stringptr && osbundlekeyfound && osbundlereq && depth == 4)
|
|
{
|
|
for (ptr2 = stringptr; *ptr2; ptr2++)
|
|
*ptr2 = grub_tolower (*ptr2);
|
|
ret = grub_strword (osbundlereq, stringptr)
|
|
|| grub_strword (osbundlereq, "all");
|
|
}
|
|
if (stringptr && binnamekeyfound && binname && depth == 4)
|
|
{
|
|
if (*binname)
|
|
grub_free (*binname);
|
|
*binname = grub_strdup (stringptr);
|
|
}
|
|
|
|
*ptr1 = '<';
|
|
keyptr = 0;
|
|
stringptr = 0;
|
|
break;
|
|
case '>':
|
|
if (! tagstart)
|
|
{
|
|
grub_free (buf);
|
|
grub_error (GRUB_ERR_BAD_OS, "can't parse %s", plistname);
|
|
return 0;
|
|
}
|
|
*ptr1 = 0;
|
|
if (tagstart[1] == '?' || ptr1[-1] == '/')
|
|
{
|
|
osbundlekeyfound = 0;
|
|
*ptr1 = '>';
|
|
break;
|
|
}
|
|
if (depth == 3 && grub_strcmp (tagstart + 1, "key") == 0)
|
|
keyptr = ptr1 + 1;
|
|
if (depth == 3 && grub_strcmp (tagstart + 1, "string") == 0)
|
|
stringptr = ptr1 + 1;
|
|
else if (grub_strcmp (tagstart + 1, "/key") != 0)
|
|
{
|
|
osbundlekeyfound = 0;
|
|
binnamekeyfound = 0;
|
|
}
|
|
*ptr1 = '>';
|
|
|
|
if (tagstart[1] == '/')
|
|
depth--;
|
|
else
|
|
depth++;
|
|
break;
|
|
}
|
|
grub_free (buf);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Context for grub_xnu_scan_dir_for_kexts. */
|
|
struct grub_xnu_scan_dir_for_kexts_ctx
|
|
{
|
|
char *dirname;
|
|
const char *osbundlerequired;
|
|
int maxrecursion;
|
|
};
|
|
|
|
/* Helper for grub_xnu_scan_dir_for_kexts. */
|
|
static int
|
|
grub_xnu_scan_dir_for_kexts_load (const char *filename,
|
|
const struct grub_dirhook_info *info,
|
|
void *data)
|
|
{
|
|
struct grub_xnu_scan_dir_for_kexts_ctx *ctx = data;
|
|
char *newdirname;
|
|
|
|
if (! info->dir)
|
|
return 0;
|
|
if (filename[0] == '.')
|
|
return 0;
|
|
|
|
if (grub_strlen (filename) < 5 ||
|
|
grub_memcmp (filename + grub_strlen (filename) - 5, ".kext", 5) != 0)
|
|
return 0;
|
|
|
|
newdirname
|
|
= grub_malloc (grub_strlen (ctx->dirname) + grub_strlen (filename) + 2);
|
|
|
|
/* It's a .kext. Try to load it. */
|
|
if (newdirname)
|
|
{
|
|
grub_strcpy (newdirname, ctx->dirname);
|
|
newdirname[grub_strlen (newdirname) + 1] = 0;
|
|
newdirname[grub_strlen (newdirname)] = '/';
|
|
grub_strcpy (newdirname + grub_strlen (newdirname), filename);
|
|
grub_xnu_load_kext_from_dir (newdirname, ctx->osbundlerequired,
|
|
ctx->maxrecursion);
|
|
if (grub_errno == GRUB_ERR_BAD_OS)
|
|
grub_errno = GRUB_ERR_NONE;
|
|
grub_free (newdirname);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Load all loadable kexts placed under DIRNAME and matching OSBUNDLEREQUIRED */
|
|
grub_err_t
|
|
grub_xnu_scan_dir_for_kexts (char *dirname, const char *osbundlerequired,
|
|
int maxrecursion)
|
|
{
|
|
struct grub_xnu_scan_dir_for_kexts_ctx ctx = {
|
|
.dirname = dirname,
|
|
.osbundlerequired = osbundlerequired,
|
|
.maxrecursion = maxrecursion
|
|
};
|
|
grub_device_t dev;
|
|
char *device_name;
|
|
grub_fs_t fs;
|
|
const char *path;
|
|
|
|
if (! grub_xnu_heap_size)
|
|
return grub_error (GRUB_ERR_BAD_OS, N_("you need to load the kernel first"));
|
|
|
|
device_name = grub_file_get_device_name (dirname);
|
|
dev = grub_device_open (device_name);
|
|
if (dev)
|
|
{
|
|
fs = grub_fs_probe (dev);
|
|
path = grub_strchr (dirname, ')');
|
|
if (! path)
|
|
path = dirname;
|
|
else
|
|
path++;
|
|
|
|
if (fs)
|
|
(fs->fs_dir) (dev, path, grub_xnu_scan_dir_for_kexts_load, &ctx);
|
|
grub_device_close (dev);
|
|
}
|
|
grub_free (device_name);
|
|
|
|
return GRUB_ERR_NONE;
|
|
}
|
|
|
|
/* Context for grub_xnu_load_kext_from_dir. */
|
|
struct grub_xnu_load_kext_from_dir_ctx
|
|
{
|
|
char *dirname;
|
|
const char *osbundlerequired;
|
|
int maxrecursion;
|
|
char *plistname;
|
|
char *newdirname;
|
|
int usemacos;
|
|
};
|
|
|
|
/* Helper for grub_xnu_load_kext_from_dir. */
|
|
static int
|
|
grub_xnu_load_kext_from_dir_load (const char *filename,
|
|
const struct grub_dirhook_info *info,
|
|
void *data)
|
|
{
|
|
struct grub_xnu_load_kext_from_dir_ctx *ctx = data;
|
|
|
|
if (grub_strlen (filename) > 15)
|
|
return 0;
|
|
grub_strcpy (ctx->newdirname + grub_strlen (ctx->dirname) + 1, filename);
|
|
|
|
/* If the kext contains directory "Contents" all real stuff is in
|
|
this directory. */
|
|
if (info->dir && grub_strcasecmp (filename, "Contents") == 0)
|
|
grub_xnu_load_kext_from_dir (ctx->newdirname, ctx->osbundlerequired,
|
|
ctx->maxrecursion - 1);
|
|
|
|
/* Directory "Plugins" contains nested kexts. */
|
|
if (info->dir && grub_strcasecmp (filename, "Plugins") == 0)
|
|
grub_xnu_scan_dir_for_kexts (ctx->newdirname, ctx->osbundlerequired,
|
|
ctx->maxrecursion - 1);
|
|
|
|
/* Directory "MacOS" contains executable, otherwise executable is
|
|
on the top. */
|
|
if (info->dir && grub_strcasecmp (filename, "MacOS") == 0)
|
|
ctx->usemacos = 1;
|
|
|
|
/* Info.plist is the file which governs our future actions. */
|
|
if (! info->dir && grub_strcasecmp (filename, "Info.plist") == 0
|
|
&& ! ctx->plistname)
|
|
ctx->plistname = grub_strdup (ctx->newdirname);
|
|
return 0;
|
|
}
|
|
|
|
/* Load extension DIRNAME. (extensions are directories in xnu) */
|
|
grub_err_t
|
|
grub_xnu_load_kext_from_dir (char *dirname, const char *osbundlerequired,
|
|
int maxrecursion)
|
|
{
|
|
struct grub_xnu_load_kext_from_dir_ctx ctx = {
|
|
.dirname = dirname,
|
|
.osbundlerequired = osbundlerequired,
|
|
.maxrecursion = maxrecursion,
|
|
.plistname = 0,
|
|
.usemacos = 0
|
|
};
|
|
grub_device_t dev;
|
|
char *newpath;
|
|
char *device_name;
|
|
grub_fs_t fs;
|
|
const char *path;
|
|
char *binsuffix;
|
|
grub_file_t binfile;
|
|
|
|
ctx.newdirname = grub_malloc (grub_strlen (dirname) + 20);
|
|
if (! ctx.newdirname)
|
|
return grub_errno;
|
|
grub_strcpy (ctx.newdirname, dirname);
|
|
ctx.newdirname[grub_strlen (dirname)] = '/';
|
|
ctx.newdirname[grub_strlen (dirname) + 1] = 0;
|
|
device_name = grub_file_get_device_name (dirname);
|
|
dev = grub_device_open (device_name);
|
|
if (dev)
|
|
{
|
|
fs = grub_fs_probe (dev);
|
|
path = grub_strchr (dirname, ')');
|
|
if (! path)
|
|
path = dirname;
|
|
else
|
|
path++;
|
|
|
|
newpath = grub_strchr (ctx.newdirname, ')');
|
|
if (! newpath)
|
|
newpath = ctx.newdirname;
|
|
else
|
|
newpath++;
|
|
|
|
/* Look at the directory. */
|
|
if (fs)
|
|
(fs->fs_dir) (dev, path, grub_xnu_load_kext_from_dir_load, &ctx);
|
|
|
|
if (ctx.plistname && grub_xnu_check_os_bundle_required
|
|
(ctx.plistname, osbundlerequired, &binsuffix))
|
|
{
|
|
if (binsuffix)
|
|
{
|
|
/* Open the binary. */
|
|
char *binname = grub_malloc (grub_strlen (dirname)
|
|
+ grub_strlen (binsuffix)
|
|
+ sizeof ("/MacOS/"));
|
|
grub_strcpy (binname, dirname);
|
|
if (ctx.usemacos)
|
|
grub_strcpy (binname + grub_strlen (binname), "/MacOS/");
|
|
else
|
|
grub_strcpy (binname + grub_strlen (binname), "/");
|
|
grub_strcpy (binname + grub_strlen (binname), binsuffix);
|
|
grub_dprintf ("xnu", "%s:%s\n", ctx.plistname, binname);
|
|
binfile = grub_file_open (binname, GRUB_FILE_TYPE_XNU_KEXT);
|
|
if (! binfile)
|
|
grub_errno = GRUB_ERR_NONE;
|
|
|
|
/* Load the extension. */
|
|
grub_xnu_load_driver (ctx.plistname, binfile,
|
|
binname);
|
|
grub_free (binname);
|
|
grub_free (binsuffix);
|
|
}
|
|
else
|
|
{
|
|
grub_dprintf ("xnu", "%s:0\n", ctx.plistname);
|
|
grub_xnu_load_driver (ctx.plistname, 0, 0);
|
|
}
|
|
}
|
|
grub_free (ctx.plistname);
|
|
grub_device_close (dev);
|
|
}
|
|
grub_free (device_name);
|
|
|
|
return GRUB_ERR_NONE;
|
|
}
|
|
|
|
|
|
static int locked=0;
|
|
static grub_dl_t my_mod;
|
|
|
|
/* Load the kext. */
|
|
static grub_err_t
|
|
grub_cmd_xnu_kext (grub_command_t cmd __attribute__ ((unused)),
|
|
int argc, char *args[])
|
|
{
|
|
grub_file_t binfile = 0;
|
|
|
|
if (! grub_xnu_heap_size)
|
|
return grub_error (GRUB_ERR_BAD_OS, N_("you need to load the kernel first"));
|
|
|
|
if (argc == 2)
|
|
{
|
|
/* User explicitly specified plist and binary. */
|
|
if (grub_strcmp (args[1], "-") != 0)
|
|
{
|
|
binfile = grub_file_open (args[1], GRUB_FILE_TYPE_XNU_KEXT);
|
|
if (! binfile)
|
|
return grub_errno;
|
|
}
|
|
return grub_xnu_load_driver (grub_strcmp (args[0], "-") ? args[0] : 0,
|
|
binfile, args[1]);
|
|
}
|
|
|
|
/* load kext normally. */
|
|
if (argc == 1)
|
|
return grub_xnu_load_kext_from_dir (args[0], 0, 10);
|
|
|
|
return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
|
|
}
|
|
|
|
/* Load a directory containing kexts. */
|
|
static grub_err_t
|
|
grub_cmd_xnu_kextdir (grub_command_t cmd __attribute__ ((unused)),
|
|
int argc, char *args[])
|
|
{
|
|
if (argc != 1 && argc != 2)
|
|
return grub_error (GRUB_ERR_BAD_ARGUMENT, "directory name required");
|
|
|
|
if (! grub_xnu_heap_size)
|
|
return grub_error (GRUB_ERR_BAD_OS, N_("you need to load the kernel first"));
|
|
|
|
if (argc == 1)
|
|
return grub_xnu_scan_dir_for_kexts (args[0],
|
|
"console,root,local-root,network-root",
|
|
10);
|
|
else
|
|
{
|
|
char *osbundlerequired = grub_strdup (args[1]), *ptr;
|
|
grub_err_t err;
|
|
if (! osbundlerequired)
|
|
return grub_errno;
|
|
for (ptr = osbundlerequired; *ptr; ptr++)
|
|
*ptr = grub_tolower (*ptr);
|
|
err = grub_xnu_scan_dir_for_kexts (args[0], osbundlerequired, 10);
|
|
grub_free (osbundlerequired);
|
|
return err;
|
|
}
|
|
}
|
|
|
|
static inline int
|
|
hextoval (char c)
|
|
{
|
|
if (c >= '0' && c <= '9')
|
|
return c - '0';
|
|
if (c >= 'a' && c <= 'z')
|
|
return c - 'a' + 10;
|
|
if (c >= 'A' && c <= 'Z')
|
|
return c - 'A' + 10;
|
|
return 0;
|
|
}
|
|
|
|
static inline void
|
|
unescape (char *name, char *curdot, char *nextdot, int *len)
|
|
{
|
|
char *ptr, *dptr;
|
|
dptr = name;
|
|
for (ptr = curdot; ptr < nextdot;)
|
|
if (ptr + 2 < nextdot && *ptr == '%')
|
|
{
|
|
*dptr = (hextoval (ptr[1]) << 4) | (hextoval (ptr[2]));
|
|
ptr += 3;
|
|
dptr++;
|
|
}
|
|
else
|
|
{
|
|
*dptr = *ptr;
|
|
ptr++;
|
|
dptr++;
|
|
}
|
|
*len = dptr - name;
|
|
}
|
|
|
|
grub_err_t
|
|
grub_xnu_fill_devicetree (void)
|
|
{
|
|
struct grub_env_var *var;
|
|
FOR_SORTED_ENV (var)
|
|
{
|
|
char *nextdot = 0, *curdot;
|
|
struct grub_xnu_devtree_key **curkey = &grub_xnu_devtree_root;
|
|
struct grub_xnu_devtree_key *curvalue;
|
|
char *name = 0, *data;
|
|
int len;
|
|
|
|
if (grub_memcmp (var->name, "XNU.DeviceTree.",
|
|
sizeof ("XNU.DeviceTree.") - 1) != 0)
|
|
continue;
|
|
|
|
curdot = var->name + sizeof ("XNU.DeviceTree.") - 1;
|
|
nextdot = grub_strchr (curdot, '.');
|
|
if (nextdot)
|
|
nextdot++;
|
|
while (nextdot)
|
|
{
|
|
name = grub_realloc (name, nextdot - curdot + 1);
|
|
|
|
if (!name)
|
|
return grub_errno;
|
|
|
|
unescape (name, curdot, nextdot, &len);
|
|
name[len - 1] = 0;
|
|
|
|
curkey = &(grub_xnu_create_key (curkey, name)->first_child);
|
|
|
|
curdot = nextdot;
|
|
nextdot = grub_strchr (nextdot, '.');
|
|
if (nextdot)
|
|
nextdot++;
|
|
}
|
|
|
|
nextdot = curdot + grub_strlen (curdot) + 1;
|
|
|
|
name = grub_realloc (name, nextdot - curdot + 1);
|
|
|
|
if (!name)
|
|
return grub_errno;
|
|
|
|
unescape (name, curdot, nextdot, &len);
|
|
name[len] = 0;
|
|
|
|
curvalue = grub_xnu_create_value (curkey, name);
|
|
grub_free (name);
|
|
if (!curvalue)
|
|
return grub_errno;
|
|
|
|
data = grub_malloc (grub_strlen (var->value) + 1);
|
|
if (!data)
|
|
return grub_errno;
|
|
|
|
unescape (data, var->value, var->value + grub_strlen (var->value),
|
|
&len);
|
|
curvalue->datasize = len;
|
|
curvalue->data = data;
|
|
}
|
|
|
|
return grub_errno;
|
|
}
|
|
|
|
struct grub_video_bitmap *grub_xnu_bitmap = 0;
|
|
grub_xnu_bitmap_mode_t grub_xnu_bitmap_mode;
|
|
|
|
/* Option array indices. */
|
|
#define XNU_SPLASH_CMD_ARGINDEX_MODE 0
|
|
|
|
static const struct grub_arg_option xnu_splash_cmd_options[] =
|
|
{
|
|
{"mode", 'm', 0, N_("Background image mode."), N_("stretch|normal"),
|
|
ARG_TYPE_STRING},
|
|
{0, 0, 0, 0, 0, 0}
|
|
};
|
|
|
|
static grub_err_t
|
|
grub_cmd_xnu_splash (grub_extcmd_context_t ctxt,
|
|
int argc, char *args[])
|
|
{
|
|
grub_err_t err;
|
|
if (argc != 1)
|
|
return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
|
|
|
|
if (! grub_xnu_heap_size)
|
|
return grub_error (GRUB_ERR_BAD_OS, N_("you need to load the kernel first"));
|
|
|
|
if (ctxt->state[XNU_SPLASH_CMD_ARGINDEX_MODE].set &&
|
|
grub_strcmp (ctxt->state[XNU_SPLASH_CMD_ARGINDEX_MODE].arg,
|
|
"stretch") == 0)
|
|
grub_xnu_bitmap_mode = GRUB_XNU_BITMAP_STRETCH;
|
|
else
|
|
grub_xnu_bitmap_mode = GRUB_XNU_BITMAP_CENTER;
|
|
|
|
err = grub_video_bitmap_load (&grub_xnu_bitmap, args[0]);
|
|
if (err)
|
|
grub_xnu_bitmap = 0;
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
#ifndef GRUB_MACHINE_EMU
|
|
static grub_err_t
|
|
grub_cmd_xnu_resume (grub_command_t cmd __attribute__ ((unused)),
|
|
int argc, char *args[])
|
|
{
|
|
if (argc != 1)
|
|
return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
|
|
|
|
return grub_xnu_resume (args[0]);
|
|
}
|
|
#endif
|
|
|
|
void
|
|
grub_xnu_lock (void)
|
|
{
|
|
if (!locked)
|
|
grub_dl_ref (my_mod);
|
|
locked = 1;
|
|
}
|
|
|
|
void
|
|
grub_xnu_unlock (void)
|
|
{
|
|
if (locked)
|
|
grub_dl_unref (my_mod);
|
|
locked = 0;
|
|
}
|
|
|
|
static grub_command_t cmd_kernel64, cmd_kernel, cmd_mkext, cmd_kext;
|
|
static grub_command_t cmd_kextdir, cmd_ramdisk, cmd_resume;
|
|
static grub_extcmd_t cmd_splash;
|
|
|
|
GRUB_MOD_INIT(xnu)
|
|
{
|
|
cmd_kernel = grub_register_command ("xnu_kernel", grub_cmd_xnu_kernel, 0,
|
|
N_("Load XNU image."));
|
|
cmd_kernel64 = grub_register_command ("xnu_kernel64", grub_cmd_xnu_kernel64,
|
|
0, N_("Load 64-bit XNU image."));
|
|
cmd_mkext = grub_register_command_lockdown ("xnu_mkext", grub_cmd_xnu_mkext, 0,
|
|
N_("Load XNU extension package."));
|
|
cmd_kext = grub_register_command_lockdown ("xnu_kext", grub_cmd_xnu_kext, 0,
|
|
N_("Load XNU extension."));
|
|
cmd_kextdir = grub_register_command_lockdown ("xnu_kextdir", grub_cmd_xnu_kextdir,
|
|
/*
|
|
* TRANSLATORS: OSBundleRequired is
|
|
* a variable name in xnu extensions
|
|
* manifests. It behaves mostly like
|
|
* GNU/Linux runlevels.
|
|
*/
|
|
N_("DIRECTORY [OSBundleRequired]"),
|
|
/*
|
|
* TRANSLATORS: There are many extensions
|
|
* in extension directory.
|
|
*/
|
|
N_("Load XNU extension directory."));
|
|
cmd_ramdisk = grub_register_command ("xnu_ramdisk", grub_cmd_xnu_ramdisk, 0,
|
|
/* TRANSLATORS: ramdisk here isn't identifier. It can be translated. */
|
|
N_("Load XNU ramdisk. "
|
|
"It will be available in OS as md0."));
|
|
cmd_splash = grub_register_extcmd ("xnu_splash",
|
|
grub_cmd_xnu_splash, 0, 0,
|
|
N_("Load a splash image for XNU."),
|
|
xnu_splash_cmd_options);
|
|
|
|
#ifndef GRUB_MACHINE_EMU
|
|
cmd_resume = grub_register_command ("xnu_resume", grub_cmd_xnu_resume,
|
|
0, N_("Load an image of hibernated"
|
|
" XNU."));
|
|
#endif
|
|
|
|
grub_cpu_xnu_init ();
|
|
|
|
my_mod = mod;
|
|
}
|
|
|
|
GRUB_MOD_FINI(xnu)
|
|
{
|
|
#ifndef GRUB_MACHINE_EMU
|
|
grub_unregister_command (cmd_resume);
|
|
#endif
|
|
grub_unregister_command (cmd_mkext);
|
|
grub_unregister_command (cmd_kext);
|
|
grub_unregister_command (cmd_kextdir);
|
|
grub_unregister_command (cmd_ramdisk);
|
|
grub_unregister_command (cmd_kernel);
|
|
grub_unregister_extcmd (cmd_splash);
|
|
grub_unregister_command (cmd_kernel64);
|
|
|
|
grub_cpu_xnu_fini ();
|
|
}
|