]>
Commit | Line | Data |
---|---|---|
7f362539 | 1 | /* chainloader.c - boot another boot loader */ |
2 | /* | |
3 | * GRUB -- GRand Unified Bootloader | |
f36cc108 | 4 | * Copyright (C) 2002,2004,2006,2007,2008 Free Software Foundation, Inc. |
7f362539 | 5 | * |
5a79f472 | 6 | * GRUB is free software: you can redistribute it and/or modify |
7f362539 | 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 |
7f362539 | 9 | * (at your option) any later version. |
10 | * | |
5a79f472 | 11 | * GRUB is distributed in the hope that it will be useful, |
7f362539 | 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/>. |
7f362539 | 18 | */ |
19 | ||
20 | /* TODO: support load options. */ | |
21 | ||
22 | #include <grub/loader.h> | |
23 | #include <grub/file.h> | |
24 | #include <grub/err.h> | |
25 | #include <grub/device.h> | |
26 | #include <grub/disk.h> | |
27 | #include <grub/misc.h> | |
1d3c6f1d | 28 | #include <grub/charset.h> |
7f362539 | 29 | #include <grub/mm.h> |
30 | #include <grub/types.h> | |
7f362539 | 31 | #include <grub/dl.h> |
32 | #include <grub/efi/api.h> | |
33 | #include <grub/efi/efi.h> | |
34 | #include <grub/efi/disk.h> | |
b1b797cb | 35 | #include <grub/command.h> |
809bbfeb | 36 | #include <grub/i18n.h> |
1ecd61a4 | 37 | #include <grub/net.h> |
3e4f3566 VS |
38 | #if defined (__i386__) || defined (__x86_64__) |
39 | #include <grub/macho.h> | |
40 | #include <grub/i386/macho.h> | |
41 | #endif | |
7f362539 | 42 | |
e745cf0c VS |
43 | GRUB_MOD_LICENSE ("GPLv3+"); |
44 | ||
7f362539 | 45 | static grub_dl_t my_mod; |
46 | ||
47 | static grub_efi_physical_address_t address; | |
48 | static grub_efi_uintn_t pages; | |
49 | static grub_efi_device_path_t *file_path; | |
50 | static grub_efi_handle_t image_handle; | |
20011694 | 51 | static grub_efi_char16_t *cmdline; |
7f362539 | 52 | |
53 | static grub_err_t | |
54 | grub_chainloader_unload (void) | |
55 | { | |
56 | grub_efi_boot_services_t *b; | |
b39f9d20 | 57 | |
7f362539 | 58 | b = grub_efi_system_table->boot_services; |
20011694 | 59 | efi_call_1 (b->unload_image, image_handle); |
60 | efi_call_2 (b->free_pages, address, pages); | |
61 | ||
7f362539 | 62 | grub_free (file_path); |
20011694 | 63 | grub_free (cmdline); |
64 | cmdline = 0; | |
52856af2 | 65 | file_path = 0; |
b39f9d20 | 66 | |
7f362539 | 67 | grub_dl_unref (my_mod); |
68 | return GRUB_ERR_NONE; | |
69 | } | |
70 | ||
71 | static grub_err_t | |
72 | grub_chainloader_boot (void) | |
73 | { | |
74 | grub_efi_boot_services_t *b; | |
75 | grub_efi_status_t status; | |
76 | grub_efi_uintn_t exit_data_size; | |
52856af2 | 77 | grub_efi_char16_t *exit_data = NULL; |
b39f9d20 | 78 | |
7f362539 | 79 | b = grub_efi_system_table->boot_services; |
20011694 | 80 | status = efi_call_3 (b->start_image, image_handle, &exit_data_size, &exit_data); |
7f362539 | 81 | if (status != GRUB_EFI_SUCCESS) |
82 | { | |
83 | if (exit_data) | |
84 | { | |
85 | char *buf; | |
b39f9d20 | 86 | |
7f362539 | 87 | buf = grub_malloc (exit_data_size * 4 + 1); |
88 | if (buf) | |
89 | { | |
90 | *grub_utf16_to_utf8 ((grub_uint8_t *) buf, | |
91 | exit_data, exit_data_size) = 0; | |
b39f9d20 | 92 | |
7f362539 | 93 | grub_error (GRUB_ERR_BAD_OS, buf); |
94 | grub_free (buf); | |
95 | } | |
7f362539 | 96 | } |
9c4b5c13 VS |
97 | else |
98 | grub_error (GRUB_ERR_BAD_OS, "unknown error"); | |
7f362539 | 99 | } |
100 | ||
101 | if (exit_data) | |
20011694 | 102 | efi_call_1 (b->free_pool, exit_data); |
7f362539 | 103 | |
52856af2 | 104 | grub_loader_unset (); |
b39f9d20 | 105 | |
7f362539 | 106 | return grub_errno; |
107 | } | |
108 | ||
109 | static void | |
110 | copy_file_path (grub_efi_file_path_device_path_t *fp, | |
111 | const char *str, grub_efi_uint16_t len) | |
112 | { | |
113 | grub_efi_char16_t *p; | |
114 | grub_efi_uint16_t size; | |
b39f9d20 | 115 | |
7f362539 | 116 | fp->header.type = GRUB_EFI_MEDIA_DEVICE_PATH_TYPE; |
117 | fp->header.subtype = GRUB_EFI_FILE_PATH_DEVICE_PATH_SUBTYPE; | |
c21b17e6 VS |
118 | |
119 | size = grub_utf8_to_utf16 (fp->path_name, len * GRUB_MAX_UTF16_PER_UTF8, | |
120 | (const grub_uint8_t *) str, len, 0); | |
121 | for (p = fp->path_name; p < fp->path_name + size; p++) | |
122 | if (*p == '/') | |
123 | *p = '\\'; | |
124 | ||
ce95549c AB |
125 | /* File Path is NULL terminated */ |
126 | fp->path_name[size++] = '\0'; | |
219401b8 | 127 | fp->header.length = size * sizeof (grub_efi_char16_t) + sizeof (*fp); |
7f362539 | 128 | } |
129 | ||
130 | static grub_efi_device_path_t * | |
131 | make_file_path (grub_efi_device_path_t *dp, const char *filename) | |
132 | { | |
133 | char *dir_start; | |
134 | char *dir_end; | |
135 | grub_size_t size; | |
136 | grub_efi_device_path_t *d; | |
137 | ||
138 | dir_start = grub_strchr (filename, ')'); | |
139 | if (! dir_start) | |
140 | dir_start = (char *) filename; | |
141 | else | |
142 | dir_start++; | |
143 | ||
144 | dir_end = grub_strrchr (dir_start, '/'); | |
145 | if (! dir_end) | |
146 | { | |
147 | grub_error (GRUB_ERR_BAD_FILENAME, "invalid EFI file path"); | |
148 | return 0; | |
149 | } | |
b39f9d20 | 150 | |
7f362539 | 151 | size = 0; |
152 | d = dp; | |
153 | while (1) | |
154 | { | |
155 | size += GRUB_EFI_DEVICE_PATH_LENGTH (d); | |
156 | if ((GRUB_EFI_END_ENTIRE_DEVICE_PATH (d))) | |
157 | break; | |
158 | d = GRUB_EFI_NEXT_DEVICE_PATH (d); | |
159 | } | |
b39f9d20 | 160 | |
ce95549c AB |
161 | /* File Path is NULL terminated. Allocate space for 2 extra characters */ |
162 | /* FIXME why we split path in two components? */ | |
7f362539 | 163 | file_path = grub_malloc (size |
ce95549c | 164 | + ((grub_strlen (dir_start) + 2) |
c21b17e6 | 165 | * GRUB_MAX_UTF16_PER_UTF8 |
7f362539 | 166 | * sizeof (grub_efi_char16_t)) |
167 | + sizeof (grub_efi_file_path_device_path_t) * 2); | |
168 | if (! file_path) | |
169 | return 0; | |
170 | ||
171 | grub_memcpy (file_path, dp, size); | |
b39f9d20 | 172 | |
7f362539 | 173 | /* Fill the file path for the directory. */ |
174 | d = (grub_efi_device_path_t *) ((char *) file_path | |
175 | + ((char *) d - (char *) dp)); | |
176 | grub_efi_print_device_path (d); | |
177 | copy_file_path ((grub_efi_file_path_device_path_t *) d, | |
178 | dir_start, dir_end - dir_start); | |
179 | ||
180 | /* Fill the file path for the file. */ | |
181 | d = GRUB_EFI_NEXT_DEVICE_PATH (d); | |
182 | copy_file_path ((grub_efi_file_path_device_path_t *) d, | |
183 | dir_end + 1, grub_strlen (dir_end + 1)); | |
184 | ||
185 | /* Fill the end of device path nodes. */ | |
186 | d = GRUB_EFI_NEXT_DEVICE_PATH (d); | |
187 | d->type = GRUB_EFI_END_DEVICE_PATH_TYPE; | |
188 | d->subtype = GRUB_EFI_END_ENTIRE_DEVICE_PATH_SUBTYPE; | |
219401b8 | 189 | d->length = sizeof (*d); |
7f362539 | 190 | |
191 | return file_path; | |
192 | } | |
193 | ||
b1b797cb | 194 | static grub_err_t |
195 | grub_cmd_chainloader (grub_command_t cmd __attribute__ ((unused)), | |
196 | int argc, char *argv[]) | |
7f362539 | 197 | { |
198 | grub_file_t file = 0; | |
199 | grub_ssize_t size; | |
200 | grub_efi_status_t status; | |
201 | grub_efi_boot_services_t *b; | |
7f362539 | 202 | grub_device_t dev = 0; |
203 | grub_efi_device_path_t *dp = 0; | |
204 | grub_efi_loaded_image_t *loaded_image; | |
20011694 | 205 | char *filename; |
3e4f3566 | 206 | void *boot_image = 0; |
1ecd61a4 | 207 | grub_efi_handle_t dev_handle = 0; |
20011694 | 208 | |
209 | if (argc == 0) | |
9c4b5c13 | 210 | return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected")); |
20011694 | 211 | filename = argv[0]; |
b39f9d20 | 212 | |
7f362539 | 213 | grub_dl_ref (my_mod); |
214 | ||
215 | /* Initialize some global variables. */ | |
216 | address = 0; | |
217 | image_handle = 0; | |
218 | file_path = 0; | |
b39f9d20 | 219 | |
7f362539 | 220 | b = grub_efi_system_table->boot_services; |
221 | ||
ca0a4f68 | 222 | file = grub_file_open (filename, GRUB_FILE_TYPE_EFI_CHAINLOADED_IMAGE); |
7f362539 | 223 | if (! file) |
224 | goto fail; | |
225 | ||
226 | /* Get the root device's device path. */ | |
227 | dev = grub_device_open (0); | |
228 | if (! dev) | |
229 | goto fail; | |
230 | ||
231 | if (dev->disk) | |
1ecd61a4 VS |
232 | dev_handle = grub_efidisk_get_device_handle (dev->disk); |
233 | else if (dev->net && dev->net->server) | |
7f362539 | 234 | { |
1ecd61a4 VS |
235 | grub_net_network_level_address_t addr; |
236 | struct grub_net_network_level_interface *inf; | |
237 | grub_net_network_level_address_t gateway; | |
238 | grub_err_t err; | |
239 | ||
240 | err = grub_net_resolve_address (dev->net->server, &addr); | |
241 | if (err) | |
242 | goto fail; | |
243 | ||
244 | err = grub_net_route_address (addr, &gateway, &inf); | |
245 | if (err) | |
246 | goto fail; | |
247 | ||
248 | dev_handle = grub_efinet_get_device_handle (inf->card); | |
7f362539 | 249 | } |
b39f9d20 | 250 | |
1ecd61a4 VS |
251 | if (dev_handle) |
252 | dp = grub_efi_get_device_path (dev_handle); | |
253 | ||
254 | if (! dp) | |
7f362539 | 255 | { |
256 | grub_error (GRUB_ERR_BAD_DEVICE, "not a valid root device"); | |
257 | goto fail; | |
258 | } | |
259 | ||
260 | file_path = make_file_path (dp, filename); | |
261 | if (! file_path) | |
262 | goto fail; | |
263 | ||
264 | grub_printf ("file path: "); | |
265 | grub_efi_print_device_path (file_path); | |
b39f9d20 | 266 | |
7f362539 | 267 | size = grub_file_size (file); |
52856af2 VS |
268 | if (!size) |
269 | { | |
d61386e2 VS |
270 | grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), |
271 | filename); | |
52856af2 VS |
272 | goto fail; |
273 | } | |
7f362539 | 274 | pages = (((grub_efi_uintn_t) size + ((1 << 12) - 1)) >> 12); |
b39f9d20 | 275 | |
20011694 | 276 | status = efi_call_4 (b->allocate_pages, GRUB_EFI_ALLOCATE_ANY_PAGES, |
7f362539 | 277 | GRUB_EFI_LOADER_CODE, |
278 | pages, &address); | |
279 | if (status != GRUB_EFI_SUCCESS) | |
280 | { | |
922aabf3 VS |
281 | grub_dprintf ("chain", "Failed to allocate %u pages\n", |
282 | (unsigned int) pages); | |
c9eb96b5 | 283 | grub_error (GRUB_ERR_OUT_OF_MEMORY, N_("out of memory")); |
7f362539 | 284 | goto fail; |
285 | } | |
286 | ||
3e4f3566 VS |
287 | boot_image = (void *) ((grub_addr_t) address); |
288 | if (grub_file_read (file, boot_image, size) != size) | |
7f362539 | 289 | { |
290 | if (grub_errno == GRUB_ERR_NONE) | |
9c4b5c13 VS |
291 | grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), |
292 | filename); | |
7f362539 | 293 | |
294 | goto fail; | |
295 | } | |
296 | ||
3e4f3566 VS |
297 | #if defined (__i386__) || defined (__x86_64__) |
298 | if (size >= (grub_ssize_t) sizeof (struct grub_macho_fat_header)) | |
299 | { | |
300 | struct grub_macho_fat_header *head = boot_image; | |
301 | if (head->magic | |
302 | == grub_cpu_to_le32_compile_time (GRUB_MACHO_FAT_EFI_MAGIC)) | |
303 | { | |
304 | grub_uint32_t i; | |
305 | struct grub_macho_fat_arch *archs | |
306 | = (struct grub_macho_fat_arch *) (head + 1); | |
307 | for (i = 0; i < grub_cpu_to_le32 (head->nfat_arch); i++) | |
308 | { | |
309 | if (GRUB_MACHO_CPUTYPE_IS_HOST_CURRENT (archs[i].cputype)) | |
310 | break; | |
311 | } | |
312 | if (i == grub_cpu_to_le32 (head->nfat_arch)) | |
313 | { | |
314 | grub_error (GRUB_ERR_BAD_OS, "no compatible arch found"); | |
315 | goto fail; | |
316 | } | |
317 | if (grub_cpu_to_le32 (archs[i].offset) | |
318 | > ~grub_cpu_to_le32 (archs[i].size) | |
319 | || grub_cpu_to_le32 (archs[i].offset) | |
320 | + grub_cpu_to_le32 (archs[i].size) | |
321 | > (grub_size_t) size) | |
322 | { | |
323 | grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), | |
324 | filename); | |
325 | goto fail; | |
326 | } | |
327 | boot_image = (char *) boot_image + grub_cpu_to_le32 (archs[i].offset); | |
328 | size = grub_cpu_to_le32 (archs[i].size); | |
329 | } | |
330 | } | |
331 | #endif | |
332 | ||
20011694 | 333 | status = efi_call_6 (b->load_image, 0, grub_efi_image_handle, file_path, |
3e4f3566 VS |
334 | boot_image, size, |
335 | &image_handle); | |
7f362539 | 336 | if (status != GRUB_EFI_SUCCESS) |
337 | { | |
338 | if (status == GRUB_EFI_OUT_OF_RESOURCES) | |
339 | grub_error (GRUB_ERR_OUT_OF_MEMORY, "out of resources"); | |
340 | else | |
341 | grub_error (GRUB_ERR_BAD_OS, "cannot load image"); | |
b39f9d20 | 342 | |
7f362539 | 343 | goto fail; |
344 | } | |
345 | ||
346 | /* LoadImage does not set a device handler when the image is | |
347 | loaded from memory, so it is necessary to set it explicitly here. | |
348 | This is a mess. */ | |
349 | loaded_image = grub_efi_get_loaded_image (image_handle); | |
350 | if (! loaded_image) | |
351 | { | |
352 | grub_error (GRUB_ERR_BAD_OS, "no loaded image available"); | |
353 | goto fail; | |
354 | } | |
355 | loaded_image->device_handle = dev_handle; | |
b39f9d20 | 356 | |
20011694 | 357 | if (argc > 1) |
358 | { | |
359 | int i, len; | |
360 | grub_efi_char16_t *p16; | |
361 | ||
362 | for (i = 1, len = 0; i < argc; i++) | |
363 | len += grub_strlen (argv[i]) + 1; | |
364 | ||
365 | len *= sizeof (grub_efi_char16_t); | |
366 | cmdline = p16 = grub_malloc (len); | |
367 | if (! cmdline) | |
368 | goto fail; | |
369 | ||
370 | for (i = 1; i < argc; i++) | |
371 | { | |
372 | char *p8; | |
373 | ||
374 | p8 = argv[i]; | |
375 | while (*p8) | |
376 | *(p16++) = *(p8++); | |
377 | ||
378 | *(p16++) = ' '; | |
379 | } | |
380 | *(--p16) = 0; | |
381 | ||
382 | loaded_image->load_options = cmdline; | |
383 | loaded_image->load_options_size = len; | |
384 | } | |
385 | ||
c058e856 AB |
386 | grub_file_close (file); |
387 | grub_device_close (dev); | |
388 | ||
7f362539 | 389 | grub_loader_set (grub_chainloader_boot, grub_chainloader_unload, 0); |
b1b797cb | 390 | return 0; |
b39f9d20 | 391 | |
7f362539 | 392 | fail: |
393 | ||
394 | if (dev) | |
395 | grub_device_close (dev); | |
b39f9d20 | 396 | |
7f362539 | 397 | if (file) |
398 | grub_file_close (file); | |
399 | ||
cbf597af | 400 | grub_free (file_path); |
b39f9d20 | 401 | |
7f362539 | 402 | if (address) |
20011694 | 403 | efi_call_2 (b->free_pages, address, pages); |
b39f9d20 | 404 | |
7f362539 | 405 | grub_dl_unref (my_mod); |
b1b797cb | 406 | |
407 | return grub_errno; | |
7f362539 | 408 | } |
409 | ||
b1b797cb | 410 | static grub_command_t cmd; |
7f362539 | 411 | |
412 | GRUB_MOD_INIT(chainloader) | |
413 | { | |
b1b797cb | 414 | cmd = grub_register_command ("chainloader", grub_cmd_chainloader, |
809bbfeb | 415 | 0, N_("Load another boot loader.")); |
7f362539 | 416 | my_mod = mod; |
417 | } | |
418 | ||
419 | GRUB_MOD_FINI(chainloader) | |
420 | { | |
b1b797cb | 421 | grub_unregister_command (cmd); |
7f362539 | 422 | } |