mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-21 16:29:59 +00:00
![Alexey Dobriyan](/assets/img/avatar_default.png)
Original problem: in some circumstances seq_file interface can present infinite proc file to the following script when normally said proc file is finite: while read line; do [do something with $line] done </proc/$FILE bash, to implement such loop does essentially read(0, buf, 128); [find \n] lseek(0, -difference, SEEK_CUR); Consider, proc file prints list of objects each of them consists of many lines, each line is shorter than 128 bytes. Two objects in list, with ->index'es being 0 and 1. Current one is 1, as bash prints second object line by line. Imagine first object being removed right before lseek(). traverse() will be called, because there is negative offset. traverse() will reset ->index to 0 (!). traverse() will call ->next() and get NULL in any usual iterate-over-list code using list_for_each_entry_continue() and such. There is one object in list now after all... traverse() will return 0, lseek() will update file position and pretend everything is OK. So, what we have now: ->f_pos points to place where second object will be printed, but ->index is 0. seq_read() instead of returning EOF, will start printing first line of first object every time it's called, until enough objects are added to ->f_pos return in bounds. Fix is to update ->index only after we're sure we saw enough objects down the road. Signed-off-by: Alexey Dobriyan <adobriyan@sw.ru> Cc: Al Viro <viro@zeniv.linux.org.uk> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
487 lines
10 KiB
C
487 lines
10 KiB
C
/*
|
|
* linux/fs/seq_file.c
|
|
*
|
|
* helper functions for making synthetic files from sequences of records.
|
|
* initial implementation -- AV, Oct 2001.
|
|
*/
|
|
|
|
#include <linux/fs.h>
|
|
#include <linux/module.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/slab.h>
|
|
|
|
#include <asm/uaccess.h>
|
|
#include <asm/page.h>
|
|
|
|
/**
|
|
* seq_open - initialize sequential file
|
|
* @file: file we initialize
|
|
* @op: method table describing the sequence
|
|
*
|
|
* seq_open() sets @file, associating it with a sequence described
|
|
* by @op. @op->start() sets the iterator up and returns the first
|
|
* element of sequence. @op->stop() shuts it down. @op->next()
|
|
* returns the next element of sequence. @op->show() prints element
|
|
* into the buffer. In case of error ->start() and ->next() return
|
|
* ERR_PTR(error). In the end of sequence they return %NULL. ->show()
|
|
* returns 0 in case of success and negative number in case of error.
|
|
*/
|
|
int seq_open(struct file *file, const struct seq_operations *op)
|
|
{
|
|
struct seq_file *p = file->private_data;
|
|
|
|
if (!p) {
|
|
p = kmalloc(sizeof(*p), GFP_KERNEL);
|
|
if (!p)
|
|
return -ENOMEM;
|
|
file->private_data = p;
|
|
}
|
|
memset(p, 0, sizeof(*p));
|
|
mutex_init(&p->lock);
|
|
p->op = op;
|
|
|
|
/*
|
|
* Wrappers around seq_open(e.g. swaps_open) need to be
|
|
* aware of this. If they set f_version themselves, they
|
|
* should call seq_open first and then set f_version.
|
|
*/
|
|
file->f_version = 0;
|
|
|
|
/* SEQ files support lseek, but not pread/pwrite */
|
|
file->f_mode &= ~(FMODE_PREAD | FMODE_PWRITE);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(seq_open);
|
|
|
|
/**
|
|
* seq_read - ->read() method for sequential files.
|
|
* @file: the file to read from
|
|
* @buf: the buffer to read to
|
|
* @size: the maximum number of bytes to read
|
|
* @ppos: the current position in the file
|
|
*
|
|
* Ready-made ->f_op->read()
|
|
*/
|
|
ssize_t seq_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
|
|
{
|
|
struct seq_file *m = (struct seq_file *)file->private_data;
|
|
size_t copied = 0;
|
|
loff_t pos;
|
|
size_t n;
|
|
void *p;
|
|
int err = 0;
|
|
|
|
mutex_lock(&m->lock);
|
|
/*
|
|
* 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
|
|
* pass the file->f_version to those methods.
|
|
*
|
|
* seq_file->version is just copy of f_version, and seq_file
|
|
* methods can treat it simply as file version.
|
|
* It is copied in first and copied out after all operations.
|
|
* It is convenient to have it as part of structure to avoid the
|
|
* need of passing another argument to all the seq_file methods.
|
|
*/
|
|
m->version = file->f_version;
|
|
/* grab buffer if we didn't have one */
|
|
if (!m->buf) {
|
|
m->buf = kmalloc(m->size = PAGE_SIZE, GFP_KERNEL);
|
|
if (!m->buf)
|
|
goto Enomem;
|
|
}
|
|
/* if not empty - flush it first */
|
|
if (m->count) {
|
|
n = min(m->count, size);
|
|
err = copy_to_user(buf, m->buf + m->from, n);
|
|
if (err)
|
|
goto Efault;
|
|
m->count -= n;
|
|
m->from += n;
|
|
size -= n;
|
|
buf += n;
|
|
copied += n;
|
|
if (!m->count)
|
|
m->index++;
|
|
if (!size)
|
|
goto Done;
|
|
}
|
|
/* we need at least one record in buffer */
|
|
while (1) {
|
|
pos = m->index;
|
|
p = m->op->start(m, &pos);
|
|
err = PTR_ERR(p);
|
|
if (!p || IS_ERR(p))
|
|
break;
|
|
err = m->op->show(m, p);
|
|
if (err)
|
|
break;
|
|
if (m->count < m->size)
|
|
goto Fill;
|
|
m->op->stop(m, p);
|
|
kfree(m->buf);
|
|
m->buf = kmalloc(m->size <<= 1, GFP_KERNEL);
|
|
if (!m->buf)
|
|
goto Enomem;
|
|
m->count = 0;
|
|
m->version = 0;
|
|
}
|
|
m->op->stop(m, p);
|
|
m->count = 0;
|
|
goto Done;
|
|
Fill:
|
|
/* they want more? let's try to get some more */
|
|
while (m->count < size) {
|
|
size_t offs = m->count;
|
|
loff_t next = pos;
|
|
p = m->op->next(m, p, &next);
|
|
if (!p || IS_ERR(p)) {
|
|
err = PTR_ERR(p);
|
|
break;
|
|
}
|
|
err = m->op->show(m, p);
|
|
if (err || m->count == m->size) {
|
|
m->count = offs;
|
|
break;
|
|
}
|
|
pos = next;
|
|
}
|
|
m->op->stop(m, p);
|
|
n = min(m->count, size);
|
|
err = copy_to_user(buf, m->buf, n);
|
|
if (err)
|
|
goto Efault;
|
|
copied += n;
|
|
m->count -= n;
|
|
if (m->count)
|
|
m->from = n;
|
|
else
|
|
pos++;
|
|
m->index = pos;
|
|
Done:
|
|
if (!copied)
|
|
copied = err;
|
|
else
|
|
*ppos += copied;
|
|
file->f_version = m->version;
|
|
mutex_unlock(&m->lock);
|
|
return copied;
|
|
Enomem:
|
|
err = -ENOMEM;
|
|
goto Done;
|
|
Efault:
|
|
err = -EFAULT;
|
|
goto Done;
|
|
}
|
|
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)
|
|
break;
|
|
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
|
|
* @offset: new position
|
|
* @origin: 0 for absolute, 1 for relative position
|
|
*
|
|
* Ready-made ->f_op->llseek()
|
|
*/
|
|
loff_t seq_lseek(struct file *file, loff_t offset, int origin)
|
|
{
|
|
struct seq_file *m = (struct seq_file *)file->private_data;
|
|
long long retval = -EINVAL;
|
|
|
|
mutex_lock(&m->lock);
|
|
m->version = file->f_version;
|
|
switch (origin) {
|
|
case 1:
|
|
offset += file->f_pos;
|
|
case 0:
|
|
if (offset < 0)
|
|
break;
|
|
retval = offset;
|
|
if (offset != file->f_pos) {
|
|
while ((retval=traverse(m, offset)) == -EAGAIN)
|
|
;
|
|
if (retval) {
|
|
/* with extreme prejudice... */
|
|
file->f_pos = 0;
|
|
m->version = 0;
|
|
m->index = 0;
|
|
m->count = 0;
|
|
} else {
|
|
retval = file->f_pos = offset;
|
|
}
|
|
}
|
|
}
|
|
file->f_version = m->version;
|
|
mutex_unlock(&m->lock);
|
|
return retval;
|
|
}
|
|
EXPORT_SYMBOL(seq_lseek);
|
|
|
|
/**
|
|
* seq_release - free the structures associated with sequential file.
|
|
* @file: file in question
|
|
* @inode: file->f_path.dentry->d_inode
|
|
*
|
|
* Frees the structures associated with sequential file; can be used
|
|
* as ->f_op->release() if you don't have private data to destroy.
|
|
*/
|
|
int seq_release(struct inode *inode, struct file *file)
|
|
{
|
|
struct seq_file *m = (struct seq_file *)file->private_data;
|
|
kfree(m->buf);
|
|
kfree(m);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(seq_release);
|
|
|
|
/**
|
|
* seq_escape - print string into buffer, escaping some characters
|
|
* @m: target buffer
|
|
* @s: string
|
|
* @esc: set of characters that need escaping
|
|
*
|
|
* Puts string into buffer, replacing each occurrence of character from
|
|
* @esc with usual octal escape. Returns 0 in case of success, -1 - in
|
|
* case of overflow.
|
|
*/
|
|
int seq_escape(struct seq_file *m, const char *s, const char *esc)
|
|
{
|
|
char *end = m->buf + m->size;
|
|
char *p;
|
|
char c;
|
|
|
|
for (p = m->buf + m->count; (c = *s) != '\0' && p < end; s++) {
|
|
if (!strchr(esc, c)) {
|
|
*p++ = c;
|
|
continue;
|
|
}
|
|
if (p + 3 < end) {
|
|
*p++ = '\\';
|
|
*p++ = '0' + ((c & 0300) >> 6);
|
|
*p++ = '0' + ((c & 070) >> 3);
|
|
*p++ = '0' + (c & 07);
|
|
continue;
|
|
}
|
|
m->count = m->size;
|
|
return -1;
|
|
}
|
|
m->count = p - m->buf;
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(seq_escape);
|
|
|
|
int seq_printf(struct seq_file *m, const char *f, ...)
|
|
{
|
|
va_list args;
|
|
int len;
|
|
|
|
if (m->count < m->size) {
|
|
va_start(args, f);
|
|
len = vsnprintf(m->buf + m->count, m->size - m->count, f, args);
|
|
va_end(args);
|
|
if (m->count + len < m->size) {
|
|
m->count += len;
|
|
return 0;
|
|
}
|
|
}
|
|
m->count = m->size;
|
|
return -1;
|
|
}
|
|
EXPORT_SYMBOL(seq_printf);
|
|
|
|
int seq_path(struct seq_file *m,
|
|
struct vfsmount *mnt, struct dentry *dentry,
|
|
char *esc)
|
|
{
|
|
if (m->count < m->size) {
|
|
char *s = m->buf + m->count;
|
|
char *p = d_path(dentry, mnt, s, m->size - m->count);
|
|
if (!IS_ERR(p)) {
|
|
while (s <= p) {
|
|
char c = *p++;
|
|
if (!c) {
|
|
p = m->buf + m->count;
|
|
m->count = s - m->buf;
|
|
return s - p;
|
|
} else if (!strchr(esc, c)) {
|
|
*s++ = c;
|
|
} else if (s + 4 > p) {
|
|
break;
|
|
} else {
|
|
*s++ = '\\';
|
|
*s++ = '0' + ((c & 0300) >> 6);
|
|
*s++ = '0' + ((c & 070) >> 3);
|
|
*s++ = '0' + (c & 07);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
m->count = m->size;
|
|
return -1;
|
|
}
|
|
EXPORT_SYMBOL(seq_path);
|
|
|
|
static void *single_start(struct seq_file *p, loff_t *pos)
|
|
{
|
|
return NULL + (*pos == 0);
|
|
}
|
|
|
|
static void *single_next(struct seq_file *p, void *v, loff_t *pos)
|
|
{
|
|
++*pos;
|
|
return NULL;
|
|
}
|
|
|
|
static void single_stop(struct seq_file *p, void *v)
|
|
{
|
|
}
|
|
|
|
int single_open(struct file *file, int (*show)(struct seq_file *, void *),
|
|
void *data)
|
|
{
|
|
struct seq_operations *op = kmalloc(sizeof(*op), GFP_KERNEL);
|
|
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);
|
|
}
|
|
return res;
|
|
}
|
|
EXPORT_SYMBOL(single_open);
|
|
|
|
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;
|
|
|
|
kfree(seq->private);
|
|
seq->private = NULL;
|
|
return seq_release(inode, file);
|
|
}
|
|
EXPORT_SYMBOL(seq_release_private);
|
|
|
|
int seq_putc(struct seq_file *m, char c)
|
|
{
|
|
if (m->count < m->size) {
|
|
m->buf[m->count++] = c;
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
EXPORT_SYMBOL(seq_putc);
|
|
|
|
int seq_puts(struct seq_file *m, const char *s)
|
|
{
|
|
int len = strlen(s);
|
|
if (m->count + len < m->size) {
|
|
memcpy(m->buf + m->count, s, len);
|
|
m->count += len;
|
|
return 0;
|
|
}
|
|
m->count = m->size;
|
|
return -1;
|
|
}
|
|
EXPORT_SYMBOL(seq_puts);
|
|
|
|
struct list_head *seq_list_start(struct list_head *head, loff_t pos)
|
|
{
|
|
struct list_head *lh;
|
|
|
|
list_for_each(lh, head)
|
|
if (pos-- == 0)
|
|
return lh;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
EXPORT_SYMBOL(seq_list_start);
|
|
|
|
struct list_head *seq_list_start_head(struct list_head *head, loff_t pos)
|
|
{
|
|
if (!pos)
|
|
return head;
|
|
|
|
return seq_list_start(head, pos - 1);
|
|
}
|
|
|
|
EXPORT_SYMBOL(seq_list_start_head);
|
|
|
|
struct list_head *seq_list_next(void *v, struct list_head *head, loff_t *ppos)
|
|
{
|
|
struct list_head *lh;
|
|
|
|
lh = ((struct list_head *)v)->next;
|
|
++*ppos;
|
|
return lh == head ? NULL : lh;
|
|
}
|
|
|
|
EXPORT_SYMBOL(seq_list_next);
|