Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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>

authored by

NeilBrown and committed by
Linus Torvalds
1f4aace6 4cdfffc8

+63 -54
+42 -21
Documentation/filesystems/seq_file.txt
··· 66 66 67 67 The iterator interface 68 68 69 - Modules implementing a virtual file with seq_file must implement a simple 70 - iterator object that allows stepping through the data of interest. 71 - Iterators must be able to move to a specific position - like the file they 72 - implement - but the interpretation of that position is up to the iterator 73 - itself. A seq_file implementation that is formatting firewall rules, for 74 - example, could interpret position N as the Nth rule in the chain. 75 - Positioning can thus be done in whatever way makes the most sense for the 76 - generator of the data, which need not be aware of how a position translates 77 - to an offset in the virtual file. The one obvious exception is that a 78 - position of zero should indicate the beginning of the file. 69 + Modules implementing a virtual file with seq_file must implement an 70 + iterator object that allows stepping through the data of interest 71 + during a "session" (roughly one read() system call). If the iterator 72 + is able to move to a specific position - like the file they implement, 73 + though with freedom to map the position number to a sequence location 74 + in whatever way is convenient - the iterator need only exist 75 + transiently during a session. If the iterator cannot easily find a 76 + numerical position but works well with a first/next interface, the 77 + iterator can be stored in the private data area and continue from one 78 + session to the next. 79 + 80 + A seq_file implementation that is formatting firewall rules from a 81 + table, for example, could provide a simple iterator that interprets 82 + position N as the Nth rule in the chain. A seq_file implementation 83 + that presents the content of a, potentially volatile, linked list 84 + might record a pointer into that list, providing that can be done 85 + without risk of the current location being removed. 86 + 87 + Positioning can thus be done in whatever way makes the most sense for 88 + the generator of the data, which need not be aware of how a position 89 + translates to an offset in the virtual file. The one obvious exception 90 + is that a position of zero should indicate the beginning of the file. 79 91 80 92 The /proc/sequence iterator just uses the count of the next number it 81 93 will output as its position. 82 94 83 - Four functions must be implemented to make the iterator work. The first, 84 - called start() takes a position as an argument and returns an iterator 85 - which will start reading at that position. For our simple sequence example, 95 + Four functions must be implemented to make the iterator work. The 96 + first, called start(), starts a session and takes a position as an 97 + argument, returning an iterator which will start reading at that 98 + position. The pos passed to start() will always be either zero, or 99 + the most recent pos used in the previous session. 100 + 101 + For our simple sequence example, 86 102 the start() function looks like: 87 103 88 104 static void *ct_seq_start(struct seq_file *s, loff_t *pos) ··· 117 101 "past end of file" condition and return NULL if need be. 118 102 119 103 For more complicated applications, the private field of the seq_file 120 - structure can be used. There is also a special value which can be returned 121 - by the start() function called SEQ_START_TOKEN; it can be used if you wish 122 - to instruct your show() function (described below) to print a header at the 123 - top of the output. SEQ_START_TOKEN should only be used if the offset is 124 - zero, however. 104 + structure can be used to hold state from session to session. There is 105 + also a special value which can be returned by the start() function 106 + called SEQ_START_TOKEN; it can be used if you wish to instruct your 107 + show() function (described below) to print a header at the top of the 108 + output. SEQ_START_TOKEN should only be used if the offset is zero, 109 + however. 125 110 126 111 The next function to implement is called, amazingly, next(); its job is to 127 112 move the iterator forward to the next position in the sequence. The ··· 138 121 return spos; 139 122 } 140 123 141 - The stop() function is called when iteration is complete; its job, of 142 - course, is to clean up. If dynamic memory is allocated for the iterator, 143 - stop() is the place to free it. 124 + The stop() function closes a session; its job, of course, is to clean 125 + up. If dynamic memory is allocated for the iterator, stop() is the 126 + place to free it; if a lock was taken by start(), stop() must release 127 + that lock. The value that *pos was set to by the last next() call 128 + before stop() is remembered, and used for the first start() call of 129 + the next session unless lseek() has been called on the file; in that 130 + case next start() will be asked to start at position zero. 144 131 145 132 static void ct_seq_stop(struct seq_file *s, void *v) 146 133 {
+21 -33
fs/seq_file.c
··· 90 90 91 91 static int traverse(struct seq_file *m, loff_t offset) 92 92 { 93 - loff_t pos = 0, index; 93 + loff_t pos = 0; 94 94 int error = 0; 95 95 void *p; 96 96 97 97 m->version = 0; 98 - index = 0; 98 + m->index = 0; 99 99 m->count = m->from = 0; 100 - if (!offset) { 101 - m->index = index; 100 + if (!offset) 102 101 return 0; 103 - } 102 + 104 103 if (!m->buf) { 105 104 m->buf = seq_buf_alloc(m->size = PAGE_SIZE); 106 105 if (!m->buf) 107 106 return -ENOMEM; 108 107 } 109 - p = m->op->start(m, &index); 108 + p = m->op->start(m, &m->index); 110 109 while (p) { 111 110 error = PTR_ERR(p); 112 111 if (IS_ERR(p)) ··· 122 123 if (pos + m->count > offset) { 123 124 m->from = offset - pos; 124 125 m->count -= m->from; 125 - m->index = index; 126 126 break; 127 127 } 128 128 pos += m->count; 129 129 m->count = 0; 130 - if (pos == offset) { 131 - index++; 132 - m->index = index; 130 + p = m->op->next(m, p, &m->index); 131 + if (pos == offset) 133 132 break; 134 - } 135 - p = m->op->next(m, p, &index); 136 133 } 137 134 m->op->stop(m, p); 138 - m->index = index; 139 135 return error; 140 136 141 137 Eoverflow: ··· 154 160 { 155 161 struct seq_file *m = file->private_data; 156 162 size_t copied = 0; 157 - loff_t pos; 158 163 size_t n; 159 164 void *p; 160 165 int err = 0; ··· 216 223 size -= n; 217 224 buf += n; 218 225 copied += n; 219 - if (!m->count) { 220 - m->from = 0; 221 - m->index++; 222 - } 223 226 if (!size) 224 227 goto Done; 225 228 } 226 229 /* we need at least one record in buffer */ 227 - pos = m->index; 228 - p = m->op->start(m, &pos); 230 + m->from = 0; 231 + p = m->op->start(m, &m->index); 229 232 while (1) { 230 233 err = PTR_ERR(p); 231 234 if (!p || IS_ERR(p)) ··· 232 243 if (unlikely(err)) 233 244 m->count = 0; 234 245 if (unlikely(!m->count)) { 235 - p = m->op->next(m, p, &pos); 236 - m->index = pos; 246 + p = m->op->next(m, p, &m->index); 237 247 continue; 238 248 } 239 249 if (m->count < m->size) ··· 244 256 if (!m->buf) 245 257 goto Enomem; 246 258 m->version = 0; 247 - pos = m->index; 248 - p = m->op->start(m, &pos); 259 + p = m->op->start(m, &m->index); 249 260 } 250 261 m->op->stop(m, p); 251 262 m->count = 0; 252 263 goto Done; 253 264 Fill: 254 265 /* they want more? let's try to get some more */ 255 - while (m->count < size) { 266 + while (1) { 256 267 size_t offs = m->count; 257 - loff_t next = pos; 258 - p = m->op->next(m, p, &next); 268 + loff_t pos = m->index; 269 + 270 + p = m->op->next(m, p, &m->index); 271 + if (pos == m->index) 272 + /* Buggy ->next function */ 273 + m->index++; 259 274 if (!p || IS_ERR(p)) { 260 275 err = PTR_ERR(p); 261 276 break; 262 277 } 278 + if (m->count >= size) 279 + break; 263 280 err = m->op->show(m, p); 264 281 if (seq_has_overflowed(m) || err) { 265 282 m->count = offs; 266 283 if (likely(err <= 0)) 267 284 break; 268 285 } 269 - pos = next; 270 286 } 271 287 m->op->stop(m, p); 272 288 n = min(m->count, size); ··· 279 287 goto Efault; 280 288 copied += n; 281 289 m->count -= n; 282 - if (m->count) 283 - m->from = n; 284 - else 285 - pos++; 286 - m->index = pos; 290 + m->from = n; 287 291 Done: 288 292 if (!copied) 289 293 copied = err;