1106 lines · 25.7 KB · C
RawDownload
/* * 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> * * support for audit of ipc object properties and permission changes * Dustin Kirkland <dustin.kirkland@us.ibm.com> * * namespaces support * OpenVZ, SWsoft Inc. * Pavel Emelianov <xemul@openvz.org> */ #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>#include <linux/capability.h>#include <linux/ptrace.h>#include <linux/seq_file.h>#include <linux/mutex.h>#include <linux/nsproxy.h>#include <linux/mount.h> #include <asm/uaccess.h> #include "util.h" 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)) static const struct file_operations shm_file_operations;static struct vm_operations_struct shm_vm_ops; static struct ipc_ids init_shm_ids; #define shm_ids(ns)	(*((ns)->ids[IPC_SHM_IDS])) #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) static int newseg (struct ipc_namespace *ns, key_t key,		int shmflg, size_t size);static void shm_open(struct vm_area_struct *vma);static void shm_close(struct vm_area_struct *vma);static void shm_destroy (struct ipc_namespace *ns, struct shmid_kernel *shp);#ifdef CONFIG_PROC_FSstatic int sysvipc_shm_proc_show(struct seq_file *s, void *it);#endif 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_NSint shm_init_ns(struct ipc_namespace *ns){	struct ipc_ids *ids; 	ids = kmalloc(sizeof(struct ipc_ids), GFP_KERNEL);	if (ids == NULL)		return -ENOMEM; 	__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); 	ipc_fini_ids(ns->ids[IPC_SHM_IDS]);	kfree(ns->ids[IPC_SHM_IDS]);	ns->ids[IPC_SHM_IDS] = NULL;}#endif void __init shm_init (void){	__shm_init_ns(&init_ipc_ns, &init_shm_ids);	ipc_init_proc_interface("sysvipc/shm",				"       key      shmid perms       size  cpid  lpid nattch   uid   gid  cuid  cgid      atime      dtime      ctime\n",				IPC_SHM_IDS, sysvipc_shm_proc_show);} static inline int shm_checkid(struct ipc_namespace *ns,		struct shmid_kernel *s, int id){	if (ipc_checkid(&shm_ids(ns), &s->shm_perm, id))		return -EIDRM;	return 0;} static inline struct shmid_kernel *shm_rmid(struct ipc_namespace *ns, int id){	return (struct shmid_kernel *)ipc_rmid(&shm_ids(ns), id);} static inline int shm_addid(struct ipc_namespace *ns, struct shmid_kernel *shp){	return ipc_addid(&shm_ids(ns), &shp->shm_perm, ns->shm_ctlmni);}   /* This is called by fork, once for every shm attach. */static void shm_open(struct vm_area_struct *vma){	struct file *file = vma->vm_file;	struct shm_file_data *sfd = shm_file_data(file);	struct shmid_kernel *shp; 	shp = shm_lock(sfd->ns, sfd->id);	BUG_ON(!shp);	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 * * It has to be called with shp and shm_ids.mutex locked, * but returns with shp unlocked and freed. */static void shm_destroy(struct ipc_namespace *ns, struct shmid_kernel *shp){	ns->shm_tot -= (shp->shm_segsz + PAGE_SIZE - 1) >> PAGE_SHIFT;	shm_rmid(ns, shp->id);	shm_unlock(shp);	if (!is_file_hugepages(shp->shm_file))		shmem_lock(shp->shm_file, 0, shp->mlock_user);	else		user_shm_unlock(shp->shm_file->f_path.dentry->d_inode->i_size,						shp->mlock_user);	fput (shp->shm_file);	security_shm_free(shp);	ipc_rcu_putref(shp);} /* * remove the attach descriptor vma. * 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. */static void shm_close(struct vm_area_struct *vma){	struct file * file = vma->vm_file;	struct shm_file_data *sfd = shm_file_data(file);	struct shmid_kernel *shp;	struct ipc_namespace *ns = sfd->ns; 	mutex_lock(&shm_ids(ns).mutex);	/* remove from the list of attaches of the shm segment */	shp = shm_lock(ns, sfd->id);	BUG_ON(!shp);	shp->shm_lprid = current->tgid;	shp->shm_dtim = get_seconds();	shp->shm_nattch--;	if(shp->shm_nattch == 0 &&	   shp->shm_perm.mode & SHM_DEST)		shm_destroy(ns, shp);	else		shm_unlock(shp);	mutex_unlock(&shm_ids(ns).mutex);} static 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_NUMAint 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 static int shm_mmap(struct file * file, struct vm_area_struct * vma){	struct shm_file_data *sfd = shm_file_data(file);	int ret; 	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); 	return ret;} static int shm_release(struct inode *ino, struct file *file){	struct shm_file_data *sfd = shm_file_data(file); 	put_ipc_ns(sfd->ns);	shm_file_data(file) = NULL;	kfree(sfd);	return 0;} static int shm_fsync(struct file *file, struct dentry *dentry, int datasync){	int (*fsync) (struct file *, struct dentry *, int datasync);	struct shm_file_data *sfd = shm_file_data(file);	int ret = -EINVAL; 	fsync = sfd->file->f_op->fsync;	if (fsync)		ret = fsync(sfd->file, sfd->file->f_path.dentry, datasync);	return ret;} 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 get_unmapped_area(sfd->file, addr, len, pgoff, flags);} int is_file_shm_hugepages(struct file *file){	int ret = 0; 	if (file->f_op == &shm_file_operations) {		struct shm_file_data *sfd;		sfd = shm_file_data(file);		ret = is_file_hugepages(sfd->file);	}	return ret;} static const struct file_operations shm_file_operations = {	.mmap		= shm_mmap,	.fsync		= shm_fsync,	.release	= shm_release,	.get_unmapped_area	= shm_get_unmapped_area,}; 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 */	.nopage	= shm_nopage,#if defined(CONFIG_NUMA)	.set_policy = shm_set_policy,	.get_policy = shm_get_policy,#endif}; static int newseg (struct ipc_namespace *ns, key_t key, int shmflg, size_t size){	int error;	struct shmid_kernel *shp;	int numpages = (size + PAGE_SIZE -1) >> PAGE_SHIFT;	struct file * file;	char name[13];	int id; 	if (size < SHMMIN || size > ns->shm_ctlmax)		return -EINVAL; 	if (ns->shm_tot + numpages > ns->shm_ctlall)		return -ENOSPC; 	shp = ipc_rcu_alloc(sizeof(*shp));	if (!shp)		return -ENOMEM; 	shp->shm_perm.key = key;	shp->shm_perm.mode = (shmflg & S_IRWXUGO);	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 {		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;		sprintf (name, "SYSV%08x", key);		file = shmem_file_setup(name, size, acctflag);	}	error = PTR_ERR(file);	if (IS_ERR(file))		goto no_file; 	error = -ENOSPC;	id = shm_addid(ns, shp);	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;	shp->id = shm_buildid(ns, id, shp->shm_perm.seq);	shp->shm_file = file; 	ns->shm_tot += numpages;	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;	struct ipc_namespace *ns; 	ns = current->nsproxy->ipc_ns; 	mutex_lock(&shm_ids(ns).mutex);	if (key == IPC_PRIVATE) {		err = newseg(ns, key, shmflg, size);	} else if ((id = ipc_findkey(&shm_ids(ns), key)) == -1) {		if (!(shmflg & IPC_CREAT))			err = -ENOENT;		else			err = newseg(ns, key, shmflg, size);	} else if ((shmflg & IPC_CREAT) && (shmflg & IPC_EXCL)) {		err = -EEXIST;	} else {		shp = shm_lock(ns, id);		BUG_ON(shp==NULL);		if (shp->shm_segsz < size)			err = -EINVAL;		else if (ipcperms(&shp->shm_perm, shmflg))			err = -EACCES;		else {			int shmid = shm_buildid(ns, id, shp->shm_perm.seq);			err = security_shm_associate(shp, shmflg);			if (!err)				err = shmid;		}		shm_unlock(shp);	}	mutex_unlock(&shm_ids(ns).mutex); 	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;		out->mode	= tbuf.shm_perm.mode; 		return 0;	    }	case IPC_OLD:	    {		struct shmid_ds tbuf_old;

Lines 1–500 of 1106.