// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2012 Red Hat, Inc. * Copyright (C) 2012 Jeremy Kerr */ #include #include #include #include #include #include "internal.h" static ssize_t efivarfs_file_write(struct file *file, const char __user *userbuf, size_t count, loff_t *ppos) { struct efivar_entry *var = file->private_data; void *data; u32 attributes; struct inode *inode = file->f_mapping->host; unsigned long datasize = count - sizeof(attributes); ssize_t bytes; bool set = false; if (count < sizeof(attributes)) return -EINVAL; if (copy_from_user(&attributes, userbuf, sizeof(attributes))) return -EFAULT; if (attributes & ~(EFI_VARIABLE_MASK)) return -EINVAL; data = memdup_user(userbuf + sizeof(attributes), datasize); if (IS_ERR(data)) return PTR_ERR(data); inode_lock(inode); if (var->removed) { /* * file got removed; don't allow a set. Caused by an * unsuccessful create or successful delete write * racing with us. */ bytes = -EIO; goto out; } bytes = efivar_entry_set_get_size(var, attributes, &datasize, data, &set); if (!set) { if (bytes == -ENOENT) bytes = -EIO; goto out; } if (bytes == -ENOENT) { /* * zero size signals to release that the write deleted * the variable */ i_size_write(inode, 0); } else { i_size_write(inode, datasize + sizeof(attributes)); inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode)); } bytes = count; out: inode_unlock(inode); kfree(data); return bytes; } static ssize_t efivarfs_file_read(struct file *file, char __user *userbuf, size_t count, loff_t *ppos) { struct efivar_entry *var = file->private_data; unsigned long datasize = 0; u32 attributes; void *data; ssize_t size = 0; int err; while (!__ratelimit(&file->f_cred->user->ratelimit)) msleep(50); err = efivar_entry_size(var, &datasize); /* * efivarfs represents uncommitted variables with * zero-length files. Reading them should return EOF. */ if (err == -ENOENT) return 0; else if (err) return err; data = kmalloc(datasize + sizeof(attributes), GFP_KERNEL); if (!data) return -ENOMEM; size = efivar_entry_get(var, &attributes, &datasize, data + sizeof(attributes)); if (size) goto out_free; memcpy(data, &attributes, sizeof(attributes)); size = simple_read_from_buffer(userbuf, count, ppos, data, datasize + sizeof(attributes)); out_free: kfree(data); return size; } static int efivarfs_file_release(struct inode *inode, struct file *file) { struct efivar_entry *var = inode->i_private; inode_lock(inode); var->removed = (--var->open_count == 0 && i_size_read(inode) == 0); inode_unlock(inode); if (var->removed) simple_recursive_removal(file->f_path.dentry, NULL); return 0; } static int efivarfs_file_open(struct inode *inode, struct file *file) { struct efivar_entry *entry = inode->i_private; file->private_data = entry; inode_lock(inode); entry->open_count++; inode_unlock(inode); return 0; } const struct file_operations efivarfs_file_operations = { .open = efivarfs_file_open, .read = efivarfs_file_read, .write = efivarfs_file_write, .release = efivarfs_file_release, };