| /* |
| * This file contains the procedures for the handling of select |
| * |
| * Created for Linux based loosely upon Mathius Lattner's minix |
| * patches by Peter MacDonald. Heavily edited by Linus. |
| */ |
| |
| #include <linux/fs.h> |
| #include <linux/kernel.h> |
| #include <linux/tty.h> |
| #include <linux/sched.h> |
| #include <linux/string.h> |
| #include <linux/stat.h> |
| |
| #include <asm/segment.h> |
| #include <asm/system.h> |
| |
| #include <sys/types.h> |
| #include <sys/time.h> |
| |
| #include <const.h> |
| #include <errno.h> |
| #include <signal.h> |
| |
| /* |
| * Ok, Peter made a complicated, but straightforward multiple_wait() function. |
| * I have rewritten this, taking some shortcuts: This code may not be easy to |
| * follow, but it should be free of race-conditions, and it's practical. If you |
| * understand what I'm doing here, then you understand how the linux sleep/wakeup |
| * mechanism works. |
| * |
| * Two very simple procedures, add_wait() and free_wait() make all the work. We |
| * have to have interrupts disabled throughout the select, but that's not really |
| * such a loss: sleeping automatically frees interrupts when we aren't in this |
| * task. |
| */ |
| |
| static select_table * sel_tables = NULL; |
| |
| static void add_wait(struct task_struct ** wait_address, select_table * p) |
| { |
| int i; |
| |
| if (!wait_address) |
| return; |
| for (i = 0 ; i < p->nr ; i++) |
| if (p->entry[i].wait_address == wait_address) |
| return; |
| current->next_wait = NULL; |
| p->entry[p->nr].wait_address = wait_address; |
| p->entry[p->nr].old_task = *wait_address; |
| *wait_address = current; |
| p->nr++; |
| } |
| |
| /* |
| * free_wait removes the current task from any wait-queues and then |
| * wakes up the queues. |
| */ |
| static void free_one_table(select_table * p) |
| { |
| int i; |
| struct task_struct ** tpp; |
| |
| for(tpp = &LAST_TASK ; tpp > &FIRST_TASK ; --tpp) |
| if (*tpp && ((*tpp)->next_wait == p->current)) |
| (*tpp)->next_wait = NULL; |
| if (!p->nr) |
| return; |
| for (i = 0; i < p->nr ; i++) { |
| wake_up(p->entry[i].wait_address); |
| wake_up(&p->entry[i].old_task); |
| } |
| p->nr = 0; |
| } |
| |
| static void free_wait(select_table * p) |
| { |
| select_table * tmp; |
| |
| if (p->woken) |
| return; |
| p = sel_tables; |
| sel_tables = NULL; |
| while (p) { |
| wake_up(&p->current); |
| p->woken = 1; |
| tmp = p->next_table; |
| p->next_table = NULL; |
| free_one_table(p); |
| p = tmp; |
| } |
| } |
| |
| static struct tty_struct * get_tty(struct inode * inode) |
| { |
| int major, minor; |
| |
| if (!S_ISCHR(inode->i_mode)) |
| return NULL; |
| if ((major = MAJOR(inode->i_rdev)) != 5 && major != 4) |
| return NULL; |
| if (major == 5) |
| minor = current->tty; |
| else |
| minor = MINOR(inode->i_rdev); |
| if (minor < 0) |
| return NULL; |
| return TTY_TABLE(minor); |
| } |
| |
| /* |
| * The check_XX functions check out a file. We know it's either |
| * a pipe, a character device or a fifo (fifo's not implemented) |
| */ |
| static int check_in(select_table * wait, struct inode * inode) |
| { |
| struct tty_struct * tty; |
| |
| if (tty = get_tty(inode)) |
| if (!EMPTY(tty->secondary)) |
| return 1; |
| else if (tty->link && !tty->link->count) |
| return 1; |
| else |
| add_wait(&tty->secondary->proc_list, wait); |
| else if (inode->i_pipe) |
| if (!PIPE_EMPTY(*inode) || !PIPE_WRITERS(*inode)) |
| return 1; |
| else |
| add_wait(&inode->i_wait, wait); |
| else if (S_ISSOCK(inode->i_mode)) |
| if (sock_select(inode, NULL, SEL_IN, wait)) |
| return 1; |
| else |
| add_wait(&inode->i_wait, wait); |
| return 0; |
| } |
| |
| static int check_out(select_table * wait, struct inode * inode) |
| { |
| struct tty_struct * tty; |
| |
| if (tty = get_tty(inode)) |
| if (!FULL(tty->write_q)) |
| return 1; |
| else |
| add_wait(&tty->write_q->proc_list, wait); |
| else if (inode->i_pipe) |
| if (!PIPE_FULL(*inode)) |
| return 1; |
| else |
| add_wait(&inode->i_wait, wait); |
| else if (S_ISSOCK(inode->i_mode)) |
| if (sock_select(inode, NULL, SEL_OUT, wait)) |
| return 1; |
| else |
| add_wait(&inode->i_wait, wait); |
| return 0; |
| } |
| |
| static int check_ex(select_table * wait, struct inode * inode) |
| { |
| struct tty_struct * tty; |
| |
| if (tty = get_tty(inode)) |
| if (!FULL(tty->write_q)) |
| return 0; |
| else |
| return 0; |
| else if (inode->i_pipe) |
| if (!PIPE_READERS(*inode) || !PIPE_WRITERS(*inode)) |
| return 1; |
| else |
| add_wait(&inode->i_wait,wait); |
| else if (S_ISSOCK(inode->i_mode)) |
| if (sock_select(inode, NULL, SEL_EX, wait)) |
| return 1; |
| else |
| add_wait(&inode->i_wait, wait); |
| return 0; |
| } |
| |
| int do_select(fd_set in, fd_set out, fd_set ex, |
| fd_set *inp, fd_set *outp, fd_set *exp) |
| { |
| int count; |
| select_table wait_table; |
| int i; |
| fd_set mask; |
| |
| mask = in | out | ex; |
| for (i = 0 ; i < NR_OPEN ; i++,mask >>= 1) { |
| if (!(mask & 1)) |
| continue; |
| if (!current->filp[i]) |
| return -EBADF; |
| if (!current->filp[i]->f_inode) |
| return -EBADF; |
| if (current->filp[i]->f_inode->i_pipe) |
| continue; |
| if (S_ISCHR(current->filp[i]->f_inode->i_mode)) |
| continue; |
| if (S_ISFIFO(current->filp[i]->f_inode->i_mode)) |
| continue; |
| if (S_ISSOCK(current->filp[i]->f_inode->i_mode)) |
| continue; |
| return -EBADF; |
| } |
| repeat: |
| wait_table.nr = 0; |
| wait_table.woken = 0; |
| wait_table.current = current; |
| wait_table.next_table = sel_tables; |
| sel_tables = &wait_table; |
| *inp = *outp = *exp = 0; |
| count = 0; |
| current->state = TASK_INTERRUPTIBLE; |
| mask = 1; |
| for (i = 0 ; i < NR_OPEN ; i++, mask += mask) { |
| if (mask & in) |
| if (check_in(&wait_table,current->filp[i]->f_inode)) { |
| *inp |= mask; |
| count++; |
| } |
| if (mask & out) |
| if (check_out(&wait_table,current->filp[i]->f_inode)) { |
| *outp |= mask; |
| count++; |
| } |
| if (mask & ex) |
| if (check_ex(&wait_table,current->filp[i]->f_inode)) { |
| *exp |= mask; |
| count++; |
| } |
| } |
| if (!(current->signal & ~current->blocked) && |
| current->timeout && !count) { |
| schedule(); |
| free_wait(&wait_table); |
| goto repeat; |
| } |
| free_wait(&wait_table); |
| current->state = TASK_RUNNING; |
| return count; |
| } |
| |
| /* |
| * Note that we cannot return -ERESTARTSYS, as we change our input |
| * parameters. Sad, but there you are. We could do some tweaking in |
| * the library function ... |
| */ |
| int sys_select( unsigned long *buffer ) |
| { |
| /* Perform the select(nd, in, out, ex, tv) system call. */ |
| int i; |
| fd_set res_in, in = 0, *inp; |
| fd_set res_out, out = 0, *outp; |
| fd_set res_ex, ex = 0, *exp; |
| fd_set mask; |
| struct timeval *tvp; |
| unsigned long timeout; |
| |
| mask = get_fs_long(buffer++); |
| if (mask >= 32) |
| mask = ~0; |
| else |
| mask = ~((~0) << mask); |
| inp = (fd_set *) get_fs_long(buffer++); |
| outp = (fd_set *) get_fs_long(buffer++); |
| exp = (fd_set *) get_fs_long(buffer++); |
| tvp = (struct timeval *) get_fs_long(buffer); |
| |
| if (inp) |
| in = mask & get_fs_long(inp); |
| if (outp) |
| out = mask & get_fs_long(outp); |
| if (exp) |
| ex = mask & get_fs_long(exp); |
| timeout = 0xffffffff; |
| if (tvp) { |
| timeout = get_fs_long((unsigned long *)&tvp->tv_usec)/(1000000/HZ); |
| timeout += get_fs_long((unsigned long *)&tvp->tv_sec) * HZ; |
| timeout += jiffies; |
| } |
| current->timeout = timeout; |
| i = do_select(in, out, ex, &res_in, &res_out, &res_ex); |
| if (current->timeout > jiffies) |
| timeout = current->timeout - jiffies; |
| else |
| timeout = 0; |
| current->timeout = 0; |
| if (i < 0) |
| return i; |
| if (inp) { |
| verify_area(inp, 4); |
| put_fs_long(res_in,inp); |
| } |
| if (outp) { |
| verify_area(outp,4); |
| put_fs_long(res_out,outp); |
| } |
| if (exp) { |
| verify_area(exp,4); |
| put_fs_long(res_ex,exp); |
| } |
| if (tvp) { |
| verify_area(tvp, sizeof(*tvp)); |
| put_fs_long(timeout/HZ, (unsigned long *) &tvp->tv_sec); |
| timeout %= HZ; |
| timeout *= (1000000/HZ); |
| put_fs_long(timeout, (unsigned long *) &tvp->tv_usec); |
| } |
| if (i) |
| return i; |
| if (current->signal & ~current->blocked) |
| return -EINTR; |
| return 0; |
| } |