]>
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> |
7f362539 | 37 | |
e745cf0c VS |
38 | GRUB_MOD_LICENSE ("GPLv3+"); |
39 | ||
7f362539 | 40 | static grub_dl_t my_mod; |
41 | ||
42 | static grub_efi_physical_address_t address; | |
43 | static grub_efi_uintn_t pages; | |
44 | static grub_efi_device_path_t *file_path; | |
45 | static grub_efi_handle_t image_handle; | |
20011694 | 46 | static grub_efi_char16_t *cmdline; |
7f362539 | 47 | |
48 | static grub_err_t | |
49 | grub_chainloader_unload (void) | |
50 | { | |
51 | grub_efi_boot_services_t *b; | |
b39f9d20 | 52 | |
7f362539 | 53 | b = grub_efi_system_table->boot_services; |
20011694 | 54 | efi_call_1 (b->unload_image, image_handle); |
55 | efi_call_2 (b->free_pages, address, pages); | |
56 | ||
7f362539 | 57 | grub_free (file_path); |
20011694 | 58 | grub_free (cmdline); |
59 | cmdline = 0; | |
52856af2 | 60 | file_path = 0; |
b39f9d20 | 61 | |
7f362539 | 62 | grub_dl_unref (my_mod); |
63 | return GRUB_ERR_NONE; | |
64 | } | |
65 | ||
66 | static grub_err_t | |
67 | grub_chainloader_boot (void) | |
68 | { | |
69 | grub_efi_boot_services_t *b; | |
70 | grub_efi_status_t status; | |
71 | grub_efi_uintn_t exit_data_size; | |
52856af2 | 72 | grub_efi_char16_t *exit_data = NULL; |
b39f9d20 | 73 | |
7f362539 | 74 | b = grub_efi_system_table->boot_services; |
20011694 | 75 | status = efi_call_3 (b->start_image, image_handle, &exit_data_size, &exit_data); |
7f362539 | 76 | if (status != GRUB_EFI_SUCCESS) |
77 | { | |
78 | if (exit_data) | |
79 | { | |
80 | char *buf; | |
b39f9d20 | 81 | |
7f362539 | 82 | buf = grub_malloc (exit_data_size * 4 + 1); |
83 | if (buf) | |
84 | { | |
85 | *grub_utf16_to_utf8 ((grub_uint8_t *) buf, | |
86 | exit_data, exit_data_size) = 0; | |
b39f9d20 | 87 | |
7f362539 | 88 | grub_error (GRUB_ERR_BAD_OS, buf); |
89 | grub_free (buf); | |
90 | } | |
91 | else | |
92 | grub_error (GRUB_ERR_BAD_OS, "unknown error"); | |
93 | } | |
94 | } | |
95 | ||
96 | if (exit_data) | |
20011694 | 97 | efi_call_1 (b->free_pool, exit_data); |
7f362539 | 98 | |
52856af2 | 99 | grub_loader_unset (); |
b39f9d20 | 100 | |
7f362539 | 101 | return grub_errno; |
102 | } | |
103 | ||
104 | static void | |
105 | copy_file_path (grub_efi_file_path_device_path_t *fp, | |
106 | const char *str, grub_efi_uint16_t len) | |
107 | { | |
108 | grub_efi_char16_t *p; | |
109 | grub_efi_uint16_t size; | |
b39f9d20 | 110 | |
7f362539 | 111 | fp->header.type = GRUB_EFI_MEDIA_DEVICE_PATH_TYPE; |
112 | fp->header.subtype = GRUB_EFI_FILE_PATH_DEVICE_PATH_SUBTYPE; | |
113 | size = len * sizeof (grub_efi_char16_t) + sizeof (*fp); | |
114 | fp->header.length[0] = (grub_efi_uint8_t) (size & 0xff); | |
115 | fp->header.length[1] = (grub_efi_uint8_t) (size >> 8); | |
116 | for (p = fp->path_name; len > 0; len--, p++, str++) | |
117 | { | |
118 | /* FIXME: this assumes that the path is in ASCII. */ | |
119 | *p = (grub_efi_char16_t) (*str == '/' ? '\\' : *str); | |
120 | } | |
121 | } | |
122 | ||
123 | static grub_efi_device_path_t * | |
124 | make_file_path (grub_efi_device_path_t *dp, const char *filename) | |
125 | { | |
126 | char *dir_start; | |
127 | char *dir_end; | |
128 | grub_size_t size; | |
129 | grub_efi_device_path_t *d; | |
130 | ||
131 | dir_start = grub_strchr (filename, ')'); | |
132 | if (! dir_start) | |
133 | dir_start = (char *) filename; | |
134 | else | |
135 | dir_start++; | |
136 | ||
137 | dir_end = grub_strrchr (dir_start, '/'); | |
138 | if (! dir_end) | |
139 | { | |
140 | grub_error (GRUB_ERR_BAD_FILENAME, "invalid EFI file path"); | |
141 | return 0; | |
142 | } | |
b39f9d20 | 143 | |
7f362539 | 144 | size = 0; |
145 | d = dp; | |
146 | while (1) | |
147 | { | |
148 | size += GRUB_EFI_DEVICE_PATH_LENGTH (d); | |
149 | if ((GRUB_EFI_END_ENTIRE_DEVICE_PATH (d))) | |
150 | break; | |
151 | d = GRUB_EFI_NEXT_DEVICE_PATH (d); | |
152 | } | |
b39f9d20 | 153 | |
7f362539 | 154 | file_path = grub_malloc (size |
155 | + ((grub_strlen (dir_start) + 1) | |
156 | * sizeof (grub_efi_char16_t)) | |
157 | + sizeof (grub_efi_file_path_device_path_t) * 2); | |
158 | if (! file_path) | |
159 | return 0; | |
160 | ||
161 | grub_memcpy (file_path, dp, size); | |
b39f9d20 | 162 | |
7f362539 | 163 | /* Fill the file path for the directory. */ |
164 | d = (grub_efi_device_path_t *) ((char *) file_path | |
165 | + ((char *) d - (char *) dp)); | |
166 | grub_efi_print_device_path (d); | |
167 | copy_file_path ((grub_efi_file_path_device_path_t *) d, | |
168 | dir_start, dir_end - dir_start); | |
169 | ||
170 | /* Fill the file path for the file. */ | |
171 | d = GRUB_EFI_NEXT_DEVICE_PATH (d); | |
172 | copy_file_path ((grub_efi_file_path_device_path_t *) d, | |
173 | dir_end + 1, grub_strlen (dir_end + 1)); | |
174 | ||
175 | /* Fill the end of device path nodes. */ | |
176 | d = GRUB_EFI_NEXT_DEVICE_PATH (d); | |
177 | d->type = GRUB_EFI_END_DEVICE_PATH_TYPE; | |
178 | d->subtype = GRUB_EFI_END_ENTIRE_DEVICE_PATH_SUBTYPE; | |
179 | d->length[0] = sizeof (*d); | |
180 | d->length[1] = 0; | |
181 | ||
182 | return file_path; | |
183 | } | |
184 | ||
b1b797cb | 185 | static grub_err_t |
186 | grub_cmd_chainloader (grub_command_t cmd __attribute__ ((unused)), | |
187 | int argc, char *argv[]) | |
7f362539 | 188 | { |
189 | grub_file_t file = 0; | |
190 | grub_ssize_t size; | |
191 | grub_efi_status_t status; | |
192 | grub_efi_boot_services_t *b; | |
193 | grub_efi_handle_t dev_handle = 0; | |
194 | grub_device_t dev = 0; | |
195 | grub_efi_device_path_t *dp = 0; | |
196 | grub_efi_loaded_image_t *loaded_image; | |
20011694 | 197 | char *filename; |
198 | ||
199 | if (argc == 0) | |
b1b797cb | 200 | return grub_error (GRUB_ERR_BAD_ARGUMENT, "no file specified"); |
20011694 | 201 | filename = argv[0]; |
b39f9d20 | 202 | |
7f362539 | 203 | grub_dl_ref (my_mod); |
204 | ||
205 | /* Initialize some global variables. */ | |
206 | address = 0; | |
207 | image_handle = 0; | |
208 | file_path = 0; | |
b39f9d20 | 209 | |
7f362539 | 210 | b = grub_efi_system_table->boot_services; |
211 | ||
212 | file = grub_file_open (filename); | |
213 | if (! file) | |
214 | goto fail; | |
215 | ||
216 | /* Get the root device's device path. */ | |
217 | dev = grub_device_open (0); | |
218 | if (! dev) | |
219 | goto fail; | |
220 | ||
221 | if (dev->disk) | |
222 | { | |
223 | dev_handle = grub_efidisk_get_device_handle (dev->disk); | |
224 | if (dev_handle) | |
225 | dp = grub_efi_get_device_path (dev_handle); | |
226 | } | |
b39f9d20 | 227 | |
7f362539 | 228 | if (! dev->disk || ! dev_handle || ! dp) |
229 | { | |
230 | grub_error (GRUB_ERR_BAD_DEVICE, "not a valid root device"); | |
231 | goto fail; | |
232 | } | |
233 | ||
234 | file_path = make_file_path (dp, filename); | |
235 | if (! file_path) | |
236 | goto fail; | |
237 | ||
238 | grub_printf ("file path: "); | |
239 | grub_efi_print_device_path (file_path); | |
b39f9d20 | 240 | |
7f362539 | 241 | size = grub_file_size (file); |
52856af2 VS |
242 | if (!size) |
243 | { | |
244 | grub_error (GRUB_ERR_BAD_OS, "file is empty"); | |
245 | goto fail; | |
246 | } | |
7f362539 | 247 | pages = (((grub_efi_uintn_t) size + ((1 << 12) - 1)) >> 12); |
b39f9d20 | 248 | |
20011694 | 249 | status = efi_call_4 (b->allocate_pages, GRUB_EFI_ALLOCATE_ANY_PAGES, |
7f362539 | 250 | GRUB_EFI_LOADER_CODE, |
251 | pages, &address); | |
252 | if (status != GRUB_EFI_SUCCESS) | |
253 | { | |
254 | grub_error (GRUB_ERR_OUT_OF_MEMORY, "cannot allocate %u pages", pages); | |
255 | goto fail; | |
256 | } | |
257 | ||
258 | if (grub_file_read (file, (void *) ((grub_addr_t) address), size) != size) | |
259 | { | |
260 | if (grub_errno == GRUB_ERR_NONE) | |
261 | grub_error (GRUB_ERR_BAD_OS, "too small"); | |
262 | ||
263 | goto fail; | |
264 | } | |
265 | ||
20011694 | 266 | status = efi_call_6 (b->load_image, 0, grub_efi_image_handle, file_path, |
7f362539 | 267 | (void *) ((grub_addr_t) address), size, |
268 | &image_handle); | |
269 | if (status != GRUB_EFI_SUCCESS) | |
270 | { | |
271 | if (status == GRUB_EFI_OUT_OF_RESOURCES) | |
272 | grub_error (GRUB_ERR_OUT_OF_MEMORY, "out of resources"); | |
273 | else | |
274 | grub_error (GRUB_ERR_BAD_OS, "cannot load image"); | |
b39f9d20 | 275 | |
7f362539 | 276 | goto fail; |
277 | } | |
278 | ||
279 | /* LoadImage does not set a device handler when the image is | |
280 | loaded from memory, so it is necessary to set it explicitly here. | |
281 | This is a mess. */ | |
282 | loaded_image = grub_efi_get_loaded_image (image_handle); | |
283 | if (! loaded_image) | |
284 | { | |
285 | grub_error (GRUB_ERR_BAD_OS, "no loaded image available"); | |
286 | goto fail; | |
287 | } | |
288 | loaded_image->device_handle = dev_handle; | |
b39f9d20 | 289 | |
7f362539 | 290 | grub_file_close (file); |
20011694 | 291 | |
292 | if (argc > 1) | |
293 | { | |
294 | int i, len; | |
295 | grub_efi_char16_t *p16; | |
296 | ||
297 | for (i = 1, len = 0; i < argc; i++) | |
298 | len += grub_strlen (argv[i]) + 1; | |
299 | ||
300 | len *= sizeof (grub_efi_char16_t); | |
301 | cmdline = p16 = grub_malloc (len); | |
302 | if (! cmdline) | |
303 | goto fail; | |
304 | ||
305 | for (i = 1; i < argc; i++) | |
306 | { | |
307 | char *p8; | |
308 | ||
309 | p8 = argv[i]; | |
310 | while (*p8) | |
311 | *(p16++) = *(p8++); | |
312 | ||
313 | *(p16++) = ' '; | |
314 | } | |
315 | *(--p16) = 0; | |
316 | ||
317 | loaded_image->load_options = cmdline; | |
318 | loaded_image->load_options_size = len; | |
319 | } | |
320 | ||
7f362539 | 321 | grub_loader_set (grub_chainloader_boot, grub_chainloader_unload, 0); |
b1b797cb | 322 | return 0; |
b39f9d20 | 323 | |
7f362539 | 324 | fail: |
325 | ||
326 | if (dev) | |
327 | grub_device_close (dev); | |
b39f9d20 | 328 | |
7f362539 | 329 | if (file) |
330 | grub_file_close (file); | |
331 | ||
332 | if (file_path) | |
333 | grub_free (file_path); | |
b39f9d20 | 334 | |
7f362539 | 335 | if (address) |
20011694 | 336 | efi_call_2 (b->free_pages, address, pages); |
b39f9d20 | 337 | |
7f362539 | 338 | grub_dl_unref (my_mod); |
b1b797cb | 339 | |
340 | return grub_errno; | |
7f362539 | 341 | } |
342 | ||
b1b797cb | 343 | static grub_command_t cmd; |
7f362539 | 344 | |
345 | GRUB_MOD_INIT(chainloader) | |
346 | { | |
b1b797cb | 347 | cmd = grub_register_command ("chainloader", grub_cmd_chainloader, |
809bbfeb | 348 | 0, N_("Load another boot loader.")); |
7f362539 | 349 | my_mod = mod; |
350 | } | |
351 | ||
352 | GRUB_MOD_FINI(chainloader) | |
353 | { | |
b1b797cb | 354 | grub_unregister_command (cmd); |
7f362539 | 355 | } |