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 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 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 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 */ 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 */ 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 */ 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 */ 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 */ 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