diff options
Diffstat (limited to 'fs/seq_file.c')
-rw-r--r-- | fs/seq_file.c | 151 |
1 files changed, 90 insertions, 61 deletions
diff --git a/fs/seq_file.c b/fs/seq_file.c index b569ff1c4dc8..a1a4cfe19210 100644 --- a/fs/seq_file.c +++ b/fs/seq_file.c @@ -48,12 +48,78 @@ int seq_open(struct file *file, const struct seq_operations *op) */ file->f_version = 0; - /* SEQ files support lseek, but not pread/pwrite */ - file->f_mode &= ~(FMODE_PREAD | FMODE_PWRITE); + /* + * seq_files support lseek() and pread(). They do not implement + * write() at all, but we clear FMODE_PWRITE here for historical + * reasons. + * + * If a client of seq_files a) implements file.write() and b) wishes to + * support pwrite() then that client will need to implement its own + * file.open() which calls seq_open() and then sets FMODE_PWRITE. + */ + file->f_mode &= ~FMODE_PWRITE; return 0; } EXPORT_SYMBOL(seq_open); +static int traverse(struct seq_file *m, loff_t offset) +{ + loff_t pos = 0, index; + int error = 0; + void *p; + + m->version = 0; + index = 0; + m->count = m->from = 0; + if (!offset) { + m->index = index; + return 0; + } + if (!m->buf) { + m->buf = kmalloc(m->size = PAGE_SIZE, GFP_KERNEL); + if (!m->buf) + return -ENOMEM; + } + p = m->op->start(m, &index); + while (p) { + error = PTR_ERR(p); + if (IS_ERR(p)) + break; + error = m->op->show(m, p); + if (error < 0) + break; + if (unlikely(error)) { + error = 0; + m->count = 0; + } + if (m->count == m->size) + goto Eoverflow; + if (pos + m->count > offset) { + m->from = offset - pos; + m->count -= m->from; + m->index = index; + break; + } + pos += m->count; + m->count = 0; + if (pos == offset) { + index++; + m->index = index; + break; + } + p = m->op->next(m, p, &index); + } + m->op->stop(m, p); + m->index = index; + return error; + +Eoverflow: + m->op->stop(m, p); + kfree(m->buf); + m->buf = kmalloc(m->size <<= 1, GFP_KERNEL); + return !m->buf ? -ENOMEM : -EAGAIN; +} + /** * seq_read - ->read() method for sequential files. * @file: the file to read from @@ -73,6 +139,22 @@ ssize_t seq_read(struct file *file, char __user *buf, size_t size, loff_t *ppos) int err = 0; mutex_lock(&m->lock); + + /* Don't assume *ppos is where we left it */ + if (unlikely(*ppos != m->read_pos)) { + m->read_pos = *ppos; + while ((err = traverse(m, *ppos)) == -EAGAIN) + ; + if (err) { + /* With prejudice... */ + m->read_pos = 0; + m->version = 0; + m->index = 0; + m->count = 0; + goto Done; + } + } + /* * seq_file->op->..m_start/m_stop/m_next may do special actions * or optimisations based on the file->f_version, so we want to @@ -172,8 +254,10 @@ Fill: Done: if (!copied) copied = err; - else + else { *ppos += copied; + m->read_pos += copied; + } file->f_version = m->version; mutex_unlock(&m->lock); return copied; @@ -186,63 +270,6 @@ Efault: } EXPORT_SYMBOL(seq_read); -static int traverse(struct seq_file *m, loff_t offset) -{ - loff_t pos = 0, index; - int error = 0; - void *p; - - m->version = 0; - index = 0; - m->count = m->from = 0; - if (!offset) { - m->index = index; - return 0; - } - if (!m->buf) { - m->buf = kmalloc(m->size = PAGE_SIZE, GFP_KERNEL); - if (!m->buf) - return -ENOMEM; - } - p = m->op->start(m, &index); - while (p) { - error = PTR_ERR(p); - if (IS_ERR(p)) - break; - error = m->op->show(m, p); - if (error < 0) - break; - if (unlikely(error)) { - error = 0; - m->count = 0; - } - if (m->count == m->size) - goto Eoverflow; - if (pos + m->count > offset) { - m->from = offset - pos; - m->count -= m->from; - m->index = index; - break; - } - pos += m->count; - m->count = 0; - if (pos == offset) { - index++; - m->index = index; - break; - } - p = m->op->next(m, p, &index); - } - m->op->stop(m, p); - return error; - -Eoverflow: - m->op->stop(m, p); - kfree(m->buf); - m->buf = kmalloc(m->size <<= 1, GFP_KERNEL); - return !m->buf ? -ENOMEM : -EAGAIN; -} - /** * seq_lseek - ->llseek() method for sequential files. * @file: the file in question @@ -265,16 +292,18 @@ loff_t seq_lseek(struct file *file, loff_t offset, int origin) if (offset < 0) break; retval = offset; - if (offset != file->f_pos) { + if (offset != m->read_pos) { while ((retval=traverse(m, offset)) == -EAGAIN) ; if (retval) { /* with extreme prejudice... */ file->f_pos = 0; + m->read_pos = 0; m->version = 0; m->index = 0; m->count = 0; } else { + m->read_pos = offset; retval = file->f_pos = offset; } } |