kernfs: sysfs: support custom llseek method for sysfs entries
authorValentine Sinitsyn <valesini@yandex-team.ru>
Mon, 25 Sep 2023 08:40:12 +0000 (11:40 +0300)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 5 Oct 2023 11:42:11 +0000 (13:42 +0200)
As of now, seeking in sysfs files is handled by generic_file_llseek().
There are situations where one may want to customize seeking logic:

- Many sysfs entries are fixed files while generic_file_llseek() accepts
  past-the-end positions. Not only being useless by itself, this
  also means a bug in userspace code will trigger not at lseek(), but at
  some later point making debugging harder.
- generic_file_llseek() relies on f_mapping->host to get the file size
  which might not be correct for all sysfs entries.
  See commit 636b21b50152 ("PCI: Revoke mappings like devmem") as an example.

Implement llseek method to override this behavior at sysfs attribute
level. The method is optional, and if it is absent,
generic_file_llseek() is called to preserve backwards compatibility.

Signed-off-by: Valentine Sinitsyn <valesini@yandex-team.ru>
Link: https://lore.kernel.org/r/20230925084013.309399-1-valesini@yandex-team.ru
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
fs/kernfs/file.c
fs/sysfs/file.c
include/linux/kernfs.h
include/linux/sysfs.h

index 180906c36f5151cf6824e2560d6642662c08cc24..855e3f9d8dccedcee6fb0b1a79e4c9be5a43caf1 100644 (file)
@@ -903,6 +903,33 @@ static __poll_t kernfs_fop_poll(struct file *filp, poll_table *wait)
        return ret;
 }
 
+static loff_t kernfs_fop_llseek(struct file *file, loff_t offset, int whence)
+{
+       struct kernfs_open_file *of = kernfs_of(file);
+       const struct kernfs_ops *ops;
+       loff_t ret;
+
+       /*
+        * @of->mutex nests outside active ref and is primarily to ensure that
+        * the ops aren't called concurrently for the same open file.
+        */
+       mutex_lock(&of->mutex);
+       if (!kernfs_get_active(of->kn)) {
+               mutex_unlock(&of->mutex);
+               return -ENODEV;
+       }
+
+       ops = kernfs_ops(of->kn);
+       if (ops->llseek)
+               ret = ops->llseek(of, offset, whence);
+       else
+               ret = generic_file_llseek(file, offset, whence);
+
+       kernfs_put_active(of->kn);
+       mutex_unlock(&of->mutex);
+       return ret;
+}
+
 static void kernfs_notify_workfn(struct work_struct *work)
 {
        struct kernfs_node *kn;
@@ -1005,7 +1032,7 @@ EXPORT_SYMBOL_GPL(kernfs_notify);
 const struct file_operations kernfs_file_fops = {
        .read_iter      = kernfs_fop_read_iter,
        .write_iter     = kernfs_fop_write_iter,
-       .llseek         = generic_file_llseek,
+       .llseek         = kernfs_fop_llseek,
        .mmap           = kernfs_fop_mmap,
        .open           = kernfs_fop_open,
        .release        = kernfs_fop_release,
index a12ac0356c69cd409a856b5a551ce1768cf7ad16..6b7652fb805057b8a155a0c94a3746fcb72eb5c7 100644 (file)
@@ -167,6 +167,18 @@ static int sysfs_kf_bin_mmap(struct kernfs_open_file *of,
        return battr->mmap(of->file, kobj, battr, vma);
 }
 
+static loff_t sysfs_kf_bin_llseek(struct kernfs_open_file *of, loff_t offset,
+                                 int whence)
+{
+       struct bin_attribute *battr = of->kn->priv;
+       struct kobject *kobj = of->kn->parent->priv;
+
+       if (battr->llseek)
+               return battr->llseek(of->file, kobj, battr, offset, whence);
+       else
+               return generic_file_llseek(of->file, offset, whence);
+}
+
 static int sysfs_kf_bin_open(struct kernfs_open_file *of)
 {
        struct bin_attribute *battr = of->kn->priv;
@@ -249,6 +261,7 @@ static const struct kernfs_ops sysfs_bin_kfops_mmap = {
        .write          = sysfs_kf_bin_write,
        .mmap           = sysfs_kf_bin_mmap,
        .open           = sysfs_kf_bin_open,
+       .llseek         = sysfs_kf_bin_llseek,
 };
 
 int sysfs_add_file_mode_ns(struct kernfs_node *parent,
index 2a36f3218b51060c8b997f89156b555da529b1f1..99aaa050ccb767954ebcbb0fc47dd522987582e5 100644 (file)
@@ -316,6 +316,7 @@ struct kernfs_ops {
                         struct poll_table_struct *pt);
 
        int (*mmap)(struct kernfs_open_file *of, struct vm_area_struct *vma);
+       loff_t (*llseek)(struct kernfs_open_file *of, loff_t offset, int whence);
 };
 
 /*
index fd3fe5c8c17fce0b18111b3b4e48c5f6a5590819..b717a70219f65186907c036f2e8b7e38e8e3083e 100644 (file)
@@ -181,6 +181,8 @@ struct bin_attribute {
                        char *, loff_t, size_t);
        ssize_t (*write)(struct file *, struct kobject *, struct bin_attribute *,
                         char *, loff_t, size_t);
+       loff_t (*llseek)(struct file *, struct kobject *, struct bin_attribute *,
+                        loff_t, int);
        int (*mmap)(struct file *, struct kobject *, struct bin_attribute *attr,
                    struct vm_area_struct *vma);
 };