Merge branch 'work.ipc' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs
[muen/linux.git] / ipc / sem.c
index 013c7981f3c74ce96369a1ee5835270b4cad6924..f7385bce5fd3bd00a9d3a42b7b61c7e96f5184f3 100644 (file)
--- a/ipc/sem.c
+++ b/ipc/sem.c
@@ -512,7 +512,7 @@ static int newary(struct ipc_namespace *ns, struct ipc_params *params)
        INIT_LIST_HEAD(&sma->pending_const);
        INIT_LIST_HEAD(&sma->list_id);
        sma->sem_nsems = nsems;
-       sma->sem_ctime = get_seconds();
+       sma->sem_ctime = ktime_get_real_seconds();
 
        retval = ipc_addid(&sem_ids(ns), &sma->sem_perm, ns->sc_semmni);
        if (retval < 0) {
@@ -1163,14 +1163,14 @@ static unsigned long copy_semid_to_user(void __user *buf, struct semid64_ds *in,
        }
 }
 
-static time_t get_semotime(struct sem_array *sma)
+static time64_t get_semotime(struct sem_array *sma)
 {
        int i;
-       time_t res;
+       time64_t res;
 
        res = sma->sems[0].sem_otime;
        for (i = 1; i < sma->sem_nsems; i++) {
-               time_t to = sma->sems[i].sem_otime;
+               time64_t to = sma->sems[i].sem_otime;
 
                if (to > res)
                        res = to;
@@ -1178,112 +1178,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;
-
-               err = security_sem_semctl(NULL, cmd);
-               if (err)
-                       return err;
+       memset(semid64, 0, sizeof(*semid64));
 
-               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;
+       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;
                }
-               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))
+               goto out_unlock;
 
-               err = -EACCES;
-               if (ipcperms(ns, &sma->sem_perm, S_IRUGO))
-                       goto out_unlock;
+       err = security_sem_semctl(sma, cmd);
+       if (err)
+               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;
 
-               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;
-       }
 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;
 
@@ -1327,7 +1310,7 @@ static int semctl_setval(struct ipc_namespace *ns, int semid, int semnum,
 
        curr->semval = val;
        curr->sempid = task_tgid_vnr(current);
-       sma->sem_ctime = get_seconds();
+       sma->sem_ctime = ktime_get_real_seconds();
        /* maybe some queued-up processes were waiting for this */
        do_smart_update(sma, NULL, 0, 0, &wake_q);
        sem_unlock(sma, -1);
@@ -1455,7 +1438,7 @@ static int semctl_main(struct ipc_namespace *ns, int semid, int semnum,
                        for (i = 0; i < nsems; i++)
                                un->semadj[i] = 0;
                }
-               sma->sem_ctime = get_seconds();
+               sma->sem_ctime = ktime_get_real_seconds();
                /* maybe some queued-up processes were waiting for this */
                do_smart_update(sma, NULL, 0, 0, &wake_q);
                err = 0;
@@ -1532,23 +1515,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;
@@ -1568,10 +1545,10 @@ 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();
+               sma->sem_ctime = ktime_get_real_seconds();
                break;
        default:
                err = -EINVAL;
@@ -1592,6 +1569,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;
@@ -1602,25 +1581,136 @@ 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:
        case GETNCNT:
        case GETZCNT:
+       case SETALL:
+               return semctl_main(ns, semid, semnum, cmd, p);
+       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:
+               if (copy_semid_from_user(&semid64, p, version))
+                       return -EFAULT;
+       case IPC_RMID:
+               return semctl_down(ns, semid, cmd, &semid64);
+       default:
+               return -EINVAL;
+       }
+}
+
+#ifdef CONFIG_COMPAT
+
+struct compat_semid_ds {
+       struct compat_ipc_perm sem_perm;
+       compat_time_t sem_otime;
+       compat_time_t sem_ctime;
+       compat_uptr_t sem_base;
+       compat_uptr_t sem_pending;
+       compat_uptr_t sem_pending_last;
+       compat_uptr_t undo;
+       unsigned short sem_nsems;
+};
+
+static int copy_compat_semid_from_user(struct semid64_ds *out, void __user *buf,
+                                       int version)
+{
+       memset(out, 0, sizeof(*out));
+       if (version == IPC_64) {
+               struct compat_semid64_ds *p = buf;
+               return get_compat_ipc64_perm(&out->sem_perm, &p->sem_perm);
+       } else {
+               struct compat_semid_ds *p = buf;
+               return get_compat_ipc_perm(&out->sem_perm, &p->sem_perm);
+       }
+}
+
+static int copy_compat_semid_to_user(void __user *buf, struct semid64_ds *in,
+                                       int version)
+{
+       if (version == IPC_64) {
+               struct compat_semid64_ds v;
+               memset(&v, 0, sizeof(v));
+               to_compat_ipc64_perm(&v.sem_perm, &in->sem_perm);
+               v.sem_otime = in->sem_otime;
+               v.sem_ctime = in->sem_ctime;
+               v.sem_nsems = in->sem_nsems;
+               return copy_to_user(buf, &v, sizeof(v));
+       } else {
+               struct compat_semid_ds v;
+               memset(&v, 0, sizeof(v));
+               to_compat_ipc_perm(&v.sem_perm, &in->sem_perm);
+               v.sem_otime = in->sem_otime;
+               v.sem_ctime = in->sem_ctime;
+               v.sem_nsems = in->sem_nsems;
+               return copy_to_user(buf, &v, sizeof(v));
+       }
+}
+
+COMPAT_SYSCALL_DEFINE4(semctl, int, semid, int, semnum, int, cmd, int, arg)
+{
+       void __user *p = compat_ptr(arg);
+       struct ipc_namespace *ns;
+       struct semid64_ds semid64;
+       int version = compat_ipc_parse_version(&cmd);
+       int err;
+
+       ns = current->nsproxy->ipc_ns;
+
+       if (semid < 0)
+               return -EINVAL;
+
+       switch (cmd & (~IPC_64)) {
+       case IPC_INFO:
+       case SEM_INFO:
+               return semctl_info(ns, semid, cmd, p);
+       case IPC_STAT:
+       case SEM_STAT:
+               err = semctl_stat(ns, semid, cmd, &semid64);
+               if (err < 0)
+                       return err;
+               if (copy_compat_semid_to_user(p, &semid64, version))
+                       err = -EFAULT;
+               return err;
+       case GETVAL:
+       case GETPID:
+       case GETNCNT:
+       case GETZCNT:
+       case GETALL:
        case SETALL:
                return semctl_main(ns, semid, semnum, cmd, p);
        case SETVAL:
                return semctl_setval(ns, semid, semnum, arg);
-       case IPC_RMID:
        case IPC_SET:
-               return semctl_down(ns, semid, cmd, version, p);
+               if (copy_compat_semid_from_user(&semid64, p, version))
+                       return -EFAULT;
+               /* fallthru */
+       case IPC_RMID:
+               return semctl_down(ns, semid, cmd, &semid64);
        default:
                return -EINVAL;
        }
 }
