semctl(): separate all layout-dependent copyin/copyout

Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
diff --git a/ipc/sem.c b/ipc/sem.c
index 9e70cd7..c8029c6 100644
--- a/ipc/sem.c
+++ b/ipc/sem.c
@@ -1177,112 +1177,95 @@ static time_t get_semotime(struct sem_array *sma)
 	return res;
 }
 
-static int semctl_nolock(struct ipc_namespace *ns, int semid,
-			 int cmd, int version, void __user *p)
+static int semctl_stat(struct ipc_namespace *ns, int semid,
+			 int cmd, struct semid64_ds *semid64)
 {
-	int err;
 	struct sem_array *sma;
+	int id = 0;
+	int err;
 
-	switch (cmd) {
-	case IPC_INFO:
-	case SEM_INFO:
-	{
-		struct seminfo seminfo;
-		int max_id;
+	memset(semid64, 0, sizeof(*semid64));
 
-		err = security_sem_semctl(NULL, cmd);
-		if (err)
-			return err;
-
-		memset(&seminfo, 0, sizeof(seminfo));
-		seminfo.semmni = ns->sc_semmni;
-		seminfo.semmns = ns->sc_semmns;
-		seminfo.semmsl = ns->sc_semmsl;
-		seminfo.semopm = ns->sc_semopm;
-		seminfo.semvmx = SEMVMX;
-		seminfo.semmnu = SEMMNU;
-		seminfo.semmap = SEMMAP;
-		seminfo.semume = SEMUME;
-		down_read(&sem_ids(ns).rwsem);
-		if (cmd == SEM_INFO) {
-			seminfo.semusz = sem_ids(ns).in_use;
-			seminfo.semaem = ns->used_sems;
-		} else {
-			seminfo.semusz = SEMUSZ;
-			seminfo.semaem = SEMAEM;
-		}
-		max_id = ipc_get_maxid(&sem_ids(ns));
-		up_read(&sem_ids(ns).rwsem);
-		if (copy_to_user(p, &seminfo, sizeof(struct seminfo)))
-			return -EFAULT;
-		return (max_id < 0) ? 0 : max_id;
-	}
-	case IPC_STAT:
-	case SEM_STAT:
-	{
-		struct semid64_ds tbuf;
-		int id = 0;
-
-		memset(&tbuf, 0, sizeof(tbuf));
-
-		rcu_read_lock();
-		if (cmd == SEM_STAT) {
-			sma = sem_obtain_object(ns, semid);
-			if (IS_ERR(sma)) {
-				err = PTR_ERR(sma);
-				goto out_unlock;
-			}
-			id = sma->sem_perm.id;
-		} else {
-			sma = sem_obtain_object_check(ns, semid);
-			if (IS_ERR(sma)) {
-				err = PTR_ERR(sma);
-				goto out_unlock;
-			}
-		}
-
-		err = -EACCES;
-		if (ipcperms(ns, &sma->sem_perm, S_IRUGO))
+	rcu_read_lock();
+	if (cmd == SEM_STAT) {
+		sma = sem_obtain_object(ns, semid);
+		if (IS_ERR(sma)) {
+			err = PTR_ERR(sma);
 			goto out_unlock;
-
-		err = security_sem_semctl(sma, cmd);
-		if (err)
+		}
+		id = sma->sem_perm.id;
+	} else {
+		sma = sem_obtain_object_check(ns, semid);
+		if (IS_ERR(sma)) {
+			err = PTR_ERR(sma);
 			goto out_unlock;
+		}
+	}
 
-		kernel_to_ipc64_perm(&sma->sem_perm, &tbuf.sem_perm);
-		tbuf.sem_otime = get_semotime(sma);
-		tbuf.sem_ctime = sma->sem_ctime;
-		tbuf.sem_nsems = sma->sem_nsems;
-		rcu_read_unlock();
-		if (copy_semid_to_user(p, &tbuf, version))
-			return -EFAULT;
-		return id;
-	}
-	default:
-		return -EINVAL;
-	}
+	err = -EACCES;
+	if (ipcperms(ns, &sma->sem_perm, S_IRUGO))
+		goto out_unlock;
+
+	err = security_sem_semctl(sma, cmd);
+	if (err)
+		goto out_unlock;
+
+	kernel_to_ipc64_perm(&sma->sem_perm, &semid64->sem_perm);
+	semid64->sem_otime = get_semotime(sma);
+	semid64->sem_ctime = sma->sem_ctime;
+	semid64->sem_nsems = sma->sem_nsems;
+	rcu_read_unlock();
+	return id;
+
 out_unlock:
 	rcu_read_unlock();
 	return err;
 }
 
