diff options
Diffstat (limited to 'ipc/msg.c')
| -rw-r--r-- | ipc/msg.c | 123 | 
1 files changed, 96 insertions, 27 deletions
diff --git a/ipc/msg.c b/ipc/msg.c index a71af5a65abf..950572f9d796 100644 --- a/ipc/msg.c +++ b/ipc/msg.c @@ -755,26 +755,91 @@ static inline int convert_mode(long *msgtyp, int msgflg)  	return SEARCH_EQUAL;  } -long do_msgrcv(int msqid, long *pmtype, void __user *mtext, -		size_t msgsz, long msgtyp, int msgflg) +static long do_msg_fill(void __user *dest, struct msg_msg *msg, size_t bufsz) +{ +	struct msgbuf __user *msgp = dest; +	size_t msgsz; + +	if (put_user(msg->m_type, &msgp->mtype)) +		return -EFAULT; + +	msgsz = (bufsz > msg->m_ts) ? msg->m_ts : bufsz; +	if (store_msg(msgp->mtext, msg, msgsz)) +		return -EFAULT; +	return msgsz; +} + +#ifdef CONFIG_CHECKPOINT_RESTORE +/* + * This function creates new kernel message structure, large enough to store + * bufsz message bytes. + */ +static inline struct msg_msg *prepare_copy(void __user *buf, size_t bufsz, +					   int msgflg, long *msgtyp, +					   unsigned long *copy_number) +{ +	struct msg_msg *copy; + +	*copy_number = *msgtyp; +	*msgtyp = 0; +	/* +	 * Create dummy message to copy real message to. +	 */ +	copy = load_msg(buf, bufsz); +	if (!IS_ERR(copy)) +		copy->m_ts = bufsz; +	return copy; +} + +static inline void free_copy(struct msg_msg *copy) +{ +	if (copy) +		free_msg(copy); +} +#else +static inline struct msg_msg *prepare_copy(void __user *buf, size_t bufsz, +					   int msgflg, long *msgtyp, +					   unsigned long *copy_number) +{ +	return ERR_PTR(-ENOSYS); +} + +static inline void free_copy(struct msg_msg *copy) +{ +} +#endif + +long do_msgrcv(int msqid, void __user *buf, size_t bufsz, long msgtyp, +	       int msgflg, +	       long (*msg_handler)(void __user *, struct msg_msg *, size_t))  {  	struct msg_queue *msq;  	struct msg_msg *msg;  	int mode;  	struct ipc_namespace *ns; +	struct msg_msg *copy = NULL; +	unsigned long copy_number = 0; -	if (msqid < 0 || (long) msgsz < 0) +	if (msqid < 0 || (long) bufsz < 0)  		return -EINVAL; +	if (msgflg & MSG_COPY) { +		copy = prepare_copy(buf, bufsz, msgflg, &msgtyp, ©_number); +		if (IS_ERR(copy)) +			return PTR_ERR(copy); +	}  	mode = convert_mode(&msgtyp, msgflg);  	ns = current->nsproxy->ipc_ns;  	msq = msg_lock_check(ns, msqid); -	if (IS_ERR(msq)) +	if (IS_ERR(msq)) { +		free_copy(copy);  		return PTR_ERR(msq); +	}  	for (;;) {  		struct msg_receiver msr_d;  		struct list_head *tmp; +		long msg_counter = 0;  		msg = ERR_PTR(-EACCES);  		if (ipcperms(ns, &msq->q_perm, S_IRUGO)) @@ -793,12 +858,21 @@ long do_msgrcv(int msqid, long *pmtype, void __user *mtext,  				msg = walk_msg;  				if (mode == SEARCH_LESSEQUAL &&  						walk_msg->m_type != 1) { -					msg = walk_msg;  					msgtyp = walk_msg->m_type - 1; -				} else { -					msg = walk_msg; +				} else if (msgflg & MSG_COPY) { +					if (copy_number == msg_counter) { +						/* +						 * Found requested message. +						 * Copy it. +						 */ +						msg = copy_msg(msg, copy); +						if (IS_ERR(msg)) +							goto out_unlock; +						break; +					} +				} else  					break; -				} +				msg_counter++;  			}  			tmp = tmp->next;  		} @@ -807,10 +881,16 @@ long do_msgrcv(int msqid, long *pmtype, void __user *mtext,  			 * Found a suitable message.  			 * Unlink it from the queue.  			 */ -			if ((msgsz < msg->m_ts) && !(msgflg & MSG_NOERROR)) { +			if ((bufsz < msg->m_ts) && !(msgflg & MSG_NOERROR)) {  				msg = ERR_PTR(-E2BIG);  				goto out_unlock;  			} +			/* +			 * If we are copying, then do not unlink message and do +			 * not update queue parameters. +			 */ +			if (msgflg & MSG_COPY) +				goto out_unlock;  			list_del(&msg->m_list);  			msq->q_qnum--;  			msq->q_rtime = get_seconds(); @@ -834,7 +914,7 @@ long do_msgrcv(int msqid, long *pmtype, void __user *mtext,  		if (msgflg & MSG_NOERROR)  			msr_d.r_maxsize = INT_MAX;  		else -			msr_d.r_maxsize = msgsz; +			msr_d.r_maxsize = bufsz;  		msr_d.r_msg = ERR_PTR(-EAGAIN);  		current->state = TASK_INTERRUPTIBLE;  		msg_unlock(msq); @@ -894,32 +974,21 @@ out_unlock:  			break;  		}  	} -	if (IS_ERR(msg)) +	if (IS_ERR(msg)) { +		free_copy(copy);  		return PTR_ERR(msg); +	} -	msgsz = (msgsz > msg->m_ts) ? msg->m_ts : msgsz; -	*pmtype = msg->m_type; -	if (store_msg(mtext, msg, msgsz)) -		msgsz = -EFAULT; - +	bufsz = msg_handler(buf, msg, bufsz);  	free_msg(msg); -	return msgsz; +	return bufsz;  }  SYSCALL_DEFINE5(msgrcv, int, msqid, struct msgbuf __user *, msgp, size_t, msgsz,  		long, msgtyp, int, msgflg)  { -	long err, mtype; - -	err =  do_msgrcv(msqid, &mtype, msgp->mtext, msgsz, msgtyp, msgflg); -	if (err < 0) -		goto out; - -	if (put_user(mtype, &msgp->mtype)) -		err = -EFAULT; -out: -	return err; +	return do_msgrcv(msqid, msgp, msgsz, msgtyp, msgflg, do_msg_fill);  }  #ifdef CONFIG_PROC_FS  |