]>
Commit | Line | Data |
---|---|---|
db1771cf | 1 | /* env.c - Environment variables */ |
2 | /* | |
4b13b216 | 3 | * GRUB -- GRand Unified Bootloader |
b28bbc4e | 4 | * Copyright (C) 2003,2005,2006,2007,2008,2009 Free Software Foundation, Inc. |
db1771cf | 5 | * |
5a79f472 | 6 | * GRUB is free software: you can redistribute it and/or modify |
db1771cf | 7 | * it under the terms of the GNU General Public License as published by |
5a79f472 | 8 | * the Free Software Foundation, either version 3 of the License, or |
db1771cf | 9 | * (at your option) any later version. |
10 | * | |
5a79f472 | 11 | * GRUB is distributed in the hope that it will be useful, |
db1771cf | 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 | |
5a79f472 | 17 | * along with GRUB. If not, see <http://www.gnu.org/licenses/>. |
db1771cf | 18 | */ |
19 | ||
4b13b216 | 20 | #include <grub/env.h> |
21 | #include <grub/misc.h> | |
22 | #include <grub/mm.h> | |
db1771cf | 23 | |
5f968e1e | 24 | /* The size of the hash table. */ |
25 | #define HASHSZ 13 | |
db1771cf | 26 | |
27 | /* A hashtable for quick lookup of variables. */ | |
eaef0553 | 28 | struct grub_env_context |
29 | { | |
385bd9c1 | 30 | /* A hash table for variables. */ |
eaef0553 | 31 | struct grub_env_var *vars[HASHSZ]; |
32 | ||
eaef0553 | 33 | /* One level deeper on the stack. */ |
385bd9c1 | 34 | struct grub_env_context *prev; |
35 | }; | |
36 | ||
37 | /* This is used for sorting only. */ | |
38 | struct grub_env_sorted_var | |
39 | { | |
40 | struct grub_env_var *var; | |
41 | struct grub_env_sorted_var *next; | |
eaef0553 | 42 | }; |
43 | ||
385bd9c1 | 44 | /* The initial context. */ |
45 | static struct grub_env_context initial_context; | |
db1771cf | 46 | |
385bd9c1 | 47 | /* The current context. */ |
48 | static struct grub_env_context *current_context = &initial_context; | |
db1771cf | 49 | |
50 | /* Return the hash representation of the string S. */ | |
385bd9c1 | 51 | static unsigned int |
52 | grub_env_hashval (const char *s) | |
db1771cf | 53 | { |
54 | unsigned int i = 0; | |
55 | ||
cc85c3c3 | 56 | /* XXX: This can be done much more efficiently. */ |
db1771cf | 57 | while (*s) |
58 | i += 5 * *(s++); | |
59 | ||
60 | return i % HASHSZ; | |
61 | } | |
62 | ||
4b13b216 | 63 | static struct grub_env_var * |
64 | grub_env_find (const char *name) | |
db1771cf | 65 | { |
4b13b216 | 66 | struct grub_env_var *var; |
67 | int idx = grub_env_hashval (name); | |
db1771cf | 68 | |
eaef0553 | 69 | /* Look for the variable in the current context. */ |
385bd9c1 | 70 | for (var = current_context->vars[idx]; var; var = var->next) |
71 | if (grub_strcmp (var->name, name) == 0) | |
eaef0553 | 72 | return var; |
73 | ||
db1771cf | 74 | return 0; |
75 | } | |
76 | ||
4b13b216 | 77 | grub_err_t |
eaef0553 | 78 | grub_env_context_open (void) |
db1771cf | 79 | { |
eaef0553 | 80 | struct grub_env_context *context; |
81 | int i; | |
82 | ||
83 | context = grub_malloc (sizeof (*context)); | |
84 | if (! context) | |
85 | return grub_errno; | |
86 | ||
385bd9c1 | 87 | grub_memset (context, 0, sizeof (*context)); |
88 | context->prev = current_context; | |
89 | current_context = context; | |
90 | ||
91 | /* Copy exported variables. */ | |
eaef0553 | 92 | for (i = 0; i < HASHSZ; i++) |
385bd9c1 | 93 | { |
94 | struct grub_env_var *var; | |
95 | ||
96 | for (var = context->prev->vars[i]; var; var = var->next) | |
97 | { | |
98 | if (var->type == GRUB_ENV_VAR_GLOBAL) | |
182dd4e5 | 99 | { |
100 | if (grub_env_set (var->name, var->value) != GRUB_ERR_NONE) | |
101 | { | |
102 | grub_env_context_close (); | |
103 | return grub_errno; | |
104 | } | |
105 | grub_register_variable_hook (var->name, var->read_hook, var->write_hook); | |
106 | } | |
385bd9c1 | 107 | } |
108 | } | |
eaef0553 | 109 | |
eaef0553 | 110 | return GRUB_ERR_NONE; |
111 | } | |
112 | ||
113 | grub_err_t | |
114 | grub_env_context_close (void) | |
115 | { | |
116 | struct grub_env_context *context; | |
385bd9c1 | 117 | int i; |
eaef0553 | 118 | |
385bd9c1 | 119 | if (! current_context->prev) |
120 | grub_fatal ("cannot close the initial context"); | |
121 | ||
eaef0553 | 122 | /* Free the variables associated with this context. */ |
385bd9c1 | 123 | for (i = 0; i < HASHSZ; i++) |
eaef0553 | 124 | { |
385bd9c1 | 125 | struct grub_env_var *p, *q; |
126 | ||
9fe86034 | 127 | for (p = current_context->vars[i]; p; p = q) |
385bd9c1 | 128 | { |
129 | q = p->next; | |
b28bbc4e | 130 | grub_free (p->name); |
131 | if (p->type != GRUB_ENV_VAR_DATA) | |
132 | grub_free (p->value); | |
385bd9c1 | 133 | grub_free (p); |
134 | } | |
eaef0553 | 135 | } |
eaef0553 | 136 | |
137 | /* Restore the previous context. */ | |
385bd9c1 | 138 | context = current_context->prev; |
139 | grub_free (current_context); | |
140 | current_context = context; | |
eaef0553 | 141 | |
142 | return GRUB_ERR_NONE; | |
143 | } | |
144 | ||
145 | static void | |
146 | grub_env_insert (struct grub_env_context *context, | |
385bd9c1 | 147 | struct grub_env_var *var) |
eaef0553 | 148 | { |
385bd9c1 | 149 | int idx = grub_env_hashval (var->name); |
150 | ||
151 | /* Insert the variable into the hashtable. */ | |
42ce5170 | 152 | var->prevp = &context->vars[idx]; |
385bd9c1 | 153 | var->next = context->vars[idx]; |
154 | if (var->next) | |
42ce5170 | 155 | var->next->prevp = &(var->next); |
385bd9c1 | 156 | context->vars[idx] = var; |
eaef0553 | 157 | } |
158 | ||
eaef0553 | 159 | static void |
385bd9c1 | 160 | grub_env_remove (struct grub_env_var *var) |
eaef0553 | 161 | { |
162 | /* Remove the entry from the variable table. */ | |
385bd9c1 | 163 | *var->prevp = var->next; |
164 | if (var->next) | |
165 | var->next->prevp = var->prevp; | |
eaef0553 | 166 | } |
167 | ||
168 | grub_err_t | |
385bd9c1 | 169 | grub_env_export (const char *name) |
eaef0553 | 170 | { |
385bd9c1 | 171 | struct grub_env_var *var; |
eaef0553 | 172 | |
385bd9c1 | 173 | var = grub_env_find (name); |
174 | if (var) | |
175 | var->type = GRUB_ENV_VAR_GLOBAL; | |
eaef0553 | 176 | |
177 | return GRUB_ERR_NONE; | |
178 | } | |
179 | ||
180 | grub_err_t | |
385bd9c1 | 181 | grub_env_set (const char *name, const char *val) |
eaef0553 | 182 | { |
385bd9c1 | 183 | struct grub_env_var *var; |
eaef0553 | 184 | |
db1771cf | 185 | /* If the variable does already exist, just update the variable. */ |
385bd9c1 | 186 | var = grub_env_find (name); |
187 | if (var) | |
db1771cf | 188 | { |
385bd9c1 | 189 | char *old = var->value; |
5f968e1e | 190 | |
385bd9c1 | 191 | if (var->write_hook) |
192 | var->value = var->write_hook (var, val); | |
5f968e1e | 193 | else |
385bd9c1 | 194 | var->value = grub_strdup (val); |
5f968e1e | 195 | |
385bd9c1 | 196 | if (! var->value) |
db1771cf | 197 | { |
385bd9c1 | 198 | var->value = old; |
4b13b216 | 199 | return grub_errno; |
db1771cf | 200 | } |
201 | ||
4b13b216 | 202 | grub_free (old); |
385bd9c1 | 203 | return GRUB_ERR_NONE; |
db1771cf | 204 | } |
205 | ||
385bd9c1 | 206 | /* The variable does not exist, so create a new one. */ |
207 | var = grub_malloc (sizeof (*var)); | |
208 | if (! var) | |
4b13b216 | 209 | return grub_errno; |
db1771cf | 210 | |
385bd9c1 | 211 | grub_memset (var, 0, sizeof (*var)); |
212 | ||
213 | /* This is not necessary, because GRUB_ENV_VAR_LOCAL == 0. But leave | |
214 | this for readability. */ | |
215 | var->type = GRUB_ENV_VAR_LOCAL; | |
db1771cf | 216 | |
385bd9c1 | 217 | var->name = grub_strdup (name); |
218 | if (! var->name) | |
db1771cf | 219 | goto fail; |
220 | ||
385bd9c1 | 221 | var->value = grub_strdup (val); |
222 | if (! var->value) | |
db1771cf | 223 | goto fail; |
eaef0553 | 224 | |
385bd9c1 | 225 | grub_env_insert (current_context, var); |
db1771cf | 226 | |
385bd9c1 | 227 | return GRUB_ERR_NONE; |
07084456 | 228 | |
229 | fail: | |
385bd9c1 | 230 | grub_free (var->name); |
231 | grub_free (var->value); | |
232 | grub_free (var); | |
07084456 | 233 | |
234 | return grub_errno; | |
db1771cf | 235 | } |
236 | ||
237 | char * | |
4b13b216 | 238 | grub_env_get (const char *name) |
db1771cf | 239 | { |
385bd9c1 | 240 | struct grub_env_var *var; |
241 | ||
242 | var = grub_env_find (name); | |
243 | if (! var) | |
db1771cf | 244 | return 0; |
245 | ||
385bd9c1 | 246 | if (var->read_hook) |
247 | return var->read_hook (var, var->value); | |
db1771cf | 248 | |
385bd9c1 | 249 | return var->value; |
db1771cf | 250 | } |
251 | ||
252 | void | |
4b13b216 | 253 | grub_env_unset (const char *name) |
db1771cf | 254 | { |
385bd9c1 | 255 | struct grub_env_var *var; |
256 | ||
257 | var = grub_env_find (name); | |
258 | if (! var) | |
db1771cf | 259 | return; |
260 | ||
261 | /* XXX: It is not possible to unset variables with a read or write | |
262 | hook. */ | |
385bd9c1 | 263 | if (var->read_hook || var->write_hook) |
db1771cf | 264 | return; |
265 | ||
385bd9c1 | 266 | grub_env_remove (var); |
db1771cf | 267 | |
385bd9c1 | 268 | grub_free (var->name); |
a8aa5762 | 269 | if (var->type != GRUB_ENV_VAR_DATA) |
270 | grub_free (var->value); | |
385bd9c1 | 271 | grub_free (var); |
db1771cf | 272 | } |
273 | ||
274 | void | |
385bd9c1 | 275 | grub_env_iterate (int (*func) (struct grub_env_var *var)) |
db1771cf | 276 | { |
385bd9c1 | 277 | struct grub_env_sorted_var *sorted_list = 0; |
278 | struct grub_env_sorted_var *sorted_var; | |
279 | int i; | |
db1771cf | 280 | |
385bd9c1 | 281 | /* Add variables associated with this context into a sorted list. */ |
282 | for (i = 0; i < HASHSZ; i++) | |
eaef0553 | 283 | { |
385bd9c1 | 284 | struct grub_env_var *var; |
285 | ||
286 | for (var = current_context->vars[i]; var; var = var->next) | |
287 | { | |
288 | struct grub_env_sorted_var *p, **q; | |
289 | ||
290 | /* Ignore data slots. */ | |
291 | if (var->type == GRUB_ENV_VAR_DATA) | |
292 | continue; | |
293 | ||
294 | sorted_var = grub_malloc (sizeof (*sorted_var)); | |
295 | if (! sorted_var) | |
296 | goto fail; | |
297 | ||
298 | sorted_var->var = var; | |
299 | ||
300 | for (q = &sorted_list, p = *q; p; q = &((*q)->next), p = *q) | |
301 | { | |
302 | if (grub_strcmp (p->var->name, var->name) > 0) | |
303 | break; | |
304 | } | |
305 | ||
306 | sorted_var->next = *q; | |
307 | *q = sorted_var; | |
308 | } | |
309 | } | |
310 | ||
311 | /* Iterate FUNC on the sorted list. */ | |
312 | for (sorted_var = sorted_list; sorted_var; sorted_var = sorted_var->next) | |
313 | if (func (sorted_var->var)) | |
314 | break; | |
315 | ||
316 | fail: | |
eaef0553 | 317 | |
385bd9c1 | 318 | /* Free the sorted list. */ |
319 | for (sorted_var = sorted_list; sorted_var; ) | |
320 | { | |
321 | struct grub_env_sorted_var *tmp = sorted_var->next; | |
322 | ||
323 | grub_free (sorted_var); | |
324 | sorted_var = tmp; | |
eaef0553 | 325 | } |
db1771cf | 326 | } |
327 | ||
4b13b216 | 328 | grub_err_t |
385bd9c1 | 329 | grub_register_variable_hook (const char *name, |
5f968e1e | 330 | grub_env_read_hook_t read_hook, |
331 | grub_env_write_hook_t write_hook) | |
db1771cf | 332 | { |
385bd9c1 | 333 | struct grub_env_var *var = grub_env_find (name); |
db1771cf | 334 | |
385bd9c1 | 335 | if (! var) |
5f968e1e | 336 | { |
407aceb4 | 337 | if (grub_env_set (name, "") != GRUB_ERR_NONE) |
5f968e1e | 338 | return grub_errno; |
385bd9c1 | 339 | |
340 | var = grub_env_find (name); | |
341 | /* XXX Insert an assertion? */ | |
5f968e1e | 342 | } |
db1771cf | 343 | |
385bd9c1 | 344 | var->read_hook = read_hook; |
345 | var->write_hook = write_hook; | |
346 | ||
347 | return GRUB_ERR_NONE; | |
348 | } | |
349 | ||
350 | static char * | |
351 | mangle_data_slot_name (const char *name) | |
352 | { | |
353 | char *mangled_name; | |
354 | ||
355 | mangled_name = grub_malloc (grub_strlen (name) + 2); | |
356 | if (! mangled_name) | |
357 | return 0; | |
358 | ||
359 | grub_sprintf (mangled_name, "\e%s", name); | |
360 | return mangled_name; | |
361 | } | |
362 | ||
363 | grub_err_t | |
364 | grub_env_set_data_slot (const char *name, const void *ptr) | |
365 | { | |
366 | char *mangled_name; | |
367 | struct grub_env_var *var; | |
368 | ||
369 | mangled_name = mangle_data_slot_name (name); | |
370 | if (! mangled_name) | |
371 | goto fail; | |
372 | ||
373 | /* If the variable does already exist, just update the variable. */ | |
374 | var = grub_env_find (mangled_name); | |
375 | if (var) | |
376 | { | |
377 | var->value = (char *) ptr; | |
378 | return GRUB_ERR_NONE; | |
379 | } | |
380 | ||
381 | /* The variable does not exist, so create a new one. */ | |
382 | var = grub_malloc (sizeof (*var)); | |
383 | if (! var) | |
384 | goto fail; | |
db1771cf | 385 | |
385bd9c1 | 386 | grub_memset (var, 0, sizeof (*var)); |
387 | ||
388 | var->type = GRUB_ENV_VAR_DATA; | |
389 | var->name = mangled_name; | |
390 | var->value = (char *) ptr; | |
391 | ||
392 | grub_env_insert (current_context, var); | |
db1771cf | 393 | |
4b13b216 | 394 | return GRUB_ERR_NONE; |
385bd9c1 | 395 | |
396 | fail: | |
397 | ||
398 | grub_free (mangled_name); | |
399 | return grub_errno; | |
400 | } | |
401 | ||
402 | void * | |
403 | grub_env_get_data_slot (const char *name) | |
404 | { | |
405 | char *mangled_name; | |
406 | void *ptr = 0; | |
407 | ||
408 | mangled_name = mangle_data_slot_name (name); | |
409 | if (! mangled_name) | |
410 | goto fail; | |
411 | ||
412 | ptr = grub_env_get (mangled_name); | |
413 | grub_free (mangled_name); | |
414 | ||
415 | fail: | |
416 | ||
417 | return ptr; | |
418 | } | |
419 | ||
420 | void | |
421 | grub_env_unset_data_slot (const char *name) | |
422 | { | |
423 | char *mangled_name; | |
424 | ||
425 | mangled_name = mangle_data_slot_name (name); | |
426 | if (! mangled_name) | |
427 | return; | |
428 | ||
429 | grub_env_unset (mangled_name); | |
430 | grub_free (mangled_name); | |
db1771cf | 431 | } |