+static int semctl_info(struct ipc_namespace *ns, int semid,
+			 int cmd, void __user *p)
+{
+	struct seminfo seminfo;
+	int max_id;
+	int err;
+
+	err = security_sem_semctl(NULL, cmd);
+	if (err)
+		return err;
+
+	memset(&seminfo, 0, sizeof(seminfo));
+	seminfo.semmni = ns->sc_semmni;
+	seminfo.semmns = ns->sc_semmns;
+	seminfo.semmsl = ns->sc_semmsl;
+	seminfo.semopm = ns->sc_semopm;
+	seminfo.semvmx = SEMVMX;
+	seminfo.semmnu = SEMMNU;
+	seminfo.semmap = SEMMAP;
+	seminfo.semume = SEMUME;
+	down_read(&sem_ids(ns).rwsem);
+	if (cmd == SEM_INFO) {
+		seminfo.semusz = sem_ids(ns).in_use;
+		seminfo.semaem = ns->used_sems;
+	} else {
+		seminfo.semusz = SEMUSZ;
+		seminfo.semaem = SEMAEM;
+	}
+	max_id = ipc_get_maxid(&sem_ids(ns));
+	up_read(&sem_ids(ns).rwsem);
+	if (copy_to_user(p, &seminfo, sizeof(struct seminfo)))
+		return -EFAULT;
+	return (max_id < 0) ? 0 : max_id;
+}
+
 static int semctl_setval(struct ipc_namespace *ns, int semid, int semnum,
-		unsigned long arg)
+		int val)
 {
 	struct sem_undo *un;
 	struct sem_array *sma;
 	struct sem *curr;
-	int err, val;
+	int err;
 	DEFINE_WAKE_Q(wake_q);
 
-#if defined(CONFIG_64BIT) && defined(__BIG_ENDIAN)
-	/* big-endian 64bit */
-	val = arg >> 32;
-#else
-	/* 32bit or little-endian 64bit */
-	val = arg;
-#endif
-
 	if (val > SEMVMX || val < 0)
 		return -ERANGE;
 
@@ -1531,23 +1514,17 @@ copy_semid_from_user(struct semid64_ds *out, void __user *buf, int version)
  * NOTE: no locks must be held, the rwsem is taken inside this function.
  */
 static int semctl_down(struct ipc_namespace *ns, int semid,
-		       int cmd, int version, void __user *p)
+		       int cmd, struct semid64_ds *semid64)
 {
 	struct sem_array *sma;
 	int err;
-	struct semid64_ds semid64;
 	struct kern_ipc_perm *ipcp;
 
-	if (cmd == IPC_SET) {
-		if (copy_semid_from_user(&semid64, p, version))
-			return -EFAULT;
-	}
-
 	down_write(&sem_ids(ns).rwsem);
 	rcu_read_lock();
 
 	ipcp = ipcctl_pre_down_nolock(ns, &sem_ids(ns), semid, cmd,
-				      &semid64.sem_perm, 0);
+				      &semid64->sem_perm, 0);
 	if (IS_ERR(ipcp)) {
 		err = PTR_ERR(ipcp);
 		goto out_unlock1;
@@ -1567,7 +1544,7 @@ static int semctl_down(struct ipc_namespace *ns, int semid,
 		goto out_up;
 	case IPC_SET:
 		sem_lock(sma, NULL, -1);
-		err = ipc_update_perm(&semid64.sem_perm, ipcp);
+		err = ipc_update_perm(&semid64->sem_perm, ipcp);
 		if (err)
 			goto out_unlock0;
 		sma->sem_ctime = get_seconds();
@@ -1591,6 +1568,8 @@ SYSCALL_DEFINE4(semctl, int, semid, int, semnum, int, cmd, unsigned long, arg)
 	int version;
 	struct ipc_namespace *ns;
 	void __user *p = (void __user *)arg;
+	struct semid64_ds semid64;
+	int err;
 
 	if (semid < 0)
 		return -EINVAL;
@@ -1601,9 +1580,15 @@ SYSCALL_DEFINE4(semctl, int, semid, int, semnum, int, cmd, unsigned long, arg)
 	switch (cmd) {
 	case IPC_INFO:
 	case SEM_INFO:
+		return semctl_info(ns, semid, cmd, p);
 	case IPC_STAT:
 	case SEM_STAT:
-		return semctl_nolock(ns, semid, cmd, version, p);
+		err = semctl_stat(ns, semid, cmd, &semid64);
+		if (err < 0)
+			return err;
+		if (copy_semid_to_user(p, &semid64, version))
+			err = -EFAULT;
+		return err;
 	case GETALL:
 	case GETVAL:
 	case GETPID:
@@ -1611,11 +1596,22 @@ SYSCALL_DEFINE4(semctl, int, semid, int, semnum, int, cmd, unsigned long, arg)
 	case GETZCNT:
 	case SETALL:
 		return semctl_main(ns, semid, semnum, cmd, p);
-	case SETVAL:
-		return semctl_setval(ns, semid, semnum, arg);
-	case IPC_RMID:
+	case SETVAL: {
+		int val;
+#if defined(CONFIG_64BIT) && defined(__BIG_ENDIAN)
+		/* big-endian 64bit */
+		val = arg >> 32;
+#else
+		/* 32bit or little-endian 64bit */
+		val = arg;
+#endif
+		return semctl_setval(ns, semid, semnum, val);
+	}
 	case IPC_SET:
-		return semctl_down(ns, semid, cmd, version, p);
+		if (copy_semid_from_user(&semid64, p, version))
+			return -EFAULT;
+	case IPC_RMID:
+		return semctl_down(ns, semid, cmd, &semid64);
 	default:
 		return -EINVAL;
 	}