Merge ~xnox/grub:ubuntu-backup-and-restore-v2 into ~ubuntu-core-dev/grub/+git/ubuntu:ubuntu

Proposed by Dimitri John Ledkov
Status: Merged
Merged at revision: 143a6ce0429ad9056a23bf0387a4720185a9c7d9
Proposed branch: ~xnox/grub:ubuntu-backup-and-restore-v2
Merge into: ~ubuntu-core-dev/grub/+git/ubuntu:ubuntu
Diff against target: 391 lines (+283/-20)
7 files modified
configure.ac (+1/-1)
debian/.git-dpm (+2/-2)
debian/changelog (+12/-0)
debian/patches/grub-install-backup-and-restore.patch (+175/-0)
debian/patches/series (+1/-0)
debian/postinst.in (+2/-2)
util/grub-install-common.c (+90/-15)
Reviewer Review Type Date Requested Status
Steve Langasek Approve
Julian Andres Klode Pending
Ubuntu Core Development Team Pending
Review via email: mp+389968@code.launchpad.net

Commit message

This merge request addresses reliability and safety of applying grub-pc upgrades.
When performed non-interactively, postinst instead of showing critical prompts may simply exit successfully indicating successful package configure even though MBR update may have failed, and modules in /boot/grub might no longer match the MBR core image leading to failure to boot.
Separately, I have submitted a patch upstream to make grub-install more resilient. Instead of irrevocably removing grub modules prior to updating MBR, I now made it create a backup and register on_exit handle. If everything is successful the on_exit handle removes the backup. If critical errors have occurred the backup is restored. This prevents mismatch of grub modules and MBR in case of attempts to call grub-install on non-existing devices, or calling on a device that refuses writes to MBR (as I have seen in one particular public cloud, it felt as if the underlying disk storage was refusing writes to mbr).
I have tested above using apparmor profile that prohibited writes to /dev/*. And ensured that backups/rollback/cleanup all operate correctly using Debian cloud image as a base VM image.

To post a comment you must log in.
Revision history for this message
Dimitri John Ledkov (xnox) wrote :

I'm sorry about git-dpm noise w.r.t. how many characters are used for short hash. I don't know what I need to do to have it the same as previous uploads.

Revision history for this message
Steve Langasek (vorlon) wrote :

LGTM. Were you still going to do something with the git dpm hashes (AIUI by adding the upstream remote so that git will recalculate)?

Revision history for this message
Steve Langasek (vorlon) :
review: Approve
Revision history for this message
Dimitri John Ledkov (xnox) wrote :

`git config --global core.abbrev 10`

worked!

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/configure.ac b/configure.ac
2index 1819188..6a88b9b 100644
3--- a/configure.ac
4+++ b/configure.ac
5@@ -420,7 +420,7 @@ else
6 fi
7
8 # Check for functions and headers.
9-AC_CHECK_FUNCS(posix_memalign memalign getextmntent)
10+AC_CHECK_FUNCS(posix_memalign memalign getextmntent on_exit)
11 AC_CHECK_HEADERS(sys/param.h sys/mount.h sys/mnttab.h limits.h)
12
13 # glibc 2.25 still includes sys/sysmacros.h in sys/types.h but emits deprecation
14diff --git a/debian/.git-dpm b/debian/.git-dpm
15index ed27285..f278adb 100644
16--- a/debian/.git-dpm
17+++ b/debian/.git-dpm
18@@ -1,6 +1,6 @@
19 # see git-dpm(1) from git-dpm package
20-398371c71cd52b6c48fa1d888903bd8a85682ec0
21-398371c71cd52b6c48fa1d888903bd8a85682ec0
22+0843387b9bedf43212e3148c0c9b6d013565c87e
23+0843387b9bedf43212e3148c0c9b6d013565c87e
24 578bb115fbd47e1c464696f1f8d6183e5443975d
25 578bb115fbd47e1c464696f1f8d6183e5443975d
26 grub2_2.04.orig.tar.xz
27diff --git a/debian/changelog b/debian/changelog
28index f7c0ce2..5fa8da9 100644
29--- a/debian/changelog
30+++ b/debian/changelog
31@@ -1,3 +1,15 @@
32+grub2 (2.04-1ubuntu29) UNRELEASED; urgency=medium
33+
34+ * grub-install: cherry-pick patch from grub-devel to make grub-install
35+ fault tolerant. Create backup of files in /boot/grub, and restore them
36+ on failure to complete grub-install. LP: #1891680
37+ * postinst.in: do not exit successfully when failing to show critical
38+ grub-pc/install_devices_failed and grub-pc/install_devices_empty
39+ prompts in non-interactive mode. This enables surfacing upgrade errors
40+ to the users and/or automation. LP: #1891680
41+
42+ -- Dimitri John Ledkov <xnox@ubuntu.com> Fri, 28 Aug 2020 15:15:45 +0100
43+
44 grub2 (2.04-1ubuntu28) groovy; urgency=medium
45
46 * Ensure that grub-multi-install can always find templates (LP: #1879948)
47diff --git a/debian/patches/grub-install-backup-and-restore.patch b/debian/patches/grub-install-backup-and-restore.patch
48new file mode 100644
49index 0000000..b947b44
50--- /dev/null
51+++ b/debian/patches/grub-install-backup-and-restore.patch
52@@ -0,0 +1,175 @@
53+From 0843387b9bedf43212e3148c0c9b6d013565c87e Mon Sep 17 00:00:00 2001
54+From: Dimitri John Ledkov <xnox@ubuntu.com>
55+Date: Wed, 19 Aug 2020 01:49:09 +0100
56+Subject: grub-install: Add backup and restore
57+
58+Refactor clean_grub_dir to create a backup of all the files, instead
59+of just irrevocably removing them as the first action. If available,
60+register on_exit handle to restore the backup if any errors occur, or
61+remove the backup if everything was successful. If on_exit is not
62+available, the backup remains on disk for manual recovery.
63+
64+This allows safer upgrades of MBR & modules, such that
65+modules/images/fonts/translations are consistent with MBR in case of
66+errors. For example accidental grub-install /dev/non-existent-disk
67+currently clobbers and upgrades modules in /boot/grub, despite not
68+actually updating any MBR. This increases peak disk-usage slightly, by
69+requiring temporarily twice the disk space to complete grub-install.
70+
71+Also add modinfo.sh to the cleanup/backup/restore codepath, to ensure
72+it is also cleaned / backed up / restored.
73+
74+Signed-off-by: Dimitri John Ledkov <xnox@ubuntu.com>
75+
76+Patch-Name: grub-install-backup-and-restore.patch
77+---
78+ configure.ac | 2 +-
79+ util/grub-install-common.c | 105 +++++++++++++++++++++++++++++++------
80+ 2 files changed, 91 insertions(+), 16 deletions(-)
81+
82+diff --git a/configure.ac b/configure.ac
83+index 1819188f9f..6a88b9b0c0 100644
84+--- a/configure.ac
85++++ b/configure.ac
86+@@ -420,7 +420,7 @@ else
87+ fi
88+
89+ # Check for functions and headers.
90+-AC_CHECK_FUNCS(posix_memalign memalign getextmntent)
91++AC_CHECK_FUNCS(posix_memalign memalign getextmntent on_exit)
92+ AC_CHECK_HEADERS(sys/param.h sys/mount.h sys/mnttab.h limits.h)
93+
94+ # glibc 2.25 still includes sys/sysmacros.h in sys/types.h but emits deprecation
95+diff --git a/util/grub-install-common.c b/util/grub-install-common.c
96+index 447504d3f4..a883b6daef 100644
97+--- a/util/grub-install-common.c
98++++ b/util/grub-install-common.c
99+@@ -185,38 +185,113 @@ grub_install_mkdir_p (const char *dst)
100+ free (t);
101+ }
102+
103++static int
104++strcmp_ext (const char *a, const char *b, const char *ext)
105++{
106++ char *bsuffix = grub_util_path_concat_ext (1, b, ext);
107++ int r = strcmp (a, bsuffix);
108++ free (bsuffix);
109++ return r;
110++}
111++
112++enum clean_grub_dir_mode
113++{
114++ CLEAN = 0,
115++ CLEAN_BACKUP = 1,
116++ CREATE_BACKUP = 2,
117++ RESTORE_BACKUP = 3,
118++};
119++
120+ static void
121+-clean_grub_dir (const char *di)
122++clean_grub_dir_real (const char *di, enum clean_grub_dir_mode mode)
123+ {
124+ grub_util_fd_dir_t d;
125+ grub_util_fd_dirent_t de;
126++ char suffix[2] = "";
127++
128++ if ((mode == CLEAN_BACKUP) || (mode == RESTORE_BACKUP))
129++ {
130++ strcpy (suffix, "-");
131++ }
132+
133+ d = grub_util_fd_opendir (di);
134+ if (!d)
135+- grub_util_error (_("cannot open directory `%s': %s"),
136+- di, grub_util_fd_strerror ());
137++ {
138++ if (mode == CLEAN_BACKUP)
139++ return;
140++ grub_util_error (_("cannot open directory `%s': %s"),
141++ di, grub_util_fd_strerror ());
142++ }
143+
144+ while ((de = grub_util_fd_readdir (d)))
145+ {
146+ const char *ext = strrchr (de->d_name, '.');
147+- if ((ext && (strcmp (ext, ".mod") == 0
148+- || strcmp (ext, ".lst") == 0
149+- || strcmp (ext, ".img") == 0
150+- || strcmp (ext, ".mo") == 0)
151+- && strcmp (de->d_name, "menu.lst") != 0)
152+- || strcmp (de->d_name, "efiemu32.o") == 0
153+- || strcmp (de->d_name, "efiemu64.o") == 0)
154++ if ((ext && (strcmp_ext (ext, ".mod", suffix) == 0
155++ || strcmp_ext (ext, ".lst", suffix) == 0
156++ || strcmp_ext (ext, ".img", suffix) == 0
157++ || strcmp_ext (ext, ".mo", suffix) == 0)
158++ && strcmp_ext (de->d_name, "menu.lst", suffix) != 0)
159++ || strcmp_ext (de->d_name, "modinfo.sh", suffix) == 0
160++ || strcmp_ext (de->d_name, "efiemu32.o", suffix) == 0
161++ || strcmp_ext (de->d_name, "efiemu64.o", suffix) == 0)
162+ {
163+- char *x = grub_util_path_concat (2, di, de->d_name);
164+- if (grub_util_unlink (x) < 0)
165+- grub_util_error (_("cannot delete `%s': %s"), x,
166+- grub_util_fd_strerror ());
167+- free (x);
168++ char *srcf = grub_util_path_concat (2, di, de->d_name);
169++
170++ if (mode == CREATE_BACKUP)
171++ {
172++ char *dstf = grub_util_path_concat_ext (2, di, de->d_name, "-");
173++ if (grub_util_rename (srcf, dstf) < 0)
174++ grub_util_error (_("cannot backup `%s': %s"), srcf,
175++ grub_util_fd_strerror ());
176++ free (dstf);
177++ }
178++ else if (mode == RESTORE_BACKUP)
179++ {
180++ char *dstf = grub_util_path_concat_ext (2, di, de->d_name);
181++ dstf[strlen (dstf) - 1] = 0;
182++ if (grub_util_rename (srcf, dstf) < 0)
183++ grub_util_error (_("cannot restore `%s': %s"), dstf,
184++ grub_util_fd_strerror ());
185++ free (dstf);
186++ }
187++ else
188++ {
189++ if (grub_util_unlink (srcf) < 0)
190++ grub_util_error (_("cannot delete `%s': %s"), srcf,
191++ grub_util_fd_strerror ());
192++ }
193++ free (srcf);
194+ }
195+ }
196+ grub_util_fd_closedir (d);
197+ }
198+
199++static void
200++restore_backup_on_exit (int status, void *arg)
201++{
202++ if (status == 0)
203++ {
204++ clean_grub_dir_real ((char *) arg, CLEAN_BACKUP);
205++ }
206++ else
207++ {
208++ clean_grub_dir_real ((char *) arg, CLEAN);
209++ clean_grub_dir_real ((char *) arg, RESTORE_BACKUP);
210++ }
211++ free (arg);
212++ arg = NULL;
213++}
214++
215++static void
216++clean_grub_dir (const char *di)
217++{
218++ clean_grub_dir_real (di, CLEAN_BACKUP);
219++ clean_grub_dir_real (di, CREATE_BACKUP);
220++#if defined(HAVE_ON_EXIT)
221++ on_exit (restore_backup_on_exit, strdup (di));
222++#endif
223++}
224++
225+ struct install_list
226+ {
227+ int is_default;
228diff --git a/debian/patches/series b/debian/patches/series
229index 4f8471c..0cbd443 100644
230--- a/debian/patches/series
231+++ b/debian/patches/series
232@@ -106,3 +106,4 @@ ubuntu-speed-zsys-history.patch
233 ubuntu-flavour-order.patch
234 ubuntu-dont-verify-loopback-images.patch
235 ubuntu-recovery-dis_ucode_ldr.patch
236+grub-install-backup-and-restore.patch
237diff --git a/debian/postinst.in b/debian/postinst.in
238index 3da6939..e11c451 100644
239--- a/debian/postinst.in
240+++ b/debian/postinst.in
241@@ -662,7 +662,7 @@ case "$1" in
242 continue
243 fi
244 else
245- break # noninteractive
246+ exit 1 # noninteractive
247 fi
248 fi
249 fi
250@@ -685,7 +685,7 @@ case "$1" in
251 db_fset grub-pc/install_devices_empty seen false
252 fi
253 else
254- break # noninteractive
255+ exit 1 # noninteractive
256 fi
257 else
258 break
259diff --git a/util/grub-install-common.c b/util/grub-install-common.c
260index 447504d..a883b6d 100644
261--- a/util/grub-install-common.c
262+++ b/util/grub-install-common.c
263@@ -185,38 +185,113 @@ grub_install_mkdir_p (const char *dst)
264 free (t);
265 }
266
267+static int
268+strcmp_ext (const char *a, const char *b, const char *ext)
269+{
270+ char *bsuffix = grub_util_path_concat_ext (1, b, ext);
271+ int r = strcmp (a, bsuffix);
272+ free (bsuffix);
273+ return r;
274+}
275+
276+enum clean_grub_dir_mode
277+{
278+ CLEAN = 0,
279+ CLEAN_BACKUP = 1,
280+ CREATE_BACKUP = 2,
281+ RESTORE_BACKUP = 3,
282+};
283+
284 static void
285-clean_grub_dir (const char *di)
286+clean_grub_dir_real (const char *di, enum clean_grub_dir_mode mode)
287 {
288 grub_util_fd_dir_t d;
289 grub_util_fd_dirent_t de;
290+ char suffix[2] = "";
291+
292+ if ((mode == CLEAN_BACKUP) || (mode == RESTORE_BACKUP))
293+ {
294+ strcpy (suffix, "-");
295+ }
296
297 d = grub_util_fd_opendir (di);
298 if (!d)
299- grub_util_error (_("cannot open directory `%s': %s"),
300- di, grub_util_fd_strerror ());
301+ {
302+ if (mode == CLEAN_BACKUP)
303+ return;
304+ grub_util_error (_("cannot open directory `%s': %s"),
305+ di, grub_util_fd_strerror ());
306+ }
307
308 while ((de = grub_util_fd_readdir (d)))
309 {
310 const char *ext = strrchr (de->d_name, '.');
311- if ((ext && (strcmp (ext, ".mod") == 0
312- || strcmp (ext, ".lst") == 0
313- || strcmp (ext, ".img") == 0
314- || strcmp (ext, ".mo") == 0)
315- && strcmp (de->d_name, "menu.lst") != 0)
316- || strcmp (de->d_name, "efiemu32.o") == 0
317- || strcmp (de->d_name, "efiemu64.o") == 0)
318+ if ((ext && (strcmp_ext (ext, ".mod", suffix) == 0
319+ || strcmp_ext (ext, ".lst", suffix) == 0
320+ || strcmp_ext (ext, ".img", suffix) == 0
321+ || strcmp_ext (ext, ".mo", suffix) == 0)
322+ && strcmp_ext (de->d_name, "menu.lst", suffix) != 0)
323+ || strcmp_ext (de->d_name, "modinfo.sh", suffix) == 0
324+ || strcmp_ext (de->d_name, "efiemu32.o", suffix) == 0
325+ || strcmp_ext (de->d_name, "efiemu64.o", suffix) == 0)
326 {
327- char *x = grub_util_path_concat (2, di, de->d_name);
328- if (grub_util_unlink (x) < 0)
329- grub_util_error (_("cannot delete `%s': %s"), x,
330- grub_util_fd_strerror ());
331- free (x);
332+ char *srcf = grub_util_path_concat (2, di, de->d_name);
333+
334+ if (mode == CREATE_BACKUP)
335+ {
336+ char *dstf = grub_util_path_concat_ext (2, di, de->d_name, "-");
337+ if (grub_util_rename (srcf, dstf) < 0)
338+ grub_util_error (_("cannot backup `%s': %s"), srcf,
339+ grub_util_fd_strerror ());
340+ free (dstf);
341+ }
342+ else if (mode == RESTORE_BACKUP)
343+ {
344+ char *dstf = grub_util_path_concat_ext (2, di, de->d_name);
345+ dstf[strlen (dstf) - 1] = 0;
346+ if (grub_util_rename (srcf, dstf) < 0)
347+ grub_util_error (_("cannot restore `%s': %s"), dstf,
348+ grub_util_fd_strerror ());
349+ free (dstf);
350+ }
351+ else
352+ {
353+ if (grub_util_unlink (srcf) < 0)
354+ grub_util_error (_("cannot delete `%s': %s"), srcf,
355+ grub_util_fd_strerror ());
356+ }
357+ free (srcf);
358 }
359 }
360 grub_util_fd_closedir (d);
361 }
362
363+static void
364+restore_backup_on_exit (int status, void *arg)
365+{
366+ if (status == 0)
367+ {
368+ clean_grub_dir_real ((char *) arg, CLEAN_BACKUP);
369+ }
370+ else
371+ {
372+ clean_grub_dir_real ((char *) arg, CLEAN);
373+ clean_grub_dir_real ((char *) arg, RESTORE_BACKUP);
374+ }
375+ free (arg);
376+ arg = NULL;
377+}
378+
379+static void
380+clean_grub_dir (const char *di)
381+{
382+ clean_grub_dir_real (di, CLEAN_BACKUP);
383+ clean_grub_dir_real (di, CREATE_BACKUP);
384+#if defined(HAVE_ON_EXIT)
385+ on_exit (restore_backup_on_exit, strdup (di));
386+#endif
387+}
388+
389 struct install_list
390 {
391 int is_default;

Subscribers

People subscribed via source and target branches