18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright (C) 2012 Red Hat, Inc. 48c2ecf20Sopenharmony_ci * Copyright (C) 2012 Jeremy Kerr <jeremy.kerr@canonical.com> 58c2ecf20Sopenharmony_ci */ 68c2ecf20Sopenharmony_ci 78c2ecf20Sopenharmony_ci#include <linux/ctype.h> 88c2ecf20Sopenharmony_ci#include <linux/efi.h> 98c2ecf20Sopenharmony_ci#include <linux/fs.h> 108c2ecf20Sopenharmony_ci#include <linux/fs_context.h> 118c2ecf20Sopenharmony_ci#include <linux/module.h> 128c2ecf20Sopenharmony_ci#include <linux/pagemap.h> 138c2ecf20Sopenharmony_ci#include <linux/ucs2_string.h> 148c2ecf20Sopenharmony_ci#include <linux/slab.h> 158c2ecf20Sopenharmony_ci#include <linux/magic.h> 168c2ecf20Sopenharmony_ci#include <linux/printk.h> 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_ci#include "internal.h" 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_ciLIST_HEAD(efivarfs_list); 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_cistatic void efivarfs_evict_inode(struct inode *inode) 238c2ecf20Sopenharmony_ci{ 248c2ecf20Sopenharmony_ci clear_inode(inode); 258c2ecf20Sopenharmony_ci} 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_cistatic const struct super_operations efivarfs_ops = { 288c2ecf20Sopenharmony_ci .statfs = simple_statfs, 298c2ecf20Sopenharmony_ci .drop_inode = generic_delete_inode, 308c2ecf20Sopenharmony_ci .evict_inode = efivarfs_evict_inode, 318c2ecf20Sopenharmony_ci}; 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_ci/* 348c2ecf20Sopenharmony_ci * Compare two efivarfs file names. 358c2ecf20Sopenharmony_ci * 368c2ecf20Sopenharmony_ci * An efivarfs filename is composed of two parts, 378c2ecf20Sopenharmony_ci * 388c2ecf20Sopenharmony_ci * 1. A case-sensitive variable name 398c2ecf20Sopenharmony_ci * 2. A case-insensitive GUID 408c2ecf20Sopenharmony_ci * 418c2ecf20Sopenharmony_ci * So we need to perform a case-sensitive match on part 1 and a 428c2ecf20Sopenharmony_ci * case-insensitive match on part 2. 438c2ecf20Sopenharmony_ci */ 448c2ecf20Sopenharmony_cistatic int efivarfs_d_compare(const struct dentry *dentry, 458c2ecf20Sopenharmony_ci unsigned int len, const char *str, 468c2ecf20Sopenharmony_ci const struct qstr *name) 478c2ecf20Sopenharmony_ci{ 488c2ecf20Sopenharmony_ci int guid = len - EFI_VARIABLE_GUID_LEN; 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_ci if (name->len != len) 518c2ecf20Sopenharmony_ci return 1; 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_ci /* Case-sensitive compare for the variable name */ 548c2ecf20Sopenharmony_ci if (memcmp(str, name->name, guid)) 558c2ecf20Sopenharmony_ci return 1; 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_ci /* Case-insensitive compare for the GUID */ 588c2ecf20Sopenharmony_ci return strncasecmp(name->name + guid, str + guid, EFI_VARIABLE_GUID_LEN); 598c2ecf20Sopenharmony_ci} 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_cistatic int efivarfs_d_hash(const struct dentry *dentry, struct qstr *qstr) 628c2ecf20Sopenharmony_ci{ 638c2ecf20Sopenharmony_ci unsigned long hash = init_name_hash(dentry); 648c2ecf20Sopenharmony_ci const unsigned char *s = qstr->name; 658c2ecf20Sopenharmony_ci unsigned int len = qstr->len; 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_ci if (!efivarfs_valid_name(s, len)) 688c2ecf20Sopenharmony_ci return -EINVAL; 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_ci while (len-- > EFI_VARIABLE_GUID_LEN) 718c2ecf20Sopenharmony_ci hash = partial_name_hash(*s++, hash); 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_ci /* GUID is case-insensitive. */ 748c2ecf20Sopenharmony_ci while (len--) 758c2ecf20Sopenharmony_ci hash = partial_name_hash(tolower(*s++), hash); 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_ci qstr->hash = end_name_hash(hash); 788c2ecf20Sopenharmony_ci return 0; 798c2ecf20Sopenharmony_ci} 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_cistatic const struct dentry_operations efivarfs_d_ops = { 828c2ecf20Sopenharmony_ci .d_compare = efivarfs_d_compare, 838c2ecf20Sopenharmony_ci .d_hash = efivarfs_d_hash, 848c2ecf20Sopenharmony_ci .d_delete = always_delete_dentry, 858c2ecf20Sopenharmony_ci}; 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_cistatic struct dentry *efivarfs_alloc_dentry(struct dentry *parent, char *name) 888c2ecf20Sopenharmony_ci{ 898c2ecf20Sopenharmony_ci struct dentry *d; 908c2ecf20Sopenharmony_ci struct qstr q; 918c2ecf20Sopenharmony_ci int err; 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_ci q.name = name; 948c2ecf20Sopenharmony_ci q.len = strlen(name); 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_ci err = efivarfs_d_hash(parent, &q); 978c2ecf20Sopenharmony_ci if (err) 988c2ecf20Sopenharmony_ci return ERR_PTR(err); 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_ci d = d_alloc(parent, &q); 1018c2ecf20Sopenharmony_ci if (d) 1028c2ecf20Sopenharmony_ci return d; 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci return ERR_PTR(-ENOMEM); 1058c2ecf20Sopenharmony_ci} 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_cistatic int efivarfs_callback(efi_char16_t *name16, efi_guid_t vendor, 1088c2ecf20Sopenharmony_ci unsigned long name_size, void *data) 1098c2ecf20Sopenharmony_ci{ 1108c2ecf20Sopenharmony_ci struct super_block *sb = (struct super_block *)data; 1118c2ecf20Sopenharmony_ci struct efivar_entry *entry; 1128c2ecf20Sopenharmony_ci struct inode *inode = NULL; 1138c2ecf20Sopenharmony_ci struct dentry *dentry, *root = sb->s_root; 1148c2ecf20Sopenharmony_ci unsigned long size = 0; 1158c2ecf20Sopenharmony_ci char *name; 1168c2ecf20Sopenharmony_ci int len; 1178c2ecf20Sopenharmony_ci int err = -ENOMEM; 1188c2ecf20Sopenharmony_ci bool is_removable = false; 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ci entry = kzalloc(sizeof(*entry), GFP_KERNEL); 1218c2ecf20Sopenharmony_ci if (!entry) 1228c2ecf20Sopenharmony_ci return err; 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_ci memcpy(entry->var.VariableName, name16, name_size); 1258c2ecf20Sopenharmony_ci memcpy(&(entry->var.VendorGuid), &vendor, sizeof(efi_guid_t)); 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_ci len = ucs2_utf8size(entry->var.VariableName); 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_ci /* name, plus '-', plus GUID, plus NUL*/ 1308c2ecf20Sopenharmony_ci name = kmalloc(len + 1 + EFI_VARIABLE_GUID_LEN + 1, GFP_KERNEL); 1318c2ecf20Sopenharmony_ci if (!name) 1328c2ecf20Sopenharmony_ci goto fail; 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_ci ucs2_as_utf8(name, entry->var.VariableName, len); 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci if (efivar_variable_is_removable(entry->var.VendorGuid, name, len)) 1378c2ecf20Sopenharmony_ci is_removable = true; 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_ci name[len] = '-'; 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci efi_guid_to_str(&entry->var.VendorGuid, name + len + 1); 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci name[len + EFI_VARIABLE_GUID_LEN+1] = '\0'; 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_ci /* replace invalid slashes like kobject_set_name_vargs does for /sys/firmware/efi/vars. */ 1468c2ecf20Sopenharmony_ci strreplace(name, '/', '!'); 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ci inode = efivarfs_get_inode(sb, d_inode(root), S_IFREG | 0644, 0, 1498c2ecf20Sopenharmony_ci is_removable); 1508c2ecf20Sopenharmony_ci if (!inode) 1518c2ecf20Sopenharmony_ci goto fail_name; 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci dentry = efivarfs_alloc_dentry(root, name); 1548c2ecf20Sopenharmony_ci if (IS_ERR(dentry)) { 1558c2ecf20Sopenharmony_ci err = PTR_ERR(dentry); 1568c2ecf20Sopenharmony_ci goto fail_inode; 1578c2ecf20Sopenharmony_ci } 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci efivar_entry_size(entry, &size); 1608c2ecf20Sopenharmony_ci err = efivar_entry_add(entry, &efivarfs_list); 1618c2ecf20Sopenharmony_ci if (err) 1628c2ecf20Sopenharmony_ci goto fail_inode; 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_ci /* copied by the above to local storage in the dentry. */ 1658c2ecf20Sopenharmony_ci kfree(name); 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_ci inode_lock(inode); 1688c2ecf20Sopenharmony_ci inode->i_private = entry; 1698c2ecf20Sopenharmony_ci i_size_write(inode, size + sizeof(entry->var.Attributes)); 1708c2ecf20Sopenharmony_ci inode_unlock(inode); 1718c2ecf20Sopenharmony_ci d_add(dentry, inode); 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_ci return 0; 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_cifail_inode: 1768c2ecf20Sopenharmony_ci iput(inode); 1778c2ecf20Sopenharmony_cifail_name: 1788c2ecf20Sopenharmony_ci kfree(name); 1798c2ecf20Sopenharmony_cifail: 1808c2ecf20Sopenharmony_ci kfree(entry); 1818c2ecf20Sopenharmony_ci return err; 1828c2ecf20Sopenharmony_ci} 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_cistatic int efivarfs_destroy(struct efivar_entry *entry, void *data) 1858c2ecf20Sopenharmony_ci{ 1868c2ecf20Sopenharmony_ci int err = efivar_entry_remove(entry); 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_ci if (err) 1898c2ecf20Sopenharmony_ci return err; 1908c2ecf20Sopenharmony_ci kfree(entry); 1918c2ecf20Sopenharmony_ci return 0; 1928c2ecf20Sopenharmony_ci} 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_cistatic int efivarfs_fill_super(struct super_block *sb, struct fs_context *fc) 1958c2ecf20Sopenharmony_ci{ 1968c2ecf20Sopenharmony_ci struct inode *inode = NULL; 1978c2ecf20Sopenharmony_ci struct dentry *root; 1988c2ecf20Sopenharmony_ci int err; 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_ci sb->s_maxbytes = MAX_LFS_FILESIZE; 2018c2ecf20Sopenharmony_ci sb->s_blocksize = PAGE_SIZE; 2028c2ecf20Sopenharmony_ci sb->s_blocksize_bits = PAGE_SHIFT; 2038c2ecf20Sopenharmony_ci sb->s_magic = EFIVARFS_MAGIC; 2048c2ecf20Sopenharmony_ci sb->s_op = &efivarfs_ops; 2058c2ecf20Sopenharmony_ci sb->s_d_op = &efivarfs_d_ops; 2068c2ecf20Sopenharmony_ci sb->s_time_gran = 1; 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_ci if (!efivar_supports_writes()) 2098c2ecf20Sopenharmony_ci sb->s_flags |= SB_RDONLY; 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_ci inode = efivarfs_get_inode(sb, NULL, S_IFDIR | 0755, 0, true); 2128c2ecf20Sopenharmony_ci if (!inode) 2138c2ecf20Sopenharmony_ci return -ENOMEM; 2148c2ecf20Sopenharmony_ci inode->i_op = &efivarfs_dir_inode_operations; 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_ci root = d_make_root(inode); 2178c2ecf20Sopenharmony_ci sb->s_root = root; 2188c2ecf20Sopenharmony_ci if (!root) 2198c2ecf20Sopenharmony_ci return -ENOMEM; 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_ci INIT_LIST_HEAD(&efivarfs_list); 2228c2ecf20Sopenharmony_ci 2238c2ecf20Sopenharmony_ci err = efivar_init(efivarfs_callback, (void *)sb, true, &efivarfs_list); 2248c2ecf20Sopenharmony_ci if (err) 2258c2ecf20Sopenharmony_ci __efivar_entry_iter(efivarfs_destroy, &efivarfs_list, NULL, NULL); 2268c2ecf20Sopenharmony_ci 2278c2ecf20Sopenharmony_ci return err; 2288c2ecf20Sopenharmony_ci} 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_cistatic int efivarfs_get_tree(struct fs_context *fc) 2318c2ecf20Sopenharmony_ci{ 2328c2ecf20Sopenharmony_ci return get_tree_single(fc, efivarfs_fill_super); 2338c2ecf20Sopenharmony_ci} 2348c2ecf20Sopenharmony_ci 2358c2ecf20Sopenharmony_cistatic int efivarfs_reconfigure(struct fs_context *fc) 2368c2ecf20Sopenharmony_ci{ 2378c2ecf20Sopenharmony_ci if (!efivar_supports_writes() && !(fc->sb_flags & SB_RDONLY)) { 2388c2ecf20Sopenharmony_ci pr_err("Firmware does not support SetVariableRT. Can not remount with rw\n"); 2398c2ecf20Sopenharmony_ci return -EINVAL; 2408c2ecf20Sopenharmony_ci } 2418c2ecf20Sopenharmony_ci 2428c2ecf20Sopenharmony_ci return 0; 2438c2ecf20Sopenharmony_ci} 2448c2ecf20Sopenharmony_ci 2458c2ecf20Sopenharmony_cistatic const struct fs_context_operations efivarfs_context_ops = { 2468c2ecf20Sopenharmony_ci .get_tree = efivarfs_get_tree, 2478c2ecf20Sopenharmony_ci .reconfigure = efivarfs_reconfigure, 2488c2ecf20Sopenharmony_ci}; 2498c2ecf20Sopenharmony_ci 2508c2ecf20Sopenharmony_cistatic int efivarfs_init_fs_context(struct fs_context *fc) 2518c2ecf20Sopenharmony_ci{ 2528c2ecf20Sopenharmony_ci fc->ops = &efivarfs_context_ops; 2538c2ecf20Sopenharmony_ci return 0; 2548c2ecf20Sopenharmony_ci} 2558c2ecf20Sopenharmony_ci 2568c2ecf20Sopenharmony_cistatic void efivarfs_kill_sb(struct super_block *sb) 2578c2ecf20Sopenharmony_ci{ 2588c2ecf20Sopenharmony_ci kill_litter_super(sb); 2598c2ecf20Sopenharmony_ci 2608c2ecf20Sopenharmony_ci /* Remove all entries and destroy */ 2618c2ecf20Sopenharmony_ci __efivar_entry_iter(efivarfs_destroy, &efivarfs_list, NULL, NULL); 2628c2ecf20Sopenharmony_ci} 2638c2ecf20Sopenharmony_ci 2648c2ecf20Sopenharmony_cistatic struct file_system_type efivarfs_type = { 2658c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 2668c2ecf20Sopenharmony_ci .name = "efivarfs", 2678c2ecf20Sopenharmony_ci .init_fs_context = efivarfs_init_fs_context, 2688c2ecf20Sopenharmony_ci .kill_sb = efivarfs_kill_sb, 2698c2ecf20Sopenharmony_ci}; 2708c2ecf20Sopenharmony_ci 2718c2ecf20Sopenharmony_cistatic __init int efivarfs_init(void) 2728c2ecf20Sopenharmony_ci{ 2738c2ecf20Sopenharmony_ci if (!efivars_kobject()) 2748c2ecf20Sopenharmony_ci return -ENODEV; 2758c2ecf20Sopenharmony_ci 2768c2ecf20Sopenharmony_ci return register_filesystem(&efivarfs_type); 2778c2ecf20Sopenharmony_ci} 2788c2ecf20Sopenharmony_ci 2798c2ecf20Sopenharmony_cistatic __exit void efivarfs_exit(void) 2808c2ecf20Sopenharmony_ci{ 2818c2ecf20Sopenharmony_ci unregister_filesystem(&efivarfs_type); 2828c2ecf20Sopenharmony_ci} 2838c2ecf20Sopenharmony_ci 2848c2ecf20Sopenharmony_ciMODULE_AUTHOR("Matthew Garrett, Jeremy Kerr"); 2858c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("EFI Variable Filesystem"); 2868c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 2878c2ecf20Sopenharmony_ciMODULE_ALIAS_FS("efivarfs"); 2888c2ecf20Sopenharmony_ci 2898c2ecf20Sopenharmony_cimodule_init(efivarfs_init); 2908c2ecf20Sopenharmony_cimodule_exit(efivarfs_exit); 291