file, i915: fix file reference for mmap_singleton()
[linux-block.git] / fs / efivarfs / super.c
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * Copyright (C) 2012 Red Hat, Inc.
4  * Copyright (C) 2012 Jeremy Kerr <jeremy.kerr@canonical.com>
5  */
6
7 #include <linux/ctype.h>
8 #include <linux/efi.h>
9 #include <linux/fs.h>
10 #include <linux/fs_context.h>
11 #include <linux/module.h>
12 #include <linux/pagemap.h>
13 #include <linux/ucs2_string.h>
14 #include <linux/slab.h>
15 #include <linux/magic.h>
16 #include <linux/statfs.h>
17
18 #include "internal.h"
19
20 LIST_HEAD(efivarfs_list);
21
22 static void efivarfs_evict_inode(struct inode *inode)
23 {
24         clear_inode(inode);
25 }
26
27 static int efivarfs_statfs(struct dentry *dentry, struct kstatfs *buf)
28 {
29         const u32 attr = EFI_VARIABLE_NON_VOLATILE |
30                          EFI_VARIABLE_BOOTSERVICE_ACCESS |
31                          EFI_VARIABLE_RUNTIME_ACCESS;
32         u64 storage_space, remaining_space, max_variable_size;
33         efi_status_t status;
34
35         status = efivar_query_variable_info(attr, &storage_space, &remaining_space,
36                                             &max_variable_size);
37         if (status != EFI_SUCCESS)
38                 return efi_status_to_err(status);
39
40         /*
41          * This is not a normal filesystem, so no point in pretending it has a block
42          * size; we declare f_bsize to 1, so that we can then report the exact value
43          * sent by EFI QueryVariableInfo in f_blocks and f_bfree
44          */
45         buf->f_bsize    = 1;
46         buf->f_namelen  = NAME_MAX;
47         buf->f_blocks   = storage_space;
48         buf->f_bfree    = remaining_space;
49         buf->f_type     = dentry->d_sb->s_magic;
50
51         /*
52          * In f_bavail we declare the free space that the kernel will allow writing
53          * when the storage_paranoia x86 quirk is active. To use more, users
54          * should boot the kernel with efi_no_storage_paranoia.
55          */
56         if (remaining_space > efivar_reserved_space())
57                 buf->f_bavail = remaining_space - efivar_reserved_space();
58         else
59                 buf->f_bavail = 0;
60
61         return 0;
62 }
63 static const struct super_operations efivarfs_ops = {
64         .statfs = efivarfs_statfs,
65         .drop_inode = generic_delete_inode,
66         .evict_inode = efivarfs_evict_inode,
67 };
68
69 /*
70  * Compare two efivarfs file names.
71  *
72  * An efivarfs filename is composed of two parts,
73  *
74  *      1. A case-sensitive variable name
75  *      2. A case-insensitive GUID
76  *
77  * So we need to perform a case-sensitive match on part 1 and a
78  * case-insensitive match on part 2.
79  */
80 static int efivarfs_d_compare(const struct dentry *dentry,
81                               unsigned int len, const char *str,
82                               const struct qstr *name)
83 {
84         int guid = len - EFI_VARIABLE_GUID_LEN;
85
86         if (name->len != len)
87                 return 1;
88
89         /* Case-sensitive compare for the variable name */
90         if (memcmp(str, name->name, guid))
91                 return 1;
92
93         /* Case-insensitive compare for the GUID */
94         return strncasecmp(name->name + guid, str + guid, EFI_VARIABLE_GUID_LEN);
95 }
96
97 static int efivarfs_d_hash(const struct dentry *dentry, struct qstr *qstr)
98 {
99         unsigned long hash = init_name_hash(dentry);
100         const unsigned char *s = qstr->name;
101         unsigned int len = qstr->len;
102
103         if (!efivarfs_valid_name(s, len))
104                 return -EINVAL;
105
106         while (len-- > EFI_VARIABLE_GUID_LEN)
107                 hash = partial_name_hash(*s++, hash);
108
109         /* GUID is case-insensitive. */
110         while (len--)
111                 hash = partial_name_hash(tolower(*s++), hash);
112
113         qstr->hash = end_name_hash(hash);
114         return 0;
115 }
116
117 static const struct dentry_operations efivarfs_d_ops = {
118         .d_compare = efivarfs_d_compare,
119         .d_hash = efivarfs_d_hash,
120         .d_delete = always_delete_dentry,
121 };
122
123 static struct dentry *efivarfs_alloc_dentry(struct dentry *parent, char *name)
124 {
125         struct dentry *d;
126         struct qstr q;
127         int err;
128
129         q.name = name;
130         q.len = strlen(name);
131
132         err = efivarfs_d_hash(parent, &q);
133         if (err)
134                 return ERR_PTR(err);
135
136         d = d_alloc(parent, &q);
137         if (d)
138                 return d;
139
140         return ERR_PTR(-ENOMEM);
141 }
142
143 static int efivarfs_callback(efi_char16_t *name16, efi_guid_t vendor,
144                              unsigned long name_size, void *data)
145 {
146         struct super_block *sb = (struct super_block *)data;
147         struct efivar_entry *entry;
148         struct inode *inode = NULL;
149         struct dentry *dentry, *root = sb->s_root;
150         unsigned long size = 0;
151         char *name;
152         int len;
153         int err = -ENOMEM;
154         bool is_removable = false;
155
156         if (guid_equal(&vendor, &LINUX_EFI_RANDOM_SEED_TABLE_GUID))
157                 return 0;
158
159         entry = kzalloc(sizeof(*entry), GFP_KERNEL);
160         if (!entry)
161                 return err;
162
163         memcpy(entry->var.VariableName, name16, name_size);
164         memcpy(&(entry->var.VendorGuid), &vendor, sizeof(efi_guid_t));
165
166         len = ucs2_utf8size(entry->var.VariableName);
167
168         /* name, plus '-', plus GUID, plus NUL*/
169         name = kmalloc(len + 1 + EFI_VARIABLE_GUID_LEN + 1, GFP_KERNEL);
170         if (!name)
171                 goto fail;
172
173         ucs2_as_utf8(name, entry->var.VariableName, len);
174
175         if (efivar_variable_is_removable(entry->var.VendorGuid, name, len))
176                 is_removable = true;
177
178         name[len] = '-';
179
180         efi_guid_to_str(&entry->var.VendorGuid, name + len + 1);
181
182         name[len + EFI_VARIABLE_GUID_LEN+1] = '\0';
183
184         /* replace invalid slashes like kobject_set_name_vargs does for /sys/firmware/efi/vars. */
185         strreplace(name, '/', '!');
186
187         inode = efivarfs_get_inode(sb, d_inode(root), S_IFREG | 0644, 0,
188                                    is_removable);
189         if (!inode)
190                 goto fail_name;
191
192         dentry = efivarfs_alloc_dentry(root, name);
193         if (IS_ERR(dentry)) {
194                 err = PTR_ERR(dentry);
195                 goto fail_inode;
196         }
197
198         __efivar_entry_get(entry, NULL, &size, NULL);
199         __efivar_entry_add(entry, &efivarfs_list);
200
201         /* copied by the above to local storage in the dentry. */
202         kfree(name);
203
204         inode_lock(inode);
205         inode->i_private = entry;
206         i_size_write(inode, size + sizeof(entry->var.Attributes));
207         inode_unlock(inode);
208         d_add(dentry, inode);
209
210         return 0;
211
212 fail_inode:
213         iput(inode);
214 fail_name:
215         kfree(name);
216 fail:
217         kfree(entry);
218         return err;
219 }
220
221 static int efivarfs_destroy(struct efivar_entry *entry, void *data)
222 {
223         efivar_entry_remove(entry);
224         kfree(entry);
225         return 0;
226 }
227
228 static int efivarfs_fill_super(struct super_block *sb, struct fs_context *fc)
229 {
230         struct inode *inode = NULL;
231         struct dentry *root;
232         int err;
233
234         if (!efivar_is_available())
235                 return -EOPNOTSUPP;
236
237         sb->s_maxbytes          = MAX_LFS_FILESIZE;
238         sb->s_blocksize         = PAGE_SIZE;
239         sb->s_blocksize_bits    = PAGE_SHIFT;
240         sb->s_magic             = EFIVARFS_MAGIC;
241         sb->s_op                = &efivarfs_ops;
242         sb->s_d_op              = &efivarfs_d_ops;
243         sb->s_time_gran         = 1;
244
245         if (!efivar_supports_writes())
246                 sb->s_flags |= SB_RDONLY;
247
248         inode = efivarfs_get_inode(sb, NULL, S_IFDIR | 0755, 0, true);
249         if (!inode)
250                 return -ENOMEM;
251         inode->i_op = &efivarfs_dir_inode_operations;
252
253         root = d_make_root(inode);
254         sb->s_root = root;
255         if (!root)
256                 return -ENOMEM;
257
258         INIT_LIST_HEAD(&efivarfs_list);
259
260         err = efivar_init(efivarfs_callback, (void *)sb, true, &efivarfs_list);
261         if (err)
262                 efivar_entry_iter(efivarfs_destroy, &efivarfs_list, NULL);
263
264         return err;
265 }
266
267 static int efivarfs_get_tree(struct fs_context *fc)
268 {
269         return get_tree_single(fc, efivarfs_fill_super);
270 }
271
272 static const struct fs_context_operations efivarfs_context_ops = {
273         .get_tree       = efivarfs_get_tree,
274 };
275
276 static int efivarfs_init_fs_context(struct fs_context *fc)
277 {
278         fc->ops = &efivarfs_context_ops;
279         return 0;
280 }
281
282 static void efivarfs_kill_sb(struct super_block *sb)
283 {
284         kill_litter_super(sb);
285
286         if (!efivar_is_available())
287                 return;
288
289         /* Remove all entries and destroy */
290         efivar_entry_iter(efivarfs_destroy, &efivarfs_list, NULL);
291 }
292
293 static struct file_system_type efivarfs_type = {
294         .owner   = THIS_MODULE,
295         .name    = "efivarfs",
296         .init_fs_context = efivarfs_init_fs_context,
297         .kill_sb = efivarfs_kill_sb,
298 };
299
300 static __init int efivarfs_init(void)
301 {
302         return register_filesystem(&efivarfs_type);
303 }
304
305 static __exit void efivarfs_exit(void)
306 {
307         unregister_filesystem(&efivarfs_type);
308 }
309
310 MODULE_AUTHOR("Matthew Garrett, Jeremy Kerr");
311 MODULE_DESCRIPTION("EFI Variable Filesystem");
312 MODULE_LICENSE("GPL");
313 MODULE_ALIAS_FS("efivarfs");
314
315 module_init(efivarfs_init);
316 module_exit(efivarfs_exit);