grub2/util/grub-mkstandalone.c
Dimitri John Ledkov 8ddbdc3bc2 grub-install: Add backup and restore
Refactor clean_grub_dir() to create a backup of all the files, instead
of just irrevocably removing them as the first action. If available,
register atexit() handler to restore the backup if errors occur before
point of no return, or remove the backup if everything was successful.
If atexit() is not available, the backup remains on disk for manual
recovery.

Some platforms defined a point of no return, i.e. after modules & core
images were updated. Failures from any commands after that stage are
ignored, and backup is cleaned up. For example, on EFI platforms update
is not reverted when efibootmgr fails.

Extra care is taken to ensure atexit() handler is only invoked by the
parent process and not any children forks. Some older GRUB codebases
can invoke parent atexit() hooks from forks, which can mess up the
backup.

This allows safer upgrades of MBR & modules, such that
modules/images/fonts/translations are consistent with MBR in case of
errors. For example accidental grub-install /dev/non-existent-disk
currently clobbers and upgrades modules in /boot/grub, despite not
actually updating any MBR.

This patch only handles backup and restore of files copied to /boot/grub.
This patch does not perform backup (or restoration) of MBR itself or
blocklists. Thus when installing i386-pc platform, corruption may still
occur with MBR and blocklists which will not be attempted to be
automatically recovered.

Also add modinfo.sh and *.efi to the cleanup/backup/restore code path,
to ensure it is also cleaned, backed up and restored.

Signed-off-by: Dimitri John Ledkov <xnox@ubuntu.com>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
2021-06-01 17:20:20 +02:00

364 lines
8.3 KiB
C

