1279 lines · 29.8 KB · C
RawDownload
/* * POSIX message queues filesystem for Linux. * * Copyright (C) 2003,2004  Krzysztof Benedyczak    (golbi@mat.uni.torun.pl) *                          Michal Wronski          (michal.wronski@gmail.com) * * Spinlocks:               Mohamed Abbas           (abbas.mohamed@intel.com) * Lockless receive & send, fd based notify: * 			    Manfred Spraul	    (manfred@colorfullife.com) * * Audit:                   George Wilson           (ltcgcw@us.ibm.com) * * This file is released under the GPL. */ #include <linux/capability.h>#include <linux/init.h>#include <linux/pagemap.h>#include <linux/file.h>#include <linux/mount.h>#include <linux/namei.h>#include <linux/sysctl.h>#include <linux/poll.h>#include <linux/mqueue.h>#include <linux/msg.h>#include <linux/skbuff.h>#include <linux/netlink.h>#include <linux/syscalls.h>#include <linux/audit.h>#include <linux/signal.h>#include <linux/mutex.h>#include <linux/nsproxy.h>#include <linux/pid.h> #include <net/sock.h>#include "util.h" #define MQUEUE_MAGIC	0x19800202#define DIRENT_SIZE	20#define FILENT_SIZE	80 #define SEND		0#define RECV		1 #define STATE_NONE	0#define STATE_PENDING	1#define STATE_READY	2 /* default values */#define DFLT_QUEUESMAX	256	/* max number of message queues */#define DFLT_MSGMAX 	10	/* max number of messages in each queue */#define HARD_MSGMAX 	(131072/sizeof(void*))#define DFLT_MSGSIZEMAX 8192	/* max message size */  struct ext_wait_queue {		/* queue of sleeping tasks */	struct task_struct *task;	struct list_head list;	struct msg_msg *msg;	/* ptr of loaded message */	int state;		/* one of STATE_* values */}; struct mqueue_inode_info {	spinlock_t lock;	struct inode vfs_inode;	wait_queue_head_t wait_q; 	struct msg_msg **messages;	struct mq_attr attr; 	struct sigevent notify;	struct pid* notify_owner;	struct user_struct *user;	/* user who created, for accounting */	struct sock *notify_sock;	struct sk_buff *notify_cookie; 	/* for tasks waiting for free space and messages, respectively */	struct ext_wait_queue e_wait_q[2]; 	unsigned long qsize; /* size of queue in memory (sum of all msgs) */}; static const struct inode_operations mqueue_dir_inode_operations;static const struct file_operations mqueue_file_operations;static struct super_operations mqueue_super_ops;static void remove_notification(struct mqueue_inode_info *info); static spinlock_t mq_lock;static struct kmem_cache *mqueue_inode_cachep;static struct vfsmount *mqueue_mnt; static unsigned int queues_count;static unsigned int queues_max 	= DFLT_QUEUESMAX;static unsigned int msg_max 	= DFLT_MSGMAX;static unsigned int msgsize_max = DFLT_MSGSIZEMAX; static struct ctl_table_header * mq_sysctl_table; static inline struct mqueue_inode_info *MQUEUE_I(struct inode *inode){	return container_of(inode, struct mqueue_inode_info, vfs_inode);} static struct inode *mqueue_get_inode(struct super_block *sb, int mode,							struct mq_attr *attr){	struct inode *inode; 	inode = new_inode(sb);	if (inode) {		inode->i_mode = mode;		inode->i_uid = current->fsuid;		inode->i_gid = current->fsgid;		inode->i_blocks = 0;		inode->i_mtime = inode->i_ctime = inode->i_atime =				CURRENT_TIME; 		if (S_ISREG(mode)) {			struct mqueue_inode_info *info;			struct task_struct *p = current;			struct user_struct *u = p->user;			unsigned long mq_bytes, mq_msg_tblsz; 			inode->i_fop = &mqueue_file_operations;			inode->i_size = FILENT_SIZE;			/* mqueue specific info */			info = MQUEUE_I(inode);			spin_lock_init(&info->lock);			init_waitqueue_head(&info->wait_q);			INIT_LIST_HEAD(&info->e_wait_q[0].list);			INIT_LIST_HEAD(&info->e_wait_q[1].list);			info->messages = NULL;			info->notify_owner = NULL;			info->qsize = 0;			info->user = NULL;	/* set when all is ok */			memset(&info->attr, 0, sizeof(info->attr));			info->attr.mq_maxmsg = DFLT_MSGMAX;			info->attr.mq_msgsize = DFLT_MSGSIZEMAX;			if (attr) {				info->attr.mq_maxmsg = attr->mq_maxmsg;				info->attr.mq_msgsize = attr->mq_msgsize;			}			mq_msg_tblsz = info->attr.mq_maxmsg * sizeof(struct msg_msg *);			mq_bytes = (mq_msg_tblsz +				(info->attr.mq_maxmsg * info->attr.mq_msgsize)); 			spin_lock(&mq_lock);			if (u->mq_bytes + mq_bytes < u->mq_bytes ||		 	    u->mq_bytes + mq_bytes >			    p->signal->rlim[RLIMIT_MSGQUEUE].rlim_cur) {				spin_unlock(&mq_lock);				goto out_inode;			}			u->mq_bytes += mq_bytes;			spin_unlock(&mq_lock); 			info->messages = kmalloc(mq_msg_tblsz, GFP_KERNEL);			if (!info->messages) {				spin_lock(&mq_lock);				u->mq_bytes -= mq_bytes;				spin_unlock(&mq_lock);				goto out_inode;			}			/* all is ok */			info->user = get_uid(u);		} else if (S_ISDIR(mode)) {			inc_nlink(inode);			/* Some things misbehave if size == 0 on a directory */			inode->i_size = 2 * DIRENT_SIZE;			inode->i_op = &mqueue_dir_inode_operations;			inode->i_fop = &simple_dir_operations;		}	}	return inode;out_inode:	make_bad_inode(inode);	iput(inode);	return NULL;} static int mqueue_fill_super(struct super_block *sb, void *data, int silent){	struct inode *inode; 	sb->s_blocksize = PAGE_CACHE_SIZE;	sb->s_blocksize_bits = PAGE_CACHE_SHIFT;	sb->s_magic = MQUEUE_MAGIC;	sb->s_op = &mqueue_super_ops; 	inode = mqueue_get_inode(sb, S_IFDIR | S_ISVTX | S_IRWXUGO, NULL);	if (!inode)		return -ENOMEM; 	sb->s_root = d_alloc_root(inode);	if (!sb->s_root) {		iput(inode);		return -ENOMEM;	} 	return 0;} static int mqueue_get_sb(struct file_system_type *fs_type,			 int flags, const char *dev_name,			 void *data, struct vfsmount *mnt){	return get_sb_single(fs_type, flags, data, mqueue_fill_super, mnt);} static void init_once(struct kmem_cache *cachep, void *foo){	struct mqueue_inode_info *p = (struct mqueue_inode_info *) foo; 	inode_init_once(&p->vfs_inode);} static struct inode *mqueue_alloc_inode(struct super_block *sb){	struct mqueue_inode_info *ei; 	ei = kmem_cache_alloc(mqueue_inode_cachep, GFP_KERNEL);	if (!ei)		return NULL;	return &ei->vfs_inode;} static void mqueue_destroy_inode(struct inode *inode){	kmem_cache_free(mqueue_inode_cachep, MQUEUE_I(inode));} static void mqueue_delete_inode(struct inode *inode){	struct mqueue_inode_info *info;	struct user_struct *user;	unsigned long mq_bytes;	int i; 	if (S_ISDIR(inode->i_mode)) {		clear_inode(inode);		return;	}	info = MQUEUE_I(inode);	spin_lock(&info->lock);	for (i = 0; i < info->attr.mq_curmsgs; i++)		free_msg(info->messages[i]);	kfree(info->messages);	spin_unlock(&info->lock); 	clear_inode(inode); 	mq_bytes = (info->attr.mq_maxmsg * sizeof(struct msg_msg *) +		   (info->attr.mq_maxmsg * info->attr.mq_msgsize));	user = info->user;	if (user) {		spin_lock(&mq_lock);		user->mq_bytes -= mq_bytes;		queues_count--;		spin_unlock(&mq_lock);		free_uid(user);	}} static int mqueue_create(struct inode *dir, struct dentry *dentry,				int mode, struct nameidata *nd){	struct inode *inode;	struct mq_attr *attr = dentry->d_fsdata;	int error; 	spin_lock(&mq_lock);	if (queues_count >= queues_max && !capable(CAP_SYS_RESOURCE)) {		error = -ENOSPC;		goto out_lock;	}	queues_count++;	spin_unlock(&mq_lock); 	inode = mqueue_get_inode(dir->i_sb, mode, attr);	if (!inode) {		error = -ENOMEM;		spin_lock(&mq_lock);		queues_count--;		goto out_lock;	} 	dir->i_size += DIRENT_SIZE;	dir->i_ctime = dir->i_mtime = dir->i_atime = CURRENT_TIME; 	d_instantiate(dentry, inode);	dget(dentry);	return 0;out_lock:	spin_unlock(&mq_lock);	return error;} static int mqueue_unlink(struct inode *dir, struct dentry *dentry){  	struct inode *inode = dentry->d_inode; 	dir->i_ctime = dir->i_mtime = dir->i_atime = CURRENT_TIME;	dir->i_size -= DIRENT_SIZE;  	drop_nlink(inode);  	dput(dentry);  	return 0;} /**	This is routine for system read from queue file.*	To avoid mess with doing here some sort of mq_receive we allow*	to read only queue size & notification info (the only values*	that are interesting from user point of view and aren't accessible*	through std routines)*/static ssize_t mqueue_read_file(struct file *filp, char __user *u_data,				size_t count, loff_t * off){	struct mqueue_inode_info *info = MQUEUE_I(filp->f_path.dentry->d_inode);	char buffer[FILENT_SIZE];	size_t slen;	loff_t o; 	if (!count)		return 0; 	spin_lock(&info->lock);	snprintf(buffer, sizeof(buffer),			"QSIZE:%-10lu NOTIFY:%-5d SIGNO:%-5d NOTIFY_PID:%-6d\n",			info->qsize,			info->notify_owner ? info->notify.sigev_notify : 0,			(info->notify_owner &&			 info->notify.sigev_notify == SIGEV_SIGNAL) ?				info->notify.sigev_signo : 0,			pid_nr_ns(info->notify_owner,				current->nsproxy->pid_ns));	spin_unlock(&info->lock);	buffer[sizeof(buffer)-1] = '\0';	slen = strlen(buffer)+1; 	o = *off;	if (o > slen)		return 0; 	if (o + count > slen)		count = slen - o; 	if (copy_to_user(u_data, buffer + o, count))		return -EFAULT; 	*off = o + count;	filp->f_path.dentry->d_inode->i_atime = filp->f_path.dentry->d_inode->i_ctime = CURRENT_TIME;	return count;} static int mqueue_flush_file(struct file *filp, fl_owner_t id){	struct mqueue_inode_info *info = MQUEUE_I(filp->f_path.dentry->d_inode); 	spin_lock(&info->lock);	if (task_tgid(current) == info->notify_owner)		remove_notification(info); 	spin_unlock(&info->lock);	return 0;} static unsigned int mqueue_poll_file(struct file *filp, struct poll_table_struct *poll_tab){	struct mqueue_inode_info *info = MQUEUE_I(filp->f_path.dentry->d_inode);	int retval = 0; 	poll_wait(filp, &info->wait_q, poll_tab); 	spin_lock(&info->lock);	if (info->attr.mq_curmsgs)		retval = POLLIN | POLLRDNORM; 	if (info->attr.mq_curmsgs < info->attr.mq_maxmsg)		retval |= POLLOUT | POLLWRNORM;	spin_unlock(&info->lock); 	return retval;} /* Adds current to info->e_wait_q[sr] before element with smaller prio */static void wq_add(struct mqueue_inode_info *info, int sr,			struct ext_wait_queue *ewp){	struct ext_wait_queue *walk; 	ewp->task = current; 	list_for_each_entry(walk, &info->e_wait_q[sr].list, list) {		if (walk->task->static_prio <= current->static_prio) {			list_add_tail(&ewp->list, &walk->list);			return;		}	}	list_add_tail(&ewp->list, &info->e_wait_q[sr].list);} /* * Puts current task to sleep. Caller must hold queue lock. After return * lock isn't held. * sr: SEND or RECV */static int wq_sleep(struct mqueue_inode_info *info, int sr,			long timeout, struct ext_wait_queue *ewp){	int retval;	signed long time; 	wq_add(info, sr, ewp); 	for (;;) {		set_current_state(TASK_INTERRUPTIBLE); 		spin_unlock(&info->lock);		time = schedule_timeout(timeout); 		while (ewp->state == STATE_PENDING)			cpu_relax(); 		if (ewp->state == STATE_READY) {			retval = 0;			goto out;		}		spin_lock(&info->lock);		if (ewp->state == STATE_READY) {			retval = 0;			goto out_unlock;		}		if (signal_pending(current)) {			retval = -ERESTARTSYS;			break;		}		if (time == 0) {			retval = -ETIMEDOUT;			break;		}	}	list_del(&ewp->list);out_unlock:	spin_unlock(&info->lock);out:	return retval;} /* * Returns waiting task that should be serviced first or NULL if none exists */static struct ext_wait_queue *wq_get_first_waiter(		struct mqueue_inode_info *info, int sr){	struct list_head *ptr; 	ptr = info->e_wait_q[sr].list.prev;	if (ptr == &info->e_wait_q[sr].list)		return NULL;	return list_entry(ptr, struct ext_wait_queue, list);} /* Auxiliary functions to manipulate messages' list */static void msg_insert(struct msg_msg *ptr, struct mqueue_inode_info *info){	int k; 	k = info->attr.mq_curmsgs - 1;	while (k >= 0 && info->messages[k]->m_type >= ptr->m_type) {		info->messages[k + 1] = info->messages[k];		k--;	}	info->attr.mq_curmsgs++;	info->qsize += ptr->m_ts;	info->messages[k + 1] = ptr;} static inline struct msg_msg *msg_get(struct mqueue_inode_info *info){	info->qsize -= info->messages[--info->attr.mq_curmsgs]->m_ts;	return info->messages[info->attr.mq_curmsgs];} static inline void set_cookie(struct sk_buff *skb, char code){	((char*)skb->data)[NOTIFY_COOKIE_LEN-1] = code;} /* * The next function is only to split too long sys_mq_timedsend */static void __do_notify(struct mqueue_inode_info *info){	/* notification	 * invoked when there is registered process and there isn't process	 * waiting synchronously for message AND state of queue changed from	 * empty to not empty. Here we are sure that no one is waiting	 * synchronously. */	if (info->notify_owner &&

Lines 1–500 of 1279.