2005-04-16 15:20:36 -07:00
/*
* Copyright ( C ) 2002 - 2003 Jeff Dike ( jdike @ addtoit . com )
* Licensed under the GPL
*/
2005-05-01 08:58:54 -07:00
# include "linux/compiler.h"
2005-04-16 15:20:36 -07:00
# include "linux/stddef.h"
# include "linux/kernel.h"
# include "linux/string.h"
# include "linux/fs.h"
# include "linux/highmem.h"
# include "asm/page.h"
# include "asm/pgtable.h"
# include "asm/uaccess.h"
# include "kern_util.h"
# include "user_util.h"
extern void * um_virt_to_phys ( struct task_struct * task , unsigned long addr ,
pte_t * pte_out ) ;
static unsigned long maybe_map ( unsigned long virt , int is_write )
{
pte_t pte ;
int err ;
void * phys = um_virt_to_phys ( current , virt , & pte ) ;
int dummy_code ;
if ( IS_ERR ( phys ) | | ( is_write & & ! pte_write ( pte ) ) ) {
err = handle_page_fault ( virt , 0 , is_write , 1 , & dummy_code ) ;
if ( err )
2005-05-06 21:30:55 -07:00
return ( - 1UL ) ;
2005-04-16 15:20:36 -07:00
phys = um_virt_to_phys ( current , virt , NULL ) ;
}
2005-05-06 21:30:55 -07:00
if ( IS_ERR ( phys ) )
phys = ( void * ) - 1 ;
2005-04-16 15:20:36 -07:00
return ( ( unsigned long ) phys ) ;
}
static int do_op ( unsigned long addr , int len , int is_write ,
int ( * op ) ( unsigned long addr , int len , void * arg ) , void * arg )
{
struct page * page ;
int n ;
addr = maybe_map ( addr , is_write ) ;
2005-05-06 21:30:55 -07:00
if ( addr = = - 1UL )
2005-04-16 15:20:36 -07:00
return ( - 1 ) ;
page = phys_to_page ( addr ) ;
addr = ( unsigned long ) kmap ( page ) + ( addr & ~ PAGE_MASK ) ;
n = ( * op ) ( addr , len , arg ) ;
kunmap ( page ) ;
return ( n ) ;
}
static void do_buffer_op ( void * jmpbuf , void * arg_ptr )
{
va_list args ;
unsigned long addr ;
int len , is_write , size , remain , n ;
int ( * op ) ( unsigned long , int , void * ) ;
void * arg ;
int * res ;
2005-05-01 08:58:54 -07:00
va_copy ( args , * ( va_list * ) arg_ptr ) ;
2005-04-16 15:20:36 -07:00
addr = va_arg ( args , unsigned long ) ;
len = va_arg ( args , int ) ;
is_write = va_arg ( args , int ) ;
op = va_arg ( args , void * ) ;
arg = va_arg ( args , void * ) ;
res = va_arg ( args , int * ) ;
va_end ( args ) ;
size = min ( PAGE_ALIGN ( addr ) - addr , ( unsigned long ) len ) ;
remain = len ;
current - > thread . fault_catcher = jmpbuf ;
n = do_op ( addr , size , is_write , op , arg ) ;
if ( n ! = 0 ) {
* res = ( n < 0 ? remain : 0 ) ;
goto out ;
}
addr + = size ;
remain - = size ;
if ( remain = = 0 ) {
* res = 0 ;
goto out ;
}
while ( addr < ( ( addr + remain ) & PAGE_MASK ) ) {
n = do_op ( addr , PAGE_SIZE , is_write , op , arg ) ;
if ( n ! = 0 ) {
* res = ( n < 0 ? remain : 0 ) ;
goto out ;
}
addr + = PAGE_SIZE ;
remain - = PAGE_SIZE ;
}
if ( remain = = 0 ) {
* res = 0 ;
goto out ;
}
n = do_op ( addr , remain , is_write , op , arg ) ;
if ( n ! = 0 )
* res = ( n < 0 ? remain : 0 ) ;
else * res = 0 ;
out :
current - > thread . fault_catcher = NULL ;
}
static int buffer_op ( unsigned long addr , int len , int is_write ,
int ( * op ) ( unsigned long addr , int len , void * arg ) ,
void * arg )
{
int faulted , res ;
faulted = setjmp_wrapper ( do_buffer_op , addr , len , is_write , op , arg ,
& res ) ;
if ( ! faulted )
return ( res ) ;
return ( addr + len - ( unsigned long ) current - > thread . fault_addr ) ;
}
static int copy_chunk_from_user ( unsigned long from , int len , void * arg )
{
unsigned long * to_ptr = arg , to = * to_ptr ;
memcpy ( ( void * ) to , ( void * ) from , len ) ;
* to_ptr + = len ;
return ( 0 ) ;
}
int copy_from_user_skas ( void * to , const void __user * from , int n )
{
if ( segment_eq ( get_fs ( ) , KERNEL_DS ) ) {
memcpy ( to , ( __force void * ) from , n ) ;
return ( 0 ) ;
}
return ( access_ok_skas ( VERIFY_READ , from , n ) ?
buffer_op ( ( unsigned long ) from , n , 0 , copy_chunk_from_user , & to ) :
n ) ;
}
static int copy_chunk_to_user ( unsigned long to , int len , void * arg )
{
unsigned long * from_ptr = arg , from = * from_ptr ;
memcpy ( ( void * ) to , ( void * ) from , len ) ;
* from_ptr + = len ;
return ( 0 ) ;
}
int copy_to_user_skas ( void __user * to , const void * from , int n )
{
if ( segment_eq ( get_fs ( ) , KERNEL_DS ) ) {
memcpy ( ( __force void * ) to , from , n ) ;
return ( 0 ) ;
}
return ( access_ok_skas ( VERIFY_WRITE , to , n ) ?
buffer_op ( ( unsigned long ) to , n , 1 , copy_chunk_to_user , & from ) :
n ) ;
}
static int strncpy_chunk_from_user ( unsigned long from , int len , void * arg )
{
char * * to_ptr = arg , * to = * to_ptr ;
int n ;
strncpy ( to , ( void * ) from , len ) ;
n = strnlen ( to , len ) ;
* to_ptr + = n ;
if ( n < len )
return ( 1 ) ;
return ( 0 ) ;
}
int strncpy_from_user_skas ( char * dst , const char __user * src , int count )
{
int n ;
char * ptr = dst ;
if ( segment_eq ( get_fs ( ) , KERNEL_DS ) ) {
strncpy ( dst , ( __force void * ) src , count ) ;
return ( strnlen ( dst , count ) ) ;
}
if ( ! access_ok_skas ( VERIFY_READ , src , 1 ) )
return ( - EFAULT ) ;
n = buffer_op ( ( unsigned long ) src , count , 0 , strncpy_chunk_from_user ,
& ptr ) ;
if ( n ! = 0 )
return ( - EFAULT ) ;
return ( strnlen ( dst , count ) ) ;
}
static int clear_chunk ( unsigned long addr , int len , void * unused )
{
memset ( ( void * ) addr , 0 , len ) ;
return ( 0 ) ;
}
int __clear_user_skas ( void __user * mem , int len )
{
return ( buffer_op ( ( unsigned long ) mem , len , 1 , clear_chunk , NULL ) ) ;
}
int clear_user_skas ( void __user * mem , int len )
{
if ( segment_eq ( get_fs ( ) , KERNEL_DS ) ) {
memset ( ( __force void * ) mem , 0 , len ) ;
return ( 0 ) ;
}
return ( access_ok_skas ( VERIFY_WRITE , mem , len ) ?
buffer_op ( ( unsigned long ) mem , len , 1 , clear_chunk , NULL ) : len ) ;
}
static int strnlen_chunk ( unsigned long str , int len , void * arg )
{
int * len_ptr = arg , n ;
n = strnlen ( ( void * ) str , len ) ;
* len_ptr + = n ;
if ( n < len )
return ( 1 ) ;
return ( 0 ) ;
}
int strnlen_user_skas ( const void __user * str , int len )
{
int count = 0 , n ;
if ( segment_eq ( get_fs ( ) , KERNEL_DS ) )
return ( strnlen ( ( __force char * ) str , len ) + 1 ) ;
n = buffer_op ( ( unsigned long ) str , len , 0 , strnlen_chunk , & count ) ;
if ( n = = 0 )
return ( count + 1 ) ;
return ( - EFAULT ) ;
}
/*
* Overrides for Emacs so that we follow Linus ' s tabbing style .
* Emacs will notice this stuff at the end of the file and automatically
* adjust the settings for this buffer only . This must remain at the end
* of the file .
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* Local variables :
* c - file - style : " linux "
* End :
*/