single_open() currently allocates seq_operations with kmalloc(). This is
suboptimal, because that's four pointers, of which three are constant, and
only the 'show' op differs. We also have to be careful to use single_release()
to avoid leaking the ops structure.

Instead of this we can have a fixed single_show() function and constant ops
structure for these seq_files. We can store the pointer to the 'show' op as
a new field of struct seq_file. That's also not terribly elegant, because the
field is there also for non-single_open() seq files, but it's a single pointer
in an already existing (and already relatively large) structure instead of
an extra kmalloc of four pointers, so the tradeoff is OK.

Suggested-by: Matthew Wilcox <[email protected]>
Signed-off-by: Vlastimil Babka <[email protected]>
---
 Documentation/filesystems/seq_file.txt |  5 +++-
 fs/seq_file.c                          | 40 ++++++++++++--------------
 include/linux/seq_file.h               |  5 ++--
 3 files changed, 25 insertions(+), 25 deletions(-)

diff --git a/Documentation/filesystems/seq_file.txt 
b/Documentation/filesystems/seq_file.txt
index 9de4303201e1..ed61495abee8 100644
--- a/Documentation/filesystems/seq_file.txt
+++ b/Documentation/filesystems/seq_file.txt
@@ -335,4 +335,7 @@ When output time comes, the show() function will be called 
once. The data
 value given to single_open() can be found in the private field of the
 seq_file structure. When using single_open(), the programmer should use
 single_release() instead of seq_release() in the file_operations structure
-to avoid a memory leak.
+to avoid a memory leak. Note that the implementation has changed and current
+kernels will not leak anymore, but it's better to keep using single_release()
+in case the implementation details change again.
+
diff --git a/fs/seq_file.c b/fs/seq_file.c
index 4cc090b50cc5..3fd2ded04d93 100644
--- a/fs/seq_file.c
+++ b/fs/seq_file.c
@@ -563,22 +563,27 @@ static void single_stop(struct seq_file *p, void *v)
 {
 }
 
+static int single_show(struct seq_file *p, void *v)
+{
+       return p->single_show_op(p, v);
+}
+
+static const struct seq_operations single_seq_op = {
+       .start  = single_start,
+       .next   = single_next,
+       .stop   = single_stop,
+       .show   = single_show
+};
+
 int single_open(struct file *file, int (*show)(struct seq_file *, void *),
                void *data)
 {
-       struct seq_operations *op = kmalloc(sizeof(*op), GFP_KERNEL_ACCOUNT);
-       int res = -ENOMEM;
-
-       if (op) {
-               op->start = single_start;
-               op->next = single_next;
-               op->stop = single_stop;
-               op->show = show;
-               res = seq_open(file, op);
-               if (!res)
-                       ((struct seq_file *)file->private_data)->private = data;
-               else
-                       kfree(op);
+       int res;
+
+       res = seq_open(file, &single_seq_op);
+       if (!res) {
+               ((struct seq_file *)file->private_data)->private = data;
+               ((struct seq_file *)file->private_data)->single_show_op = show;
        }
        return res;
 }
@@ -602,15 +607,6 @@ int single_open_size(struct file *file, int (*show)(struct 
seq_file *, void *),
 }
 EXPORT_SYMBOL(single_open_size);
 
-int single_release(struct inode *inode, struct file *file)
-{
-       const struct seq_operations *op = ((struct seq_file 
*)file->private_data)->op;
-       int res = seq_release(inode, file);
-       kfree(op);
-       return res;
-}
-EXPORT_SYMBOL(single_release);
-
 int seq_release_private(struct inode *inode, struct file *file)
 {
        struct seq_file *seq = file->private_data;
diff --git a/include/linux/seq_file.h b/include/linux/seq_file.h
index a121982af0f5..c9a70c584a7d 100644
--- a/include/linux/seq_file.h
+++ b/include/linux/seq_file.h
@@ -24,9 +24,10 @@ struct seq_file {
        u64 version;
        struct mutex lock;
        const struct seq_operations *op;
-       int poll_event;
+       int (*single_show_op)(struct seq_file *, void *);
        const struct file *file;
        void *private;
+       int poll_event;
 };
 
 struct seq_operations {
@@ -140,7 +141,7 @@ int seq_path_root(struct seq_file *m, const struct path 
*path,
 
 int single_open(struct file *, int (*)(struct seq_file *, void *), void *);
 int single_open_size(struct file *, int (*)(struct seq_file *, void *), void 
*, size_t);
-int single_release(struct inode *, struct file *);
+#define single_release seq_release
 void *__seq_open_private(struct file *, const struct seq_operations *, int);
 int seq_open_private(struct file *, const struct seq_operations *, int);
 int seq_release_private(struct inode *, struct file *);
-- 
2.18.0

Reply via email to