/*
* Copyright (C) 1999,2000,2001,2002,2003,2004,2005,2006,2007,2008,2009,2010,2011,2012,2013 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 <config.h>
#include <grub/util/install.h>
#include <grub/util/misc.h>
#include <grub/emu/config.h>
#include <string.h>
#pragma GCC diagnostic ignored "-Wmissing-prototypes"
#pragma GCC diagnostic ignored "-Wmissing-declarations"
#include <argp.h>
#pragma GCC diagnostic error "-Wmissing-prototypes"
#pragma GCC diagnostic error "-Wmissing-declarations"
static char *output_image;
static char **files;
static int nfiles;
const struct grub_install_image_target_desc *format;
static FILE *memdisk;
enum
{
OPTION_OUTPUT = 'o',
OPTION_FORMAT = 'O'
};
static struct argp_option options[] = {
GRUB_INSTALL_OPTIONS,
{"output", 'o', N_("FILE"),
0, N_("save output in FILE [required]"), 2},
{"format", 'O', N_("FILE"), 0, 0, 2},
{"compression", 'C', "xz|none|auto", OPTION_HIDDEN, 0, 2},
{0, 0, 0, 0, 0, 0}
};
static char *
help_filter (int key, const char *text, void *input __attribute__ ((unused)))
{
switch (key)
{
case 'O':
{
char *formats = grub_install_get_image_targets_string (), *ret;
ret = xasprintf ("%s\n%s %s", _("generate an image in FORMAT"),
_("available formats:"), formats);
free (formats);
return ret;
}
default:
return grub_install_help_filter (key, text, input);
}
}
static error_t
argp_parser (int key, char *arg, struct argp_state *state)
{
if (key == 'C')
key = GRUB_INSTALL_OPTIONS_INSTALL_CORE_COMPRESS;
if (grub_install_parse (key, arg))
return 0;
switch (key)
{
case 'o':
if (output_image)
free (output_image);
output_image = xstrdup (arg);
break;
case 'O':
{
format = grub_install_get_image_target (arg);
if (!format)
{
printf (_("unknown target format %s\n"), arg);
argp_usage (state);
exit (1);
}
break;
}
case ARGP_KEY_ARG:
files[nfiles++] = xstrdup (arg);
break;
default:
return ARGP_ERR_UNKNOWN;
}
return 0;
}
struct argp argp = {
options, argp_parser, N_("[OPTION] SOURCE..."),
N_("Generate a standalone image (containing all modules) in the selected format")"\v"N_("Graft point syntax (E.g. /boot/grub/grub.cfg=./grub.cfg) is accepted"),
NULL, help_filter, NULL
};
/* tar support */
#define MAGIC "ustar"
struct head
{
char name[100];
char mode[8];
char uid[8];
char gid[8];
char size[12];
char mtime[12];
char chksum[8];
char typeflag;
char linkname[100];
char magic[6];
char version[2];
char uname[32];
char gname[32];
char devmajor[8];
char devminor[8];
char prefix[155];
char pad[12];
} GRUB_PACKED;
static void
write_zeros (unsigned rsz)
{
char buf[512];
memset (buf, 0, 512);
fwrite (buf, 1, rsz, memdisk);
}
static void
write_pad (unsigned sz)
{
write_zeros ((~sz + 1) & 511);
}
static void
set_tar_value (char *field, grub_uint32_t val,
unsigned len)
{
unsigned i;
for (i = 0; i < len - 1; i++)
field[len - 2 - i] = '0' + ((val >> (3 * i)) & 7);
}
static void
compute_checksum (struct head *hd)
{
unsigned int chk = 0;
unsigned char *ptr;
memset (hd->chksum, ' ', 8);
for (ptr = (unsigned char *) hd; ptr < (unsigned char *) (hd + 1); ptr++)
chk += *ptr;
set_tar_value (hd->chksum, chk, 8);
}
static void
add_tar_file (const char *from,
const char *to)
{
char *tcn;
const char *iptr;
char *optr;
struct head hd;
grub_util_fd_t in;
ssize_t r;
grub_uint32_t mtime = 0;
grub_uint32_t size;
COMPILE_TIME_ASSERT (sizeof (hd) == 512);
if (grub_util_is_special_file (from))
return;
mtime = grub_util_get_mtime (from);
optr = tcn = xmalloc (strlen (to) + 1);
for (iptr = to; *iptr == '/'; iptr++);
for (; *iptr; iptr++)
if (!(iptr[0] == '/' && iptr[1] == '/'))
*optr++ = *iptr;
*optr = '\0';
if (grub_util_is_directory (from))
{
grub_util_fd_dir_t d;
grub_util_fd_dirent_t de;
d = grub_util_fd_opendir (from);
while ((de = grub_util_fd_readdir (d)))
{
char *fp, *tfp;
if (strcmp (de->d_name, ".") == 0)
continue;
if (strcmp (de->d_name, "..") == 0)
continue;
fp = grub_util_path_concat (2, from, de->d_name);
tfp = xasprintf ("%s/%s", to, de->d_name);
add_tar_file (fp, tfp);
free (fp);
}
grub_util_fd_closedir (d);
free (tcn);
return;
}
if (optr - tcn > 99)
{
memset (&hd, 0, sizeof (hd));
memcpy (hd.name, tcn, 99);
memcpy (hd.mode, "0000600", 7);
memcpy (hd.uid, "0001750", 7);
memcpy (hd.gid, "0001750", 7);
set_tar_value (hd.size, optr - tcn, 12);
set_tar_value (hd.mtime, mtime, 12);
hd.typeflag = 'L';
memcpy (hd.magic, MAGIC, sizeof (hd.magic));
memcpy (hd.uname, "grub", 4);
memcpy (hd.gname, "grub", 4);
compute_checksum (&hd);
fwrite (&hd, 1, sizeof (hd), memdisk);
fwrite (tcn, 1, optr - tcn, memdisk);
write_pad (optr - tcn);
}
in = grub_util_fd_open (from, GRUB_UTIL_FD_O_RDONLY);
if (!GRUB_UTIL_FD_IS_VALID (in))
grub_util_error (_("cannot open `%s': %s"), from, grub_util_fd_strerror ());
if (!grub_install_copy_buffer)
grub_install_copy_buffer = xmalloc (GRUB_INSTALL_COPY_BUFFER_SIZE);
size = grub_util_get_fd_size (in, from, NULL);
memset (&hd, 0, sizeof (hd));
memcpy (hd.name, tcn, optr - tcn < 99 ? optr - tcn : 99);
memcpy (hd.mode, "0000600", 7);
memcpy (hd.uid, "0001750", 7);
memcpy (hd.gid, "0001750", 7);
set_tar_value (hd.size, size, 12);
set_tar_value (hd.mtime, mtime, 12);
hd.typeflag = '0';
memcpy (hd.magic, MAGIC, sizeof (hd.magic));
memcpy (hd.uname, "grub", 4);
memcpy (hd.gname, "grub", 4);
compute_checksum (&hd);
fwrite (&hd, 1, sizeof (hd), memdisk);
while (1)
{
r = grub_util_fd_read (in, grub_install_copy_buffer, GRUB_INSTALL_COPY_BUFFER_SIZE);
if (r <= 0)
break;
fwrite (grub_install_copy_buffer, 1, r, memdisk);
}
grub_util_fd_close (in);
write_pad (size);
free (tcn);
}
int
main (int argc, char *argv[])
{
const char *pkglibdir;
int i;
grub_util_host_init (&argc, &argv);
grub_util_disable_fd_syncs ();
files = xcalloc (argc + 1, sizeof (files[0]));
argp_parse (&argp, argc, argv, 0, 0, 0);
pkglibdir = grub_util_get_pkglibdir ();
if (!output_image)
grub_util_error ("%s", _("output file must be specified"));
if (!format)
grub_util_error ("%s", _("Target format not specified (use the -O option)."));
if (!grub_install_source_directory)
grub_install_source_directory = grub_util_path_concat (2, pkglibdir, grub_util_get_target_dirname (format));
enum grub_install_plat plat = grub_install_get_target (grub_install_source_directory);
char *memdisk_dir = grub_util_make_temporary_dir ();
char *boot_grub = grub_util_path_concat (3, memdisk_dir, "boot", "grub");
grub_install_copy_files (grub_install_source_directory,
boot_grub, plat);
grub_set_install_backup_ponr ();
char *memdisk_img = grub_util_make_temporary_file ();
memdisk = grub_util_fopen (memdisk_img, "wb");
add_tar_file (memdisk_dir, "");
for (i = 0; i < nfiles; i++)
{
char *eq = grub_strchr (files[i], '=');
char *from, *to;
if (!eq)
{
from = files[i];
to = files[i];
}
else
{
*eq = '\0';
to = files[i];
from = eq + 1;
}
while (*to == '/')
to++;
add_tar_file (from, to);
}
write_zeros (512);
fclose (memdisk);
grub_util_unlink_recursive (memdisk_dir);
grub_install_push_module ("memdisk");
grub_install_push_module ("tar");
grub_install_make_image_wrap (grub_install_source_directory,
"(memdisk)/boot/grub", output_image,
memdisk_img, NULL,
grub_util_get_target_name (format), 0);
grub_util_unlink (memdisk_img);
return 0;
}