+#endif
 
 /* If the task doesn't already have a undo_list, then allocate one
  * here.  We guarantee there is only one thread using this undo list,
@@ -1766,8 +1856,8 @@ out:
        return un;
 }
 
-SYSCALL_DEFINE4(semtimedop, int, semid, struct sembuf __user *, tsops,
-               unsigned, nsops, const struct timespec __user *, timeout)
+static long do_semtimedop(int semid, struct sembuf __user *tsops,
+               unsigned nsops, const struct timespec64 *timeout)
 {
        int error = -EINVAL;
        struct sem_array *sma;
@@ -1798,17 +1888,12 @@ SYSCALL_DEFINE4(semtimedop, int, semid, struct sembuf __user *, tsops,
        }
 
        if (timeout) {
-               struct timespec _timeout;
-               if (copy_from_user(&_timeout, timeout, sizeof(*timeout))) {
-                       error = -EFAULT;
-                       goto out_free;
-               }
-               if (_timeout.tv_sec < 0 || _timeout.tv_nsec < 0 ||
-                       _timeout.tv_nsec >= 1000000000L) {
+               if (timeout->tv_sec < 0 || timeout->tv_nsec < 0 ||
+                       timeout->tv_nsec >= 1000000000L) {
                        error = -EINVAL;
                        goto out_free;
                }
-               jiffies_left = timespec_to_jiffies(&_timeout);
+               jiffies_left = timespec64_to_jiffies(timeout);
        }
 
        max = 0;
@@ -2023,10 +2108,37 @@ out_free:
        return error;
 }
 
+SYSCALL_DEFINE4(semtimedop, int, semid, struct sembuf __user *, tsops,
+               unsigned, nsops, const struct timespec __user *, timeout)
+{
+       if (timeout) {
+               struct timespec64 ts;
+               if (get_timespec64(&ts, timeout))
+                       return -EFAULT;
+               return do_semtimedop(semid, tsops, nsops, &ts);
+       }
+       return do_semtimedop(semid, tsops, nsops, NULL);
+}
+
+#ifdef CONFIG_COMPAT
+COMPAT_SYSCALL_DEFINE4(semtimedop, int, semid, struct sembuf __user *, tsems,
+                      unsigned, nsops,
+                      const struct compat_timespec __user *, timeout)
+{
+       if (timeout) {
+               struct timespec64 ts;
+               if (compat_get_timespec64(&ts, timeout))
+                       return -EFAULT;
+               return do_semtimedop(semid, tsems, nsops, &ts);
+       }
+       return do_semtimedop(semid, tsems, nsops, NULL);
+}
+#endif
+
 SYSCALL_DEFINE3(semop, int, semid, struct sembuf __user *, tsops,
                unsigned, nsops)
 {
-       return sys_semtimedop(semid, tsops, nsops, NULL);
+       return do_semtimedop(semid, tsops, nsops, NULL);
 }
 
 /* If CLONE_SYSVSEM is set, establish sharing of SEM_UNDO state between
@@ -2183,7 +2295,7 @@ static int sysvipc_sem_proc_show(struct seq_file *s, void *it)
        struct user_namespace *user_ns = seq_user_ns(s);
        struct kern_ipc_perm *ipcp = it;
        struct sem_array *sma = container_of(ipcp, struct sem_array, sem_perm);
-       time_t sem_otime;
+       time64_t sem_otime;
 
        /*
         * The proc interface isn't aware of sem_lock(), it calls
@@ -2196,7 +2308,7 @@ static int sysvipc_sem_proc_show(struct seq_file *s, void *it)
        sem_otime = get_semotime(sma);
 
        seq_printf(s,
-                  "%10d %10d  %4o %10u %5u %5u %5u %5u %10lu %10lu\n",
+                  "%10d %10d  %4o %10u %5u %5u %5u %5u %10llu %10llu\n",
                   sma->sem_perm.key,
                   sma->sem_perm.id,
                   sma->sem_perm.mode,