aboutsummaryrefslogtreecommitdiffstats
path: root/fs/kernfs/file.c
diff options
context:
space:
mode:
authorTejun Heo <tj@kernel.org>2014-01-14 09:52:01 -0500
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2014-01-14 11:49:22 -0500
commitbb305947bdbb67325e1f949183cdd208fc2a7999 (patch)
tree47e2f102a0dd40ae822a8b7c470da07f3a1b9081 /fs/kernfs/file.c
parent683bb2761fbf123b24aed03a1c0d5d7556ec3018 (diff)
kernfs: fix get_active failure handling in kernfs_seq_*()
When kernfs_seq_start() fails to obtain an active reference, it returns ERR_PTR(-ENODEV). kernfs_seq_stop() is then invoked with the error pointer value; however, it still proceeds to invoke kernfs_put_active() on the node leading to unbalanced put. If kernfs_seq_stop() is called even after active ref failure, it should skip invocation of @ops->seq_stop() and put_active. Unfortunately, this is a bit complicated because active ref failure isn't the only thing which may fail with ERR_PTR(-ENODEV). @ops->seq_start/next() may also fail with the error value and kernfs_seq_stop() doesn't have a way to tell apart those failures. Work it around by factoring out the active part of kernfs_seq_stop() into kernfs_seq_stop_active() and invoking it directly if @ops->seq_start/next() fail with ERR_PTR(-ENODEV) and updating kernfs_seq_stop() to skip kernfs_seq_stop_active() on ERR_PTR(-ENODEV). This is a bit nasty but ensures that the active put is skipped iff get_active failed in kernfs_seq_start(). tj: This was originally committed as d92d2e6bd72b but got reverted by 683bb2761fbf along with other kernfs self removal patches. However, this one is an independent fix and shouldn't have been reverted together. Reinstate the change. Sorry about the mess. Signed-off-by: Tejun Heo <tj@kernel.org> Cc: Sasha Levin <sasha.levin@oracle.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'fs/kernfs/file.c')
-rw-r--r--fs/kernfs/file.c51
1 files changed, 44 insertions, 7 deletions
diff --git a/fs/kernfs/file.c b/fs/kernfs/file.c
index 316604cc3a1c..bdd38854ef65 100644
--- a/fs/kernfs/file.c
+++ b/fs/kernfs/file.c
@@ -54,6 +54,38 @@ static const struct kernfs_ops *kernfs_ops(struct kernfs_node *kn)
54 return kn->attr.ops; 54 return kn->attr.ops;
55} 55}
56 56
57/*
58 * As kernfs_seq_stop() is also called after kernfs_seq_start() or
59 * kernfs_seq_next() failure, it needs to distinguish whether it's stopping
60 * a seq_file iteration which is fully initialized with an active reference
61 * or an aborted kernfs_seq_start() due to get_active failure. The
62 * position pointer is the only context for each seq_file iteration and
63 * thus the stop condition should be encoded in it. As the return value is
64 * directly visible to userland, ERR_PTR(-ENODEV) is the only acceptable
65 * choice to indicate get_active failure.
66 *
67 * Unfortunately, this is complicated due to the optional custom seq_file
68 * operations which may return ERR_PTR(-ENODEV) too. kernfs_seq_stop()
69 * can't distinguish whether ERR_PTR(-ENODEV) is from get_active failure or
70 * custom seq_file operations and thus can't decide whether put_active
71 * should be performed or not only on ERR_PTR(-ENODEV).
72 *
73 * This is worked around by factoring out the custom seq_stop() and
74 * put_active part into kernfs_seq_stop_active(), skipping it from
75 * kernfs_seq_stop() if ERR_PTR(-ENODEV) while invoking it directly after
76 * custom seq_file operations fail with ERR_PTR(-ENODEV) - this ensures
77 * that kernfs_seq_stop_active() is skipped only after get_active failure.
78 */
79static void kernfs_seq_stop_active(struct seq_file *sf, void *v)
80{
81 struct kernfs_open_file *of = sf->private;
82 const struct kernfs_ops *ops = kernfs_ops(of->kn);
83
84 if (ops->seq_stop)
85 ops->seq_stop(sf, v);
86 kernfs_put_active(of->kn);
87}
88
57static void *kernfs_seq_start(struct seq_file *sf, loff_t *ppos) 89static void *kernfs_seq_start(struct seq_file *sf, loff_t *ppos)
58{ 90{
59 struct kernfs_open_file *of = sf->private; 91 struct kernfs_open_file *of = sf->private;
@@ -69,7 +101,11 @@ static void *kernfs_seq_start(struct seq_file *sf, loff_t *ppos)
69 101
70 ops = kernfs_ops(of->kn); 102 ops = kernfs_ops(of->kn);
71 if (ops->seq_start) { 103 if (ops->seq_start) {
72 return ops->seq_start(sf, ppos); 104 void *next = ops->seq_start(sf, ppos);
105 /* see the comment above kernfs_seq_stop_active() */
106 if (next == ERR_PTR(-ENODEV))
107 kernfs_seq_stop_active(sf, next);
108 return next;
73 } else { 109 } else {
74 /* 110 /*
75 * The same behavior and code as single_open(). Returns 111 * The same behavior and code as single_open(). Returns
@@ -85,7 +121,11 @@ static void *kernfs_seq_next(struct seq_file *sf, void *v, loff_t *ppos)
85 const struct kernfs_ops *ops = kernfs_ops(of->kn); 121 const struct kernfs_ops *ops = kernfs_ops(of->kn);
86 122
87 if (ops->seq_next) { 123 if (ops->seq_next) {
88 return ops->seq_next(sf, v, ppos); 124 void *next = ops->seq_next(sf, v, ppos);
125 /* see the comment above kernfs_seq_stop_active() */
126 if (next == ERR_PTR(-ENODEV))
127 kernfs_seq_stop_active(sf, next);
128 return next;
89 } else { 129 } else {
90 /* 130 /*
91 * The same behavior and code as single_open(), always 131 * The same behavior and code as single_open(), always
@@ -99,12 +139,9 @@ static void *kernfs_seq_next(struct seq_file *sf, void *v, loff_t *ppos)
99static void kernfs_seq_stop(struct seq_file *sf, void *v) 139static void kernfs_seq_stop(struct seq_file *sf, void *v)
100{ 140{
101 struct kernfs_open_file *of = sf->private; 141 struct kernfs_open_file *of = sf->private;
102 const struct kernfs_ops *ops = kernfs_ops(of->kn);
103 142
104 if (ops->seq_stop) 143 if (v != ERR_PTR(-ENODEV))
105 ops->seq_stop(sf, v); 144 kernfs_seq_stop_active(sf, v);
106
107 kernfs_put_active(of->kn);
108 mutex_unlock(&of->mutex); 145 mutex_unlock(&of->mutex);
109} 146}
110 147