2005-04-17 02:20:36 +04:00
/*
* linux / ipc / shm . c
* Copyright ( C ) 1992 , 1993 Krishna Balasubramanian
* Many improvements / fixes by Bruno Haible .
* Replaced ` struct shm_desc ' by ` struct vm_area_struct ' , July 1994.
* Fixed the shm swap deallocation ( shm_unuse ( ) ) , August 1998 Andrea Arcangeli .
*
* / proc / sysvipc / shm support ( c ) 1999 Dragos Acostachioaie < dragos @ iname . com >
* BIGMEM support , Andrea Arcangeli < andrea @ suse . de >
* SMP thread shm , Jean - Luc Boyard < jean - luc . boyard @ siemens . fr >
* HIGHMEM support , Ingo Molnar < mingo @ redhat . com >
* Make shmmax , shmall , shmmni sysctl ' able , Christoph Rohland < cr @ sap . com >
* Shared / dev / zero support , Kanoj Sarcar < kanoj @ sgi . com >
* Move the mm functionality over to mm / shmem . c , Christoph Rohland < cr @ sap . com >
*
2006-04-03 01:07:33 +04:00
* support for audit of ipc object properties and permission changes
* Dustin Kirkland < dustin . kirkland @ us . ibm . com >
2006-10-02 13:18:22 +04:00
*
* namespaces support
* OpenVZ , SWsoft Inc .
* Pavel Emelianov < xemul @ openvz . org >
2005-04-17 02:20:36 +04:00
*/
# include <linux/slab.h>
# include <linux/mm.h>
# include <linux/hugetlb.h>
# include <linux/shm.h>
# include <linux/init.h>
# include <linux/file.h>
# include <linux/mman.h>
# include <linux/shmem_fs.h>
# include <linux/security.h>
# include <linux/syscalls.h>
# include <linux/audit.h>
2006-01-11 23:17:46 +03:00
# include <linux/capability.h>
2005-05-01 19:59:12 +04:00
# include <linux/ptrace.h>
2005-09-07 02:17:10 +04:00
# include <linux/seq_file.h>
2006-03-26 13:37:17 +04:00
# include <linux/mutex.h>
2006-10-02 13:18:22 +04:00
# include <linux/nsproxy.h>
2007-02-21 00:57:53 +03:00
# include <linux/mount.h>
2005-05-01 19:59:12 +04:00
2005-04-17 02:20:36 +04:00
# include <asm/uaccess.h>
# include "util.h"
2007-02-21 00:57:53 +03:00
struct shm_file_data {
int id ;
struct ipc_namespace * ns ;
struct file * file ;
const struct vm_operations_struct * vm_ops ;
} ;
# define shm_file_data(file) (*((struct shm_file_data **)&(file)->private_data))
2007-02-12 11:55:35 +03:00
static const struct file_operations shm_file_operations ;
2005-04-17 02:20:36 +04:00
static struct vm_operations_struct shm_vm_ops ;
2006-10-02 13:18:22 +04:00
static struct ipc_ids init_shm_ids ;
# define shm_ids(ns) (*((ns)->ids[IPC_SHM_IDS]))
2005-04-17 02:20:36 +04:00
2006-10-02 13:18:22 +04:00
# define shm_lock(ns, id) \
( ( struct shmid_kernel * ) ipc_lock ( & shm_ids ( ns ) , id ) )
# define shm_unlock(shp) \
ipc_unlock ( & ( shp ) - > shm_perm )
# define shm_get(ns, id) \
( ( struct shmid_kernel * ) ipc_get ( & shm_ids ( ns ) , id ) )
# define shm_buildid(ns, id, seq) \
ipc_buildid ( & shm_ids ( ns ) , id , seq )
2005-04-17 02:20:36 +04:00
2006-10-02 13:18:22 +04:00
static int newseg ( struct ipc_namespace * ns , key_t key ,
int shmflg , size_t size ) ;
2007-02-21 00:57:53 +03:00
static void shm_open ( struct vm_area_struct * vma ) ;
static void shm_close ( struct vm_area_struct * vma ) ;
2006-10-02 13:18:22 +04:00
static void shm_destroy ( struct ipc_namespace * ns , struct shmid_kernel * shp ) ;
2005-04-17 02:20:36 +04:00
# ifdef CONFIG_PROC_FS
2005-09-07 02:17:10 +04:00
static int sysvipc_shm_proc_show ( struct seq_file * s , void * it ) ;
2005-04-17 02:20:36 +04:00
# endif
2006-10-02 13:18:22 +04:00
static void __ipc_init __shm_init_ns ( struct ipc_namespace * ns , struct ipc_ids * ids )
{
ns - > ids [ IPC_SHM_IDS ] = ids ;
ns - > shm_ctlmax = SHMMAX ;
ns - > shm_ctlall = SHMALL ;
ns - > shm_ctlmni = SHMMNI ;
ns - > shm_tot = 0 ;
ipc_init_ids ( ids , 1 ) ;
}
static void do_shm_rmid ( struct ipc_namespace * ns , struct shmid_kernel * shp )
{
if ( shp - > shm_nattch ) {
shp - > shm_perm . mode | = SHM_DEST ;
/* Do not find it any more */
shp - > shm_perm . key = IPC_PRIVATE ;
shm_unlock ( shp ) ;
} else
shm_destroy ( ns , shp ) ;
}
# ifdef CONFIG_IPC_NS
int shm_init_ns ( struct ipc_namespace * ns )
{
struct ipc_ids * ids ;
ids = kmalloc ( sizeof ( struct ipc_ids ) , GFP_KERNEL ) ;
if ( ids = = NULL )
return - ENOMEM ;
2005-04-17 02:20:36 +04:00
2006-10-02 13:18:22 +04:00
__shm_init_ns ( ns , ids ) ;
return 0 ;
}
void shm_exit_ns ( struct ipc_namespace * ns )
{
int i ;
struct shmid_kernel * shp ;
mutex_lock ( & shm_ids ( ns ) . mutex ) ;
for ( i = 0 ; i < = shm_ids ( ns ) . max_id ; i + + ) {
shp = shm_lock ( ns , i ) ;
if ( shp = = NULL )
continue ;
do_shm_rmid ( ns , shp ) ;
}
mutex_unlock ( & shm_ids ( ns ) . mutex ) ;
2006-11-03 09:07:03 +03:00
ipc_fini_ids ( ns - > ids [ IPC_SHM_IDS ] ) ;
2006-10-02 13:18:22 +04:00
kfree ( ns - > ids [ IPC_SHM_IDS ] ) ;
ns - > ids [ IPC_SHM_IDS ] = NULL ;
}
# endif
2005-04-17 02:20:36 +04:00
void __init shm_init ( void )
{
2006-10-02 13:18:22 +04:00
__shm_init_ns ( & init_ipc_ns , & init_shm_ids ) ;
2005-09-07 02:17:10 +04:00
ipc_init_proc_interface ( " sysvipc/shm " ,
" key shmid perms size cpid lpid nattch uid gid cuid cgid atime dtime ctime \n " ,
2006-10-02 13:18:22 +04:00
IPC_SHM_IDS , sysvipc_shm_proc_show ) ;
2005-04-17 02:20:36 +04:00
}
2006-10-02 13:18:22 +04:00
static inline int shm_checkid ( struct ipc_namespace * ns ,
struct shmid_kernel * s , int id )
2005-04-17 02:20:36 +04:00
{
2006-10-02 13:18:22 +04:00
if ( ipc_checkid ( & shm_ids ( ns ) , & s - > shm_perm , id ) )
2005-04-17 02:20:36 +04:00
return - EIDRM ;
return 0 ;
}
2006-10-02 13:18:22 +04:00
static inline struct shmid_kernel * shm_rmid ( struct ipc_namespace * ns , int id )
2005-04-17 02:20:36 +04:00
{
2006-10-02 13:18:22 +04:00
return ( struct shmid_kernel * ) ipc_rmid ( & shm_ids ( ns ) , id ) ;
2005-04-17 02:20:36 +04:00
}
2006-10-02 13:18:22 +04:00
static inline int shm_addid ( struct ipc_namespace * ns , struct shmid_kernel * shp )
2005-04-17 02:20:36 +04:00
{
2006-10-02 13:18:22 +04:00
return ipc_addid ( & shm_ids ( ns ) , & shp - > shm_perm , ns - > shm_ctlmni ) ;
2005-04-17 02:20:36 +04:00
}
2007-02-21 00:57:53 +03:00
/* This is called by fork, once for every shm attach. */
static void shm_open ( struct vm_area_struct * vma )
2006-10-02 13:18:22 +04:00
{
2007-02-21 00:57:53 +03:00
struct file * file = vma - > vm_file ;
struct shm_file_data * sfd = shm_file_data ( file ) ;
2005-04-17 02:20:36 +04:00
struct shmid_kernel * shp ;
2007-02-21 00:57:53 +03:00
shp = shm_lock ( sfd - > ns , sfd - > id ) ;
2006-04-02 15:42:42 +04:00
BUG_ON ( ! shp ) ;
2005-04-17 02:20:36 +04:00
shp - > shm_atim = get_seconds ( ) ;
shp - > shm_lprid = current - > tgid ;
shp - > shm_nattch + + ;
shm_unlock ( shp ) ;
}
/*
* shm_destroy - free the struct shmid_kernel
*
* @ shp : struct to free
*
2006-03-26 13:37:17 +04:00
* It has to be called with shp and shm_ids . mutex locked ,
2005-04-17 02:20:36 +04:00
* but returns with shp unlocked and freed .
*/
2006-10-02 13:18:22 +04:00
static void shm_destroy ( struct ipc_namespace * ns , struct shmid_kernel * shp )
2005-04-17 02:20:36 +04:00
{
2006-10-02 13:18:22 +04:00
ns - > shm_tot - = ( shp - > shm_segsz + PAGE_SIZE - 1 ) > > PAGE_SHIFT ;
shm_rmid ( ns , shp - > id ) ;
2005-04-17 02:20:36 +04:00
shm_unlock ( shp ) ;
if ( ! is_file_hugepages ( shp - > shm_file ) )
shmem_lock ( shp - > shm_file , 0 , shp - > mlock_user ) ;
else
2006-12-08 13:37:11 +03:00
user_shm_unlock ( shp - > shm_file - > f_path . dentry - > d_inode - > i_size ,
2005-04-17 02:20:36 +04:00
shp - > mlock_user ) ;
fput ( shp - > shm_file ) ;
security_shm_free ( shp ) ;
ipc_rcu_putref ( shp ) ;
}
/*
2007-02-21 00:57:53 +03:00
* remove the attach descriptor vma .
2005-04-17 02:20:36 +04:00
* free memory for segment if it is marked destroyed .
* The descriptor has already been removed from the current - > mm - > mmap list
* and will later be kfree ( ) d .
*/
2007-02-21 00:57:53 +03:00
static void shm_close ( struct vm_area_struct * vma )
2005-04-17 02:20:36 +04:00
{
2007-02-21 00:57:53 +03:00
struct file * file = vma - > vm_file ;
struct shm_file_data * sfd = shm_file_data ( file ) ;
2005-04-17 02:20:36 +04:00
struct shmid_kernel * shp ;
2007-02-21 00:57:53 +03:00
struct ipc_namespace * ns = sfd - > ns ;
2006-10-02 13:18:22 +04:00
mutex_lock ( & shm_ids ( ns ) . mutex ) ;
2005-04-17 02:20:36 +04:00
/* remove from the list of attaches of the shm segment */
2007-02-21 00:57:53 +03:00
shp = shm_lock ( ns , sfd - > id ) ;
2006-04-02 15:42:42 +04:00
BUG_ON ( ! shp ) ;
2005-04-17 02:20:36 +04:00
shp - > shm_lprid = current - > tgid ;
shp - > shm_dtim = get_seconds ( ) ;
shp - > shm_nattch - - ;
if ( shp - > shm_nattch = = 0 & &
2006-01-08 12:02:21 +03:00
shp - > shm_perm . mode & SHM_DEST )
2006-10-02 13:18:22 +04:00
shm_destroy ( ns , shp ) ;
2005-04-17 02:20:36 +04:00
else
shm_unlock ( shp ) ;
2006-10-02 13:18:22 +04:00
mutex_unlock ( & shm_ids ( ns ) . mutex ) ;
2005-04-17 02:20:36 +04:00
}
2007-02-21 00:57:53 +03:00
struct page * shm_nopage ( struct vm_area_struct * vma , unsigned long address ,
int * type )
{
struct file * file = vma - > vm_file ;
struct shm_file_data * sfd = shm_file_data ( file ) ;
return sfd - > vm_ops - > nopage ( vma , address , type ) ;
}
# ifdef CONFIG_NUMA
int shm_set_policy ( struct vm_area_struct * vma , struct mempolicy * new )
{
struct file * file = vma - > vm_file ;
struct shm_file_data * sfd = shm_file_data ( file ) ;
int err = 0 ;
if ( sfd - > vm_ops - > set_policy )
err = sfd - > vm_ops - > set_policy ( vma , new ) ;
return err ;
}
struct mempolicy * shm_get_policy ( struct vm_area_struct * vma , unsigned long addr )
{
struct file * file = vma - > vm_file ;
struct shm_file_data * sfd = shm_file_data ( file ) ;
struct mempolicy * pol = NULL ;
if ( sfd - > vm_ops - > get_policy )
pol = sfd - > vm_ops - > get_policy ( vma , addr ) ;
else
pol = vma - > vm_policy ;
return pol ;
}
# endif
2005-04-17 02:20:36 +04:00
static int shm_mmap ( struct file * file , struct vm_area_struct * vma )
{
2007-02-21 00:57:53 +03:00
struct shm_file_data * sfd = shm_file_data ( file ) ;
2006-01-06 11:11:42 +03:00
int ret ;
2007-02-21 00:57:53 +03:00
ret = sfd - > file - > f_op - > mmap ( sfd - > file , vma ) ;
if ( ret ! = 0 )
return ret ;
sfd - > vm_ops = vma - > vm_ops ;
vma - > vm_ops = & shm_vm_ops ;
shm_open ( vma ) ;
2006-01-06 11:11:42 +03:00
return ret ;
2005-04-17 02:20:36 +04:00
}
2006-10-02 13:18:22 +04:00
static int shm_release ( struct inode * ino , struct file * file )
{
2007-02-21 00:57:53 +03:00
struct shm_file_data * sfd = shm_file_data ( file ) ;
2006-10-02 13:18:22 +04:00
2007-02-21 00:57:53 +03:00
put_ipc_ns ( sfd - > ns ) ;
shm_file_data ( file ) = NULL ;
kfree ( sfd ) ;
2006-10-02 13:18:22 +04:00
return 0 ;
}
2007-02-21 00:57:53 +03:00
# ifndef CONFIG_MMU
static unsigned long shm_get_unmapped_area ( struct file * file ,
unsigned long addr , unsigned long len , unsigned long pgoff ,
unsigned long flags )
{
struct shm_file_data * sfd = shm_file_data ( file ) ;
return sfd - > file - > f_op - > get_unmapped_area ( sfd - > file , addr , len , pgoff ,
flags ) ;
}
# else
# define shm_get_unmapped_area NULL
# endif
2007-02-12 11:55:35 +03:00
static const struct file_operations shm_file_operations = {
2006-10-02 13:18:22 +04:00
. mmap = shm_mmap ,
. release = shm_release ,
2007-02-21 00:57:53 +03:00
. get_unmapped_area = shm_get_unmapped_area ,
2005-04-17 02:20:36 +04:00
} ;
static struct vm_operations_struct shm_vm_ops = {
. open = shm_open , /* callback for a new vm-area open */
. close = shm_close , /* callback for when the vm-area is released */
2007-02-21 00:57:53 +03:00
. nopage = shm_nopage ,
# if defined(CONFIG_NUMA)
. set_policy = shm_set_policy ,
. get_policy = shm_get_policy ,
2005-04-17 02:20:36 +04:00
# endif
} ;
2006-10-02 13:18:22 +04:00
static int newseg ( struct ipc_namespace * ns , key_t key , int shmflg , size_t size )
2005-04-17 02:20:36 +04:00
{
int error ;
struct shmid_kernel * shp ;
int numpages = ( size + PAGE_SIZE - 1 ) > > PAGE_SHIFT ;
struct file * file ;
char name [ 13 ] ;
int id ;
2006-10-02 13:18:22 +04:00
if ( size < SHMMIN | | size > ns - > shm_ctlmax )
2005-04-17 02:20:36 +04:00
return - EINVAL ;
2007-01-23 21:20:04 +03:00
if ( ns - > shm_tot + numpages > ns - > shm_ctlall )
2005-04-17 02:20:36 +04:00
return - ENOSPC ;
shp = ipc_rcu_alloc ( sizeof ( * shp ) ) ;
if ( ! shp )
return - ENOMEM ;
shp - > shm_perm . key = key ;
2006-01-08 12:02:21 +03:00
shp - > shm_perm . mode = ( shmflg & S_IRWXUGO ) ;
2005-04-17 02:20:36 +04:00
shp - > mlock_user = NULL ;
shp - > shm_perm . security = NULL ;
error = security_shm_alloc ( shp ) ;
if ( error ) {
ipc_rcu_putref ( shp ) ;
return error ;
}
if ( shmflg & SHM_HUGETLB ) {
/* hugetlb_zero_setup takes care of mlock user accounting */
file = hugetlb_zero_setup ( size ) ;
shp - > mlock_user = current - > user ;
} else {
2005-11-07 11:59:27 +03:00
int acctflag = VM_ACCOUNT ;
/*
* Do not allow no accounting for OVERCOMMIT_NEVER , even
* if it ' s asked for .
*/
if ( ( shmflg & SHM_NORESERVE ) & &
sysctl_overcommit_memory ! = OVERCOMMIT_NEVER )
acctflag = 0 ;
2005-04-17 02:20:36 +04:00
sprintf ( name , " SYSV%08x " , key ) ;
2005-11-07 11:59:27 +03:00
file = shmem_file_setup ( name , size , acctflag ) ;
2005-04-17 02:20:36 +04:00
}
error = PTR_ERR ( file ) ;
if ( IS_ERR ( file ) )
goto no_file ;
error = - ENOSPC ;
2006-10-02 13:18:22 +04:00
id = shm_addid ( ns , shp ) ;
2005-04-17 02:20:36 +04:00
if ( id = = - 1 )
goto no_id ;
shp - > shm_cprid = current - > tgid ;
shp - > shm_lprid = 0 ;
shp - > shm_atim = shp - > shm_dtim = 0 ;
shp - > shm_ctim = get_seconds ( ) ;
shp - > shm_segsz = size ;
shp - > shm_nattch = 0 ;
2006-10-02 13:18:22 +04:00
shp - > id = shm_buildid ( ns , id , shp - > shm_perm . seq ) ;
2005-04-17 02:20:36 +04:00
shp - > shm_file = file ;
2005-10-30 04:16:45 +03:00
2006-10-02 13:18:22 +04:00
ns - > shm_tot + = numpages ;
2005-04-17 02:20:36 +04:00
shm_unlock ( shp ) ;
return shp - > id ;
no_id :
fput ( file ) ;
no_file :
security_shm_free ( shp ) ;
ipc_rcu_putref ( shp ) ;
return error ;
}
asmlinkage long sys_shmget ( key_t key , size_t size , int shmflg )
{
struct shmid_kernel * shp ;
int err , id = 0 ;
2006-10-02 13:18:22 +04:00
struct ipc_namespace * ns ;
ns = current - > nsproxy - > ipc_ns ;
2005-04-17 02:20:36 +04:00
2006-10-02 13:18:22 +04:00
mutex_lock ( & shm_ids ( ns ) . mutex ) ;
2005-04-17 02:20:36 +04:00
if ( key = = IPC_PRIVATE ) {
2006-10-02 13:18:22 +04:00
err = newseg ( ns , key , shmflg , size ) ;
} else if ( ( id = ipc_findkey ( & shm_ids ( ns ) , key ) ) = = - 1 ) {
2005-04-17 02:20:36 +04:00
if ( ! ( shmflg & IPC_CREAT ) )
err = - ENOENT ;
else
2006-10-02 13:18:22 +04:00
err = newseg ( ns , key , shmflg , size ) ;
2005-04-17 02:20:36 +04:00
} else if ( ( shmflg & IPC_CREAT ) & & ( shmflg & IPC_EXCL ) ) {
err = - EEXIST ;
} else {
2006-10-02 13:18:22 +04:00
shp = shm_lock ( ns , id ) ;
2006-04-02 15:42:42 +04:00
BUG_ON ( shp = = NULL ) ;
2005-04-17 02:20:36 +04:00
if ( shp - > shm_segsz < size )
err = - EINVAL ;
else if ( ipcperms ( & shp - > shm_perm , shmflg ) )
err = - EACCES ;
else {
2006-10-02 13:18:22 +04:00
int shmid = shm_buildid ( ns , id , shp - > shm_perm . seq ) ;
2005-04-17 02:20:36 +04:00
err = security_shm_associate ( shp , shmflg ) ;
if ( ! err )
err = shmid ;
}
shm_unlock ( shp ) ;
}
2006-10-02 13:18:22 +04:00
mutex_unlock ( & shm_ids ( ns ) . mutex ) ;
2005-04-17 02:20:36 +04:00
return err ;
}
static inline unsigned long copy_shmid_to_user ( void __user * buf , struct shmid64_ds * in , int version )
{
switch ( version ) {
case IPC_64 :
return copy_to_user ( buf , in , sizeof ( * in ) ) ;
case IPC_OLD :
{
struct shmid_ds out ;
ipc64_perm_to_ipc_perm ( & in - > shm_perm , & out . shm_perm ) ;
out . shm_segsz = in - > shm_segsz ;
out . shm_atime = in - > shm_atime ;
out . shm_dtime = in - > shm_dtime ;
out . shm_ctime = in - > shm_ctime ;
out . shm_cpid = in - > shm_cpid ;
out . shm_lpid = in - > shm_lpid ;
out . shm_nattch = in - > shm_nattch ;
return copy_to_user ( buf , & out , sizeof ( out ) ) ;
}
default :
return - EINVAL ;
}
}
struct shm_setbuf {
uid_t uid ;
gid_t gid ;
mode_t mode ;
} ;
static inline unsigned long copy_shmid_from_user ( struct shm_setbuf * out , void __user * buf , int version )
{
switch ( version ) {
case IPC_64 :
{
struct shmid64_ds tbuf ;
if ( copy_from_user ( & tbuf , buf , sizeof ( tbuf ) ) )
return - EFAULT ;
out - > uid = tbuf . shm_perm . uid ;
out - > gid = tbuf . shm_perm . gid ;
2006-01-08 12:02:21 +03:00
out - > mode = tbuf . shm_perm . mode ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
case IPC_OLD :
{
struct shmid_ds tbuf_old ;
if ( copy_from_user ( & tbuf_old , buf , sizeof ( tbuf_old ) ) )
return - EFAULT ;
out - > uid = tbuf_old . shm_perm . uid ;
out - > gid = tbuf_old . shm_perm . gid ;
2006-01-08 12:02:21 +03:00
out - > mode = tbuf_old . shm_perm . mode ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
default :
return - EINVAL ;
}
}
static inline unsigned long copy_shminfo_to_user ( void __user * buf , struct shminfo64 * in , int version )
{
switch ( version ) {
case IPC_64 :
return copy_to_user ( buf , in , sizeof ( * in ) ) ;
case IPC_OLD :
{
struct shminfo out ;
if ( in - > shmmax > INT_MAX )
out . shmmax = INT_MAX ;
else
out . shmmax = ( int ) in - > shmmax ;
out . shmmin = in - > shmmin ;
out . shmmni = in - > shmmni ;
out . shmseg = in - > shmseg ;
out . shmall = in - > shmall ;
return copy_to_user ( buf , & out , sizeof ( out ) ) ;
}
default :
return - EINVAL ;
}
}
2006-10-02 13:18:22 +04:00
static void shm_get_stat ( struct ipc_namespace * ns , unsigned long * rss ,
unsigned long * swp )
2005-04-17 02:20:36 +04:00
{
int i ;
* rss = 0 ;
* swp = 0 ;
2006-10-02 13:18:22 +04:00
for ( i = 0 ; i < = shm_ids ( ns ) . max_id ; i + + ) {
2005-04-17 02:20:36 +04:00
struct shmid_kernel * shp ;
struct inode * inode ;
2006-10-02 13:18:22 +04:00
shp = shm_get ( ns , i ) ;
2005-04-17 02:20:36 +04:00
if ( ! shp )
continue ;
2006-12-08 13:37:11 +03:00
inode = shp - > shm_file - > f_path . dentry - > d_inode ;
2005-04-17 02:20:36 +04:00
if ( is_file_hugepages ( shp - > shm_file ) ) {
struct address_space * mapping = inode - > i_mapping ;
* rss + = ( HPAGE_SIZE / PAGE_SIZE ) * mapping - > nrpages ;
} else {
struct shmem_inode_info * info = SHMEM_I ( inode ) ;
spin_lock ( & info - > lock ) ;
* rss + = inode - > i_mapping - > nrpages ;
* swp + = info - > swapped ;
spin_unlock ( & info - > lock ) ;
}
}
}
asmlinkage long sys_shmctl ( int shmid , int cmd , struct shmid_ds __user * buf )
{
struct shm_setbuf setbuf ;
struct shmid_kernel * shp ;
int err , version ;
2006-10-02 13:18:22 +04:00
struct ipc_namespace * ns ;
2005-04-17 02:20:36 +04:00
if ( cmd < 0 | | shmid < 0 ) {
err = - EINVAL ;
goto out ;
}
version = ipc_parse_version ( & cmd ) ;
2006-10-02 13:18:22 +04:00
ns = current - > nsproxy - > ipc_ns ;
2005-04-17 02:20:36 +04:00
switch ( cmd ) { /* replace with proc interface ? */
case IPC_INFO :
{
struct shminfo64 shminfo ;
err = security_shm_shmctl ( NULL , cmd ) ;
if ( err )
return err ;
memset ( & shminfo , 0 , sizeof ( shminfo ) ) ;
2006-10-02 13:18:22 +04:00
shminfo . shmmni = shminfo . shmseg = ns - > shm_ctlmni ;
shminfo . shmmax = ns - > shm_ctlmax ;
shminfo . shmall = ns - > shm_ctlall ;
2005-04-17 02:20:36 +04:00
shminfo . shmmin = SHMMIN ;
if ( copy_shminfo_to_user ( buf , & shminfo , version ) )
return - EFAULT ;
/* reading a integer is always atomic */
2006-10-02 13:18:22 +04:00
err = shm_ids ( ns ) . max_id ;
2005-04-17 02:20:36 +04:00
if ( err < 0 )
err = 0 ;
goto out ;
}
case SHM_INFO :
{
struct shm_info shm_info ;
err = security_shm_shmctl ( NULL , cmd ) ;
if ( err )
return err ;
memset ( & shm_info , 0 , sizeof ( shm_info ) ) ;
2006-10-02 13:18:22 +04:00
mutex_lock ( & shm_ids ( ns ) . mutex ) ;
shm_info . used_ids = shm_ids ( ns ) . in_use ;
shm_get_stat ( ns , & shm_info . shm_rss , & shm_info . shm_swp ) ;
shm_info . shm_tot = ns - > shm_tot ;
2005-04-17 02:20:36 +04:00
shm_info . swap_attempts = 0 ;
shm_info . swap_successes = 0 ;
2006-10-02 13:18:22 +04:00
err = shm_ids ( ns ) . max_id ;
mutex_unlock ( & shm_ids ( ns ) . mutex ) ;
2005-04-17 02:20:36 +04:00
if ( copy_to_user ( buf , & shm_info , sizeof ( shm_info ) ) ) {
err = - EFAULT ;
goto out ;
}
err = err < 0 ? 0 : err ;
goto out ;
}
case SHM_STAT :
case IPC_STAT :
{
struct shmid64_ds tbuf ;
int result ;
memset ( & tbuf , 0 , sizeof ( tbuf ) ) ;
2006-10-02 13:18:22 +04:00
shp = shm_lock ( ns , shmid ) ;
2005-04-17 02:20:36 +04:00
if ( shp = = NULL ) {
err = - EINVAL ;
goto out ;
} else if ( cmd = = SHM_STAT ) {
err = - EINVAL ;
2006-10-02 13:18:22 +04:00
if ( shmid > shm_ids ( ns ) . max_id )
2005-04-17 02:20:36 +04:00
goto out_unlock ;
2006-10-02 13:18:22 +04:00
result = shm_buildid ( ns , shmid , shp - > shm_perm . seq ) ;
2005-04-17 02:20:36 +04:00
} else {
2006-10-02 13:18:22 +04:00
err = shm_checkid ( ns , shp , shmid ) ;
2005-04-17 02:20:36 +04:00
if ( err )
goto out_unlock ;
result = 0 ;
}
err = - EACCES ;
if ( ipcperms ( & shp - > shm_perm , S_IRUGO ) )
goto out_unlock ;
err = security_shm_shmctl ( shp , cmd ) ;
if ( err )
goto out_unlock ;
kernel_to_ipc64_perm ( & shp - > shm_perm , & tbuf . shm_perm ) ;
tbuf . shm_segsz = shp - > shm_segsz ;
tbuf . shm_atime = shp - > shm_atim ;
tbuf . shm_dtime = shp - > shm_dtim ;
tbuf . shm_ctime = shp - > shm_ctim ;
tbuf . shm_cpid = shp - > shm_cprid ;
tbuf . shm_lpid = shp - > shm_lprid ;
2007-02-21 00:57:53 +03:00
tbuf . shm_nattch = shp - > shm_nattch ;
2005-04-17 02:20:36 +04:00
shm_unlock ( shp ) ;
if ( copy_shmid_to_user ( buf , & tbuf , version ) )
err = - EFAULT ;
else
err = result ;
goto out ;
}
case SHM_LOCK :
case SHM_UNLOCK :
{
2006-10-02 13:18:22 +04:00
shp = shm_lock ( ns , shmid ) ;
2005-04-17 02:20:36 +04:00
if ( shp = = NULL ) {
err = - EINVAL ;
goto out ;
}
2006-10-02 13:18:22 +04:00
err = shm_checkid ( ns , shp , shmid ) ;
2005-04-17 02:20:36 +04:00
if ( err )
goto out_unlock ;
2006-04-03 01:07:33 +04:00
err = audit_ipc_obj ( & ( shp - > shm_perm ) ) ;
if ( err )
goto out_unlock ;
2005-04-17 02:20:36 +04:00
if ( ! capable ( CAP_IPC_LOCK ) ) {
err = - EPERM ;
if ( current - > euid ! = shp - > shm_perm . uid & &
current - > euid ! = shp - > shm_perm . cuid )
goto out_unlock ;
if ( cmd = = SHM_LOCK & &
! current - > signal - > rlim [ RLIMIT_MEMLOCK ] . rlim_cur )
goto out_unlock ;
}
err = security_shm_shmctl ( shp , cmd ) ;
if ( err )
goto out_unlock ;
if ( cmd = = SHM_LOCK ) {
struct user_struct * user = current - > user ;
if ( ! is_file_hugepages ( shp - > shm_file ) ) {
err = shmem_lock ( shp - > shm_file , 1 , user ) ;
if ( ! err ) {
2006-01-08 12:02:21 +03:00
shp - > shm_perm . mode | = SHM_LOCKED ;
2005-04-17 02:20:36 +04:00
shp - > mlock_user = user ;
}
}
} else if ( ! is_file_hugepages ( shp - > shm_file ) ) {
shmem_lock ( shp - > shm_file , 0 , shp - > mlock_user ) ;
2006-01-08 12:02:21 +03:00
shp - > shm_perm . mode & = ~ SHM_LOCKED ;
2005-04-17 02:20:36 +04:00
shp - > mlock_user = NULL ;
}
shm_unlock ( shp ) ;
goto out ;
}
case IPC_RMID :
{
/*
* We cannot simply remove the file . The SVID states
* that the block remains until the last person
* detaches from it , then is deleted . A shmat ( ) on
* an RMID segment is legal in older Linux and if
* we change it apps break . . .
*
* Instead we set a destroyed flag , and then blow
* the name away when the usage hits zero .
*/
2006-10-02 13:18:22 +04:00
mutex_lock ( & shm_ids ( ns ) . mutex ) ;
shp = shm_lock ( ns , shmid ) ;
2005-04-17 02:20:36 +04:00
err = - EINVAL ;
if ( shp = = NULL )
goto out_up ;
2006-10-02 13:18:22 +04:00
err = shm_checkid ( ns , shp , shmid ) ;
2005-04-17 02:20:36 +04:00
if ( err )
goto out_unlock_up ;
2006-04-03 01:07:33 +04:00
err = audit_ipc_obj ( & ( shp - > shm_perm ) ) ;
if ( err )
goto out_unlock_up ;
2005-04-17 02:20:36 +04:00
if ( current - > euid ! = shp - > shm_perm . uid & &
current - > euid ! = shp - > shm_perm . cuid & &
! capable ( CAP_SYS_ADMIN ) ) {
err = - EPERM ;
goto out_unlock_up ;
}
err = security_shm_shmctl ( shp , cmd ) ;
if ( err )
goto out_unlock_up ;
2006-10-02 13:18:22 +04:00
do_shm_rmid ( ns , shp ) ;
mutex_unlock ( & shm_ids ( ns ) . mutex ) ;
2005-04-17 02:20:36 +04:00
goto out ;
}
case IPC_SET :
{
if ( copy_shmid_from_user ( & setbuf , buf , version ) ) {
err = - EFAULT ;
goto out ;
}
2006-10-02 13:18:22 +04:00
mutex_lock ( & shm_ids ( ns ) . mutex ) ;
shp = shm_lock ( ns , shmid ) ;
2005-04-17 02:20:36 +04:00
err = - EINVAL ;
if ( shp = = NULL )
goto out_up ;
2006-10-02 13:18:22 +04:00
err = shm_checkid ( ns , shp , shmid ) ;
2005-04-17 02:20:36 +04:00
if ( err )
goto out_unlock_up ;
2006-04-03 01:07:33 +04:00
err = audit_ipc_obj ( & ( shp - > shm_perm ) ) ;
if ( err )
goto out_unlock_up ;
2006-05-17 06:03:48 +04:00
err = audit_ipc_set_perm ( 0 , setbuf . uid , setbuf . gid , setbuf . mode ) ;
2006-04-03 01:07:33 +04:00
if ( err )
goto out_unlock_up ;
2005-04-17 02:20:36 +04:00
err = - EPERM ;
if ( current - > euid ! = shp - > shm_perm . uid & &
current - > euid ! = shp - > shm_perm . cuid & &
! capable ( CAP_SYS_ADMIN ) ) {
goto out_unlock_up ;
}
err = security_shm_shmctl ( shp , cmd ) ;
if ( err )
goto out_unlock_up ;
shp - > shm_perm . uid = setbuf . uid ;
shp - > shm_perm . gid = setbuf . gid ;
2006-01-08 12:02:21 +03:00
shp - > shm_perm . mode = ( shp - > shm_perm . mode & ~ S_IRWXUGO )
2005-04-17 02:20:36 +04:00
| ( setbuf . mode & S_IRWXUGO ) ;
shp - > shm_ctim = get_seconds ( ) ;
break ;
}
default :
err = - EINVAL ;
goto out ;
}
err = 0 ;
out_unlock_up :
shm_unlock ( shp ) ;
out_up :
2006-10-02 13:18:22 +04:00
mutex_unlock ( & shm_ids ( ns ) . mutex ) ;
2005-04-17 02:20:36 +04:00
goto out ;
out_unlock :
shm_unlock ( shp ) ;
out :
return err ;
}
/*
* Fix shmaddr , allocate descriptor , map shm , add attach descriptor to lists .
*
* NOTE ! Despite the name , this is NOT a direct system call entrypoint . The
* " raddr " thing points to kernel space , and there has to be a wrapper around
* this .
*/
long do_shmat ( int shmid , char __user * shmaddr , int shmflg , ulong * raddr )
{
struct shmid_kernel * shp ;
unsigned long addr ;
unsigned long size ;
struct file * file ;
int err ;
unsigned long flags ;
unsigned long prot ;
int acc_mode ;
2007-02-21 00:57:53 +03:00
unsigned long user_addr ;
2006-10-02 13:18:22 +04:00
struct ipc_namespace * ns ;
2007-02-21 00:57:53 +03:00
struct shm_file_data * sfd ;
struct path path ;
mode_t f_mode ;
2005-04-17 02:20:36 +04:00
2007-02-21 00:57:53 +03:00
err = - EINVAL ;
if ( shmid < 0 )
2005-04-17 02:20:36 +04:00
goto out ;
2007-02-21 00:57:53 +03:00
else if ( ( addr = ( ulong ) shmaddr ) ) {
2005-04-17 02:20:36 +04:00
if ( addr & ( SHMLBA - 1 ) ) {
if ( shmflg & SHM_RND )
addr & = ~ ( SHMLBA - 1 ) ; /* round down */
else
# ifndef __ARCH_FORCE_SHMLBA
if ( addr & ~ PAGE_MASK )
# endif
2007-02-21 00:57:53 +03:00
goto out ;
2005-04-17 02:20:36 +04:00
}
flags = MAP_SHARED | MAP_FIXED ;
} else {
if ( ( shmflg & SHM_REMAP ) )
2007-02-21 00:57:53 +03:00
goto out ;
2005-04-17 02:20:36 +04:00
flags = MAP_SHARED ;
}
if ( shmflg & SHM_RDONLY ) {
prot = PROT_READ ;
acc_mode = S_IRUGO ;
2007-02-21 00:57:53 +03:00
f_mode = FMODE_READ ;
2005-04-17 02:20:36 +04:00
} else {
prot = PROT_READ | PROT_WRITE ;
acc_mode = S_IRUGO | S_IWUGO ;
2007-02-21 00:57:53 +03:00
f_mode = FMODE_READ | FMODE_WRITE ;
2005-04-17 02:20:36 +04:00
}
if ( shmflg & SHM_EXEC ) {
prot | = PROT_EXEC ;
acc_mode | = S_IXUGO ;
}
/*
* We cannot rely on the fs check since SYSV IPC does have an
* additional creator id . . .
*/
2006-10-02 13:18:22 +04:00
ns = current - > nsproxy - > ipc_ns ;
shp = shm_lock ( ns , shmid ) ;
2007-02-21 00:57:53 +03:00
if ( shp = = NULL )
2005-04-17 02:20:36 +04:00
goto out ;
2007-02-21 00:57:53 +03:00
2006-10-02 13:18:22 +04:00
err = shm_checkid ( ns , shp , shmid ) ;
2007-02-21 00:57:53 +03:00
if ( err )
goto out_unlock ;
err = - EACCES ;
if ( ipcperms ( & shp - > shm_perm , acc_mode ) )
goto out_unlock ;
2005-04-17 02:20:36 +04:00
err = security_shm_shmat ( shp , shmaddr , shmflg ) ;
2007-02-21 00:57:53 +03:00
if ( err )
goto out_unlock ;
path . dentry = dget ( shp - > shm_file - > f_path . dentry ) ;
path . mnt = mntget ( shp - > shm_file - > f_path . mnt ) ;
2005-04-17 02:20:36 +04:00
shp - > shm_nattch + + ;
2007-02-21 00:57:53 +03:00
size = i_size_read ( path . dentry - > d_inode ) ;
2005-04-17 02:20:36 +04:00
shm_unlock ( shp ) ;
2007-02-21 00:57:53 +03:00
err = - ENOMEM ;
sfd = kzalloc ( sizeof ( * sfd ) , GFP_KERNEL ) ;
if ( ! sfd )
goto out_put_path ;
err = - ENOMEM ;
file = get_empty_filp ( ) ;
if ( ! file )
goto out_free ;
file - > f_op = & shm_file_operations ;
file - > private_data = sfd ;
file - > f_path = path ;
file - > f_mapping = shp - > shm_file - > f_mapping ;
file - > f_mode = f_mode ;
sfd - > id = shp - > id ;
sfd - > ns = get_ipc_ns ( ns ) ;
sfd - > file = shp - > shm_file ;
sfd - > vm_ops = NULL ;
2005-04-17 02:20:36 +04:00
down_write ( & current - > mm - > mmap_sem ) ;
if ( addr & & ! ( shmflg & SHM_REMAP ) ) {
2007-02-21 00:57:53 +03:00
err = - EINVAL ;
2005-04-17 02:20:36 +04:00
if ( find_vma_intersection ( current - > mm , addr , addr + size ) )
goto invalid ;
/*
* If shm segment goes below stack , make sure there is some
* space left for the stack to grow ( at least 4 pages ) .
*/
if ( addr < current - > mm - > start_stack & &
addr > current - > mm - > start_stack - size - PAGE_SIZE * 5 )
goto invalid ;
}
2007-02-21 00:57:53 +03:00
user_addr = do_mmap ( file , addr , size , prot , flags , 0 ) ;
* raddr = user_addr ;
err = 0 ;
if ( IS_ERR_VALUE ( user_addr ) )
err = ( long ) user_addr ;
2005-04-17 02:20:36 +04:00
invalid :
up_write ( & current - > mm - > mmap_sem ) ;
2007-02-21 00:57:53 +03:00
fput ( file ) ;
out_nattch :
2006-10-02 13:18:22 +04:00
mutex_lock ( & shm_ids ( ns ) . mutex ) ;
shp = shm_lock ( ns , shmid ) ;
2006-04-02 15:42:42 +04:00
BUG_ON ( ! shp ) ;
2005-04-17 02:20:36 +04:00
shp - > shm_nattch - - ;
if ( shp - > shm_nattch = = 0 & &
2006-01-08 12:02:21 +03:00
shp - > shm_perm . mode & SHM_DEST )
2006-10-02 13:18:22 +04:00
shm_destroy ( ns , shp ) ;
2005-04-17 02:20:36 +04:00
else
shm_unlock ( shp ) ;
2006-10-02 13:18:22 +04:00
mutex_unlock ( & shm_ids ( ns ) . mutex ) ;
2005-04-17 02:20:36 +04:00
out :
return err ;
2007-02-21 00:57:53 +03:00
out_unlock :
shm_unlock ( shp ) ;
goto out ;
out_free :
kfree ( sfd ) ;
out_put_path :
dput ( path . dentry ) ;
mntput ( path . mnt ) ;
goto out_nattch ;
2005-04-17 02:20:36 +04:00
}
2005-05-01 19:59:12 +04:00
asmlinkage long sys_shmat ( int shmid , char __user * shmaddr , int shmflg )
{
unsigned long ret ;
long err ;
err = do_shmat ( shmid , shmaddr , shmflg , & ret ) ;
if ( err )
return err ;
force_successful_syscall_return ( ) ;
return ( long ) ret ;
}
2005-04-17 02:20:36 +04:00
/*
* detach and kill segment if marked destroyed .
* The work is done in shm_close .
*/
asmlinkage long sys_shmdt ( char __user * shmaddr )
{
struct mm_struct * mm = current - > mm ;
struct vm_area_struct * vma , * next ;
unsigned long addr = ( unsigned long ) shmaddr ;
loff_t size = 0 ;
int retval = - EINVAL ;
2006-03-24 14:18:06 +03:00
if ( addr & ~ PAGE_MASK )
return retval ;
2005-04-17 02:20:36 +04:00
down_write ( & mm - > mmap_sem ) ;
/*
* This function tries to be smart and unmap shm segments that
* were modified by partial mlock or munmap calls :
* - It first determines the size of the shm segment that should be
* unmapped : It searches for a vma that is backed by shm and that
* started at address shmaddr . It records it ' s size and then unmaps
* it .
* - Then it unmaps all shm vmas that started at shmaddr and that
* are within the initially determined size .
* Errors from do_munmap are ignored : the function only fails if
* it ' s called with invalid parameters or if it ' s called to unmap
* a part of a vma . Both calls in this function are for full vmas ,
* the parameters are directly copied from the vma itself and always
* valid - therefore do_munmap cannot fail . ( famous last words ? )
*/
/*
* If it had been mremap ( ) ' d , the starting address would not
* match the usual checks anyway . So assume all vma ' s are
* above the starting address given .
*/
vma = find_vma ( mm , addr ) ;
while ( vma ) {
next = vma - > vm_next ;
/*
* Check if the starting address would match , i . e . it ' s
* a fragment created by mprotect ( ) and / or munmap ( ) , or it
* otherwise it starts at this address with no hassles .
*/
2007-02-21 00:57:53 +03:00
if ( ( vma - > vm_ops = = & shm_vm_ops ) & &
2005-04-17 02:20:36 +04:00
( vma - > vm_start - addr ) / PAGE_SIZE = = vma - > vm_pgoff ) {
2006-12-08 13:37:11 +03:00
size = vma - > vm_file - > f_path . dentry - > d_inode - > i_size ;
2005-04-17 02:20:36 +04:00
do_munmap ( mm , vma - > vm_start , vma - > vm_end - vma - > vm_start ) ;
/*
* We discovered the size of the shm segment , so
* break out of here and fall through to the next
* loop that uses the size information to stop
* searching for matching vma ' s .
*/
retval = 0 ;
vma = next ;
break ;
}
vma = next ;
}
/*
* We need look no further than the maximum address a fragment
* could possibly have landed at . Also cast things to loff_t to
* prevent overflows and make comparisions vs . equal - width types .
*/
2006-02-10 12:51:12 +03:00
size = PAGE_ALIGN ( size ) ;
2005-04-17 02:20:36 +04:00
while ( vma & & ( loff_t ) ( vma - > vm_end - addr ) < = size ) {
next = vma - > vm_next ;
/* finding a matching vma now does not alter retval */
2007-02-21 00:57:53 +03:00
if ( ( vma - > vm_ops = = & shm_vm_ops ) & &
2005-04-17 02:20:36 +04:00
( vma - > vm_start - addr ) / PAGE_SIZE = = vma - > vm_pgoff )
do_munmap ( mm , vma - > vm_start , vma - > vm_end - vma - > vm_start ) ;
vma = next ;
}
up_write ( & mm - > mmap_sem ) ;
return retval ;
}
# ifdef CONFIG_PROC_FS
2005-09-07 02:17:10 +04:00
static int sysvipc_shm_proc_show ( struct seq_file * s , void * it )
2005-04-17 02:20:36 +04:00
{
2005-09-07 02:17:10 +04:00
struct shmid_kernel * shp = it ;
char * format ;
2005-04-17 02:20:36 +04:00
# define SMALL_STRING "%10d %10d %4o %10u %5u %5u %5d %5u %5u %5u %5u %10lu %10lu %10lu\n"
# define BIG_STRING "%10d %10d %4o %21u %5u %5u %5d %5u %5u %5u %5u %10lu %10lu %10lu\n"
2005-09-07 02:17:10 +04:00
if ( sizeof ( size_t ) < = sizeof ( int ) )
format = SMALL_STRING ;
else
format = BIG_STRING ;
return seq_printf ( s , format ,
shp - > shm_perm . key ,
shp - > id ,
2006-01-08 12:02:21 +03:00
shp - > shm_perm . mode ,
2005-09-07 02:17:10 +04:00
shp - > shm_segsz ,
shp - > shm_cprid ,
shp - > shm_lprid ,
2007-02-21 00:57:53 +03:00
shp - > shm_nattch ,
2005-09-07 02:17:10 +04:00
shp - > shm_perm . uid ,
shp - > shm_perm . gid ,
shp - > shm_perm . cuid ,
shp - > shm_perm . cgid ,
shp - > shm_atim ,
shp - > shm_dtim ,
shp - > shm_ctim ) ;
2005-04-17 02:20:36 +04:00
}
# endif