/* grub-editenv.c - tool to edit environment block. */ /* * 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 . */ #include #include #include #include #include #include #include #include #include #include #include #include #pragma GCC diagnostic ignored "-Wmissing-prototypes" #pragma GCC diagnostic ignored "-Wmissing-declarations" #include #pragma GCC diagnostic error "-Wmissing-prototypes" #pragma GCC diagnostic error "-Wmissing-declarations" #include "progname.h" #define DEFAULT_ENVBLK_PATH DEFAULT_DIRECTORY "/" GRUB_ENVBLK_DEFCFG static struct argp_option options[] = { {0, 0, 0, OPTION_DOC, N_("Commands:"), 1}, {"create", 0, 0, OPTION_DOC|OPTION_NO_USAGE, N_("Create a blank environment block file."), 0}, {"list", 0, 0, OPTION_DOC|OPTION_NO_USAGE, N_("List the current variables."), 0}, /* TRANSLATORS: "set" is a keyword. It's a summary of "set" subcommand. */ {N_("set [NAME=VALUE ...]"), 0, 0, OPTION_DOC|OPTION_NO_USAGE, N_("Set variables."), 0}, /* TRANSLATORS: "unset" is a keyword. It's a summary of "unset" subcommand. */ {N_("unset [NAME ...]"), 0, 0, OPTION_DOC|OPTION_NO_USAGE, N_("Delete variables."), 0}, {0, 0, 0, OPTION_DOC, N_("Options:"), -1}, {"verbose", 'v', 0, 0, N_("print verbose messages."), 0}, { 0, 0, 0, 0, 0, 0 } }; /* Print the version information. */ static void print_version (FILE *stream, struct argp_state *state) { fprintf (stream, "%s (%s) %s\n", program_name, PACKAGE_NAME, PACKAGE_VERSION); } void (*argp_program_version_hook) (FILE *, struct argp_state *) = print_version; /* Set the bug report address */ const char *argp_program_bug_address = "<"PACKAGE_BUGREPORT">"; static error_t argp_parser (int key, char *arg, struct argp_state *state) { switch (key) { case 'v': verbosity++; break; case ARGP_KEY_NO_ARGS: fprintf (stderr, "%s", _("You need to specify at least one command.\n")); argp_usage (state); break; default: return ARGP_ERR_UNKNOWN; } return 0; } #pragma GCC diagnostic ignored "-Wformat-nonliteral" static char * help_filter (int key, const char *text, void *input __attribute__ ((unused))) { switch (key) { case ARGP_KEY_HELP_POST_DOC: return xasprintf (text, DEFAULT_ENVBLK_PATH, DEFAULT_ENVBLK_PATH); default: return (char *) text; } } #pragma GCC diagnostic error "-Wformat-nonliteral" struct argp argp = { options, argp_parser, N_("FILENAME COMMAND"), "\n"N_("\ Tool to edit environment block.") "\v"N_("\ If FILENAME is `-', the default value %s is used.\n\n\ There is no `delete' command; if you want to delete the whole environment\n\ block, use `rm %s'."), NULL, help_filter, NULL }; struct fs_envblk_spec { const char *fs_name; off_t offset; size_t size; }; typedef struct fs_envblk_spec fs_envblk_spec_t; static grub_envblk_t fs_envblk_open (grub_envblk_t envblk); struct fs_envblk_ops { grub_envblk_t (*open) (grub_envblk_t); }; typedef struct fs_envblk_ops fs_envblk_ops_t; struct fs_envblk { fs_envblk_spec_t *spec; fs_envblk_ops_t *ops; const char *dev; }; typedef struct fs_envblk *fs_envblk_t; static fs_envblk_ops_t fs_envblk_ops = { .open = fs_envblk_open }; /* * fs_envblk_spec describes the file-system specific layout of reserved raw * blocks used as environment blocks. */ static fs_envblk_spec_t fs_envblk_spec[] = { { NULL, 0, 0 } }; static fs_envblk_t fs_envblk = NULL; static void __attribute__ ((unused)) fs_envblk_init (const char *fs_name, const char *dev) { fs_envblk_spec_t *p; if (fs_name == NULL || dev == NULL) return; for (p = fs_envblk_spec; p->fs_name != NULL; p++) { if (strcmp (fs_name, p->fs_name) == 0) { if (fs_envblk == NULL) fs_envblk = xmalloc (sizeof (*fs_envblk)); fs_envblk->spec = p; fs_envblk->dev = xstrdup (dev); fs_envblk->ops = &fs_envblk_ops; break; } } } static int read_env_block_var (const char *varname, const char *value, void *hook_data) { grub_envblk_t *p_envblk = (grub_envblk_t *) hook_data; off_t off; size_t sz; char *p, *buf; FILE *fp; if (p_envblk == NULL || fs_envblk == NULL) return 1; if (strcmp (varname, "env_block") != 0) return 0; off = strtol (value, &p, 10); if (*p == '+') sz = strtol (p + 1, &p, 10); else return 0; if (*p != '\0' || sz == 0) return 0; off <<= GRUB_DISK_SECTOR_BITS; sz <<= GRUB_DISK_SECTOR_BITS; fp = grub_util_fopen (fs_envblk->dev, "rb"); if (fp == NULL) grub_util_error (_("cannot open `%s': %s"), fs_envblk->dev, strerror (errno)); if (fseek (fp, off, SEEK_SET) < 0) grub_util_error (_("cannot seek `%s': %s"), fs_envblk->dev, strerror (errno)); buf = xmalloc (sz); if ((fread (buf, 1, sz, fp)) != sz) grub_util_error (_("cannot read `%s': %s"), fs_envblk->dev, strerror (errno)); fclose (fp); *p_envblk = grub_envblk_open (buf, sz); return 1; } static void create_env_on_block (void) { FILE *fp; char *buf; const char *device; off_t offset; size_t size; if (fs_envblk == NULL) return; device = fs_envblk->dev; offset = fs_envblk->spec->offset; size = fs_envblk->spec->size; fp = grub_util_fopen (device, "r+b"); if (fp == NULL) grub_util_error (_("cannot open `%s': %s"), device, strerror (errno)); buf = xmalloc (size); memcpy (buf, GRUB_ENVBLK_SIGNATURE, sizeof (GRUB_ENVBLK_SIGNATURE) - 1); memset (buf + sizeof (GRUB_ENVBLK_SIGNATURE) - 1, '#', size - sizeof (GRUB_ENVBLK_SIGNATURE) + 1); if (fseek (fp, offset, SEEK_SET) < 0) grub_util_error (_("cannot seek `%s': %s"), device, strerror (errno)); if (fwrite (buf, 1, size, fp) != size) grub_util_error (_("cannot write to `%s': %s"), device, strerror (errno)); grub_util_file_sync (fp); free (buf); fclose (fp); } static grub_envblk_t fs_envblk_open (grub_envblk_t envblk) { grub_envblk_t envblk_on_block = NULL; char *val; off_t offset; size_t size; if (envblk == NULL) return NULL; offset = fs_envblk->spec->offset; size = fs_envblk->spec->size; grub_envblk_iterate (envblk, &envblk_on_block, read_env_block_var); if (envblk_on_block != NULL && grub_envblk_size (envblk_on_block) == size) return envblk_on_block; create_env_on_block (); offset = offset >> GRUB_DISK_SECTOR_BITS; size = (size + GRUB_DISK_SECTOR_SIZE - 1) >> GRUB_DISK_SECTOR_BITS; val = xasprintf ("%lld+%zu", (long long) offset, size); if (grub_envblk_set (envblk, "env_block", val) == 0) grub_util_error ("%s", _("environment block too small")); grub_envblk_iterate (envblk, &envblk_on_block, read_env_block_var); free (val); return envblk_on_block; } static grub_envblk_t open_envblk_file (const char *name) { FILE *fp; char *buf; long loc; size_t size; grub_envblk_t envblk; fp = grub_util_fopen (name, "rb"); if (! fp) { /* Create the file implicitly. */ grub_util_create_envblk_file (name); fp = grub_util_fopen (name, "rb"); if (! fp) grub_util_error (_("cannot open `%s': %s"), name, strerror (errno)); } if (fseek (fp, 0, SEEK_END) < 0) grub_util_error (_("cannot seek `%s': %s"), name, strerror (errno)); loc = ftell (fp); if (loc < 0) grub_util_error (_("cannot get file location `%s': %s"), name, strerror (errno)); size = (size_t) loc; if (fseek (fp, 0, SEEK_SET) < 0) grub_util_error (_("cannot seek `%s': %s"), name, strerror (errno)); buf = xmalloc (size); if (fread (buf, 1, size, fp) != size) grub_util_error (_("cannot read `%s': %s"), name, strerror (errno)); fclose (fp); envblk = grub_envblk_open (buf, size); if (! envblk) grub_util_error ("%s", _("invalid environment block")); return envblk; } static int print_var (const char *varname, const char *value, void *hook_data __attribute__ ((unused))) { printf ("%s=%s\n", varname, value); return 0; } static void list_variables (const char *name) { grub_envblk_t envblk; envblk = open_envblk_file (name); grub_envblk_iterate (envblk, NULL, print_var); grub_envblk_close (envblk); } static void write_envblk (const char *name, grub_envblk_t envblk) { FILE *fp; fp = grub_util_fopen (name, "wb"); if (! fp) grub_util_error (_("cannot open `%s': %s"), name, strerror (errno)); if (fwrite (grub_envblk_buffer (envblk), 1, grub_envblk_size (envblk), fp) != grub_envblk_size (envblk)) grub_util_error (_("cannot write to `%s': %s"), name, strerror (errno)); if (grub_util_file_sync (fp) < 0) grub_util_error (_("cannot sync `%s': %s"), name, strerror (errno)); fclose (fp); } static void set_variables (const char *name, int argc, char *argv[]) { grub_envblk_t envblk; envblk = open_envblk_file (name); while (argc) { char *p; p = strchr (argv[0], '='); if (! p) grub_util_error (_("invalid parameter %s"), argv[0]); *(p++) = 0; if (! grub_envblk_set (envblk, argv[0], p)) grub_util_error ("%s", _("environment block too small")); argc--; argv++; } write_envblk (name, envblk); grub_envblk_close (envblk); } static void unset_variables (const char *name, int argc, char *argv[]) { grub_envblk_t envblk; envblk = open_envblk_file (name); while (argc) { grub_envblk_delete (envblk, argv[0]); argc--; argv++; } write_envblk (name, envblk); grub_envblk_close (envblk); } int main (int argc, char *argv[]) { const char *filename; char *command; int curindex, arg_count; grub_util_host_init (&argc, &argv); /* Parse our arguments */ if (argp_parse (&argp, argc, argv, 0, &curindex, 0) != 0) { fprintf (stderr, "%s", _("Error in parsing command line arguments\n")); exit(1); } arg_count = argc - curindex; if (arg_count == 1) { filename = DEFAULT_ENVBLK_PATH; command = argv[curindex++]; } else { filename = argv[curindex++]; if (strcmp (filename, "-") == 0) filename = DEFAULT_ENVBLK_PATH; command = argv[curindex++]; } if (strcmp (command, "create") == 0) grub_util_create_envblk_file (filename); else if (strcmp (command, "list") == 0) list_variables (filename); else if (strcmp (command, "set") == 0) set_variables (filename, argc - curindex, argv + curindex); else if (strcmp (command, "unset") == 0) unset_variables (filename, argc - curindex, argv + curindex); else { char *program = xstrdup(program_name); fprintf (stderr, _("Unknown command `%s'.\n"), command); argp_help (&argp, stderr, ARGP_HELP_STD_USAGE, program); free(program); exit(1); } return 0; }