]> glassweightruler.freedombox.rocks Git - Ventoy.git/blob - GRUB2/MOD_SRC/grub-2.04/grub-core/commands/loadenv.c
Fix the issue that SLES/OpenSUSE can not boot after install. (#3125)
[Ventoy.git] / GRUB2 / MOD_SRC / grub-2.04 / grub-core / commands / loadenv.c
1 /* loadenv.c - command to load/save environment variable. */
2 /*
3 * GRUB -- GRand Unified Bootloader
4 * Copyright (C) 2008,2009,2010 Free Software Foundation, Inc.
5 *
6 * GRUB is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * GRUB is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include <grub/dl.h>
21 #include <grub/mm.h>
22 #include <grub/file.h>
23 #include <grub/disk.h>
24 #include <grub/misc.h>
25 #include <grub/env.h>
26 #include <grub/partition.h>
27 #include <grub/lib/envblk.h>
28 #include <grub/extcmd.h>
29 #include <grub/i18n.h>
30
31 #include "loadenv.h"
32
33 GRUB_MOD_LICENSE ("GPLv3+");
34
35 static const struct grub_arg_option options[] =
36 {
37 /* TRANSLATORS: This option is used to override default filename
38 for loading and storing environment. */
39 {"file", 'f', 0, N_("Specify filename."), 0, ARG_TYPE_PATHNAME},
40 {"skip-sig", 's', 0,
41 N_("Skip signature-checking of the environment file."), 0, ARG_TYPE_NONE},
42 {0, 0, 0, 0, 0, 0}
43 };
44
45 /* Opens 'filename' with compression filters disabled. Optionally disables the
46 PUBKEY filter (that insists upon properly signed files) as well. PUBKEY
47 filter is restored before the function returns. */
48 static grub_file_t
49 open_envblk_file (char *filename,
50 enum grub_file_type type)
51 {
52 grub_file_t file;
53 char *buf = 0;
54
55 if (! filename)
56 {
57 const char *prefix;
58 int len;
59
60 prefix = grub_env_get ("prefix");
61 if (! prefix)
62 {
63 grub_error (GRUB_ERR_FILE_NOT_FOUND, N_("variable `%s' isn't set"), "prefix");
64 return 0;
65 }
66
67 len = grub_strlen (prefix);
68 buf = grub_malloc (len + 1 + sizeof (GRUB_ENVBLK_DEFCFG));
69 if (! buf)
70 return 0;
71 filename = buf;
72
73 grub_strcpy (filename, prefix);
74 filename[len] = '/';
75 grub_strcpy (filename + len + 1, GRUB_ENVBLK_DEFCFG);
76 }
77
78 file = grub_file_open (filename, type);
79
80 grub_free (buf);
81 return file;
82 }
83
84 static grub_err_t
85 grub_cmd_load_env (grub_extcmd_context_t ctxt, int argc, char **args)
86 {
87 struct grub_arg_list *state = ctxt->state;
88 grub_file_t file;
89 grub_envblk_t envblk;
90 grub_env_whitelist_t whitelist;
91
92 whitelist.len = argc;
93 whitelist.list = args;
94
95 /* state[0] is the -f flag; state[1] is the --skip-sig flag */
96 file = open_envblk_file ((state[0].set) ? state[0].arg : 0,
97 GRUB_FILE_TYPE_LOADENV
98 | (state[1].set
99 ? GRUB_FILE_TYPE_SKIP_SIGNATURE : GRUB_FILE_TYPE_NONE));
100 if (! file)
101 return grub_errno;
102
103 envblk = read_envblk_file (file);
104 if (! envblk)
105 goto fail;
106
107 /* argc > 0 indicates caller provided a whitelist of variables to read. */
108 grub_envblk_iterate (envblk, argc > 0 ? &whitelist : 0, set_var);
109 grub_envblk_close (envblk);
110
111 fail:
112 grub_file_close (file);
113 return grub_errno;
114 }
115
116 /* Print all variables in current context. */
117 static int
118 print_var (const char *name, const char *value,
119 void *hook_data __attribute__ ((unused)))
120 {
121 grub_printf ("%s=%s\n", name, value);
122 return 0;
123 }
124
125 static grub_err_t
126 grub_cmd_list_env (grub_extcmd_context_t ctxt,
127 int argc __attribute__ ((unused)),
128 char **args __attribute__ ((unused)))
129 {
130 struct grub_arg_list *state = ctxt->state;
131 grub_file_t file;
132 grub_envblk_t envblk;
133
134 file = open_envblk_file ((state[0].set) ? state[0].arg : 0,
135 GRUB_FILE_TYPE_LOADENV
136 | (state[1].set
137 ? GRUB_FILE_TYPE_SKIP_SIGNATURE : GRUB_FILE_TYPE_NONE));
138 if (! file)
139 return grub_errno;
140
141 envblk = read_envblk_file (file);
142 if (! envblk)
143 goto fail;
144
145 grub_envblk_iterate (envblk, NULL, print_var);
146 grub_envblk_close (envblk);
147
148 fail:
149 grub_file_close (file);
150 return grub_errno;
151 }
152
153 /* Used to maintain a variable length of blocklists internally. */
154 struct blocklist
155 {
156 grub_disk_addr_t sector;
157 unsigned offset;
158 unsigned length;
159 struct blocklist *next;
160 };
161
162 static void
163 free_blocklists (struct blocklist *p)
164 {
165 struct blocklist *q;
166
167 for (; p; p = q)
168 {
169 q = p->next;
170 grub_free (p);
171 }
172 }
173
174 static grub_err_t
175 check_blocklists (grub_envblk_t envblk, struct blocklist *blocklists,
176 grub_file_t file)
177 {
178 grub_size_t total_length;
179 grub_size_t index;
180 grub_disk_t disk;
181 grub_disk_addr_t part_start;
182 struct blocklist *p;
183 char *buf;
184
185 /* Sanity checks. */
186 total_length = 0;
187 for (p = blocklists; p; p = p->next)
188 {
189 struct blocklist *q;
190 /* Check if any pair of blocks overlap. */
191 for (q = p->next; q; q = q->next)
192 {
193 grub_disk_addr_t s1, s2;
194 grub_disk_addr_t e1, e2;
195
196 s1 = p->sector;
197 e1 = s1 + ((p->length + GRUB_DISK_SECTOR_SIZE - 1) >> GRUB_DISK_SECTOR_BITS);
198
199 s2 = q->sector;
200 e2 = s2 + ((q->length + GRUB_DISK_SECTOR_SIZE - 1) >> GRUB_DISK_SECTOR_BITS);
201
202 if (s1 < e2 && s2 < e1)
203 {
204 /* This might be actually valid, but it is unbelievable that
205 any filesystem makes such a silly allocation. */
206 return grub_error (GRUB_ERR_BAD_FS, "malformed file");
207 }
208 }
209
210 total_length += p->length;
211 }
212
213 if (total_length != grub_file_size (file))
214 {
215 /* Maybe sparse, unallocated sectors. No way in GRUB. */
216 return grub_error (GRUB_ERR_BAD_FILE_TYPE, "sparse file not allowed");
217 }
218
219 /* One more sanity check. Re-read all sectors by blocklists, and compare
220 those with the data read via a file. */
221 disk = file->device->disk;
222
223 part_start = grub_partition_get_start (disk->partition);
224
225 buf = grub_envblk_buffer (envblk);
226 char *blockbuf = NULL;
227 grub_size_t blockbuf_len = 0;
228 for (p = blocklists, index = 0; p; index += p->length, p = p->next)
229 {
230 if (p->length > blockbuf_len)
231 {
232 grub_free (blockbuf);
233 blockbuf_len = 2 * p->length;
234 blockbuf = grub_malloc (blockbuf_len);
235 if (!blockbuf)
236 return grub_errno;
237 }
238
239 if (grub_disk_read (disk, p->sector - part_start,
240 p->offset, p->length, blockbuf))
241 return grub_errno;
242
243 if (grub_memcmp (buf + index, blockbuf, p->length) != 0)
244 return grub_error (GRUB_ERR_FILE_READ_ERROR, "invalid blocklist");
245 }
246
247 return GRUB_ERR_NONE;
248 }
249
250 static int
251 write_blocklists (grub_envblk_t envblk, struct blocklist *blocklists,
252 grub_file_t file)
253 {
254 char *buf;
255 grub_disk_t disk;
256 grub_disk_addr_t part_start;
257 struct blocklist *p;
258 grub_size_t index;
259
260 buf = grub_envblk_buffer (envblk);
261 disk = file->device->disk;
262 part_start = grub_partition_get_start (disk->partition);
263
264 index = 0;
265 for (p = blocklists; p; index += p->length, p = p->next)
266 {
267 if (grub_disk_write (disk, p->sector - part_start,
268 p->offset, p->length, buf + index))
269 return 0;
270 }
271
272 return 1;
273 }
274
275 /* Context for grub_cmd_save_env. */
276 struct grub_cmd_save_env_ctx
277 {
278 struct blocklist *head, *tail;
279 };
280
281 /* Store blocklists in a linked list. */
282 static void
283 save_env_read_hook (grub_disk_addr_t sector, unsigned offset, unsigned length,
284 void *data)
285 {
286 struct grub_cmd_save_env_ctx *ctx = data;
287 struct blocklist *block;
288
289 block = grub_malloc (sizeof (*block));
290 if (! block)
291 return;
292
293 block->sector = sector;
294 block->offset = offset;
295 block->length = length;
296
297 /* Slightly complicated, because the list should be FIFO. */
298 block->next = 0;
299 if (ctx->tail)
300 ctx->tail->next = block;
301 ctx->tail = block;
302 if (! ctx->head)
303 ctx->head = block;
304 }
305
306 static grub_err_t
307 grub_cmd_save_env (grub_extcmd_context_t ctxt, int argc, char **args)
308 {
309 struct grub_arg_list *state = ctxt->state;
310 grub_file_t file;
311 grub_envblk_t envblk;
312 struct grub_cmd_save_env_ctx ctx = {
313 .head = 0,
314 .tail = 0
315 };
316
317 if (! argc)
318 return grub_error (GRUB_ERR_BAD_ARGUMENT, "no variable is specified");
319
320 file = open_envblk_file ((state[0].set) ? state[0].arg : 0,
321 GRUB_FILE_TYPE_SAVEENV
322 | GRUB_FILE_TYPE_SKIP_SIGNATURE);
323 if (! file)
324 return grub_errno;
325
326 if (! file->device->disk)
327 {
328 grub_file_close (file);
329 return grub_error (GRUB_ERR_BAD_DEVICE, "disk device required");
330 }
331
332 file->read_hook = save_env_read_hook;
333 file->read_hook_data = &ctx;
334 envblk = read_envblk_file (file);
335 file->read_hook = 0;
336 if (! envblk)
337 goto fail;
338
339 if (check_blocklists (envblk, ctx.head, file))
340 goto fail;
341
342 while (argc)
343 {
344 const char *value;
345
346 value = grub_env_get (args[0]);
347 if (value)
348 {
349 if (! grub_envblk_set (envblk, args[0], value))
350 {
351 grub_error (GRUB_ERR_BAD_ARGUMENT, "environment block too small");
352 goto fail;
353 }
354 }
355 else
356 grub_envblk_delete (envblk, args[0]);
357
358 argc--;
359 args++;
360 }
361
362 write_blocklists (envblk, ctx.head, file);
363
364 fail:
365 if (envblk)
366 grub_envblk_close (envblk);
367 free_blocklists (ctx.head);
368 grub_file_close (file);
369 return grub_errno;
370 }
371
372 static grub_extcmd_t cmd_load, cmd_list, cmd_save;
373
374 GRUB_MOD_INIT(loadenv)
375 {
376 cmd_load =
377 grub_register_extcmd ("load_env", grub_cmd_load_env, 0,
378 N_("[-f FILE] [-s|--skip-sig] [variable_name_to_whitelist] [...]"),
379 N_("Load variables from environment block file."),
380 options);
381 cmd_list =
382 grub_register_extcmd ("list_env", grub_cmd_list_env, 0, N_("[-f FILE]"),
383 N_("List variables from environment block file."),
384 options);
385 cmd_save =
386 grub_register_extcmd ("save_env", grub_cmd_save_env, 0,
387 N_("[-f FILE] variable_name [...]"),
388 N_("Save variables to environment block file."),
389 options);
390 }
391
392 GRUB_MOD_FINI(loadenv)
393 {
394 grub_unregister_extcmd (cmd_load);
395 grub_unregister_extcmd (cmd_list);
396 grub_unregister_extcmd (cmd_save);
397 }