]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - fs/select.c
Merge tag 'tty-4.12-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/tty
[karo-tx-linux.git] / fs / select.c
index e2112270d75a5f878e291bb5bb681474e3c4eeaf..d6c652a31e99d18d2d924af983c11504315af56f 100644 (file)
@@ -338,6 +338,53 @@ sticky:
        return ret;
 }
 
+/*
+ * Scalable version of the fd_set.
+ */
+
+typedef struct {
+       unsigned long *in, *out, *ex;
+       unsigned long *res_in, *res_out, *res_ex;
+} fd_set_bits;
+
+/*
+ * How many longwords for "nr" bits?
+ */
+#define FDS_BITPERLONG (8*sizeof(long))
+#define FDS_LONGS(nr)  (((nr)+FDS_BITPERLONG-1)/FDS_BITPERLONG)
+#define FDS_BYTES(nr)  (FDS_LONGS(nr)*sizeof(long))
+
+/*
+ * We do a VERIFY_WRITE here even though we are only reading this time:
+ * we'll write to it eventually..
+ *
+ * Use "unsigned long" accesses to let user-mode fd_set's be long-aligned.
+ */
+static inline
+int get_fd_set(unsigned long nr, void __user *ufdset, unsigned long *fdset)
+{
+       nr = FDS_BYTES(nr);
+       if (ufdset)
+               return copy_from_user(fdset, ufdset, nr) ? -EFAULT : 0;
+
+       memset(fdset, 0, nr);
+       return 0;
+}
+
+static inline unsigned long __must_check
+set_fd_set(unsigned long nr, void __user *ufdset, unsigned long *fdset)
+{
+       if (ufdset)
+               return __copy_to_user(ufdset, fdset, FDS_BYTES(nr));
+       return 0;
+}
+
+static inline
+void zero_fd_set(unsigned long nr, unsigned long *fdset)
+{
+       memset(fdset, 0, FDS_BYTES(nr));
+}
+
 #define FDS_IN(fds, n)         (fds->in + n)
 #define FDS_OUT(fds, n)                (fds->out + n)
 #define FDS_EX(fds, n)         (fds->ex + n)
@@ -401,7 +448,7 @@ static inline void wait_key_set(poll_table *wait, unsigned long in,
                wait->_key |= POLLOUT_SET;
 }
 
