2N/A/* loadenv.c - command to load/save environment variable. */
2N/A/*
2N/A * GRUB -- GRand Unified Bootloader
2N/A * Copyright (C) 2008,2009,2010 Free Software Foundation, Inc.
2N/A *
2N/A * GRUB is free software: you can redistribute it and/or modify
2N/A * it under the terms of the GNU General Public License as published by
2N/A * the Free Software Foundation, either version 3 of the License, or
2N/A * (at your option) any later version.
2N/A *
2N/A * GRUB is distributed in the hope that it will be useful,
2N/A * but WITHOUT ANY WARRANTY; without even the implied warranty of
2N/A * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2N/A * GNU General Public License for more details.
2N/A *
2N/A * You should have received a copy of the GNU General Public License
2N/A * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
2N/A */
2N/A
2N/A#include <grub/dl.h>
2N/A#include <grub/mm.h>
2N/A#include <grub/file.h>
2N/A#include <grub/disk.h>
2N/A#include <grub/misc.h>
2N/A#include <grub/env.h>
2N/A#include <grub/partition.h>
2N/A#include <grub/lib/envblk.h>
2N/A#include <grub/extcmd.h>
2N/A#include <grub/i18n.h>
2N/A
2N/AGRUB_MOD_LICENSE ("GPLv3+");
2N/A
2N/Astatic const struct grub_arg_option options[] =
2N/A {
2N/A {"file", 'f', 0, N_("Specify filename."), 0, ARG_TYPE_PATHNAME},
2N/A {0, 0, 0, 0, 0, 0}
2N/A };
2N/A
2N/Astatic grub_file_t
2N/Aopen_envblk_file (char *filename)
2N/A{
2N/A grub_file_t file;
2N/A
2N/A if (! filename)
2N/A {
2N/A const char *prefix;
2N/A
2N/A prefix = grub_env_get ("prefix");
2N/A if (prefix)
2N/A {
2N/A int len;
2N/A
2N/A len = grub_strlen (prefix);
2N/A filename = grub_malloc (len + 1 + sizeof (GRUB_ENVBLK_DEFCFG));
2N/A if (! filename)
2N/A return 0;
2N/A
2N/A grub_strcpy (filename, prefix);
2N/A filename[len] = '/';
2N/A grub_strcpy (filename + len + 1, GRUB_ENVBLK_DEFCFG);
2N/A grub_file_filter_disable_compression ();
2N/A file = grub_file_open (filename);
2N/A grub_free (filename);
2N/A return file;
2N/A }
2N/A else
2N/A {
2N/A grub_error (GRUB_ERR_FILE_NOT_FOUND, "prefix is not found");
2N/A return 0;
2N/A }
2N/A }
2N/A
2N/A grub_file_filter_disable_compression ();
2N/A return grub_file_open (filename);
2N/A}
2N/A
2N/Astatic grub_envblk_t
2N/Aread_envblk_file (grub_file_t file)
2N/A{
2N/A grub_off_t offset = 0;
2N/A char *buf;
2N/A grub_size_t size = grub_file_size (file);
2N/A grub_envblk_t envblk;
2N/A
2N/A buf = grub_malloc (size);
2N/A if (! buf)
2N/A return 0;
2N/A
2N/A while (size > 0)
2N/A {
2N/A grub_ssize_t ret;
2N/A
2N/A ret = grub_file_read (file, buf + offset, size);
2N/A if (ret <= 0)
2N/A {
2N/A if (grub_errno == GRUB_ERR_NONE)
2N/A grub_error (GRUB_ERR_FILE_READ_ERROR, "cannot read");
2N/A grub_free (buf);
2N/A return 0;
2N/A }
2N/A
2N/A size -= ret;
2N/A offset += ret;
2N/A }
2N/A
2N/A envblk = grub_envblk_open (buf, offset);
2N/A if (! envblk)
2N/A {
2N/A grub_free (buf);
2N/A grub_error (GRUB_ERR_BAD_FILE_TYPE, "invalid environment block");
2N/A return 0;
2N/A }
2N/A
2N/A return envblk;
2N/A}
2N/A
2N/Astatic grub_err_t
2N/Agrub_cmd_load_env (grub_extcmd_context_t ctxt,
2N/A int argc __attribute__ ((unused)),
2N/A char **args __attribute__ ((unused)))
2N/A{
2N/A struct grub_arg_list *state = ctxt->state;
2N/A grub_file_t file;
2N/A grub_envblk_t envblk;
2N/A
2N/A auto int set_var (const char *name, const char *value);
2N/A int set_var (const char *name, const char *value)
2N/A {
2N/A grub_env_set (name, value);
2N/A return 0;
2N/A }
2N/A
2N/A file = open_envblk_file ((state[0].set) ? state[0].arg : 0);
2N/A if (! file)
2N/A return grub_errno;
2N/A
2N/A envblk = read_envblk_file (file);
2N/A if (! envblk)
2N/A goto fail;
2N/A
2N/A grub_envblk_iterate (envblk, set_var);
2N/A grub_envblk_close (envblk);
2N/A
2N/A fail:
2N/A grub_file_close (file);
2N/A return grub_errno;
2N/A}
2N/A
2N/Astatic grub_err_t
2N/Agrub_cmd_list_env (grub_extcmd_context_t ctxt,
2N/A int argc __attribute__ ((unused)),
2N/A char **args __attribute__ ((unused)))
2N/A{
2N/A struct grub_arg_list *state = ctxt->state;
2N/A grub_file_t file;
2N/A grub_envblk_t envblk;
2N/A
2N/A /* Print all variables in current context. */
2N/A auto int print_var (const char *name, const char *value);
2N/A int print_var (const char *name, const char *value)
2N/A {
2N/A grub_printf ("%s=%s\n", name, value);
2N/A return 0;
2N/A }
2N/A
2N/A file = open_envblk_file ((state[0].set) ? state[0].arg : 0);
2N/A if (! file)
2N/A return grub_errno;
2N/A
2N/A envblk = read_envblk_file (file);
2N/A if (! envblk)
2N/A goto fail;
2N/A
2N/A grub_envblk_iterate (envblk, print_var);
2N/A grub_envblk_close (envblk);
2N/A
2N/A fail:
2N/A grub_file_close (file);
2N/A return grub_errno;
2N/A}
2N/A
2N/A/* Used to maintain a variable length of blocklists internally. */
2N/Astruct blocklist
2N/A{
2N/A grub_disk_addr_t sector;
2N/A unsigned offset;
2N/A unsigned length;
2N/A struct blocklist *next;
2N/A};
2N/A
2N/Astatic void
2N/Afree_blocklists (struct blocklist *p)
2N/A{
2N/A struct blocklist *q;
2N/A
2N/A for (; p; p = q)
2N/A {
2N/A q = p->next;
2N/A grub_free (p);
2N/A }
2N/A}
2N/A
2N/Astatic grub_err_t
2N/Acheck_blocklists (grub_envblk_t envblk, struct blocklist *blocklists,
2N/A grub_file_t file)
2N/A{
2N/A grub_size_t total_length;
2N/A grub_size_t index;
2N/A grub_disk_t disk;
2N/A grub_disk_addr_t part_start;
2N/A struct blocklist *p;
2N/A char *buf;
2N/A
2N/A /* Sanity checks. */
2N/A total_length = 0;
2N/A for (p = blocklists; p; p = p->next)
2N/A {
2N/A struct blocklist *q;
2N/A for (q = p->next; q; q = q->next)
2N/A {
2N/A /* Check if any pair of blocks overlap. */
2N/A if (p->sector == q->sector)
2N/A {
2N/A /* This might be actually valid, but it is unbelievable that
2N/A any filesystem makes such a silly allocation. */
2N/A return grub_error (GRUB_ERR_BAD_FS, "malformed file");
2N/A }
2N/A }
2N/A
2N/A total_length += p->length;
2N/A }
2N/A
2N/A if (total_length != grub_file_size (file))
2N/A {
2N/A /* Maybe sparse, unallocated sectors. No way in GRUB. */
2N/A return grub_error (GRUB_ERR_BAD_FILE_TYPE, "sparse file not allowed");
2N/A }
2N/A
2N/A /* One more sanity check. Re-read all sectors by blocklists, and compare
2N/A those with the data read via a file. */
2N/A disk = file->device->disk;
2N/A
2N/A part_start = grub_partition_get_start (disk->partition);
2N/A
2N/A buf = grub_envblk_buffer (envblk);
2N/A for (p = blocklists, index = 0; p; index += p->length, p = p->next)
2N/A {
2N/A char blockbuf[GRUB_DISK_SECTOR_SIZE];
2N/A
2N/A if (grub_disk_read (disk, p->sector - part_start,
2N/A p->offset, p->length, blockbuf))
2N/A return grub_errno;
2N/A
2N/A if (grub_memcmp (buf + index, blockbuf, p->length) != 0)
2N/A return grub_error (GRUB_ERR_FILE_READ_ERROR, "invalid blocklist");
2N/A }
2N/A
2N/A return GRUB_ERR_NONE;
2N/A}
2N/A
2N/Astatic int
2N/Awrite_blocklists (grub_envblk_t envblk, struct blocklist *blocklists,
2N/A grub_file_t file)
2N/A{
2N/A char *buf;
2N/A grub_disk_t disk;
2N/A grub_disk_addr_t part_start;
2N/A struct blocklist *p;
2N/A grub_size_t index;
2N/A
2N/A buf = grub_envblk_buffer (envblk);
2N/A disk = file->device->disk;
2N/A part_start = grub_partition_get_start (disk->partition);
2N/A
2N/A index = 0;
2N/A for (p = blocklists; p; index += p->length, p = p->next)
2N/A {
2N/A if (grub_disk_write (disk, p->sector - part_start,
2N/A p->offset, p->length, buf + index))
2N/A return 0;
2N/A }
2N/A
2N/A return 1;
2N/A}
2N/A
2N/Astatic grub_err_t
2N/Agrub_cmd_save_env (grub_extcmd_context_t ctxt, int argc, char **args)
2N/A{
2N/A struct grub_arg_list *state = ctxt->state;
2N/A grub_file_t file;
2N/A grub_envblk_t envblk;
2N/A struct blocklist *head = 0;
2N/A struct blocklist *tail = 0;
2N/A
2N/A /* Store blocklists in a linked list. */
2N/A auto void NESTED_FUNC_ATTR read_hook (grub_disk_addr_t sector,
2N/A unsigned offset,
2N/A unsigned length);
2N/A void NESTED_FUNC_ATTR read_hook (grub_disk_addr_t sector,
2N/A unsigned offset, unsigned length)
2N/A {
2N/A struct blocklist *block;
2N/A
2N/A if (offset + length > GRUB_DISK_SECTOR_SIZE)
2N/A /* Seemingly a bug. */
2N/A return;
2N/A
2N/A block = grub_malloc (sizeof (*block));
2N/A if (! block)
2N/A return;
2N/A
2N/A block->sector = sector;
2N/A block->offset = offset;
2N/A block->length = length;
2N/A
2N/A /* Slightly complicated, because the list should be FIFO. */
2N/A block->next = 0;
2N/A if (tail)
2N/A tail->next = block;
2N/A tail = block;
2N/A if (! head)
2N/A head = block;
2N/A }
2N/A
2N/A if (! argc)
2N/A return grub_error (GRUB_ERR_BAD_ARGUMENT, "no variable is specified");
2N/A
2N/A file = open_envblk_file ((state[0].set) ? state[0].arg : 0);
2N/A if (! file)
2N/A return grub_errno;
2N/A
2N/A if (! file->device->disk)
2N/A {
2N/A grub_file_close (file);
2N/A return grub_error (GRUB_ERR_BAD_DEVICE, "disk device required");
2N/A }
2N/A
2N/A file->read_hook = read_hook;
2N/A envblk = read_envblk_file (file);
2N/A file->read_hook = 0;
2N/A if (! envblk)
2N/A goto fail;
2N/A
2N/A if (check_blocklists (envblk, head, file))
2N/A goto fail;
2N/A
2N/A while (argc)
2N/A {
2N/A const char *value;
2N/A
2N/A value = grub_env_get (args[0]);
2N/A if (value)
2N/A {
2N/A if (! grub_envblk_set (envblk, args[0], value))
2N/A {
2N/A grub_error (GRUB_ERR_BAD_ARGUMENT, "environment block too small");
2N/A goto fail;
2N/A }
2N/A }
2N/A
2N/A argc--;
2N/A args++;
2N/A }
2N/A
2N/A write_blocklists (envblk, head, file);
2N/A
2N/A fail:
2N/A if (envblk)
2N/A grub_envblk_close (envblk);
2N/A free_blocklists (head);
2N/A grub_file_close (file);
2N/A return grub_errno;
2N/A}
2N/A
2N/Astatic grub_extcmd_t cmd_load, cmd_list, cmd_save;
2N/A
2N/AGRUB_MOD_INIT(loadenv)
2N/A{
2N/A cmd_load =
2N/A grub_register_extcmd ("load_env", grub_cmd_load_env, 0, N_("[-f FILE]"),
2N/A N_("Load variables from environment block file."),
2N/A options);
2N/A cmd_list =
2N/A grub_register_extcmd ("list_env", grub_cmd_list_env, 0, N_("[-f FILE]"),
2N/A N_("List variables from environment block file."),
2N/A options);
2N/A cmd_save =
2N/A grub_register_extcmd ("save_env", grub_cmd_save_env, 0,
2N/A N_("[-f FILE] variable_name [...]"),
2N/A N_("Save variables to environment block file."),
2N/A options);
2N/A}
2N/A
2N/AGRUB_MOD_FINI(loadenv)
2N/A{
2N/A grub_unregister_extcmd (cmd_load);
2N/A grub_unregister_extcmd (cmd_list);
2N/A grub_unregister_extcmd (cmd_save);
2N/A}