aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNeilBrown <neilb@suse.com>2018-08-17 18:44:41 -0400
committerLinus Torvalds <torvalds@linux-foundation.org>2018-08-17 19:20:28 -0400
commit1f4aace60b0edc2d885aaa263abf4df42c8c65a8 (patch)
treeb1c06100cf4aaa12f0174e169d67dd0e0ce7dd07
parent4cdfffc8722e99be8d400d8fa1fcd615d078ad43 (diff)
fs/seq_file.c: simplify seq_file iteration code and interface
The documentation for seq_file suggests that it is necessary to be able to move the iterator to a given offset, however that is not the case. If the iterator is stored in the private data and is stable from one read() syscall to the next, it is only necessary to support first/next interactions. Implementing this in a client is a little clumsy. - if ->start() is given a pos of zero, it should go to start of sequence. - if ->start() is given the name pos that was given to the most recent next() or start(), it should restore the iterator to state just before that last call - if ->start is given another number, it should set the iterator one beyond the start just before the last ->start or ->next call. Also, the documentation says that the implementation can interpret the pos however it likes (other than zero meaning start), but seq_file increments the pos sometimes which does impose on the implementation. This patch simplifies the interface for first/next iteration and simplifies the code, while maintaining complete backward compatability. Now: - if ->start() is given a pos of zero, it should return an iterator placed at the start of the sequence - if ->start() is given a non-zero pos, it should return the iterator in the same state it was after the last ->start or ->next. This is particularly useful for interators which walk the multiple chains in a hash table, e.g. using rhashtable_walk*. See fs/gfs2/glock.c and drivers/staging/lustre/lustre/llite/vvp_dev.c A large part of achieving this is to *always* call ->next after ->show has successfully stored all of an entry in the buffer. Never just increment the index instead. Also: - always pass &m->index to ->start() and ->next(), never a temp variable - don't clear ->from when ->count is zero, as ->from is dead when ->count is zero. Some ->next functions do not increment *pos when they return NULL. To maintain compatability with this, we still need to increment m->index in one place, if ->next didn't increment it. Note that such ->next functions are buggy and should be fixed. A simple demonstration is dd if=/proc/swaps bs=1000 skip=1 Choose any block size larger than the size of /proc/swaps. This will always show the whole last line of /proc/swaps. This patch doesn't work around buggy next() functions for this case. [neilb@suse.com: ensure ->from is valid] Link: http://lkml.kernel.org/r/87601ryb8a.fsf@notabene.neil.brown.name Signed-off-by: NeilBrown <neilb@suse.com> Acked-by: Jonathan Corbet <corbet@lwn.net> [docs] Tested-by: Jann Horn <jannh@google.com> Cc: Alexander Viro <viro@zeniv.linux.org.uk> Cc: Kees Cook <keescook@chromium.org> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
-rw-r--r--Documentation/filesystems/seq_file.txt63
-rw-r--r--fs/seq_file.c54
2 files changed, 63 insertions, 54 deletions
diff --git a/Documentation/filesystems/seq_file.txt b/Documentation/filesystems/seq_file.txt
index 9de4303201e1..d412b236a9d6 100644
--- a/Documentation/filesystems/seq_file.txt
+++ b/Documentation/filesystems/seq_file.txt
@@ -66,23 +66,39 @@ kernel 3.10. Current versions require the following update
66 66
67The iterator interface 67The iterator interface
68 68
69Modules implementing a virtual file with seq_file must implement a simple 69Modules implementing a virtual file with seq_file must implement an
70iterator object that allows stepping through the data of interest. 70iterator object that allows stepping through the data of interest
71Iterators must be able to move to a specific position - like the file they 71during a "session" (roughly one read() system call). If the iterator
72implement - but the interpretation of that position is up to the iterator 72is able to move to a specific position - like the file they implement,
73itself. A seq_file implementation that is formatting firewall rules, for 73though with freedom to map the position number to a sequence location
74example, could interpret position N as the Nth rule in the chain. 74in whatever way is convenient - the iterator need only exist
75Positioning can thus be done in whatever way makes the most sense for the 75transiently during a session. If the iterator cannot easily find a
76generator of the data, which need not be aware of how a position translates 76numerical position but works well with a first/next interface, the
77to an offset in the virtual file. The one obvious exception is that a 77iterator can be stored in the private data area and continue from one
78position of zero should indicate the beginning of the file. 78session to the next.
79
80A seq_file implementation that is formatting firewall rules from a
81table, for example, could provide a simple iterator that interprets
82position N as the Nth rule in the chain. A seq_file implementation
83that presents the content of a, potentially volatile, linked list
84might record a pointer into that list, providing that can be done
85without risk of the current location being removed.
86
87Positioning can thus be done in whatever way makes the most sense for
88the generator of the data, which need not be aware of how a position
89translates to an offset in the virtual file. The one obvious exception
90is that a position of zero should indicate the beginning of the file.
79 91
80The /proc/sequence iterator just uses the count of the next number it 92The /proc/sequence iterator just uses the count of the next number it
81will output as its position. 93will output as its position.
82 94
83Four functions must be implemented to make the iterator work. The first, 95Four functions must be implemented to make the iterator work. The
84called start() takes a position as an argument and returns an iterator 96first, called start(), starts a session and takes a position as an
85which will start reading at that position. For our simple sequence example, 97argument, returning an iterator which will start reading at that
98position. The pos passed to start() will always be either zero, or
99the most recent pos used in the previous session.
100
101For our simple sequence example,
86the start() function looks like: 102the start() function looks like:
87 103
88 static void *ct_seq_start(struct seq_file *s, loff_t *pos) 104 static void *ct_seq_start(struct seq_file *s, loff_t *pos)
@@ -101,11 +117,12 @@ implementations; in most cases the start() function should check for a
101"past end of file" condition and return NULL if need be. 117"past end of file" condition and return NULL if need be.
102 118
103For more complicated applications, the private field of the seq_file 119For more complicated applications, the private field of the seq_file
104structure can be used. There is also a special value which can be returned 120structure can be used to hold state from session to session. There is
105by the start() function called SEQ_START_TOKEN; it can be used if you wish 121also a special value which can be returned by the start() function
106to instruct your show() function (described below) to print a header at the 122called SEQ_START_TOKEN; it can be used if you wish to instruct your
107top of the output. SEQ_START_TOKEN should only be used if the offset is 123show() function (described below) to print a header at the top of the
108zero, however. 124output. SEQ_START_TOKEN should only be used if the offset is zero,
125however.
109 126
110The next function to implement is called, amazingly, next(); its job is to 127The next function to implement is called, amazingly, next(); its job is to
111move the iterator forward to the next position in the sequence. The 128move the iterator forward to the next position in the sequence. The
@@ -121,9 +138,13 @@ complete. Here's the example version:
121 return spos; 138 return spos;
122 } 139 }
123 140
124The stop() function is called when iteration is complete; its job, of 141The stop() function closes a session; its job, of course, is to clean
125course, is to clean up. If dynamic memory is allocated for the iterator, 142up. If dynamic memory is allocated for the iterator, stop() is the
126stop() is the place to free it. 143place to free it; if a lock was taken by start(), stop() must release
144that lock. The value that *pos was set to by the last next() call
145before stop() is remembered, and used for the first start() call of
146the next session unless lseek() has been called on the file; in that
147case next start() will be asked to start at position zero.
127 148
128 static void ct_seq_stop(struct seq_file *s, void *v) 149 static void ct_seq_stop(struct seq_file *s, void *v)
129 { 150 {
diff --git a/fs/seq_file.c b/fs/seq_file.c
index 4cc090b50cc5..1dea7a8a5255 100644
--- a/fs/seq_file.c
+++ b/fs/seq_file.c
@@ -90,23 +90,22 @@ EXPORT_SYMBOL(seq_open);
90 90
91static int traverse(struct seq_file *m, loff_t offset) 91static int traverse(struct seq_file *m, loff_t offset)
92{ 92{
93 loff_t pos = 0, index; 93 loff_t pos = 0;
94 int error = 0; 94 int error = 0;
95 void *p; 95 void *p;
96 96
97 m->version = 0; 97 m->version = 0;
98 index = 0; 98 m->index = 0;
99 m->count = m->from = 0; 99 m->count = m->from = 0;
100 if (!offset) { 100 if (!offset)
101 m->index = index;
102 return 0; 101 return 0;
103 } 102
104 if (!m->buf) { 103 if (!m->buf) {
105 m->buf = seq_buf_alloc(m->size = PAGE_SIZE); 104 m->buf = seq_buf_alloc(m->size = PAGE_SIZE);
106 if (!m->buf) 105 if (!m->buf)
107 return -ENOMEM; 106 return -ENOMEM;
108 } 107 }
109 p = m->op->start(m, &index); 108 p = m->op->start(m, &m->index);
110 while (p) { 109 while (p) {
111 error = PTR_ERR(p); 110 error = PTR_ERR(p);
112 if (IS_ERR(p)) 111 if (IS_ERR(p))
@@ -123,20 +122,15 @@ static int traverse(struct seq_file *m, loff_t offset)
123 if (pos + m->count > offset) { 122 if (pos + m->count > offset) {
124 m->from = offset - pos; 123 m->from = offset - pos;
125 m->count -= m->from; 124 m->count -= m->from;
126 m->index = index;
127 break; 125 break;
128 } 126 }
129 pos += m->count; 127 pos += m->count;
130 m->count = 0; 128 m->count = 0;
131 if (pos == offset) { 129 p = m->op->next(m, p, &m->index);
132 index++; 130 if (pos == offset)
133 m->index = index;
134 break; 131 break;
135 }
136 p = m->op->next(m, p, &index);
137 } 132 }
138 m->op->stop(m, p); 133 m->op->stop(m, p);
139 m->index = index;
140 return error; 134 return error;
141 135
142Eoverflow: 136Eoverflow:
@@ -160,7 +154,6 @@ ssize_t seq_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
160{ 154{
161 struct seq_file *m = file->private_data; 155 struct seq_file *m = file->private_data;
162 size_t copied = 0; 156 size_t copied = 0;
163 loff_t pos;
164 size_t n; 157 size_t n;
165 void *p; 158 void *p;
166 int err = 0; 159 int err = 0;
@@ -223,16 +216,12 @@ ssize_t seq_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
223 size -= n; 216 size -= n;
224 buf += n; 217 buf += n;
225 copied += n; 218 copied += n;
226 if (!m->count) {
227 m->from = 0;
228 m->index++;
229 }
230 if (!size) 219 if (!size)
231 goto Done; 220 goto Done;
232 } 221 }
233 /* we need at least one record in buffer */ 222 /* we need at least one record in buffer */
234 pos = m->index; 223 m->from = 0;
235 p = m->op->start(m, &pos); 224 p = m->op->start(m, &m->index);
236 while (1) { 225 while (1) {
237 err = PTR_ERR(p); 226 err = PTR_ERR(p);
238 if (!p || IS_ERR(p)) 227 if (!p || IS_ERR(p))
@@ -243,8 +232,7 @@ ssize_t seq_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
243 if (unlikely(err)) 232 if (unlikely(err))
244 m->count = 0; 233 m->count = 0;
245 if (unlikely(!m->count)) { 234 if (unlikely(!m->count)) {
246 p = m->op->next(m, p, &pos); 235 p = m->op->next(m, p, &m->index);
247 m->index = pos;
248 continue; 236 continue;
249 } 237 }
250 if (m->count < m->size) 238 if (m->count < m->size)
@@ -256,29 +244,33 @@ ssize_t seq_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
256 if (!m->buf) 244 if (!m->buf)
257 goto Enomem; 245 goto Enomem;
258 m->version = 0; 246 m->version = 0;
259 pos = m->index; 247 p = m->op->start(m, &m->index);
260 p = m->op->start(m, &pos);
261 } 248 }
262 m->op->stop(m, p); 249 m->op->stop(m, p);
263 m->count = 0; 250 m->count = 0;
264 goto Done; 251 goto Done;
265Fill: 252Fill:
266 /* they want more? let's try to get some more */ 253 /* they want more? let's try to get some more */
267 while (m->count < size) { 254 while (1) {
268 size_t offs = m->count; 255 size_t offs = m->count;
269 loff_t next = pos; 256 loff_t pos = m->index;
270 p = m->op->next(m, p, &next); 257
258 p = m->op->next(m, p, &m->index);
259 if (pos == m->index)
260 /* Buggy ->next function */
261 m->index++;
271 if (!p || IS_ERR(p)) { 262 if (!p || IS_ERR(p)) {
272 err = PTR_ERR(p); 263 err = PTR_ERR(p);
273 break; 264 break;
274 } 265 }
266 if (m->count >= size)
267 break;
275 err = m->op->show(m, p); 268 err = m->op->show(m, p);
276 if (seq_has_overflowed(m) || err) { 269 if (seq_has_overflowed(m) || err) {
277 m->count = offs; 270 m->count = offs;
278 if (likely(err <= 0)) 271 if (likely(err <= 0))
279 break; 272 break;
280 } 273 }
281 pos = next;
282 } 274 }
283 m->op->stop(m, p); 275 m->op->stop(m, p);
284 n = min(m->count, size); 276 n = min(m->count, size);
@@ -287,11 +279,7 @@ Fill:
287 goto Efault; 279 goto Efault;
288 copied += n; 280 copied += n;
289 m->count -= n; 281 m->count -= n;
290 if (m->count) 282 m->from = n;
291 m->from = n;
292 else
293 pos++;
294 m->index = pos;
295Done: 283Done:
296 if (!copied) 284 if (!copied)
297 copied = err; 285 copied = err;