/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include /* * 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 add_wait(&tty->secondary->proc_list, wait); else if (inode->i_pipe) if (!PIPE_EMPTY(*inode) || inode->i_count < 2) 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 (inode->i_count < 2) 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; }