2013-02-08 20:27:24 +04:00
/*
* Copyright ( C ) 2012 Red Hat , Inc .
* Copyright ( C ) 2012 Jeremy Kerr < jeremy . kerr @ canonical . com >
*
* 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 .
*/
# include <linux/ctype.h>
# include <linux/efi.h>
# include <linux/fs.h>
# include <linux/module.h>
# include <linux/pagemap.h>
2013-04-30 14:30:24 +04:00
# include <linux/ucs2_string.h>
2013-05-02 04:51:54 +04:00
# include <linux/slab.h>
# include <linux/magic.h>
2013-02-08 20:27:24 +04:00
# include "internal.h"
LIST_HEAD ( efivarfs_list ) ;
static void efivarfs_evict_inode ( struct inode * inode )
{
clear_inode ( inode ) ;
}
static const struct super_operations efivarfs_ops = {
. statfs = simple_statfs ,
. drop_inode = generic_delete_inode ,
. evict_inode = efivarfs_evict_inode ,
. show_options = generic_show_options ,
} ;
static struct super_block * efivarfs_sb ;
/*
* Compare two efivarfs file names .
*
* An efivarfs filename is composed of two parts ,
*
* 1. A case - sensitive variable name
* 2. A case - insensitive GUID
*
* So we need to perform a case - sensitive match on part 1 and a
* case - insensitive match on part 2.
*/
2013-05-22 02:22:44 +04:00
static int efivarfs_d_compare ( const struct dentry * parent ,
const struct dentry * dentry ,
2013-02-08 20:27:24 +04:00
unsigned int len , const char * str ,
const struct qstr * name )
{
int guid = len - EFI_VARIABLE_GUID_LEN ;
if ( name - > len ! = len )
return 1 ;
/* Case-sensitive compare for the variable name */
if ( memcmp ( str , name - > name , guid ) )
return 1 ;
/* Case-insensitive compare for the GUID */
return strncasecmp ( name - > name + guid , str + guid , EFI_VARIABLE_GUID_LEN ) ;
}
2013-05-22 02:22:44 +04:00
static int efivarfs_d_hash ( const struct dentry * dentry , struct qstr * qstr )
2013-02-08 20:27:24 +04:00
{
unsigned long hash = init_name_hash ( ) ;
const unsigned char * s = qstr - > name ;
unsigned int len = qstr - > len ;
if ( ! efivarfs_valid_name ( s , len ) )
return - EINVAL ;
while ( len - - > EFI_VARIABLE_GUID_LEN )
hash = partial_name_hash ( * s + + , hash ) ;
/* GUID is case-insensitive. */
while ( len - - )
hash = partial_name_hash ( tolower ( * s + + ) , hash ) ;
qstr - > hash = end_name_hash ( hash ) ;
return 0 ;
}
static struct dentry_operations efivarfs_d_ops = {
. d_compare = efivarfs_d_compare ,
. d_hash = efivarfs_d_hash ,
2013-10-26 02:47:37 +04:00
. d_delete = always_delete_dentry ,
2013-02-08 20:27:24 +04:00
} ;
static struct dentry * efivarfs_alloc_dentry ( struct dentry * parent , char * name )
{
struct dentry * d ;
struct qstr q ;
int err ;
q . name = name ;
q . len = strlen ( name ) ;
2013-05-22 02:22:44 +04:00
err = efivarfs_d_hash ( NULL , & q ) ;
2013-02-08 20:27:24 +04:00
if ( err )
return ERR_PTR ( err ) ;
d = d_alloc ( parent , & q ) ;
if ( d )
return d ;
return ERR_PTR ( - ENOMEM ) ;
}
static int efivarfs_callback ( efi_char16_t * name16 , efi_guid_t vendor ,
unsigned long name_size , void * data )
{
struct super_block * sb = ( struct super_block * ) data ;
struct efivar_entry * entry ;
struct inode * inode = NULL ;
struct dentry * dentry , * root = sb - > s_root ;
unsigned long size = 0 ;
char * name ;
int len , i ;
int err = - ENOMEM ;
entry = kmalloc ( sizeof ( * entry ) , GFP_KERNEL ) ;
if ( ! entry )
return err ;
memcpy ( entry - > var . VariableName , name16 , name_size ) ;
memcpy ( & ( entry - > var . VendorGuid ) , & vendor , sizeof ( efi_guid_t ) ) ;
2013-04-30 14:30:24 +04:00
len = ucs2_strlen ( entry - > var . VariableName ) ;
2013-02-08 20:27:24 +04:00
/* name, plus '-', plus GUID, plus NUL*/
name = kmalloc ( len + 1 + EFI_VARIABLE_GUID_LEN + 1 , GFP_KERNEL ) ;
if ( ! name )
goto fail ;
for ( i = 0 ; i < len ; i + + )
name [ i ] = entry - > var . VariableName [ i ] & 0xFF ;
name [ len ] = ' - ' ;
efi_guid_unparse ( & entry - > var . VendorGuid , name + len + 1 ) ;
name [ len + EFI_VARIABLE_GUID_LEN + 1 ] = ' \0 ' ;
inode = efivarfs_get_inode ( sb , root - > d_inode , S_IFREG | 0644 , 0 ) ;
if ( ! inode )
goto fail_name ;
dentry = efivarfs_alloc_dentry ( root , name ) ;
if ( IS_ERR ( dentry ) ) {
err = PTR_ERR ( dentry ) ;
goto fail_inode ;
}
/* copied by the above to local storage in the dentry. */
kfree ( name ) ;
efivar_entry_size ( entry , & size ) ;
efivar_entry_add ( entry , & efivarfs_list ) ;
mutex_lock ( & inode - > i_mutex ) ;
inode - > i_private = entry ;
i_size_write ( inode , size + sizeof ( entry - > var . Attributes ) ) ;
mutex_unlock ( & inode - > i_mutex ) ;
d_add ( dentry , inode ) ;
return 0 ;
fail_inode :
iput ( inode ) ;
fail_name :
kfree ( name ) ;
fail :
kfree ( entry ) ;
return err ;
}
static int efivarfs_destroy ( struct efivar_entry * entry , void * data )
{
efivar_entry_remove ( entry ) ;
kfree ( entry ) ;
return 0 ;
}
static int efivarfs_fill_super ( struct super_block * sb , void * data , int silent )
{
struct inode * inode = NULL ;
struct dentry * root ;
int err ;
efivarfs_sb = sb ;
sb - > s_maxbytes = MAX_LFS_FILESIZE ;
sb - > s_blocksize = PAGE_CACHE_SIZE ;
sb - > s_blocksize_bits = PAGE_CACHE_SHIFT ;
sb - > s_magic = EFIVARFS_MAGIC ;
sb - > s_op = & efivarfs_ops ;
sb - > s_d_op = & efivarfs_d_ops ;
sb - > s_time_gran = 1 ;
inode = efivarfs_get_inode ( sb , NULL , S_IFDIR | 0755 , 0 ) ;
if ( ! inode )
return - ENOMEM ;
inode - > i_op = & efivarfs_dir_inode_operations ;
root = d_make_root ( inode ) ;
sb - > s_root = root ;
if ( ! root )
return - ENOMEM ;
INIT_LIST_HEAD ( & efivarfs_list ) ;
err = efivar_init ( efivarfs_callback , ( void * ) sb , false ,
true , & efivarfs_list ) ;
if ( err )
__efivar_entry_iter ( efivarfs_destroy , & efivarfs_list , NULL , NULL ) ;
return err ;
}
static struct dentry * efivarfs_mount ( struct file_system_type * fs_type ,
int flags , const char * dev_name , void * data )
{
return mount_single ( fs_type , flags , data , efivarfs_fill_super ) ;
}
static void efivarfs_kill_sb ( struct super_block * sb )
{
kill_litter_super ( sb ) ;
efivarfs_sb = NULL ;
/* Remove all entries and destroy */
__efivar_entry_iter ( efivarfs_destroy , & efivarfs_list , NULL , NULL ) ;
}
static struct file_system_type efivarfs_type = {
. name = " efivarfs " ,
. mount = efivarfs_mount ,
. kill_sb = efivarfs_kill_sb ,
} ;
static __init int efivarfs_init ( void )
{
if ( ! efi_enabled ( EFI_RUNTIME_SERVICES ) )
return 0 ;
if ( ! efivars_kobject ( ) )
return 0 ;
return register_filesystem ( & efivarfs_type ) ;
}
MODULE_AUTHOR ( " Matthew Garrett, Jeremy Kerr " ) ;
MODULE_DESCRIPTION ( " EFI Variable Filesystem " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS_FS ( " efivarfs " ) ;
module_init ( efivarfs_init ) ;