]> git.proxmox.com Git - grub2.git/blame - grub-core/commands/loadenv.c
probe: Support probing for partition UUID with --part-uuid
[grub2.git] / grub-core / commands / loadenv.c
CommitLineData
2270f77b 1/* loadenv.c - command to load/save environment variable. */
2/*
3 * GRUB -- GRand Unified Bootloader
c893cc87 4 * Copyright (C) 2008,2009,2010 Free Software Foundation, Inc.
2270f77b 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
2270f77b 20#include <grub/dl.h>
21#include <grub/mm.h>
2270f77b 22#include <grub/file.h>
23#include <grub/disk.h>
24#include <grub/misc.h>
25#include <grub/env.h>
2270f77b 26#include <grub/partition.h>
a85cd5a0 27#include <grub/lib/envblk.h>
b1b797cb 28#include <grub/extcmd.h>
77a79592 29#include <grub/i18n.h>
2270f77b 30
e745cf0c
VS
31GRUB_MOD_LICENSE ("GPLv3+");
32
2270f77b 33static const struct grub_arg_option options[] =
34 {
40211ab8
VS
35 /* TRANSLATORS: This option is used to override default filename
36 for loading and storing environment. */
77a79592 37 {"file", 'f', 0, N_("Specify filename."), 0, ARG_TYPE_PATHNAME},
0340bdbc
JM
38 {"skip-sig", 's', 0,
39 N_("Skip signature-checking of the environment file."), 0, ARG_TYPE_NONE},
2270f77b 40 {0, 0, 0, 0, 0, 0}
41 };
42
0340bdbc
JM
43/* Opens 'filename' with compression filters disabled. Optionally disables the
44 PUBKEY filter (that insists upon properly signed files) as well. PUBKEY
45 filter is restored before the function returns. */
2270f77b 46static grub_file_t
ca0a4f68
VS
47open_envblk_file (char *filename,
48 enum grub_file_type type)
2270f77b 49{
2270f77b 50 grub_file_t file;
0340bdbc 51 char *buf = 0;
2270f77b 52
53 if (! filename)
54 {
d35d0d37 55 const char *prefix;
0340bdbc 56 int len;
2270f77b 57
58 prefix = grub_env_get ("prefix");
0340bdbc 59 if (! prefix)
2270f77b 60 {
9c4b5c13 61 grub_error (GRUB_ERR_FILE_NOT_FOUND, N_("variable `%s' isn't set"), "prefix");
2270f77b 62 return 0;
63 }
0340bdbc
JM
64
65 len = grub_strlen (prefix);
66 buf = grub_malloc (len + 1 + sizeof (GRUB_ENVBLK_DEFCFG));
67 if (! buf)
68 return 0;
69 filename = buf;
70
71 grub_strcpy (filename, prefix);
72 filename[len] = '/';
73 grub_strcpy (filename + len + 1, GRUB_ENVBLK_DEFCFG);
2270f77b 74 }
75
ca0a4f68 76 file = grub_file_open (filename, type);
0340bdbc
JM
77
78 grub_free (buf);
79 return file;
5709cfc4 80}
81
82static grub_envblk_t
83read_envblk_file (grub_file_t file)
84{
85 grub_off_t offset = 0;
86 char *buf;
87 grub_size_t size = grub_file_size (file);
88 grub_envblk_t envblk;
2270f77b 89
5709cfc4 90 buf = grub_malloc (size);
91 if (! buf)
92 return 0;
b39f9d20 93
5709cfc4 94 while (size > 0)
2270f77b 95 {
5709cfc4 96 grub_ssize_t ret;
97
98 ret = grub_file_read (file, buf + offset, size);
99 if (ret <= 0)
2270f77b 100 {
5709cfc4 101 grub_free (buf);
2270f77b 102 return 0;
103 }
2270f77b 104
5709cfc4 105 size -= ret;
106 offset += ret;
2270f77b 107 }
108
5709cfc4 109 envblk = grub_envblk_open (buf, offset);
2270f77b 110 if (! envblk)
111 {
5709cfc4 112 grub_free (buf);
113 grub_error (GRUB_ERR_BAD_FILE_TYPE, "invalid environment block");
2270f77b 114 return 0;
115 }
116
5709cfc4 117 return envblk;
2270f77b 118}
119
0340bdbc
JM
120struct grub_env_whitelist
121{
122 grub_size_t len;
123 char **list;
124};
125typedef struct grub_env_whitelist grub_env_whitelist_t;
126
127static int
128test_whitelist_membership (const char* name,
129 const grub_env_whitelist_t* whitelist)
130{
131 grub_size_t i;
132
133 for (i = 0; i < whitelist->len; i++)
134 if (grub_strcmp (name, whitelist->list[i]) == 0)
135 return 1; /* found it */
136
137 return 0; /* not found */
138}
139
5c67ea6c
CW
140/* Helper for grub_cmd_load_env. */
141static int
0340bdbc 142set_var (const char *name, const char *value, void *whitelist)
5c67ea6c 143{
0340bdbc
JM
144 if (! whitelist)
145 {
146 grub_env_set (name, value);
147 return 0;
148 }
149
150 if (test_whitelist_membership (name,
151 (const grub_env_whitelist_t *) whitelist))
152 grub_env_set (name, value);
153
5c67ea6c
CW
154 return 0;
155}
156
2270f77b 157static grub_err_t
0340bdbc 158grub_cmd_load_env (grub_extcmd_context_t ctxt, int argc, char **args)
2270f77b 159{
28be0e94 160 struct grub_arg_list *state = ctxt->state;
2270f77b 161 grub_file_t file;
5709cfc4 162 grub_envblk_t envblk;
0340bdbc
JM
163 grub_env_whitelist_t whitelist;
164
165 whitelist.len = argc;
166 whitelist.list = args;
2270f77b 167
0340bdbc 168 /* state[0] is the -f flag; state[1] is the --skip-sig flag */
ca0a4f68
VS
169 file = open_envblk_file ((state[0].set) ? state[0].arg : 0,
170 GRUB_FILE_TYPE_LOADENV
171 | (state[1].set
172 ? GRUB_FILE_TYPE_SKIP_SIGNATURE : GRUB_FILE_TYPE_NONE));
2270f77b 173 if (! file)
174 return grub_errno;
175
5709cfc4 176 envblk = read_envblk_file (file);
177 if (! envblk)
178 goto fail;
2270f77b 179
0340bdbc
JM
180 /* argc > 0 indicates caller provided a whitelist of variables to read. */
181 grub_envblk_iterate (envblk, argc > 0 ? &whitelist : 0, set_var);
5709cfc4 182 grub_envblk_close (envblk);
b39f9d20 183
5709cfc4 184 fail:
185 grub_file_close (file);
2270f77b 186 return grub_errno;
187}
188
74a27421
VS
189/* Print all variables in current context. */
190static int
0340bdbc
JM
191print_var (const char *name, const char *value,
192 void *hook_data __attribute__ ((unused)))
74a27421
VS
193{
194 grub_printf ("%s=%s\n", name, value);
195 return 0;
196}
197
2270f77b 198static grub_err_t
28be0e94 199grub_cmd_list_env (grub_extcmd_context_t ctxt,
b1b797cb 200 int argc __attribute__ ((unused)),
201 char **args __attribute__ ((unused)))
2270f77b 202{
28be0e94 203 struct grub_arg_list *state = ctxt->state;
2270f77b 204 grub_file_t file;
5709cfc4 205 grub_envblk_t envblk;
2270f77b 206
ca0a4f68
VS
207 file = open_envblk_file ((state[0].set) ? state[0].arg : 0,
208 GRUB_FILE_TYPE_LOADENV
209 | (state[1].set
210 ? GRUB_FILE_TYPE_SKIP_SIGNATURE : GRUB_FILE_TYPE_NONE));
2270f77b 211 if (! file)
212 return grub_errno;
213
5709cfc4 214 envblk = read_envblk_file (file);
215 if (! envblk)
216 goto fail;
217
0340bdbc 218 grub_envblk_iterate (envblk, NULL, print_var);
5709cfc4 219 grub_envblk_close (envblk);
b39f9d20 220
5709cfc4 221 fail:
2270f77b 222 grub_file_close (file);
5709cfc4 223 return grub_errno;
224}
2270f77b 225
5709cfc4 226/* Used to maintain a variable length of blocklists internally. */
227struct blocklist
228{
229 grub_disk_addr_t sector;
230 unsigned offset;
231 unsigned length;
232 struct blocklist *next;
233};
234
235static void
236free_blocklists (struct blocklist *p)
237{
238 struct blocklist *q;
2270f77b 239
5709cfc4 240 for (; p; p = q)
241 {
242 q = p->next;
243 grub_free (p);
244 }
245}
246
c893cc87 247static grub_err_t
5709cfc4 248check_blocklists (grub_envblk_t envblk, struct blocklist *blocklists,
249 grub_file_t file)
250{
251 grub_size_t total_length;
252 grub_size_t index;
253 grub_disk_t disk;
254 grub_disk_addr_t part_start;
255 struct blocklist *p;
256 char *buf;
b39f9d20 257
5709cfc4 258 /* Sanity checks. */
259 total_length = 0;
260 for (p = blocklists; p; p = p->next)
261 {
262 struct blocklist *q;
cb72aa18 263 /* Check if any pair of blocks overlap. */
5709cfc4 264 for (q = p->next; q; q = q->next)
265 {
cb72aa18 266 grub_disk_addr_t s1, s2;
1f6af2a9 267 grub_disk_addr_t e1, e2;
cb72aa18
VS
268
269 s1 = p->sector;
270 e1 = s1 + ((p->length + GRUB_DISK_SECTOR_SIZE - 1) >> GRUB_DISK_SECTOR_BITS);
271
272 s2 = q->sector;
273 e2 = s2 + ((q->length + GRUB_DISK_SECTOR_SIZE - 1) >> GRUB_DISK_SECTOR_BITS);
274
1f6af2a9 275 if (s1 < e2 && s2 < e1)
5709cfc4 276 {
277 /* This might be actually valid, but it is unbelievable that
278 any filesystem makes such a silly allocation. */
c893cc87 279 return grub_error (GRUB_ERR_BAD_FS, "malformed file");
5709cfc4 280 }
281 }
b39f9d20 282
5709cfc4 283 total_length += p->length;
284 }
b39f9d20 285
5709cfc4 286 if (total_length != grub_file_size (file))
287 {
288 /* Maybe sparse, unallocated sectors. No way in GRUB. */
c893cc87 289 return grub_error (GRUB_ERR_BAD_FILE_TYPE, "sparse file not allowed");
5709cfc4 290 }
291
292 /* One more sanity check. Re-read all sectors by blocklists, and compare
293 those with the data read via a file. */
294 disk = file->device->disk;
15cb7d43
VS
295
296 part_start = grub_partition_get_start (disk->partition);
5709cfc4 297
298 buf = grub_envblk_buffer (envblk);
cb72aa18
VS
299 char *blockbuf = NULL;
300 grub_size_t blockbuf_len = 0;
aa0f752d 301 for (p = blocklists, index = 0; p; index += p->length, p = p->next)
5709cfc4 302 {
cb72aa18
VS
303 if (p->length > blockbuf_len)
304 {
305 grub_free (blockbuf);
306 blockbuf_len = 2 * p->length;
307 blockbuf = grub_malloc (blockbuf_len);
308 if (!blockbuf)
309 return grub_errno;
310 }
b39f9d20 311
5709cfc4 312 if (grub_disk_read (disk, p->sector - part_start,
313 p->offset, p->length, blockbuf))
c893cc87 314 return grub_errno;
5709cfc4 315
316 if (grub_memcmp (buf + index, blockbuf, p->length) != 0)
c893cc87 317 return grub_error (GRUB_ERR_FILE_READ_ERROR, "invalid blocklist");
5709cfc4 318 }
319
c893cc87 320 return GRUB_ERR_NONE;
5709cfc4 321}
322
323static int
324write_blocklists (grub_envblk_t envblk, struct blocklist *blocklists,
325 grub_file_t file)
326{
327 char *buf;
328 grub_disk_t disk;
329 grub_disk_addr_t part_start;
330 struct blocklist *p;
331 grub_size_t index;
b39f9d20 332
5709cfc4 333 buf = grub_envblk_buffer (envblk);
334 disk = file->device->disk;
15cb7d43 335 part_start = grub_partition_get_start (disk->partition);
5709cfc4 336
337 index = 0;
aa0f752d 338 for (p = blocklists; p; index += p->length, p = p->next)
5709cfc4 339 {
340 if (grub_disk_write (disk, p->sector - part_start,
341 p->offset, p->length, buf + index))
342 return 0;
343 }
344
345 return 1;
2270f77b 346}
347
4eb8b756
CW
348/* Context for grub_cmd_save_env. */
349struct grub_cmd_save_env_ctx
350{
351 struct blocklist *head, *tail;
352};
353
354/* Store blocklists in a linked list. */
355static void
356save_env_read_hook (grub_disk_addr_t sector, unsigned offset, unsigned length,
357 void *data)
358{
359 struct grub_cmd_save_env_ctx *ctx = data;
360 struct blocklist *block;
361
4eb8b756
CW
362 block = grub_malloc (sizeof (*block));
363 if (! block)
364 return;
365
366 block->sector = sector;
367 block->offset = offset;
368 block->length = length;
369
370 /* Slightly complicated, because the list should be FIFO. */
371 block->next = 0;
372 if (ctx->tail)
373 ctx->tail->next = block;
374 ctx->tail = block;
375 if (! ctx->head)
376 ctx->head = block;
377}
378
2270f77b 379static grub_err_t
28be0e94 380grub_cmd_save_env (grub_extcmd_context_t ctxt, int argc, char **args)
2270f77b 381{
28be0e94 382 struct grub_arg_list *state = ctxt->state;
2270f77b 383 grub_file_t file;
5709cfc4 384 grub_envblk_t envblk;
4eb8b756
CW
385 struct grub_cmd_save_env_ctx ctx = {
386 .head = 0,
387 .tail = 0
388 };
2270f77b 389
390 if (! argc)
7fd0baee 391 return grub_error (GRUB_ERR_BAD_ARGUMENT, "no variable is specified");
2270f77b 392
0340bdbc 393 file = open_envblk_file ((state[0].set) ? state[0].arg : 0,
ca0a4f68
VS
394 GRUB_FILE_TYPE_SAVEENV
395 | GRUB_FILE_TYPE_SKIP_SIGNATURE);
2270f77b 396 if (! file)
397 return grub_errno;
398
5709cfc4 399 if (! file->device->disk)
2270f77b 400 {
5709cfc4 401 grub_file_close (file);
402 return grub_error (GRUB_ERR_BAD_DEVICE, "disk device required");
2270f77b 403 }
404
4eb8b756
CW
405 file->read_hook = save_env_read_hook;
406 file->read_hook_data = &ctx;
5709cfc4 407 envblk = read_envblk_file (file);
408 file->read_hook = 0;
409 if (! envblk)
410 goto fail;
2270f77b 411
4eb8b756 412 if (check_blocklists (envblk, ctx.head, file))
5709cfc4 413 goto fail;
b39f9d20 414
2270f77b 415 while (argc)
416 {
d35d0d37 417 const char *value;
2270f77b 418
419 value = grub_env_get (args[0]);
420 if (value)
421 {
5709cfc4 422 if (! grub_envblk_set (envblk, args[0], value))
2270f77b 423 {
424 grub_error (GRUB_ERR_BAD_ARGUMENT, "environment block too small");
5709cfc4 425 goto fail;
2270f77b 426 }
427 }
5ade4a42
VS
428 else
429 grub_envblk_delete (envblk, args[0]);
2270f77b 430
431 argc--;
432 args++;
433 }
434
4eb8b756 435 write_blocklists (envblk, ctx.head, file);
b39f9d20 436
5709cfc4 437 fail:
438 if (envblk)
439 grub_envblk_close (envblk);
4eb8b756 440 free_blocklists (ctx.head);
2270f77b 441 grub_file_close (file);
2270f77b 442 return grub_errno;
443}
444
b1b797cb 445static grub_extcmd_t cmd_load, cmd_list, cmd_save;
446
2270f77b 447GRUB_MOD_INIT(loadenv)
448{
b1b797cb 449 cmd_load =
0340bdbc 450 grub_register_extcmd ("load_env", grub_cmd_load_env, 0,
bfdfeb25 451 N_("[-f FILE] [-s|--skip-sig] [variable_name_to_whitelist] [...]"),
29c44ad1 452 N_("Load variables from environment block file."),
b1b797cb 453 options);
454 cmd_list =
ed80f7d5 455 grub_register_extcmd ("list_env", grub_cmd_list_env, 0, N_("[-f FILE]"),
29c44ad1 456 N_("List variables from environment block file."),
b1b797cb 457 options);
458 cmd_save =
ed80f7d5 459 grub_register_extcmd ("save_env", grub_cmd_save_env, 0,
29c44ad1 460 N_("[-f FILE] variable_name [...]"),
461 N_("Save variables to environment block file."),
b1b797cb 462 options);
2270f77b 463}
464
465GRUB_MOD_FINI(loadenv)
466{
b1b797cb 467 grub_unregister_extcmd (cmd_load);
468 grub_unregister_extcmd (cmd_list);
469 grub_unregister_extcmd (cmd_save);
2270f77b 470}