diff options
Diffstat (limited to 'grub-core/loader/i386/pc/plan9.c')
-rw-r--r-- | grub-core/loader/i386/pc/plan9.c | 607 |
1 files changed, 607 insertions, 0 deletions
diff --git a/grub-core/loader/i386/pc/plan9.c b/grub-core/loader/i386/pc/plan9.c new file mode 100644 index 0000000..3755015 --- /dev/null +++ b/grub-core/loader/i386/pc/plan9.c @@ -0,0 +1,607 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2010 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/loader.h> +#include <grub/file.h> +#include <grub/err.h> +#include <grub/device.h> +#include <grub/disk.h> +#include <grub/misc.h> +#include <grub/types.h> +#include <grub/partition.h> +#include <grub/msdos_partition.h> +#include <grub/scsi.h> +#include <grub/dl.h> +#include <grub/command.h> +#include <grub/i18n.h> +#include <grub/video.h> +#include <grub/mm.h> +#include <grub/cpu/relocator.h> +#include <grub/extcmd.h> +#include <grub/verify.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +static grub_dl_t my_mod; +static struct grub_relocator *rel; +static grub_uint32_t eip = 0xffffffff; + +#define GRUB_PLAN9_TARGET 0x100000 +#define GRUB_PLAN9_ALIGN 4096 +#define GRUB_PLAN9_CONFIG_ADDR 0x001200 +#define GRUB_PLAN9_CONFIG_PATH_SIZE 0x000040 +#define GRUB_PLAN9_CONFIG_MAGIC "ZORT 0\r\n" + +static const struct grub_arg_option options[] = + { + {"map", 'm', GRUB_ARG_OPTION_REPEATABLE, + /* TRANSLATORS: it's about guessing which GRUB disk + is which Plan9 disk. If your language has no + word "mapping" you can use another word which + means that the GRUBDEVICE and PLAN9DEVICE are + actually the same device, just named differently + in OS and GRUB. */ + N_("Override guessed mapping of Plan9 devices."), + N_("GRUBDEVICE=PLAN9DEVICE"), + ARG_TYPE_STRING}, + {0, 0, 0, 0, 0, 0} + }; + +struct grub_plan9_header +{ + grub_uint32_t magic; +#define GRUB_PLAN9_MAGIC 0x1eb + grub_uint32_t text_size; + grub_uint32_t data_size; + grub_uint32_t bss_size; + grub_uint32_t sectiona; + grub_uint32_t entry_addr; + grub_uint32_t zero; + grub_uint32_t sectionb; +}; + +static grub_err_t +grub_plan9_boot (void) +{ + struct grub_relocator32_state state = { + .eax = 0, + .eip = eip, + .ebx = 0, + .ecx = 0, + .edx = 0, + .edi = 0, + .esp = 0, + .ebp = 0, + .esi = 0 + }; + grub_video_set_mode ("text", 0, 0); + + return grub_relocator32_boot (rel, state, 0); +} + +static grub_err_t +grub_plan9_unload (void) +{ + grub_relocator_unload (rel); + rel = NULL; + grub_dl_unref (my_mod); + return GRUB_ERR_NONE; +} + +/* Context for grub_cmd_plan9. */ +struct grub_cmd_plan9_ctx +{ + grub_extcmd_context_t ctxt; + grub_file_t file; + char *pmap; + grub_size_t pmapalloc; + grub_size_t pmapptr; + int noslash; + int prefixescnt[5]; + char *bootdisk, *bootpart; +}; + +static const char prefixes[5][10] = { + "dos", "plan9", "ntfs", "linux", "linuxswap" +}; + +#include <grub/err.h> + +static inline grub_err_t +grub_extend_alloc (grub_size_t sz, grub_size_t *allocated, char **ptr) +{ + void *n; + if (sz < *allocated) + return GRUB_ERR_NONE; + + *allocated = 2 * sz; + n = grub_realloc (*ptr, *allocated); + if (!n) + return grub_errno; + *ptr = n; + return GRUB_ERR_NONE; +} + +/* Helper for grub_cmd_plan9. */ +static int +fill_partition (grub_disk_t disk, const grub_partition_t partition, void *data) +{ + struct grub_cmd_plan9_ctx *fill_ctx = data; + int file_disk = 0; + int pstart, pend; + + if (!fill_ctx->noslash) + { + if (grub_extend_alloc (fill_ctx->pmapptr + 1, &fill_ctx->pmapalloc, + &fill_ctx->pmap)) + return 1; + fill_ctx->pmap[fill_ctx->pmapptr++] = '/'; + } + fill_ctx->noslash = 0; + + file_disk = fill_ctx->file->device->disk + && disk->id == fill_ctx->file->device->disk->id + && disk->dev->id == fill_ctx->file->device->disk->dev->id; + + pstart = fill_ctx->pmapptr; + if (grub_strcmp (partition->partmap->name, "plan") == 0) + { + unsigned ptr = partition->index + sizeof ("part ") - 1; + grub_err_t err; + disk->partition = partition->parent; + do + { + if (grub_extend_alloc (fill_ctx->pmapptr + 1, &fill_ctx->pmapalloc, + &fill_ctx->pmap)) + return 1; + err = grub_disk_read (disk, 1, ptr, 1, + fill_ctx->pmap + fill_ctx->pmapptr); + if (err) + { + disk->partition = 0; + return err; + } + ptr++; + fill_ctx->pmapptr++; + } + while (grub_isalpha (fill_ctx->pmap[fill_ctx->pmapptr - 1]) + || grub_isdigit (fill_ctx->pmap[fill_ctx->pmapptr - 1])); + fill_ctx->pmapptr--; + } + else + { + char name[50]; + int c = 0; + if (grub_strcmp (partition->partmap->name, "msdos") == 0) + { + switch (partition->msdostype) + { + case GRUB_PC_PARTITION_TYPE_PLAN9: + c = 1; + break; + case GRUB_PC_PARTITION_TYPE_NTFS: + c = 2; + break; + case GRUB_PC_PARTITION_TYPE_MINIX: + case GRUB_PC_PARTITION_TYPE_LINUX_MINIX: + case GRUB_PC_PARTITION_TYPE_EXT2FS: + c = 3; + break; + case GRUB_PC_PARTITION_TYPE_LINUX_SWAP: + c = 4; + break; + } + } + + if (fill_ctx->prefixescnt[c] == 0) + grub_strcpy (name, prefixes[c]); + else + grub_snprintf (name, sizeof (name), "%s.%d", prefixes[c], + fill_ctx->prefixescnt[c]); + fill_ctx->prefixescnt[c]++; + if (grub_extend_alloc (fill_ctx->pmapptr + grub_strlen (name) + 1, + &fill_ctx->pmapalloc, &fill_ctx->pmap)) + return 1; + grub_strcpy (fill_ctx->pmap + fill_ctx->pmapptr, name); + fill_ctx->pmapptr += grub_strlen (name); + } + pend = fill_ctx->pmapptr; + if (grub_extend_alloc (fill_ctx->pmapptr + 2 + 25 + 5 + 25, + &fill_ctx->pmapalloc, &fill_ctx->pmap)) + return 1; + fill_ctx->pmap[fill_ctx->pmapptr++] = ' '; + grub_snprintf (fill_ctx->pmap + fill_ctx->pmapptr, 25 + 5 + 25, + "%" PRIuGRUB_UINT64_T " %" PRIuGRUB_UINT64_T, + grub_partition_get_start (partition), + grub_partition_get_start (partition) + + grub_partition_get_len (partition)); + if (file_disk && grub_partition_get_start (partition) + == grub_partition_get_start (fill_ctx->file->device->disk->partition) + && grub_partition_get_len (partition) + == grub_partition_get_len (fill_ctx->file->device->disk->partition)) + { + grub_free (fill_ctx->bootpart); + fill_ctx->bootpart = grub_strndup (fill_ctx->pmap + pstart, + pend - pstart); + } + + fill_ctx->pmapptr += grub_strlen (fill_ctx->pmap + fill_ctx->pmapptr); + return 0; +} + +/* Helper for grub_cmd_plan9. */ +static int +fill_disk (const char *name, void *data) +{ + struct grub_cmd_plan9_ctx *fill_ctx = data; + grub_device_t dev; + char *plan9name = NULL; + unsigned i; + int file_disk = 0; + + dev = grub_device_open (name); + if (!dev) + { + grub_print_error (); + return 0; + } + if (!dev->disk) + { + grub_device_close (dev); + return 0; + } + file_disk = fill_ctx->file->device->disk + && dev->disk->id == fill_ctx->file->device->disk->id + && dev->disk->dev->id == fill_ctx->file->device->disk->dev->id; + for (i = 0; + fill_ctx->ctxt->state[0].args && fill_ctx->ctxt->state[0].args[i]; i++) + if (grub_strncmp (name, fill_ctx->ctxt->state[0].args[i], + grub_strlen (name)) == 0 + && fill_ctx->ctxt->state[0].args[i][grub_strlen (name)] == '=') + break; + if (fill_ctx->ctxt->state[0].args && fill_ctx->ctxt->state[0].args[i]) + plan9name = grub_strdup (fill_ctx->ctxt->state[0].args[i] + + grub_strlen (name) + 1); + else + switch (dev->disk->dev->id) + { + case GRUB_DISK_DEVICE_BIOSDISK_ID: + if (dev->disk->id & 0x80) + plan9name = grub_xasprintf ("sdB%u", + (unsigned) (dev->disk->id & 0x7f)); + else + plan9name = grub_xasprintf ("fd%u", + (unsigned) (dev->disk->id & 0x7f)); + break; + /* Shouldn't happen as Plan9 doesn't work on these platforms. */ + case GRUB_DISK_DEVICE_OFDISK_ID: + case GRUB_DISK_DEVICE_EFIDISK_ID: + + /* Plan9 doesn't see those. */ + default: + + /* Not sure how to handle those. */ + case GRUB_DISK_DEVICE_NAND_ID: + if (!file_disk) + { + grub_device_close (dev); + return 0; + } + + /* if it's the disk the kernel is loaded from we need to name + it nevertheless. */ + plan9name = grub_strdup ("sdZ0"); + break; + + case GRUB_DISK_DEVICE_ATA_ID: + { + unsigned unit; + if (grub_strlen (dev->disk->name) < sizeof ("ata0") - 1) + unit = 0; + else + unit = grub_strtoul (dev->disk->name + sizeof ("ata0") - 1, 0, 0); + plan9name = grub_xasprintf ("sd%c%d", 'C' + unit / 2, unit % 2); + } + break; + case GRUB_DISK_DEVICE_SCSI_ID: + if (((dev->disk->id >> GRUB_SCSI_ID_SUBSYSTEM_SHIFT) & 0xff) + == GRUB_SCSI_SUBSYSTEM_PATA) + { + unsigned unit; + if (grub_strlen (dev->disk->name) < sizeof ("ata0") - 1) + unit = 0; + else + unit = grub_strtoul (dev->disk->name + sizeof ("ata0") - 1, + 0, 0); + plan9name = grub_xasprintf ("sd%c%d", 'C' + unit / 2, unit % 2); + break; + } + + /* FIXME: how does Plan9 number controllers? + We probably need save the SCSI devices and sort them */ + plan9name + = grub_xasprintf ("sd0%u", (unsigned) + ((dev->disk->id >> GRUB_SCSI_ID_BUS_SHIFT) + & 0xf)); + break; + } + if (!plan9name) + { + grub_print_error (); + grub_device_close (dev); + return 0; + } + if (grub_extend_alloc (fill_ctx->pmapptr + grub_strlen (plan9name) + + sizeof ("part="), &fill_ctx->pmapalloc, + &fill_ctx->pmap)) + { + grub_free (plan9name); + grub_device_close (dev); + return 1; + } + grub_strcpy (fill_ctx->pmap + fill_ctx->pmapptr, plan9name); + fill_ctx->pmapptr += grub_strlen (plan9name); + if (!file_disk) + grub_free (plan9name); + else + { + grub_free (fill_ctx->bootdisk); + fill_ctx->bootdisk = plan9name; + } + grub_strcpy (fill_ctx->pmap + fill_ctx->pmapptr, "part="); + fill_ctx->pmapptr += sizeof ("part=") - 1; + + fill_ctx->noslash = 1; + grub_memset (fill_ctx->prefixescnt, 0, sizeof (fill_ctx->prefixescnt)); + if (grub_partition_iterate (dev->disk, fill_partition, fill_ctx)) + { + grub_device_close (dev); + return 1; + } + if (grub_extend_alloc (fill_ctx->pmapptr + 1, &fill_ctx->pmapalloc, + &fill_ctx->pmap)) + { + grub_device_close (dev); + return 1; + } + fill_ctx->pmap[fill_ctx->pmapptr++] = '\n'; + + grub_device_close (dev); + return 0; +} + +static grub_err_t +grub_cmd_plan9 (grub_extcmd_context_t ctxt, int argc, char *argv[]) +{ + struct grub_cmd_plan9_ctx fill_ctx = { + .ctxt = ctxt, + .file = 0, + .pmap = NULL, + .pmapalloc = 256, + .pmapptr = 0, + .noslash = 1, + .bootdisk = NULL, + .bootpart = NULL + }; + void *mem; + grub_size_t memsize, padsize; + struct grub_plan9_header hdr; + char *config, *configptr; + grub_size_t configsize; + char *bootpath = NULL; + + if (argc == 0) + return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected")); + + grub_dl_ref (my_mod); + + rel = grub_relocator_new (); + if (!rel) + goto fail; + + fill_ctx.file = grub_file_open (argv[0], GRUB_FILE_TYPE_PLAN9_KERNEL); + if (! fill_ctx.file) + goto fail; + + fill_ctx.pmap = grub_malloc (fill_ctx.pmapalloc); + if (!fill_ctx.pmap) + goto fail; + + if (grub_disk_dev_iterate (fill_disk, &fill_ctx)) + goto fail; + + if (grub_extend_alloc (fill_ctx.pmapptr + 1, &fill_ctx.pmapalloc, + &fill_ctx.pmap)) + goto fail; + fill_ctx.pmap[fill_ctx.pmapptr] = 0; + + { + char *file_name = grub_strchr (argv[0], ')'); + if (file_name) + file_name++; + else + file_name = argv[0]; + if (*file_name) + file_name++; + + if (fill_ctx.bootpart) + bootpath = grub_xasprintf ("%s!%s!%s", fill_ctx.bootdisk, + fill_ctx.bootpart, file_name); + else + bootpath = grub_xasprintf ("%s!%s", fill_ctx.bootdisk, file_name); + grub_free (fill_ctx.bootdisk); + grub_free (fill_ctx.bootpart); + } + if (!bootpath) + goto fail; + + if (grub_file_read (fill_ctx.file, &hdr, + sizeof (hdr)) != (grub_ssize_t) sizeof (hdr)) + { + if (!grub_errno) + grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), + argv[0]); + goto fail; + } + + if (grub_be_to_cpu32 (hdr.magic) != GRUB_PLAN9_MAGIC + || hdr.zero) + { + grub_error (GRUB_ERR_BAD_OS, "unsupported Plan9"); + goto fail; + } + + memsize = ALIGN_UP (grub_be_to_cpu32 (hdr.text_size) + sizeof (hdr), + GRUB_PLAN9_ALIGN); + memsize += ALIGN_UP (grub_be_to_cpu32 (hdr.data_size), GRUB_PLAN9_ALIGN); + memsize += ALIGN_UP(grub_be_to_cpu32 (hdr.bss_size), GRUB_PLAN9_ALIGN); + eip = grub_be_to_cpu32 (hdr.entry_addr) & 0xfffffff; + + /* path */ + configsize = GRUB_PLAN9_CONFIG_PATH_SIZE; + /* magic */ + configsize += sizeof (GRUB_PLAN9_CONFIG_MAGIC) - 1; + { + int i; + for (i = 1; i < argc; i++) + configsize += grub_strlen (argv[i]) + 1; + } + configsize += (sizeof ("bootfile=") - 1) + grub_strlen (bootpath) + 1; + configsize += fill_ctx.pmapptr; + /* Terminating \0. */ + configsize++; + + { + grub_relocator_chunk_t ch; + grub_err_t err; + err = grub_relocator_alloc_chunk_addr (rel, &ch, GRUB_PLAN9_CONFIG_ADDR, + configsize); + if (err) + goto fail; + config = get_virtual_current_address (ch); + } + + grub_memset (config, 0, GRUB_PLAN9_CONFIG_PATH_SIZE); + grub_strncpy (config, bootpath, GRUB_PLAN9_CONFIG_PATH_SIZE - 1); + + configptr = config + GRUB_PLAN9_CONFIG_PATH_SIZE; + grub_memcpy (configptr, GRUB_PLAN9_CONFIG_MAGIC, + sizeof (GRUB_PLAN9_CONFIG_MAGIC) - 1); + configptr += sizeof (GRUB_PLAN9_CONFIG_MAGIC) - 1; + configptr = grub_stpcpy (configptr, "bootfile="); + configptr = grub_stpcpy (configptr, bootpath); + *configptr++ = '\n'; + char *cmdline = configptr; + { + int i; + for (i = 1; i < argc; i++) + { + configptr = grub_stpcpy (configptr, argv[i]); + *configptr++ = '\n'; + } + } + + { + grub_err_t err; + *configptr = '\0'; + err = grub_verify_string (cmdline, GRUB_VERIFY_KERNEL_CMDLINE); + if (err) + goto fail; + } + + configptr = grub_stpcpy (configptr, fill_ctx.pmap); + + { + grub_relocator_chunk_t ch; + grub_err_t err; + + err = grub_relocator_alloc_chunk_addr (rel, &ch, GRUB_PLAN9_TARGET, + memsize); + if (err) + goto fail; + mem = get_virtual_current_address (ch); + } + + { + grub_uint8_t *ptr; + ptr = mem; + grub_memcpy (ptr, &hdr, sizeof (hdr)); + ptr += sizeof (hdr); + + if (grub_file_read (fill_ctx.file, ptr, grub_be_to_cpu32 (hdr.text_size)) + != (grub_ssize_t) grub_be_to_cpu32 (hdr.text_size)) + { + if (!grub_errno) + grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), + argv[0]); + goto fail; + } + ptr += grub_be_to_cpu32 (hdr.text_size); + padsize = ALIGN_UP (grub_be_to_cpu32 (hdr.text_size) + sizeof (hdr), + GRUB_PLAN9_ALIGN) - grub_be_to_cpu32 (hdr.text_size) + - sizeof (hdr); + + grub_memset (ptr, 0, padsize); + ptr += padsize; + + if (grub_file_read (fill_ctx.file, ptr, grub_be_to_cpu32 (hdr.data_size)) + != (grub_ssize_t) grub_be_to_cpu32 (hdr.data_size)) + { + if (!grub_errno) + grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), + argv[0]); + goto fail; + } + ptr += grub_be_to_cpu32 (hdr.data_size); + padsize = ALIGN_UP (grub_be_to_cpu32 (hdr.data_size), GRUB_PLAN9_ALIGN) + - grub_be_to_cpu32 (hdr.data_size); + + grub_memset (ptr, 0, padsize); + ptr += padsize; + grub_memset (ptr, 0, ALIGN_UP(grub_be_to_cpu32 (hdr.bss_size), + GRUB_PLAN9_ALIGN)); + } + grub_loader_set (grub_plan9_boot, grub_plan9_unload, 1); + return GRUB_ERR_NONE; + + fail: + grub_free (fill_ctx.pmap); + + if (fill_ctx.file) + grub_file_close (fill_ctx.file); + + grub_plan9_unload (); + + return grub_errno; +} + +static grub_extcmd_t cmd; + +GRUB_MOD_INIT(plan9) +{ + cmd = grub_register_extcmd ("plan9", grub_cmd_plan9, + GRUB_COMMAND_OPTIONS_AT_START, + N_("KERNEL ARGS"), N_("Load Plan9 kernel."), + options); + my_mod = mod; +} + +GRUB_MOD_FINI(plan9) +{ + grub_unregister_extcmd (cmd); +} |