2005-04-17 02:20:36 +04:00
/*
* 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 , struct seq_operations * op )
{
struct seq_file * p = kmalloc ( sizeof ( * p ) , GFP_KERNEL ) ;
if ( ! p )
return - ENOMEM ;
memset ( p , 0 , sizeof ( * p ) ) ;
sema_init ( & p - > sem , 1 ) ;
p - > op = op ;
file - > private_data = p ;
/*
* 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 .
2005-05-01 19:59:26 +04:00
* @ 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
2005-04-17 02:20:36 +04:00
*
* 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 ;
down ( & m - > sem ) ;
/*
* 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 ;
up ( & m - > sem ) ;
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 ;
int error = 0 ;
void * p ;
m - > version = 0 ;
m - > index = 0 ;
m - > count = m - > from = 0 ;
if ( ! offset )
return 0 ;
if ( ! m - > buf ) {
m - > buf = kmalloc ( m - > size = PAGE_SIZE , GFP_KERNEL ) ;
if ( ! m - > buf )
return - ENOMEM ;
}
p = m - > op - > start ( m , & 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 ;
break ;
}
pos + = m - > count ;
m - > count = 0 ;
if ( pos = = offset ) {
m - > index + + ;
break ;
}
p = m - > op - > next ( m , p , & m - > 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 .
2005-05-01 19:59:26 +04:00
* @ file : the file in question
* @ offset : new position
* @ origin : 0 for absolute , 1 for relative position
2005-04-17 02:20:36 +04:00
*
* 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 ;
down ( & m - > sem ) ;
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 ;
}
}
}
up ( & m - > sem ) ;
file - > f_version = m - > version ;
return retval ;
}
EXPORT_SYMBOL ( seq_lseek ) ;
/**
* seq_release - free the structures associated with sequential file .
* @ file : file in question
* @ inode : file - > f_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 )
{
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 ) ;