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() 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;
}
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);
/*
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
* 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 */
pos
= m->index;
p =
m->op->start(m, &pos);
while
(1) {
err
= PTR_ERR(p);
if
(!p || IS_ERR(p))
break;
err =
m->op->show(m, p);
if
(err < 0)
break;
if
(unlikely(err))
m->count
= 0;
if
(unlikely(!m->count)) {
p =
m->op->next(m, p, &pos);
m->index
= pos;
continue;
}
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;
pos
= m->index;
p =
m->op->start(m, &pos);
}
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
(m->count == m->size || err) {
m->count
= offs;
if
(likely(err <= 0))
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;
m->read_pos
+= copied;
}
file->f_version
= m->version;
mutex_unlock(&m->lock);
return
copied;
Enomem:
err
= -ENOMEM;
goto
Done;
Efault:
err
= -EFAULT;
goto
Done;
}