diff options
Diffstat (limited to 'grub-core/loader/linux.c')
-rw-r--r-- | grub-core/loader/linux.c | 335 |
1 files changed, 335 insertions, 0 deletions
diff --git a/grub-core/loader/linux.c b/grub-core/loader/linux.c new file mode 100644 index 0000000..3fe390f --- /dev/null +++ b/grub-core/loader/linux.c @@ -0,0 +1,335 @@ +#include <grub/types.h> +#include <grub/err.h> +#include <grub/linux.h> +#include <grub/misc.h> +#include <grub/file.h> +#include <grub/mm.h> +#include <grub/safemath.h> + +struct newc_head +{ + char magic[6]; + char ino[8]; + char mode[8]; + char uid[8]; + char gid[8]; + char nlink[8]; + char mtime[8]; + char filesize[8]; + char devmajor[8]; + char devminor[8]; + char rdevmajor[8]; + char rdevminor[8]; + char namesize[8]; + char check[8]; +} GRUB_PACKED; + +struct grub_linux_initrd_component +{ + grub_file_t file; + char *newc_name; + grub_off_t size; +}; + +struct dir +{ + char *name; + struct dir *next; + struct dir *child; +}; + +static char +hex (grub_uint8_t val) +{ + if (val < 10) + return '0' + val; + return 'a' + val - 10; +} + +static void +set_field (char *var, grub_uint32_t val) +{ + int i; + char *ptr = var; + for (i = 28; i >= 0; i -= 4) + *ptr++ = hex((val >> i) & 0xf); +} + +static grub_uint8_t * +make_header (grub_uint8_t *ptr, + const char *name, grub_size_t len, + grub_uint32_t mode, + grub_off_t fsize) +{ + struct newc_head *head = (struct newc_head *) ptr; + grub_uint8_t *optr; + grub_size_t oh = 0; + grub_memcpy (head->magic, "070701", 6); + set_field (head->ino, 0); + set_field (head->mode, mode); + set_field (head->uid, 0); + set_field (head->gid, 0); + set_field (head->nlink, 1); + set_field (head->mtime, 0); + set_field (head->filesize, fsize); + set_field (head->devmajor, 0); + set_field (head->devminor, 0); + set_field (head->rdevmajor, 0); + set_field (head->rdevminor, 0); + set_field (head->namesize, len); + set_field (head->check, 0); + optr = ptr; + ptr += sizeof (struct newc_head); + grub_memcpy (ptr, name, len); + ptr += len; + oh = ALIGN_UP_OVERHEAD (ptr - optr, 4); + grub_memset (ptr, 0, oh); + ptr += oh; + return ptr; +} + +static void +free_dir (struct dir *root) +{ + if (!root) + return; + free_dir (root->next); + free_dir (root->child); + grub_free (root->name); + grub_free (root); +} + +static grub_err_t +insert_dir (const char *name, struct dir **root, + grub_uint8_t *ptr, grub_size_t *size) +{ + struct dir *cur, **head = root; + const char *cb, *ce = name; + *size = 0; + while (1) + { + for (cb = ce; *cb == '/'; cb++); + for (ce = cb; *ce && *ce != '/'; ce++); + if (!*ce) + break; + + for (cur = *root; cur; cur = cur->next) + if (grub_memcmp (cur->name, cb, ce - cb) + && cur->name[ce - cb] == 0) + break; + if (!cur) + { + struct dir *n; + n = grub_zalloc (sizeof (*n)); + if (!n) + return 0; + n->next = *head; + n->name = grub_strndup (cb, ce - cb); + if (ptr) + { + grub_dprintf ("linux", "Creating directory %s, %s\n", name, ce); + ptr = make_header (ptr, name, ce - name, + 040777, 0); + } + if (grub_add (*size, + ALIGN_UP ((ce - (char *) name) + + sizeof (struct newc_head), 4), + size)) + { + grub_error (GRUB_ERR_OUT_OF_RANGE, N_("overflow is detected")); + grub_free (n->name); + grub_free (n); + return grub_errno; + } + *head = n; + cur = n; + } + root = &cur->next; + } + return GRUB_ERR_NONE; +} + +grub_err_t +grub_initrd_init (int argc, char *argv[], + struct grub_linux_initrd_context *initrd_ctx) +{ + int i; + int newc = 0; + struct dir *root = 0; + + initrd_ctx->nfiles = 0; + initrd_ctx->components = 0; + + initrd_ctx->components = grub_calloc (argc, sizeof (initrd_ctx->components[0])); + if (!initrd_ctx->components) + return grub_errno; + + initrd_ctx->size = 0; + + for (i = 0; i < argc; i++) + { + const char *fname = argv[i]; + + initrd_ctx->size = ALIGN_UP (initrd_ctx->size, 4); + + if (grub_memcmp (argv[i], "newc:", 5) == 0) + { + const char *ptr, *eptr; + ptr = argv[i] + 5; + while (*ptr == '/') + ptr++; + eptr = grub_strchr (ptr, ':'); + if (eptr) + { + grub_size_t dir_size, name_len; + + initrd_ctx->components[i].newc_name = grub_strndup (ptr, eptr - ptr); + if (!initrd_ctx->components[i].newc_name || + insert_dir (initrd_ctx->components[i].newc_name, &root, 0, + &dir_size)) + { + grub_initrd_close (initrd_ctx); + return grub_errno; + } + name_len = grub_strlen (initrd_ctx->components[i].newc_name); + if (grub_add (initrd_ctx->size, + ALIGN_UP (sizeof (struct newc_head) + name_len, 4), + &initrd_ctx->size) || + grub_add (initrd_ctx->size, dir_size, &initrd_ctx->size)) + goto overflow; + newc = 1; + fname = eptr + 1; + } + } + else if (newc) + { + if (grub_add (initrd_ctx->size, + ALIGN_UP (sizeof (struct newc_head) + + sizeof ("TRAILER!!!") - 1, 4), + &initrd_ctx->size)) + goto overflow; + free_dir (root); + root = 0; + newc = 0; + } + initrd_ctx->components[i].file = grub_file_open (fname, + GRUB_FILE_TYPE_LINUX_INITRD + | GRUB_FILE_TYPE_NO_DECOMPRESS); + if (!initrd_ctx->components[i].file) + { + grub_initrd_close (initrd_ctx); + return grub_errno; + } + initrd_ctx->nfiles++; + initrd_ctx->components[i].size + = grub_file_size (initrd_ctx->components[i].file); + if (grub_add (initrd_ctx->size, initrd_ctx->components[i].size, + &initrd_ctx->size)) + goto overflow; + } + + if (newc) + { + initrd_ctx->size = ALIGN_UP (initrd_ctx->size, 4); + if (grub_add (initrd_ctx->size, + ALIGN_UP (sizeof (struct newc_head) + + sizeof ("TRAILER!!!") - 1, 4), + &initrd_ctx->size)) + goto overflow; + free_dir (root); + root = 0; + } + + return GRUB_ERR_NONE; + + overflow: + free_dir (root); + grub_initrd_close (initrd_ctx); + return grub_error (GRUB_ERR_OUT_OF_RANGE, N_("overflow is detected")); +} + +grub_size_t +grub_get_initrd_size (struct grub_linux_initrd_context *initrd_ctx) +{ + return initrd_ctx->size; +} + +void +grub_initrd_close (struct grub_linux_initrd_context *initrd_ctx) +{ + int i; + if (!initrd_ctx->components) + return; + for (i = 0; i < initrd_ctx->nfiles; i++) + { + grub_free (initrd_ctx->components[i].newc_name); + grub_file_close (initrd_ctx->components[i].file); + } + grub_free (initrd_ctx->components); + initrd_ctx->components = 0; +} + +grub_err_t +grub_initrd_load (struct grub_linux_initrd_context *initrd_ctx, + char *argv[], void *target) +{ + grub_uint8_t *ptr = target; + int i; + int newc = 0; + struct dir *root = 0; + grub_ssize_t cursize = 0; + + for (i = 0; i < initrd_ctx->nfiles; i++) + { + grub_memset (ptr, 0, ALIGN_UP_OVERHEAD (cursize, 4)); + ptr += ALIGN_UP_OVERHEAD (cursize, 4); + + if (initrd_ctx->components[i].newc_name) + { + grub_size_t dir_size; + + if (insert_dir (initrd_ctx->components[i].newc_name, &root, ptr, + &dir_size)) + { + free_dir (root); + grub_initrd_close (initrd_ctx); + return grub_errno; + } + ptr += dir_size; + ptr = make_header (ptr, initrd_ctx->components[i].newc_name, + grub_strlen (initrd_ctx->components[i].newc_name), + 0100777, + initrd_ctx->components[i].size); + newc = 1; + } + else if (newc) + { + ptr = make_header (ptr, "TRAILER!!!", sizeof ("TRAILER!!!") - 1, + 0, 0); + free_dir (root); + root = 0; + newc = 0; + } + + cursize = initrd_ctx->components[i].size; + if (grub_file_read (initrd_ctx->components[i].file, ptr, cursize) + != cursize) + { + if (!grub_errno) + grub_error (GRUB_ERR_FILE_READ_ERROR, N_("premature end of file %s"), + argv[i]); + grub_initrd_close (initrd_ctx); + return grub_errno; + } + ptr += cursize; + } + if (newc) + { + grub_memset (ptr, 0, ALIGN_UP_OVERHEAD (cursize, 4)); + ptr += ALIGN_UP_OVERHEAD (cursize, 4); + ptr = make_header (ptr, "TRAILER!!!", sizeof ("TRAILER!!!") - 1, 0, 0); + } + free_dir (root); + root = 0; + return GRUB_ERR_NONE; +} |