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