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