-int do_select(int n, fd_set_bits *fds, struct timespec64 *end_time)
+static int do_select(int n, fd_set_bits *fds, struct timespec64 *end_time)
 {
        ktime_t expire, *to = NULL;
        struct poll_wqueues table;
@@ -409,7 +456,7 @@ int do_select(int n, fd_set_bits *fds, struct timespec64 *end_time)
        int retval, i, timed_out = 0;
        u64 slack = 0;
        unsigned int busy_flag = net_busy_loop_on() ? POLL_BUSY_LOOP : 0;
-       unsigned long busy_end = 0;
+       unsigned long busy_start = 0;
 
        rcu_read_lock();
        retval = max_select_fd(n, fds);
@@ -512,11 +559,11 @@ int do_select(int n, fd_set_bits *fds, struct timespec64 *end_time)
 
                /* only if found POLL_BUSY_LOOP sockets && not out of time */
                if (can_busy_loop && !need_resched()) {
-                       if (!busy_end) {
-                               busy_end = busy_loop_end_time();
+                       if (!busy_start) {
+                               busy_start = busy_loop_current_time();
                                continue;
                        }
-                       if (!busy_loop_timeout(busy_end))
+                       if (!busy_loop_timeout(busy_start))
                                continue;
                }
                busy_flag = 0;
@@ -586,10 +633,7 @@ int core_sys_select(int n, fd_set __user *inp, fd_set __user *outp,
                        goto out_nofds;
 
                alloc_size = 6 * size;
-               bits = kmalloc(alloc_size, GFP_KERNEL|__GFP_NOWARN);
-               if (!bits && alloc_size > PAGE_SIZE)
-                       bits = vmalloc(alloc_size);
-
+               bits = kvmalloc(alloc_size, GFP_KERNEL);
                if (!bits)
                        goto out_nofds;
        }
@@ -800,7 +844,7 @@ static int do_poll(struct poll_list *list, struct poll_wqueues *wait,
        int timed_out = 0, count = 0;
        u64 slack = 0;
        unsigned int busy_flag = net_busy_loop_on() ? POLL_BUSY_LOOP : 0;
-       unsigned long busy_end = 0;
+       unsigned long busy_start = 0;
 
        /* Optimise the no-wait case */
        if (end_time && !end_time->tv_sec && !end_time->tv_nsec) {
@@ -853,11 +897,11 @@ static int do_poll(struct poll_list *list, struct poll_wqueues *wait,
 
                /* only if found POLL_BUSY_LOOP sockets && not out of time */
                if (can_busy_loop && !need_resched()) {
-                       if (!busy_end) {
-                               busy_end = busy_loop_end_time();
+                       if (!busy_start) {
+                               busy_start = busy_loop_current_time();
                                continue;
                        }
-                       if (!busy_loop_timeout(busy_end))
+                       if (!busy_loop_timeout(busy_start))
                                continue;
                }
                busy_flag = 0;
@@ -881,7 +925,7 @@ static int do_poll(struct poll_list *list, struct poll_wqueues *wait,
 #define N_STACK_PPS ((sizeof(stack_pps) - sizeof(struct poll_list))  / \
                        sizeof(struct pollfd))
 
-int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds,
+static int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds,
                struct timespec64 *end_time)
 {
        struct poll_wqueues table;
@@ -1053,3 +1097,373 @@ SYSCALL_DEFINE5(ppoll, struct pollfd __user *, ufds, unsigned int, nfds,
 
        return ret;
 }
+
+#ifdef CONFIG_COMPAT
+#define __COMPAT_NFDBITS       (8 * sizeof(compat_ulong_t))
+
+static
+int compat_poll_select_copy_remaining(struct timespec *end_time, void __user *p,
+                                     int timeval, int ret)
+{
+       struct timespec ts;
+
+       if (!p)
+               return ret;
+
+       if (current->personality & STICKY_TIMEOUTS)
+               goto sticky;
+
+       /* No update for zero timeout */
+       if (!end_time->tv_sec && !end_time->tv_nsec)
+               return ret;
+
+       ktime_get_ts(&ts);
+       ts = timespec_sub(*end_time, ts);
+       if (ts.tv_sec < 0)
+               ts.tv_sec = ts.tv_nsec = 0;
+
+       if (timeval) {
+               struct compat_timeval rtv;
+
+               rtv.tv_sec = ts.tv_sec;
+               rtv.tv_usec = ts.tv_nsec / NSEC_PER_USEC;
+
+               if (!copy_to_user(p, &rtv, sizeof(rtv)))
+                       return ret;
+       } else {
+               struct compat_timespec rts;
+
+               rts.tv_sec = ts.tv_sec;
+               rts.tv_nsec = ts.tv_nsec;
+
+               if (!copy_to_user(p, &rts, sizeof(rts)))
+                       return ret;
+       }
+       /*
+        * If an application puts its timeval in read-only memory, we
+        * don't want the Linux-specific update to the timeval to
+        * cause a fault after the select has completed
+        * successfully. However, because we're not updating the
+        * timeval, we can't restart the system call.
+        */
+
+sticky:
+       if (ret == -ERESTARTNOHAND)
+               ret = -EINTR;
+       return ret;
+}
+
+/*
+ * Ooo, nasty.  We need here to frob 32-bit unsigned longs to
+ * 64-bit unsigned longs.
+ */
+static
+int compat_get_fd_set(unsigned long nr, compat_ulong_t __user *ufdset,
+                       unsigned long *fdset)
+{
+       nr = DIV_ROUND_UP(nr, __COMPAT_NFDBITS);
+       if (ufdset) {
+               unsigned long odd;
+
+               if (!access_ok(VERIFY_WRITE, ufdset, nr*sizeof(compat_ulong_t)))
+                       return -EFAULT;
+
+               odd = nr & 1UL;
+               nr &= ~1UL;
+               while (nr) {
+                       unsigned long h, l;
+                       if (__get_user(l, ufdset) || __get_user(h, ufdset+1))
+                               return -EFAULT;
+                       ufdset += 2;
+                       *fdset++ = h << 32 | l;
+                       nr -= 2;
+               }
+               if (odd && __get_user(*fdset, ufdset))
+                       return -EFAULT;
+       } else {
+               /* Tricky, must clear full unsigned long in the
+                * kernel fdset at the end, this makes sure that
+                * actually happens.
+                */
+               memset(fdset, 0, ((nr + 1) & ~1)*sizeof(compat_ulong_t));
+       }
+       return 0;
+}
+
+static
+int compat_set_fd_set(unsigned long nr, compat_ulong_t __user *ufdset,
+                     unsigned long *fdset)
+{
+       unsigned long odd;
+       nr = DIV_ROUND_UP(nr, __COMPAT_NFDBITS);
+
+       if (!ufdset)
+               return 0;
+
+       odd = nr & 1UL;
+       nr &= ~1UL;
+       while (nr) {
+               unsigned long h, l;
+               l = *fdset++;
+               h = l >> 32;
+               if (__put_user(l, ufdset) || __put_user(h, ufdset+1))
+                       return -EFAULT;
+               ufdset += 2;
+               nr -= 2;
+       }
+       if (odd && __put_user(*fdset, ufdset))
+               return -EFAULT;
+       return 0;
+}
+
+
+/*
+ * This is a virtual copy of sys_select from fs/select.c and probably
+ * should be compared to it from time to time
+ */
+
+/*
+ * We can actually return ERESTARTSYS instead of EINTR, but I'd
+ * like to be certain this leads to no problems. So I return
+ * EINTR just for safety.
+ *
+ * Update: ERESTARTSYS breaks at least the xview clock binary, so
+ * I'm trying ERESTARTNOHAND which restart only when you want to.
+ */
+static int compat_core_sys_select(int n, compat_ulong_t __user *inp,
+       compat_ulong_t __user *outp, compat_ulong_t __user *exp,
+       struct timespec *end_time)
+{
+       fd_set_bits fds;
+       void *bits;
+       int size, max_fds, ret = -EINVAL;
+       struct fdtable *fdt;
+       long stack_fds[SELECT_STACK_ALLOC/sizeof(long)];
+
+       if (n < 0)
+               goto out_nofds;
+
+       /* max_fds can increase, so grab it once to avoid race */
+       rcu_read_lock();
+       fdt = files_fdtable(current->files);
+       max_fds = fdt->max_fds;
+       rcu_read_unlock();
+       if (n > max_fds)
+               n = max_fds;
+
+       /*
+        * We need 6 bitmaps (in/out/ex for both incoming and outgoing),
+        * since we used fdset we need to allocate memory in units of
+        * long-words.
+        */
+       size = FDS_BYTES(n);
+       bits = stack_fds;
+       if (size > sizeof(stack_fds) / 6) {
+               bits = kmalloc(6 * size, GFP_KERNEL);
+               ret = -ENOMEM;
+               if (!bits)
+                       goto out_nofds;
+       }
+       fds.in      = (unsigned long *)  bits;
+       fds.out     = (unsigned long *) (bits +   size);
+       fds.ex      = (unsigned long *) (bits + 2*size);
+       fds.res_in  = (unsigned long *) (bits + 3*size);
+       fds.res_out = (unsigned long *) (bits + 4*size);
+       fds.res_ex  = (unsigned long *) (bits + 5*size);
+
+       if ((ret = compat_get_fd_set(n, inp, fds.in)) ||
+           (ret = compat_get_fd_set(n, outp, fds.out)) ||
+           (ret = compat_get_fd_set(n, exp, fds.ex)))
+               goto out;
+       zero_fd_set(n, fds.res_in);
+       zero_fd_set(n, fds.res_out);
+       zero_fd_set(n, fds.res_ex);
+
+       ret = do_select(n, &fds, end_time);
+
+       if (ret < 0)
+               goto out;
+       if (!ret) {
+               ret = -ERESTARTNOHAND;
+               if (signal_pending(current))
+                       goto out;
+               ret = 0;
+       }
+
+       if (compat_set_fd_set(n, inp, fds.res_in) ||
+           compat_set_fd_set(n, outp, fds.res_out) ||
+           compat_set_fd_set(n, exp, fds.res_ex))
+               ret = -EFAULT;
+out:
+       if (bits != stack_fds)
+               kfree(bits);
+out_nofds:
+       return ret;
+}
+
+COMPAT_SYSCALL_DEFINE5(select, int, n, compat_ulong_t __user *, inp,
+       compat_ulong_t __user *, outp, compat_ulong_t __user *, exp,
+       struct compat_timeval __user *, tvp)
+{
+       struct timespec end_time, *to = NULL;
+       struct compat_timeval tv;
+       int ret;
+
+       if (tvp) {
+               if (copy_from_user(&tv, tvp, sizeof(tv)))
+                       return -EFAULT;
+
+               to = &end_time;
+               if (poll_select_set_timeout(to,
+                               tv.tv_sec + (tv.tv_usec / USEC_PER_SEC),
+                               (tv.tv_usec % USEC_PER_SEC) * NSEC_PER_USEC))
+                       return -EINVAL;
+       }
+
+       ret = compat_core_sys_select(n, inp, outp, exp, to);
+       ret = compat_poll_select_copy_remaining(&end_time, tvp, 1, ret);
+
+       return ret;
+}
+
+struct compat_sel_arg_struct {
+       compat_ulong_t n;
+       compat_uptr_t inp;
+       compat_uptr_t outp;
+       compat_uptr_t exp;
+       compat_uptr_t tvp;
+};
+
+COMPAT_SYSCALL_DEFINE1(old_select, struct compat_sel_arg_struct __user *, arg)
+{
+       struct compat_sel_arg_struct a;
+
+       if (copy_from_user(&a, arg, sizeof(a)))
+               return -EFAULT;
+       return compat_sys_select(a.n, compat_ptr(a.inp), compat_ptr(a.outp),
+                                compat_ptr(a.exp), compat_ptr(a.tvp));
+}
+
+static long do_compat_pselect(int n, compat_ulong_t __user *inp,
+       compat_ulong_t __user *outp, compat_ulong_t __user *exp,
+       struct compat_timespec __user *tsp, compat_sigset_t __user *sigmask,
+       compat_size_t sigsetsize)
+{
+       compat_sigset_t ss32;
+       sigset_t ksigmask, sigsaved;
+       struct compat_timespec ts;
+       struct timespec end_time, *to = NULL;
+       int ret;
+
+       if (tsp) {
+               if (copy_from_user(&ts, tsp, sizeof(ts)))
+                       return -EFAULT;
+
+               to = &end_time;
+               if (poll_select_set_timeout(to, ts.tv_sec, ts.tv_nsec))
+                       return -EINVAL;
+       }
+
+       if (sigmask) {
+               if (sigsetsize != sizeof(compat_sigset_t))
+                       return -EINVAL;
+               if (copy_from_user(&ss32, sigmask, sizeof(ss32)))
+                       return -EFAULT;
+               sigset_from_compat(&ksigmask, &ss32);
+
+               sigdelsetmask(&ksigmask, sigmask(SIGKILL)|sigmask(SIGSTOP));
+               sigprocmask(SIG_SETMASK, &ksigmask, &sigsaved);
+       }
+
+       ret = compat_core_sys_select(n, inp, outp, exp, to);
+       ret = compat_poll_select_copy_remaining(&end_time, tsp, 0, ret);
+
+       if (ret == -ERESTARTNOHAND) {
+               /*
+                * Don't restore the signal mask yet. Let do_signal() deliver
+                * the signal on the way back to userspace, before the signal
+                * mask is restored.
+                */
+               if (sigmask) {
+                       memcpy(&current->saved_sigmask, &sigsaved,
+                                       sizeof(sigsaved));
+                       set_restore_sigmask();
+               }
+       } else if (sigmask)
+               sigprocmask(SIG_SETMASK, &sigsaved, NULL);
+
+       return ret;
+}
+
+COMPAT_SYSCALL_DEFINE6(pselect6, int, n, compat_ulong_t __user *, inp,
+       compat_ulong_t __user *, outp, compat_ulong_t __user *, exp,
+       struct compat_timespec __user *, tsp, void __user *, sig)
+{
+       compat_size_t sigsetsize = 0;
+       compat_uptr_t up = 0;
+
+       if (sig) {
+               if (!access_ok(VERIFY_READ, sig,
+                               sizeof(compat_uptr_t)+sizeof(compat_size_t)) ||
+                       __get_user(up, (compat_uptr_t __user *)sig) ||
+                       __get_user(sigsetsize,
+                               (compat_size_t __user *)(sig+sizeof(up))))
+                       return -EFAULT;
+       }
+       return do_compat_pselect(n, inp, outp, exp, tsp, compat_ptr(up),
+                                sigsetsize);
+}
+
+COMPAT_SYSCALL_DEFINE5(ppoll, struct pollfd __user *, ufds,
+       unsigned int,  nfds, struct compat_timespec __user *, tsp,
+       const compat_sigset_t __user *, sigmask, compat_size_t, sigsetsize)
+{
+       compat_sigset_t ss32;
+       sigset_t ksigmask, sigsaved;
+       struct compat_timespec ts;
+       struct timespec end_time, *to = NULL;
+       int ret;
+
+       if (tsp) {
+               if (copy_from_user(&ts, tsp, sizeof(ts)))
+                       return -EFAULT;
+
+               to = &end_time;
+               if (poll_select_set_timeout(to, ts.tv_sec, ts.tv_nsec))
+                       return -EINVAL;
+       }
+
+       if (sigmask) {
+               if (sigsetsize != sizeof(compat_sigset_t))
+                       return -EINVAL;
+               if (copy_from_user(&ss32, sigmask, sizeof(ss32)))
+                       return -EFAULT;
+               sigset_from_compat(&ksigmask, &ss32);
+
+               sigdelsetmask(&ksigmask, sigmask(SIGKILL)|sigmask(SIGSTOP));
+               sigprocmask(SIG_SETMASK, &ksigmask, &sigsaved);
+       }
+
+       ret = do_sys_poll(ufds, nfds, to);
+
+       /* We can restart this syscall, usually */
+       if (ret == -EINTR) {
+               /*
+                * Don't restore the signal mask yet. Let do_signal() deliver
+                * the signal on the way back to userspace, before the signal
+                * mask is restored.
+                */
+               if (sigmask) {
+                       memcpy(&current->saved_sigmask, &sigsaved,
+                               sizeof(sigsaved));
+                       set_restore_sigmask();
+               }
+               ret = -ERESTARTNOHAND;
+       } else if (sigmask)
+               sigprocmask(SIG_SETMASK, &sigsaved, NULL);
+
+       ret = compat_poll_select_copy_remaining(&end_time, tsp, 0, ret);
+
+       return ret;
+}
+#endif