| // SPDX-License-Identifier: GPL-2.0-only | |
| /* | |
| * Copyright (C) 2012 Red Hat, Inc. | |
| * Copyright (C) 2012 Jeremy Kerr <jeremy.kerr@canonical.com> | |
| */ | |
| #include <linux/efi.h> | |
| #include <linux/delay.h> | |
| #include <linux/fs.h> | |
| #include <linux/slab.h> | |
| #include <linux/mount.h> | |
| #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, | |
| }; |