1 /* ----------------------------------------------------------------------- *
2 *
3 * Copyright 2007-2008 H. Peter Anvin - All Rights Reserved
4 * Copyright 2012 Intel Corporation; author: H. Peter Anvin
5 * Chandramouli Narayanan
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, Inc., 53 Temple Place Ste 330,
10 * Boston MA 02111-1307, USA; either version 2 of the License, or
11 * (at your option) any later version; incorporated herein by reference.
12 *
13 * ----------------------------------------------------------------------- */
14
15 /*
16 * adv.c
17 *
18 * Core ADV I/O
19 * Code consolidated from libinstaller/adv*.c and core/adv.inc with the
20 * addition of EFI support
21 *
22 * Return 0 on success, -1 on error, and set errno.
23 *
24 */
25 #define _GNU_SOURCE
26
27 #include <syslinux/config.h>
28 #include <string.h>
29 #include "adv.h"
30
31 unsigned char syslinux_adv[2 * ADV_SIZE];
32
cleanup_adv(unsigned char * advbuf)33 static void cleanup_adv(unsigned char *advbuf)
34 {
35 int i;
36 uint32_t csum;
37
38 /* Make sure both copies agree, and update the checksum */
39 *(uint32_t *)advbuf = ADV_MAGIC1;
40
41 csum = ADV_MAGIC2;
42 for (i = 8; i < ADV_SIZE - 4; i += 4)
43 csum -= *(uint32_t *)(advbuf + i);
44
45 *(uint32_t *)(advbuf + 4) = csum;
46 *(uint32_t *)(advbuf + ADV_SIZE - 4) = ADV_MAGIC3;
47
48 memcpy(advbuf + ADV_SIZE, advbuf, ADV_SIZE);
49 }
50
syslinux_reset_adv(unsigned char * advbuf)51 void syslinux_reset_adv(unsigned char *advbuf)
52 {
53 /* Create an all-zero ADV */
54 memset(advbuf + 2 * 4, 0, ADV_LEN);
55 cleanup_adv(advbuf);
56 }
57
adv_consistent(const unsigned char * p)58 static int adv_consistent(const unsigned char *p)
59 {
60 int i;
61 uint32_t csum;
62
63 if (*(uint32_t *)p != ADV_MAGIC1 ||
64 *(uint32_t *)(p + ADV_SIZE - 4) != ADV_MAGIC3)
65 return 0;
66
67 csum = 0;
68 for (i = 4; i < ADV_SIZE - 4; i += 4)
69 csum += *(uint32_t *)(p + i);
70
71 return csum == ADV_MAGIC2;
72 }
73
74 /*
75 * Verify that an in-memory ADV is consistent, making the copies consistent.
76 * If neither copy is OK, return -1 and call syslinux_reset_adv().
77 */
syslinux_validate_adv(unsigned char * advbuf)78 int syslinux_validate_adv(unsigned char *advbuf)
79 {
80 if (adv_consistent(advbuf + 0 * ADV_SIZE)) {
81 memcpy(advbuf + ADV_SIZE, advbuf, ADV_SIZE);
82 return 0;
83 } else if (adv_consistent(advbuf + 1 * ADV_SIZE)) {
84 memcpy(advbuf, advbuf + ADV_SIZE, ADV_SIZE);
85 return 0;
86 } else {
87 syslinux_reset_adv(advbuf);
88 return -1;
89 }
90 }
91
92 /*
93 * Read the ADV from an existing instance, or initialize if invalid.
94 * Returns -1 on fatal errors, 0 if ADV is okay, 1 if the ADV is
95 * invalid, and 2 if the file does not exist.
96 */
97
98 /* make_filespec
99 * Take the ASCII pathname and filename and concatenate them
100 * into an allocated memory space as unicode file specification string.
101 * The path and cfg ASCII strings are assumed to be null-terminated.
102 * For EFI, the separation character in the path name is '\'
103 * and therefore it is assumed that the file spec uses '\' as separation char
104 *
105 * The function returns
106 * 0 if successful and fspec is a valid allocated CHAR16 pointer
107 * Caller is responsible to free up the allocated filespec string
108 * -1 otherwise
109 *
110 */
make_filespec(CHAR16 ** fspec,const char * path,const char * cfg)111 static int make_filespec(CHAR16 **fspec, const char *path, const char *cfg)
112 {
113 CHAR16 *p;
114 int size, append;
115
116 /* allocate size for a CHAR16 string */
117 size = sizeof(CHAR16) * (strlena((CHAR8 *)path)+strlena((CHAR8 *)cfg)+2); /* including null */
118 *fspec = malloc(size);
119 if (!*fspec) return -1;
120
121 append = path[strlena((CHAR8 *)path) - 1] != '\\';
122 for (p = *fspec; *path; path++, p++)
123 *p = (CHAR16)*path;
124 /* append the separation character to the path if need be */
125 if (append) *p++ = (CHAR16)'\\';
126 for (; *cfg; cfg++, p++)
127 *p = (CHAR16)*cfg;
128 *p = (CHAR16)CHAR_NULL;
129
130 return 0;
131 }
132
133
134 /* TODO:
135 * set_attributes() and clear_attributes() are supported for VFAT only
136 */
read_adv(const char * path,const char * cfg)137 int read_adv(const char *path, const char *cfg)
138 {
139 CHAR16 *file;
140 EFI_FILE_HANDLE fd;
141 EFI_FILE_INFO st;
142 int err = 0;
143 int rv;
144
145 rv = make_filespec(&file, path, cfg);
146 if (rv < 0 || !file) {
147 efi_perror(L"read_adv");
148 return -1;
149 }
150
151 /* TBD: Not sure if EFI accepts the attribute read only
152 * even if an existing file is opened for read access
153 */
154 fd = efi_open(file, EFI_FILE_MODE_READ);
155 if (!fd) {
156 if (efi_errno != EFI_NOT_FOUND) {
157 err = -1;
158 } else {
159 syslinux_reset_adv(syslinux_adv);
160 err = 2; /* Nonexistence is not a fatal error */
161 }
162 } else if (!efi_fstat(fd, &st)) {
163 err = -1;
164 } else if (st.FileSize < 2 * ADV_SIZE) {
165 /* Too small to be useful */
166 syslinux_reset_adv(syslinux_adv);
167 err = 0; /* Nothing to read... */
168 } else if (efi_xpread(fd, syslinux_adv, 2 * ADV_SIZE,
169 st.FileSize - 2 * ADV_SIZE) != 2 * ADV_SIZE) {
170 err = -1;
171 } else {
172 /* We got it... maybe? */
173 err = syslinux_validate_adv(syslinux_adv) ? 1 : 0;
174 }
175
176 if (err < 0)
177 efi_perror(file);
178 if (fd)
179 efi_close(fd);
180 free(file);
181
182 return err;
183 }
184
185 /* For EFI platform, initialize ADV by opening ldlinux.sys
186 * as configured and return the primary (adv0) and alternate (adv1)
187 * data into caller's buffer. File remains open for subsequent
188 * operations. This routine is to be called from comboot vector.
189 */
efi_adv_init(void)190 void efi_adv_init(void)
191 {
192 union syslinux_derivative_info sdi;
193
194 get_derivative_info(&sdi);
195
196 if (sdi.c.filesystem == SYSLINUX_FS_SYSLINUX)
197 read_adv("", SYSLINUX_FILE);
198 else {
199 __syslinux_adv_ptr = &syslinux_adv[8]; /* skip head, csum */
200 __syslinux_adv_size = ADV_LEN;
201
202 syslinux_validate_adv(syslinux_adv);
203 }
204 }
205
206 /* For EFI platform, write 2 * ADV_SIZE data to the file opened
207 * at ADV initialization. (i.e ldlinux.sys).
208 *
209 * TODO:
210 * 1. Validate assumption: write back to file from __syslinux_adv_ptr
211 * 2. What if there errors?
212 * 3. Do we need to set the attributes of the sys file?
213 *
214 */
efi_adv_write(void)215 int efi_adv_write(void)
216 {
217 char *name;
218 unsigned char advtmp[2 * ADV_SIZE];
219 unsigned char *advbuf = syslinux_adv;
220 int rv;
221 int err = 0;
222 EFI_FILE_HANDLE fd; /* handle to ldlinux.sys */
223 CHAR16 *file;
224 EFI_FILE_INFO st, xst;
225 union syslinux_derivative_info sdi;
226
227 get_derivative_info(&sdi);
228 if (sdi.c.filesystem != SYSLINUX_FS_SYSLINUX)
229 return -1;
230
231 name = SYSLINUX_FILE;
232 rv = make_filespec(&file, "", name);
233 if (rv < 0 || !file) {
234 efi_errno = EFI_OUT_OF_RESOURCES;
235 efi_perror(L"efi_adv_write:");
236 return -1;
237 }
238
239 fd = efi_open(file, EFI_FILE_MODE_READ);
240 if (fd == (EFI_FILE_HANDLE)NULL) {
241 err = -1;
242 efi_printerr(L"efi_adv_write: Unable to open file %s\n", file);
243 } else if (efi_fstat(fd, &st)) {
244 err = -1;
245 efi_printerr(L"efi_adv_write: Unable to get info for file %s\n", file);
246 } else if (st.FileSize < 2 * ADV_SIZE) {
247 /* Too small to be useful */
248 err = -2;
249 efi_printerr(L"efi_adv_write: File size too small to be useful for file %s\n", file);
250 } else if (efi_xpread(fd, advtmp, 2 * ADV_SIZE,
251 st.FileSize - 2 * ADV_SIZE) != 2 * ADV_SIZE) {
252 err = -1;
253 efi_printerr(L"efi_adv_write: Error reading ADV data from file %s\n", file);
254 } else {
255 cleanup_adv(advbuf);
256 err = syslinux_validate_adv(advbuf) ? -2 : 0;
257
258 if (!err) {
259 /* Got a good one, write our own ADV here */
260 efi_clear_attributes(fd);
261
262 /* Need to re-open read-write */
263 efi_close(fd);
264 /* There is no SYNC attribute with EFI open */
265 fd = efi_open(file, EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE);
266 if (fd == (EFI_FILE_HANDLE)NULL) {
267 err = -1;
268 } else if (efi_fstat(fd, &xst) || xst.FileSize != st.FileSize) {
269 efi_perror(L"efi_adv_write: file status error/mismatch");
270 err = -2;
271 }
272 /* Write our own version ... */
273 if (efi_xpwrite(fd, advbuf, 2 * ADV_SIZE,
274 st.FileSize - 2 * ADV_SIZE) != 2 * ADV_SIZE) {
275 err = -1;
276 efi_printerr(L"efi_adv_write: Error write ADV data to file %s\n", file);
277 }
278 if (!err) {
279 efi_sync(fd);
280 efi_set_attributes(fd);
281 }
282 }
283 }
284
285 if (err == -2)
286 efi_printerr(L"%s: cannot write auxilliary data (need --update)?\n",
287 file);
288 else if (err == -1)
289 efi_perror(L"efi_adv_write:");
290
291 if (fd)
292 efi_close(fd);
293 if (file)
294 free(file);
295
296 return err;
297 }
298