It will be desirable in the future to allow having the read hook modify the data passed back from a read function call on a disk or file. This adds that infrastructure and has no impact on code flow for existing uses of the read hook. Also changed is that now when the read hook callback is called it can also indicate what error code should be sent back to the read caller. Signed-off-by: Glenn Washburn <development@efficientek.com> Reviewed-by: Patrick Steinhardt <ps@pks.im> Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
473 lines
11 KiB
C
473 lines
11 KiB
C
/* loadenv.c - command to load/save environment variable. */
|
|
/*
|
|
* GRUB -- GRand Unified Bootloader
|
|
* Copyright (C) 2008,2009,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/dl.h>
|
|
#include <grub/mm.h>
|
|
#include <grub/file.h>
|
|
#include <grub/disk.h>
|
|
#include <grub/misc.h>
|
|
#include <grub/env.h>
|
|
#include <grub/partition.h>
|
|
#include <grub/lib/envblk.h>
|
|
#include <grub/extcmd.h>
|
|
#include <grub/i18n.h>
|
|
|
|
GRUB_MOD_LICENSE ("GPLv3+");
|
|
|
|
static const struct grub_arg_option options[] =
|
|
{
|
|
/* TRANSLATORS: This option is used to override default filename
|
|
for loading and storing environment. */
|
|
{"file", 'f', 0, N_("Specify filename."), 0, ARG_TYPE_PATHNAME},
|
|
{"skip-sig", 's', 0,
|
|
N_("Skip signature-checking of the environment file."), 0, ARG_TYPE_NONE},
|
|
{0, 0, 0, 0, 0, 0}
|
|
};
|
|
|
|
/* Opens 'filename' with compression filters disabled. Optionally disables the
|
|
PUBKEY filter (that insists upon properly signed files) as well. PUBKEY
|
|
filter is restored before the function returns. */
|
|
static grub_file_t
|
|
open_envblk_file (char *filename,
|
|
enum grub_file_type type)
|
|
{
|
|
grub_file_t file;
|
|
char *buf = 0;
|
|
|
|
if (! filename)
|
|
{
|
|
const char *prefix;
|
|
int len;
|
|
|
|
prefix = grub_env_get ("prefix");
|
|
if (! prefix)
|
|
{
|
|
grub_error (GRUB_ERR_FILE_NOT_FOUND, N_("variable `%s' isn't set"), "prefix");
|
|
return 0;
|
|
}
|
|
|
|
len = grub_strlen (prefix);
|
|
buf = grub_malloc (len + 1 + sizeof (GRUB_ENVBLK_DEFCFG));
|
|
if (! buf)
|
|
return 0;
|
|
filename = buf;
|
|
|
|
grub_strcpy (filename, prefix);
|
|
filename[len] = '/';
|
|
grub_strcpy (filename + len + 1, GRUB_ENVBLK_DEFCFG);
|
|
}
|
|
|
|
file = grub_file_open (filename, type);
|
|
|
|
grub_free (buf);
|
|
return file;
|
|
}
|
|
|
|
static grub_envblk_t
|
|
read_envblk_file (grub_file_t file)
|
|
{
|
|
grub_off_t offset = 0;
|
|
char *buf;
|
|
grub_size_t size = grub_file_size (file);
|
|
grub_envblk_t envblk;
|
|
|
|
buf = grub_malloc (size);
|
|
if (! buf)
|
|
return 0;
|
|
|
|
while (size > 0)
|
|
{
|
|
grub_ssize_t ret;
|
|
|
|
ret = grub_file_read (file, buf + offset, size);
|
|
if (ret <= 0)
|
|
{
|
|
grub_free (buf);
|
|
return 0;
|
|
}
|
|
|
|
size -= ret;
|
|
offset += ret;
|
|
}
|
|
|
|
envblk = grub_envblk_open (buf, offset);
|
|
if (! envblk)
|
|
{
|
|
grub_free (buf);
|
|
grub_error (GRUB_ERR_BAD_FILE_TYPE, "invalid environment block");
|
|
return 0;
|
|
}
|
|
|
|
return envblk;
|
|
}
|
|
|
|
struct grub_env_whitelist
|
|
{
|
|
grub_size_t len;
|
|
char **list;
|
|
};
|
|
typedef struct grub_env_whitelist grub_env_whitelist_t;
|
|
|
|
static int
|
|
test_whitelist_membership (const char* name,
|
|
const grub_env_whitelist_t* whitelist)
|
|
{
|
|
grub_size_t i;
|
|
|
|
for (i = 0; i < whitelist->len; i++)
|
|
if (grub_strcmp (name, whitelist->list[i]) == 0)
|
|
return 1; /* found it */
|
|
|
|
return 0; /* not found */
|
|
}
|
|
|
|
/* Helper for grub_cmd_load_env. */
|
|
static int
|
|
set_var (const char *name, const char *value, void *whitelist)
|
|
{
|
|
if (! whitelist)
|
|
{
|
|
grub_env_set (name, value);
|
|
return 0;
|
|
}
|
|
|
|
if (test_whitelist_membership (name,
|
|
(const grub_env_whitelist_t *) whitelist))
|
|
grub_env_set (name, value);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static grub_err_t
|
|
grub_cmd_load_env (grub_extcmd_context_t ctxt, int argc, char **args)
|
|
{
|
|
struct grub_arg_list *state = ctxt->state;
|
|
grub_file_t file;
|
|
grub_envblk_t envblk;
|
|
grub_env_whitelist_t whitelist;
|
|
|
|
whitelist.len = argc;
|
|
whitelist.list = args;
|
|
|
|
/* state[0] is the -f flag; state[1] is the --skip-sig flag */
|
|
file = open_envblk_file ((state[0].set) ? state[0].arg : 0,
|
|
GRUB_FILE_TYPE_LOADENV
|
|
| (state[1].set
|
|
? GRUB_FILE_TYPE_SKIP_SIGNATURE : GRUB_FILE_TYPE_NONE));
|
|
if (! file)
|
|
return grub_errno;
|
|
|
|
envblk = read_envblk_file (file);
|
|
if (! envblk)
|
|
goto fail;
|
|
|
|
/* argc > 0 indicates caller provided a whitelist of variables to read. */
|
|
grub_envblk_iterate (envblk, argc > 0 ? &whitelist : 0, set_var);
|
|
grub_envblk_close (envblk);
|
|
|
|
fail:
|
|
grub_file_close (file);
|
|
return grub_errno;
|
|
}
|
|
|
|
/* Print all variables in current context. */
|
|
static int
|
|
print_var (const char *name, const char *value,
|
|
void *hook_data __attribute__ ((unused)))
|
|
{
|
|
grub_printf ("%s=%s\n", name, value);
|
|
return 0;
|
|
}
|
|
|
|
static grub_err_t
|
|
grub_cmd_list_env (grub_extcmd_context_t ctxt,
|
|
int argc __attribute__ ((unused)),
|
|
char **args __attribute__ ((unused)))
|
|
{
|
|
struct grub_arg_list *state = ctxt->state;
|
|
grub_file_t file;
|
|
grub_envblk_t envblk;
|
|
|
|
file = open_envblk_file ((state[0].set) ? state[0].arg : 0,
|
|
GRUB_FILE_TYPE_LOADENV
|
|
| (state[1].set
|
|
? GRUB_FILE_TYPE_SKIP_SIGNATURE : GRUB_FILE_TYPE_NONE));
|
|
if (! file)
|
|
return grub_errno;
|
|
|
|
envblk = read_envblk_file (file);
|
|
if (! envblk)
|
|
goto fail;
|
|
|
|
grub_envblk_iterate (envblk, NULL, print_var);
|
|
grub_envblk_close (envblk);
|
|
|
|
fail:
|
|
grub_file_close (file);
|
|
return grub_errno;
|
|
}
|
|
|
|
/* Used to maintain a variable length of blocklists internally. */
|
|
struct blocklist
|
|
{
|
|
grub_disk_addr_t sector;
|
|
unsigned offset;
|
|
unsigned length;
|
|
struct blocklist *next;
|
|
};
|
|
|
|
static void
|
|
free_blocklists (struct blocklist *p)
|
|
{
|
|
struct blocklist *q;
|
|
|
|
for (; p; p = q)
|
|
{
|
|
q = p->next;
|
|
grub_free (p);
|
|
}
|
|
}
|
|
|
|
static grub_err_t
|
|
check_blocklists (grub_envblk_t envblk, struct blocklist *blocklists,
|
|
grub_file_t file)
|
|
{
|
|
grub_size_t total_length;
|
|
grub_size_t index;
|
|
grub_disk_t disk;
|
|
grub_disk_addr_t part_start;
|
|
struct blocklist *p;
|
|
char *buf;
|
|
|
|
/* Sanity checks. */
|
|
total_length = 0;
|
|
for (p = blocklists; p; p = p->next)
|
|
{
|
|
struct blocklist *q;
|
|
/* Check if any pair of blocks overlap. */
|
|
for (q = p->next; q; q = q->next)
|
|
{
|
|
grub_disk_addr_t s1, s2;
|
|
grub_disk_addr_t e1, e2;
|
|
|
|
s1 = p->sector;
|
|
e1 = s1 + ((p->length + GRUB_DISK_SECTOR_SIZE - 1) >> GRUB_DISK_SECTOR_BITS);
|
|
|
|
s2 = q->sector;
|
|
e2 = s2 + ((q->length + GRUB_DISK_SECTOR_SIZE - 1) >> GRUB_DISK_SECTOR_BITS);
|
|
|
|
if (s1 < e2 && s2 < e1)
|
|
{
|
|
/* This might be actually valid, but it is unbelievable that
|
|
any filesystem makes such a silly allocation. */
|
|
return grub_error (GRUB_ERR_BAD_FS, "malformed file");
|
|
}
|
|
}
|
|
|
|
total_length += p->length;
|
|
}
|
|
|
|
if (total_length != grub_file_size (file))
|
|
{
|
|
/* Maybe sparse, unallocated sectors. No way in GRUB. */
|
|
return grub_error (GRUB_ERR_BAD_FILE_TYPE, "sparse file not allowed");
|
|
}
|
|
|
|
/* One more sanity check. Re-read all sectors by blocklists, and compare
|
|
those with the data read via a file. */
|
|
disk = file->device->disk;
|
|
|
|
part_start = grub_partition_get_start (disk->partition);
|
|
|
|
buf = grub_envblk_buffer (envblk);
|
|
char *blockbuf = NULL;
|
|
grub_size_t blockbuf_len = 0;
|
|
for (p = blocklists, index = 0; p; index += p->length, p = p->next)
|
|
{
|
|
if (p->length > blockbuf_len)
|
|
{
|
|
grub_free (blockbuf);
|
|
blockbuf_len = 2 * p->length;
|
|
blockbuf = grub_malloc (blockbuf_len);
|
|
if (!blockbuf)
|
|
return grub_errno;
|
|
}
|
|
|
|
if (grub_disk_read (disk, p->sector - part_start,
|
|
p->offset, p->length, blockbuf))
|
|
return grub_errno;
|
|
|
|
if (grub_memcmp (buf + index, blockbuf, p->length) != 0)
|
|
return grub_error (GRUB_ERR_FILE_READ_ERROR, "invalid blocklist");
|
|
}
|
|
|
|
return GRUB_ERR_NONE;
|
|
}
|
|
|
|
static int
|
|
write_blocklists (grub_envblk_t envblk, struct blocklist *blocklists,
|
|
grub_file_t file)
|
|
{
|
|
char *buf;
|
|
grub_disk_t disk;
|
|
grub_disk_addr_t part_start;
|
|
struct blocklist *p;
|
|
grub_size_t index;
|
|
|
|
buf = grub_envblk_buffer (envblk);
|
|
disk = file->device->disk;
|
|
part_start = grub_partition_get_start (disk->partition);
|
|
|
|
index = 0;
|
|
for (p = blocklists; p; index += p->length, p = p->next)
|
|
{
|
|
if (grub_disk_write (disk, p->sector - part_start,
|
|
p->offset, p->length, buf + index))
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Context for grub_cmd_save_env. */
|
|
struct grub_cmd_save_env_ctx
|
|
{
|
|
struct blocklist *head, *tail;
|
|
};
|
|
|
|
/* Store blocklists in a linked list. */
|
|
static grub_err_t
|
|
save_env_read_hook (grub_disk_addr_t sector, unsigned offset, unsigned length,
|
|
char *buf __attribute__ ((unused)), void *data)
|
|
{
|
|
struct grub_cmd_save_env_ctx *ctx = data;
|
|
struct blocklist *block;
|
|
|
|
block = grub_malloc (sizeof (*block));
|
|
if (! block)
|
|
return GRUB_ERR_NONE;
|
|
|
|
block->sector = sector;
|
|
block->offset = offset;
|
|
block->length = length;
|
|
|
|
/* Slightly complicated, because the list should be FIFO. */
|
|
block->next = 0;
|
|
if (ctx->tail)
|
|
ctx->tail->next = block;
|
|
ctx->tail = block;
|
|
if (! ctx->head)
|
|
ctx->head = block;
|
|
|
|
return GRUB_ERR_NONE;
|
|
}
|
|
|
|
static grub_err_t
|
|
grub_cmd_save_env (grub_extcmd_context_t ctxt, int argc, char **args)
|
|
{
|
|
struct grub_arg_list *state = ctxt->state;
|
|
grub_file_t file;
|
|
grub_envblk_t envblk;
|
|
struct grub_cmd_save_env_ctx ctx = {
|
|
.head = 0,
|
|
.tail = 0
|
|
};
|
|
|
|
if (! argc)
|
|
return grub_error (GRUB_ERR_BAD_ARGUMENT, "no variable is specified");
|
|
|
|
file = open_envblk_file ((state[0].set) ? state[0].arg : 0,
|
|
GRUB_FILE_TYPE_SAVEENV
|
|
| GRUB_FILE_TYPE_SKIP_SIGNATURE);
|
|
if (! file)
|
|
return grub_errno;
|
|
|
|
if (! file->device->disk)
|
|
{
|
|
grub_file_close (file);
|
|
return grub_error (GRUB_ERR_BAD_DEVICE, "disk device required");
|
|
}
|
|
|
|
file->read_hook = save_env_read_hook;
|
|
file->read_hook_data = &ctx;
|
|
envblk = read_envblk_file (file);
|
|
file->read_hook = 0;
|
|
if (! envblk)
|
|
goto fail;
|
|
|
|
if (check_blocklists (envblk, ctx.head, file))
|
|
goto fail;
|
|
|
|
while (argc)
|
|
{
|
|
const char *value;
|
|
|
|
value = grub_env_get (args[0]);
|
|
if (value)
|
|
{
|
|
if (! grub_envblk_set (envblk, args[0], value))
|
|
{
|
|
grub_error (GRUB_ERR_BAD_ARGUMENT, "environment block too small");
|
|
goto fail;
|
|
}
|
|
}
|
|
else
|
|
grub_envblk_delete (envblk, args[0]);
|
|
|
|
argc--;
|
|
args++;
|
|
}
|
|
|
|
write_blocklists (envblk, ctx.head, file);
|
|
|
|
fail:
|
|
if (envblk)
|
|
grub_envblk_close (envblk);
|
|
free_blocklists (ctx.head);
|
|
grub_file_close (file);
|
|
return grub_errno;
|
|
}
|
|
|
|
static grub_extcmd_t cmd_load, cmd_list, cmd_save;
|
|
|
|
GRUB_MOD_INIT(loadenv)
|
|
{
|
|
cmd_load =
|
|
grub_register_extcmd ("load_env", grub_cmd_load_env, 0,
|
|
N_("[-f FILE] [-s|--skip-sig] [variable_name_to_whitelist] [...]"),
|
|
N_("Load variables from environment block file."),
|
|
options);
|
|
cmd_list =
|
|
grub_register_extcmd ("list_env", grub_cmd_list_env, 0, N_("[-f FILE]"),
|
|
N_("List variables from environment block file."),
|
|
options);
|
|
cmd_save =
|
|
grub_register_extcmd ("save_env", grub_cmd_save_env, 0,
|
|
N_("[-f FILE] variable_name [...]"),
|
|
N_("Save variables to environment block file."),
|
|
options);
|
|
}
|
|
|
|
GRUB_MOD_FINI(loadenv)
|
|
{
|
|
grub_unregister_extcmd (cmd_load);
|
|
grub_unregister_extcmd (cmd_list);
|
|
grub_unregister_extcmd (cmd_save);
|
|
}
|