Our asprintf and vasprintf have different prototypes than the glibc functions. This causes trouble when we want to share barebox code with userspace code. Change the prototypes for (v)asprintf to match the glibc prototypes. Since the current (v)asprintf are convenient to use change the existing functions to b(v)asprintf. Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
458 lines
9.2 KiB
C
458 lines
9.2 KiB
C
/*
|
|
* efivars.c - EFI variable filesystem
|
|
*
|
|
* Copyright (c) 2014 Sascha Hauer <s.hauer@pengutronix.de>, Pengutronix
|
|
*
|
|
* See file CREDITS for list of people who contributed to this
|
|
* project.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2
|
|
* as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
*/
|
|
|
|
#include <common.h>
|
|
#include <driver.h>
|
|
#include <init.h>
|
|
#include <malloc.h>
|
|
#include <fs.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <linux/stat.h>
|
|
#include <xfuncs.h>
|
|
#include <fcntl.h>
|
|
#include <efi.h>
|
|
#include <wchar.h>
|
|
#include <linux/err.h>
|
|
#include <linux/ctype.h>
|
|
#include <mach/efi.h>
|
|
#include <mach/efi-device.h>
|
|
|
|
struct efivarfs_inode {
|
|
s16 *name;
|
|
efi_guid_t vendor;
|
|
char *full_name; /* name including vendor namespacing */
|
|
struct list_head node;
|
|
};
|
|
|
|
struct efivarfs_dir {
|
|
struct list_head *current;
|
|
DIR dir;
|
|
};
|
|
|
|
struct efivarfs_priv {
|
|
struct list_head inodes;
|
|
};
|
|
|
|
static int char_to_nibble(char c)
|
|
{
|
|
int ret = tolower(c);
|
|
|
|
return ret <= '9' ? ret - '0' : ret - 'a' + 10;
|
|
}
|
|
|
|
static int read_byte_str(const char *str, u8 *out)
|
|
{
|
|
if (!isxdigit(*str) || !isxdigit(*(str + 1)))
|
|
return -EINVAL;
|
|
|
|
*out = (char_to_nibble(*str) << 4) | char_to_nibble(*(str + 1));
|
|
|
|
return 0;
|
|
}
|
|
|
|
int efi_guid_parse(const char *str, efi_guid_t *guid)
|
|
{
|
|
int i, ret;
|
|
u8 idx[] = { 3, 2, 1, 0, 5, 4, 7, 6, 8, 9, 10, 11, 12, 13, 14, 15 };
|
|
|
|
for (i = 0; i < 16; i++) {
|
|
ret = read_byte_str(str, &guid->b[idx[i]]);
|
|
if (ret)
|
|
return ret;
|
|
str += 2;
|
|
|
|
switch (i) {
|
|
case 3:
|
|
case 5:
|
|
case 7:
|
|
case 9:
|
|
if (*str != '-')
|
|
return -EINVAL;
|
|
str++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int efivarfs_parse_filename(const char *filename, efi_guid_t *vendor, s16 **name)
|
|
{
|
|
int len, ret;
|
|
const char *guidstr;
|
|
s16 *varname;
|
|
int i;
|
|
|
|
if (*filename == '/')
|
|
filename++;
|
|
|
|
len = strlen(filename);
|
|
|
|
if (len < sizeof("-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"))
|
|
return -EINVAL;
|
|
|
|
guidstr = filename + len - sizeof("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx");
|
|
if (*guidstr != '-')
|
|
return -EINVAL;
|
|
|
|
guidstr++;
|
|
|
|
ret = efi_guid_parse(guidstr, vendor);
|
|
|
|
varname = xzalloc((guidstr - filename) * sizeof(s16));
|
|
|
|
for (i = 0; i < guidstr - filename - 1; i++)
|
|
varname[i] = filename[i];
|
|
|
|
*name = varname;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int efivars_create(struct device_d *dev, const char *pathname, mode_t mode)
|
|
{
|
|
struct efivarfs_priv *priv = dev->priv;
|
|
struct efivarfs_inode *inode;
|
|
efi_guid_t vendor;
|
|
efi_status_t efiret;
|
|
u8 dummydata;
|
|
char *name8;
|
|
s16 *name;
|
|
int ret;
|
|
|
|
if (pathname[0] == '/')
|
|
pathname++;
|
|
|
|
/* deny creating files with other vendor GUID than our own */
|
|
ret = efivarfs_parse_filename(pathname, &vendor, &name);
|
|
if (ret)
|
|
return -ENOENT;
|
|
|
|
if (memcmp(&vendor, &EFI_BAREBOX_VENDOR_GUID, sizeof(efi_guid_t)))
|
|
return -EPERM;
|
|
|
|
inode = xzalloc(sizeof(*inode));
|
|
inode->name = name;
|
|
inode->vendor = vendor;
|
|
|
|
|
|
name8 = xstrdup_wchar_to_char(inode->name);
|
|
inode->full_name = basprintf("%s-%pUl", name8, &inode->vendor);
|
|
free(name8);
|
|
|
|
efiret = RT->set_variable(inode->name, &inode->vendor,
|
|
EFI_VARIABLE_NON_VOLATILE |
|
|
EFI_VARIABLE_BOOTSERVICE_ACCESS |
|
|
EFI_VARIABLE_RUNTIME_ACCESS,
|
|
1, &dummydata);
|
|
if (EFI_ERROR(efiret)) {
|
|
free(inode);
|
|
return -efi_errno(efiret);
|
|
}
|
|
|
|
list_add_tail(&inode->node, &priv->inodes);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int efivars_unlink(struct device_d *dev, const char *pathname)
|
|
{
|
|
struct efivarfs_priv *priv = dev->priv;
|
|
struct efivarfs_inode *inode, *tmp;
|
|
efi_status_t efiret;
|
|
|
|
if (pathname[0] == '/')
|
|
pathname++;
|
|
|
|
list_for_each_entry_safe(inode, tmp, &priv->inodes, node) {
|
|
if (!strcmp(inode->full_name, pathname)) {
|
|
efiret = RT->set_variable(inode->name, &inode->vendor,
|
|
0, 0, NULL);
|
|
if (EFI_ERROR(efiret))
|
|
return -efi_errno(efiret);
|
|
list_del(&inode->node);
|
|
free(inode);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct efivars_file {
|
|
void *buf;
|
|
unsigned long size;
|
|
efi_guid_t vendor;
|
|
s16 *name;
|
|
u32 attributes;
|
|
};
|
|
|
|
static int efivarfs_open(struct device_d *dev, FILE *f, const char *filename)
|
|
{
|
|
struct efivars_file *efile;
|
|
efi_status_t efiret;
|
|
int ret;
|
|
|
|
efile = xzalloc(sizeof(*efile));
|
|
|
|
ret = efivarfs_parse_filename(filename, &efile->vendor, &efile->name);
|
|
if (ret)
|
|
return -ENOENT;
|
|
|
|
efiret = RT->get_variable(efile->name, &efile->vendor,
|
|
NULL, &efile->size, NULL);
|
|
if (EFI_ERROR(efiret) && efiret != EFI_BUFFER_TOO_SMALL) {
|
|
ret = -efi_errno(efiret);
|
|
goto out;
|
|
}
|
|
|
|
efile->buf = malloc(efile->size);
|
|
if (!efile->buf) {
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
efiret = RT->get_variable(efile->name, &efile->vendor,
|
|
&efile->attributes, &efile->size,
|
|
efile->buf);
|
|
if (EFI_ERROR(efiret)) {
|
|
ret = -efi_errno(efiret);
|
|
goto out;
|
|
}
|
|
|
|
f->size = efile->size;
|
|
f->priv = efile;
|
|
|
|
return 0;
|
|
|
|
out:
|
|
free(efile->buf);
|
|
free(efile);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int efivarfs_close(struct device_d *dev, FILE *f)
|
|
{
|
|
struct efivars_file *efile = f->priv;
|
|
|
|
free(efile->buf);
|
|
free(efile);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int efivarfs_read(struct device_d *_dev, FILE *f, void *buf, size_t insize)
|
|
{
|
|
struct efivars_file *efile = f->priv;
|
|
|
|
memcpy(buf, efile->buf + f->pos, insize);
|
|
|
|
return insize;
|
|
}
|
|
|
|
static int efivarfs_write(struct device_d *_dev, FILE *f, const void *buf, size_t insize)
|
|
{
|
|
struct efivars_file *efile = f->priv;
|
|
efi_status_t efiret;
|
|
|
|
if (efile->size < f->pos + insize) {
|
|
efile->buf = realloc(efile->buf, f->pos + insize);
|
|
efile->size = f->pos + insize;
|
|
}
|
|
|
|
memcpy(efile->buf + f->pos, buf, insize);
|
|
|
|
efiret = RT->set_variable(efile->name, &efile->vendor,
|
|
efile->attributes,
|
|
efile->size ? efile->size : 1, efile->buf);
|
|
if (EFI_ERROR(efiret))
|
|
return -efi_errno(efiret);
|
|
|
|
return insize;
|
|
}
|
|
|
|
static int efivarfs_truncate(struct device_d *dev, FILE *f, ulong size)
|
|
{
|
|
struct efivars_file *efile = f->priv;
|
|
efi_status_t efiret;
|
|
|
|
efile->size = size;
|
|
efile->buf = realloc(efile->buf, efile->size + sizeof(uint32_t));
|
|
|
|
efiret = RT->set_variable(efile->name, &efile->vendor,
|
|
efile->attributes,
|
|
efile->size ? efile->size : 1, efile->buf);
|
|
if (EFI_ERROR(efiret))
|
|
return -efi_errno(efiret);
|
|
|
|
f->size = efile->size;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static loff_t efivarfs_lseek(struct device_d *dev, FILE *f, loff_t pos)
|
|
{
|
|
f->pos = pos;
|
|
|
|
return f->pos;
|
|
}
|
|
|
|
static DIR *efivarfs_opendir(struct device_d *dev, const char *pathname)
|
|
{
|
|
struct efivarfs_priv *priv = dev->priv;
|
|
struct efivarfs_dir *edir;
|
|
|
|
edir = xzalloc(sizeof(*edir));
|
|
edir->current = priv->inodes.next;
|
|
|
|
return &edir->dir;
|
|
}
|
|
|
|
static struct dirent *efivarfs_readdir(struct device_d *dev, DIR *dir)
|
|
{
|
|
struct efivarfs_priv *priv = dev->priv;
|
|
struct efivarfs_dir *edir = container_of(dir, struct efivarfs_dir, dir);
|
|
struct efivarfs_inode *inode;
|
|
|
|
if (edir->current == &priv->inodes)
|
|
return NULL;
|
|
|
|
inode = list_entry(edir->current, struct efivarfs_inode, node);
|
|
|
|
strcpy(dir->d.d_name, inode->full_name);
|
|
|
|
edir->current = edir->current->next;
|
|
|
|
return &dir->d;
|
|
}
|
|
|
|
static int efivarfs_closedir(struct device_d *dev, DIR *dir)
|
|
{
|
|
struct efivarfs_dir *edir = container_of(dir, struct efivarfs_dir, dir);
|
|
|
|
free(edir);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int efivarfs_stat(struct device_d *dev, const char *filename, struct stat *s)
|
|
{
|
|
efi_guid_t vendor;
|
|
s16 *name;
|
|
efi_status_t efiret;
|
|
unsigned long size = 0;
|
|
int ret;
|
|
|
|
ret = efivarfs_parse_filename(filename, &vendor, &name);
|
|
if (ret)
|
|
return -ENOENT;
|
|
|
|
efiret = RT->get_variable(name, &vendor, NULL, &size, NULL);
|
|
|
|
free(name);
|
|
|
|
if (EFI_ERROR(efiret) && efiret != EFI_BUFFER_TOO_SMALL)
|
|
return -efi_errno(efiret);
|
|
|
|
s->st_mode = 00666 | S_IFREG;
|
|
s->st_size = size;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int efivarfs_probe(struct device_d *dev)
|
|
{
|
|
efi_status_t efiret;
|
|
efi_guid_t vendor;
|
|
s16 name[1024];
|
|
char *name8;
|
|
unsigned long size;
|
|
struct efivarfs_priv *priv;
|
|
|
|
name[0] = 0;
|
|
|
|
priv = xzalloc(sizeof(*priv));
|
|
INIT_LIST_HEAD(&priv->inodes);
|
|
|
|
while (1) {
|
|
struct efivarfs_inode *inode;
|
|
|
|
size = sizeof(name);
|
|
efiret = RT->get_next_variable(&size, name, &vendor);
|
|
if (EFI_ERROR(efiret))
|
|
break;
|
|
|
|
inode = xzalloc(sizeof(*inode));
|
|
inode->name = xstrdup_wchar(name);
|
|
|
|
inode->vendor = vendor;
|
|
|
|
name8 = xstrdup_wchar_to_char(inode->name);
|
|
inode->full_name = basprintf("%s-%pUl", name8, &vendor);
|
|
free(name8);
|
|
|
|
list_add_tail(&inode->node, &priv->inodes);
|
|
}
|
|
|
|
dev->priv = priv;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void efivarfs_remove(struct device_d *dev)
|
|
{
|
|
struct efivarfs_priv *priv = dev->priv;
|
|
struct efivarfs_inode *inode, *tmp;
|
|
|
|
list_for_each_entry_safe(inode, tmp, &priv->inodes, node) {
|
|
free(inode->name);
|
|
free(inode);
|
|
}
|
|
|
|
free(priv);
|
|
}
|
|
|
|
static struct fs_driver_d efivarfs_driver = {
|
|
.create = efivars_create,
|
|
.unlink = efivars_unlink,
|
|
.open = efivarfs_open,
|
|
.close = efivarfs_close,
|
|
.read = efivarfs_read,
|
|
.write = efivarfs_write,
|
|
.truncate = efivarfs_truncate,
|
|
.lseek = efivarfs_lseek,
|
|
.opendir = efivarfs_opendir,
|
|
.readdir = efivarfs_readdir,
|
|
.closedir = efivarfs_closedir,
|
|
.stat = efivarfs_stat,
|
|
.drv = {
|
|
.probe = efivarfs_probe,
|
|
.remove = efivarfs_remove,
|
|
.name = "efivarfs",
|
|
}
|
|
};
|
|
|
|
static int efivarfs_init(void)
|
|
{
|
|
return register_fs_driver(&efivarfs_driver);
|
|
}
|
|
|
|
coredevice_initcall(efivarfs_init);
|