summaryrefslogtreecommitdiffstats
path: root/fs/select.c
diff options
context:
space:
mode:
authorRalf Baechle <ralf@linux-mips.org>1997-01-07 02:33:00 +0000
committer <ralf@linux-mips.org>1997-01-07 02:33:00 +0000
commitbeb116954b9b7f3bb56412b2494b562f02b864b1 (patch)
tree120e997879884e1b9d93b265221b939d2ef1ade1 /fs/select.c
parent908d4681a1dc3792ecafbe64265783a86c4cccb6 (diff)
Import of Linux/MIPS 2.1.14
Diffstat (limited to 'fs/select.c')
-rw-r--r--fs/select.c332
1 files changed, 201 insertions, 131 deletions
diff --git a/fs/select.c b/fs/select.c
index 0b277f9d6..5ffb84f86 100644
--- a/fs/select.c
+++ b/fs/select.c
@@ -22,7 +22,7 @@
#include <linux/personality.h>
#include <linux/mm.h>
-#include <asm/segment.h>
+#include <asm/uaccess.h>
#include <asm/system.h>
#define ROUND_UP(x,y) (((x)+(y)-1)/(y))
@@ -35,7 +35,7 @@
* sleep/wakeup mechanism works.
*
* Two very simple procedures, select_wait() and free_wait() make all the work.
- * select_wait() is a inline-function defined in <linux/sched.h>, as all select
+ * select_wait() is an inline-function defined in <linux/sched.h>, as all select
* functions have to call it to add an entry to the select table.
*/
@@ -67,129 +67,212 @@ static void free_wait(select_table * p)
* and we aren't going to sleep on the select_table. -- jrs
*/
-static int check(int flag, select_table * wait, struct file * file)
+static inline int __check(
+ int (*select) (struct inode *, struct file *, int, select_table *),
+ struct inode *inode,
+ struct file *file,
+ int flag,
+ select_table * wait)
{
- struct inode * inode;
- struct file_operations *fops;
- int (*select) (struct inode *, struct file *, int, select_table *);
-
- inode = file->f_inode;
- if ((fops = file->f_op) && (select = fops->select))
- return select(inode, file, flag, wait)
- || (wait && select(inode, file, flag, NULL));
- if (flag != SEL_EX)
- return 1;
- return 0;
+ return select(inode, file, flag, wait) ||
+ (wait && select(inode, file, flag, NULL));
}
-static int do_select(int n, fd_set *in, fd_set *out, fd_set *ex,
- fd_set *res_in, fd_set *res_out, fd_set *res_ex)
+#define check(flag,wait,file) \
+(((file)->f_op && (file)->f_op->select) ? \
+ __check((file)->f_op->select,(file)->f_inode,file,flag,wait) \
+ : \
+ (flag != SEL_EX))
+
+/*
+ * Due to kernel stack usage, we use a _limited_ fd_set type here, and once
+ * we really start supporting >256 file descriptors we'll probably have to
+ * allocate the kernel fd_set copies dynamically.. (The kernel select routines
+ * are careful to touch only the defined low bits of any fd_set pointer, this
+ * is important for performance too).
+ */
+typedef unsigned long limited_fd_set[NR_OPEN/(8*(sizeof(unsigned long)))];
+
+typedef struct {
+ limited_fd_set in, out, ex;
+ limited_fd_set res_in, res_out, res_ex;
+} fd_set_buffer;
+
+#define __IN(in) (in)
+#define __OUT(in) (in + sizeof(limited_fd_set)/sizeof(unsigned long))
+#define __EX(in) (in + 2*sizeof(limited_fd_set)/sizeof(unsigned long))
+#define __RES_IN(in) (in + 3*sizeof(limited_fd_set)/sizeof(unsigned long))
+#define __RES_OUT(in) (in + 4*sizeof(limited_fd_set)/sizeof(unsigned long))
+#define __RES_EX(in) (in + 5*sizeof(limited_fd_set)/sizeof(unsigned long))
+
+#define BITS(in) (*__IN(in)|*__OUT(in)|*__EX(in))
+
+static int max_select_fd(unsigned long n, fd_set_buffer *fds)
{
- int count;
- select_table wait_table, *wait;
- struct select_table_entry *entry;
+ unsigned long *open_fds, *in;
unsigned long set;
- int i,j;
- int max = -1;
+ int max;
- for (j = 0 ; j < __FDSET_LONGS ; j++) {
- i = j << 5;
- if (i >= n)
- break;
- set = in->fds_bits[j] | out->fds_bits[j] | ex->fds_bits[j];
- for ( ; set ; i++,set >>= 1) {
- if (i >= n)
- goto end_check;
- if (!(set & 1))
- continue;
- if (!current->files->fd[i])
- return -EBADF;
- if (!current->files->fd[i]->f_inode)
- return -EBADF;
- max = i;
+ /* handle last in-complete long-word first */
+ set = ~(~0UL << (n & (__NFDBITS-1)));
+ n /= __NFDBITS;
+ open_fds = current->files->open_fds.fds_bits+n;
+ in = fds->in+n;
+ max = 0;
+ if (set) {
+ set &= BITS(in);
+ if (set) {
+ if (!(set & ~*open_fds))
+ goto get_max;
+ return -EBADF;
}
}
-end_check:
- n = max + 1;
+ while (n) {
+ in--;
+ open_fds--;
+ n--;
+ set = BITS(in);
+ if (!set)
+ continue;
+ if (set & ~*open_fds)
+ return -EBADF;
+ if (max)
+ continue;
+get_max:
+ do {
+ max++;
+ set >>= 1;
+ } while (set);
+ max += n * __NFDBITS;
+ }
+
+ return max;
+}
+
+#define BIT(i) (1UL << ((i)&(__NFDBITS-1)))
+#define MEM(i,m) ((m)+(unsigned)(i)/__NFDBITS)
+#define ISSET(i,m) (((i)&*(m)) != 0)
+#define SET(i,m) (*(m) |= (i))
+
+static int do_select(int n, fd_set_buffer *fds)
+{
+ int retval;
+ select_table wait_table, *wait;
+ struct select_table_entry *entry;
+ int i;
+
+ retval = max_select_fd(n, fds);
+ if (retval < 0)
+ goto out;
+ n = retval;
+ retval = -ENOMEM;
if(!(entry = (struct select_table_entry*) __get_free_page(GFP_KERNEL)))
- return -ENOMEM;
- FD_ZERO(res_in);
- FD_ZERO(res_out);
- FD_ZERO(res_ex);
- count = 0;
+ goto out;
+ retval = 0;
wait_table.nr = 0;
wait_table.entry = entry;
wait = &wait_table;
-repeat:
- current->state = TASK_INTERRUPTIBLE;
- for (i = 0 ; i < n ; i++) {
- if (FD_ISSET(i,in) && check(SEL_IN,wait,current->files->fd[i])) {
- FD_SET(i, res_in);
- count++;
- wait = NULL;
- }
- if (FD_ISSET(i,out) && check(SEL_OUT,wait,current->files->fd[i])) {
- FD_SET(i, res_out);
- count++;
- wait = NULL;
- }
- if (FD_ISSET(i,ex) && check(SEL_EX,wait,current->files->fd[i])) {
- FD_SET(i, res_ex);
- count++;
- wait = NULL;
+ for (;;) {
+ struct file ** fd = current->files->fd;
+ current->state = TASK_INTERRUPTIBLE;
+ for (i = 0 ; i < n ; i++,fd++) {
+ unsigned long bit = BIT(i);
+ unsigned long *in = MEM(i,fds->in);
+ if (ISSET(bit,__IN(in)) && check(SEL_IN,wait,*fd)) {
+ SET(bit, __RES_IN(in));
+ retval++;
+ wait = NULL;
+ }
+ if (ISSET(bit,__OUT(in)) && check(SEL_OUT,wait,*fd)) {
+ SET(bit, __RES_OUT(in));
+ retval++;
+ wait = NULL;
+ }
+ if (ISSET(bit,__EX(in)) && check(SEL_EX,wait,*fd)) {
+ SET(bit, __RES_EX(in));
+ retval++;
+ wait = NULL;
+ }
}
- }
- wait = NULL;
- if (!count && current->timeout && !(current->signal & ~current->blocked)) {
+ wait = NULL;
+ if (retval || !current->timeout || (current->signal & ~current->blocked))
+ break;
schedule();
- goto repeat;
}
free_wait(&wait_table);
free_page((unsigned long) entry);
current->state = TASK_RUNNING;
- return count;
+out:
+ return retval;
}
/*
* We do a VERIFY_WRITE here even though we are only reading this time:
* we'll write to it eventually..
+ *
+ * Use "int" accesses to let user-mode fd_set's be int-aligned.
*/
-static int __get_fd_set(int nr, unsigned long * fs_pointer, unsigned long * fdset)
+static int __get_fd_set(unsigned long nr, int * fs_pointer, int * fdset)
{
- int error;
-
- FD_ZERO(fdset);
- if (!fs_pointer)
- return 0;
- error = verify_area(VERIFY_WRITE,fs_pointer,sizeof(fd_set));
- if (error)
+ /* round up nr to nearest "int" */
+ nr = (nr + 8*sizeof(int)-1) / (8*sizeof(int));
+ if (fs_pointer) {
+ int error = verify_area(VERIFY_WRITE,fs_pointer,nr*sizeof(int));
+ if (!error) {
+ while (nr) {
+ get_user(*fdset, fs_pointer);
+ nr--;
+ fs_pointer++;
+ fdset++;
+ }
+ }
return error;
- while (nr > 0) {
- *fdset = get_user(fs_pointer);
+ }
+ while (nr) {
+ *fdset = 0;
+ nr--;
fdset++;
- fs_pointer++;
- nr -= 8 * sizeof(unsigned long);
}
return 0;
}
-static void __set_fd_set(int nr, unsigned long * fs_pointer, unsigned long * fdset)
+static void __set_fd_set(long nr, int * fs_pointer, int * fdset)
{
if (!fs_pointer)
return;
- while (nr > 0) {
+ while (nr >= 0) {
put_user(*fdset, fs_pointer);
+ nr -= 8 * sizeof(int);
fdset++;
fs_pointer++;
- nr -= 8 * sizeof(unsigned long);
}
}
+/* We can do long accesses here, kernel fdsets are always long-aligned */
+static inline void __zero_fd_set(long nr, unsigned long * fdset)
+{
+ while (nr >= 0) {
+ *fdset = 0;
+ nr -= 8 * sizeof(unsigned long);
+ fdset++;
+ }
+}
+
+/*
+ * Note a few subtleties: we use "long" for the dummy, not int, and we do a
+ * subtract by 1 on the nr of file descriptors. The former is better for
+ * machines with long > int, and the latter allows us to test the bit count
+ * against "zero or positive", which can mostly be just a sign bit test..
+ */
+
#define get_fd_set(nr,fsp,fdp) \
-__get_fd_set(nr, (unsigned long *) (fsp), (unsigned long *) (fdp))
+__get_fd_set(nr, (int *) (fsp), (int *) (fdp))
#define set_fd_set(nr,fsp,fdp) \
-__set_fd_set(nr, (unsigned long *) (fsp), (unsigned long *) (fdp))
+__set_fd_set((nr)-1, (int *) (fsp), (int *) (fdp))
+
+#define zero_fd_set(nr,fdp) \
+__zero_fd_set((nr)-1, (unsigned long *) (fdp))
/*
* We can actually return ERESTARTSYS instead of EINTR, but I'd
@@ -201,72 +284,59 @@ __set_fd_set(nr, (unsigned long *) (fsp), (unsigned long *) (fdp))
*/
asmlinkage int sys_select(int n, fd_set *inp, fd_set *outp, fd_set *exp, struct timeval *tvp)
{
- int i;
- fd_set res_in, in;
- fd_set res_out, out;
- fd_set res_ex, ex;
+ int error;
+ fd_set_buffer fds;
unsigned long timeout;
+ error = -EINVAL;
if (n < 0)
- return -EINVAL;
+ goto out;
if (n > NR_OPEN)
n = NR_OPEN;
- if ((i = get_fd_set(n, inp, &in)) ||
- (i = get_fd_set(n, outp, &out)) ||
- (i = get_fd_set(n, exp, &ex))) return i;
+ if ((error = get_fd_set(n, inp, &fds.in)) ||
+ (error = get_fd_set(n, outp, &fds.out)) ||
+ (error = get_fd_set(n, exp, &fds.ex))) goto out;
timeout = ~0UL;
if (tvp) {
- i = verify_area(VERIFY_WRITE, tvp, sizeof(*tvp));
- if (i)
- return i;
- timeout = ROUND_UP(get_fs_long((unsigned long *)&tvp->tv_usec),(1000000/HZ));
- timeout += get_fs_long((unsigned long *)&tvp->tv_sec) * HZ;
+ error = verify_area(VERIFY_WRITE, tvp, sizeof(*tvp));
+ if (error)
+ goto out;
+ get_user(timeout, &tvp->tv_usec);
+ timeout = ROUND_UP(timeout,(1000000/HZ));
+ {
+ unsigned long tmp;
+ get_user(tmp, &tvp->tv_sec);
+ timeout += tmp * (unsigned long) HZ;
+ }
if (timeout)
timeout += jiffies + 1;
}
+ zero_fd_set(n, &fds.res_in);
+ zero_fd_set(n, &fds.res_out);
+ zero_fd_set(n, &fds.res_ex);
current->timeout = timeout;
- i = do_select(n, &in, &out, &ex, &res_in, &res_out, &res_ex);
+ error = do_select(n, &fds);
timeout = current->timeout - jiffies - 1;
current->timeout = 0;
if ((long) timeout < 0)
timeout = 0;
if (tvp && !(current->personality & STICKY_TIMEOUTS)) {
- put_fs_long(timeout/HZ, (unsigned long *) &tvp->tv_sec);
+ put_user(timeout/HZ, &tvp->tv_sec);
timeout %= HZ;
timeout *= (1000000/HZ);
- put_fs_long(timeout, (unsigned long *) &tvp->tv_usec);
+ put_user(timeout, &tvp->tv_usec);
}
- if (i < 0)
- return i;
- if (!i && (current->signal & ~current->blocked))
- return -ERESTARTNOHAND;
- set_fd_set(n, inp, &res_in);
- set_fd_set(n, outp, &res_out);
- set_fd_set(n, exp, &res_ex);
- return i;
-}
-
-/*
- * Perform the select(nd, in, out, ex, tv) system call.
- * Linux/i386 didn't use to be able to handle 5 system call
- * parameters, so the old select used a memory block for
- * parameter passing..
- */
-asmlinkage int old_select(unsigned long *buffer)
-{
- int n;
- fd_set *inp;
- fd_set *outp;
- fd_set *exp;
- struct timeval *tvp;
-
- n = verify_area(VERIFY_READ, buffer, 5*sizeof(unsigned long));
- if (n)
- return n;
- n = get_user(buffer);
- inp = (fd_set *) get_user(buffer+1);
- outp = (fd_set *) get_user(buffer+2);
- exp = (fd_set *) get_user(buffer+3);
- tvp = (struct timeval *) get_user(buffer+4);
- return sys_select(n, inp, outp, exp, tvp);
+ if (error < 0)
+ goto out;
+ if (!error) {
+ error = -ERESTARTNOHAND;
+ if (current->signal & ~current->blocked)
+ goto out;
+ error = 0;
+ }
+ set_fd_set(n, inp, &fds.res_in);
+ set_fd_set(n, outp, &fds.res_out);
+ set_fd_set(n, exp, &fds.res_ex);
+out:
+ return error;
}