efi: Handle deletions and size changes in efivarfs_write_file
authorJeremy Kerr <jeremy.kerr@canonical.com>
Fri, 5 Oct 2012 05:54:56 +0000 (13:54 +0800)
committerMatt Fleming <matt.fleming@intel.com>
Tue, 30 Oct 2012 10:39:17 +0000 (10:39 +0000)
A write to an efivarfs file will not always result in a variable of
'count' size after the EFI SetVariable() call. We may have appended to
the existing data (ie, with the EFI_VARIABLE_APPEND_WRITE attribute), or
even have deleted the variable (with an authenticated variable update,
with a zero datasize).

This change re-reads the updated variable from firmware, to check for
size changes and deletions. In the latter case, we need to drop the
dentry.

Signed-off-by: Jeremy Kerr <jeremy.kerr@canonical.com>
Signed-off-by: Matt Fleming <matt.fleming@intel.com>
drivers/firmware/efivars.c

index b605c48..d7658b4 100644 (file)
@@ -658,6 +658,7 @@ static ssize_t efivarfs_file_write(struct file *file,
        u32 attributes;
        struct inode *inode = file->f_mapping->host;
        int datasize = count - sizeof(attributes);
+       unsigned long newdatasize;
 
        if (count < sizeof(attributes))
                return -EINVAL;
@@ -696,32 +697,60 @@ static ssize_t efivarfs_file_write(struct file *file,
 
        switch (status) {
        case EFI_SUCCESS:
-               mutex_lock(&inode->i_mutex);
-               i_size_write(inode, count);
-               mutex_unlock(&inode->i_mutex);
                break;
        case EFI_INVALID_PARAMETER:
                count = -EINVAL;
-               break;
+               goto out;
        case EFI_OUT_OF_RESOURCES:
                count = -ENOSPC;
-               break;
+               goto out;
        case EFI_DEVICE_ERROR:
                count = -EIO;
-               break;
+               goto out;
        case EFI_WRITE_PROTECTED:
                count = -EROFS;
-               break;
+               goto out;
        case EFI_SECURITY_VIOLATION:
                count = -EACCES;
-               break;
+               goto out;
        case EFI_NOT_FOUND:
                count = -ENOENT;
-               break;
+               goto out;
        default:
                count = -EINVAL;
-               break;
+               goto out;
        }
+
+       /*
+        * Writing to the variable may have caused a change in size (which
+        * could either be an append or an overwrite), or the variable to be
+        * deleted. Perform a GetVariable() so we can tell what actually
+        * happened.
+        */
+       newdatasize = 0;
+       status = efivars->ops->get_variable(var->var.VariableName,
+                                           &var->var.VendorGuid,
+                                           NULL, &newdatasize,
+                                           NULL);
+
+       if (status == EFI_BUFFER_TOO_SMALL) {
+               mutex_lock(&inode->i_mutex);
+               i_size_write(inode, newdatasize + sizeof(attributes));
+               mutex_unlock(&inode->i_mutex);
+
+       } else if (status == EFI_NOT_FOUND) {
+               spin_lock(&efivars->lock);
+               list_del(&var->list);
+               spin_unlock(&efivars->lock);
+               efivar_unregister(var);
+               drop_nlink(inode);
+               dput(file->f_dentry);
+
+       } else {
+               pr_warn("efivarfs: inconsistent EFI variable implementation? "
+                               "status = %lx\n", status);
+       }
+
 out:
        kfree(data);