diff --git a/servers/fs/Makefile b/servers/fs/Makefile index 305a0b215..23e3b076e 100644 --- a/servers/fs/Makefile +++ b/servers/fs/Makefile @@ -11,12 +11,12 @@ h = $i/minix CC = exec cc CFLAGS = -I$i LDFLAGS = -i -LIBS = -lsys -lutils +LIBS = -lsys -lutils -ltimers OBJ = main.o open.o read.o write.o pipe.o dmap.o \ device.o path.o mount.o link.o super.o inode.o \ cache.o cache2.o filedes.o stadir.o protect.o time.o \ - cmostime.o lock.o misc.o utility.o select.o table.o + cmostime.o lock.o misc.o utility.o select.o timers.o table.o # build local binary all build: $(SERVER) diff --git a/servers/fs/cache.c b/servers/fs/cache.c index 3f7881411..6fe74c0fe 100644 --- a/servers/fs/cache.c +++ b/servers/fs/cache.c @@ -60,6 +60,7 @@ int only_search; /* if NO_READ, don't read, else act normal */ /* Block needed has been found. */ if (bp->b_count == 0) rm_lru(bp); bp->b_count++; /* record that block is in use */ + return(bp); } else { /* This block is not the one sought. */ @@ -114,7 +115,9 @@ int only_search; /* if NO_READ, don't read, else act normal */ #endif if (only_search == PREFETCH) bp->b_dev = NO_DEV; else - if (only_search == NORMAL) rw_block(bp, READING); + if (only_search == NORMAL) { + rw_block(bp, READING); + } } return(bp); /* return the newly acquired block */ } @@ -175,8 +178,9 @@ int block_type; /* INODE_BLOCK, DIRECTORY_BLOCK, or whatever */ * should be written to the disk immediately to avoid messing up the file * system in the event of a crash. */ - if ((block_type & WRITE_IMMED) && bp->b_dirt==DIRTY && bp->b_dev != NO_DEV) - rw_block(bp, WRITING); + if ((block_type & WRITE_IMMED) && bp->b_dirt==DIRTY && bp->b_dev != NO_DEV) { + rw_block(bp, WRITING); + } } diff --git a/servers/fs/const.h b/servers/fs/const.h index e8e9141a9..fd68904fd 100644 --- a/servers/fs/const.h +++ b/servers/fs/const.h @@ -36,9 +36,10 @@ #define NO_READ 1 /* prevents get_block from doing disk read */ #define PREFETCH 2 /* tells get_block not to read or mark dev */ -#define XPIPE (-NR_TASKS-1) /* used in fp_task when susp'd on pipe */ -#define XLOCK (-NR_TASKS-2) /* used in fp_task when susp'd on lock */ -#define XPOPEN (-NR_TASKS-3) /* used in fp_task when susp'd on pipe open */ +#define XPIPE (-NR_TASKS-1) /* used in fp_task when susp'd on pipe */ +#define XLOCK (-NR_TASKS-2) /* used in fp_task when susp'd on lock */ +#define XPOPEN (-NR_TASKS-3) /* used in fp_task when susp'd on pipe open */ +#define XSELECT (-NR_TASKS-4) /* used in fp_task when susp'd on select */ #define NO_BIT ((bit_t) 0) /* returned by alloc_bit() to signal failure */ diff --git a/servers/fs/device.c b/servers/fs/device.c index d6f100304..aa8105475 100644 --- a/servers/fs/device.c +++ b/servers/fs/device.c @@ -282,14 +282,17 @@ message *mess_ptr; /* pointer to message for task */ * trying to send a REVIVE message for an earlier request. * Handle it and go try again. */ - if ((r = receive(task_nr, &local_m)) != OK) break; + if ((r = receive(task_nr, &local_m)) != OK) { + break; + } /* If we're trying to send a cancel message to a task which has just * sent a completion reply, ignore the reply and abort the cancel * request. The caller will do the revive for the process. */ - if (mess_ptr->m_type == CANCEL && local_m.REP_PROC_NR == proc_nr) + if (mess_ptr->m_type == CANCEL && local_m.REP_PROC_NR == proc_nr) { return; + } /* Otherwise it should be a REVIVE. */ if (local_m.m_type != REVIVE) { @@ -313,7 +316,9 @@ message *mess_ptr; /* pointer to message for task */ } /* Did the process we did the sendrec() for get a result? */ - if (mess_ptr->REP_PROC_NR == proc_nr) break; + if (mess_ptr->REP_PROC_NR == proc_nr) { + break; + } /* Otherwise it should be a REVIVE. */ if (mess_ptr->m_type != REVIVE) { diff --git a/servers/fs/file.h b/servers/fs/file.h index 04ea3d41b..32bcba113 100644 --- a/servers/fs/file.h +++ b/servers/fs/file.h @@ -8,6 +8,15 @@ EXTERN struct filp { int filp_count; /* how many file descriptors share this slot?*/ struct inode *filp_ino; /* pointer to the inode */ off_t filp_pos; /* file position */ + + /* the following fields are for select() and are owned by the generic + * select() code (i.e., fd-type-specific select() code can't touch these). + */ + int filp_selectors; /* select()ing processes blocking on this fd */ + int filp_select_ops; /* interested in these SEL_* operations */ + + /* following are for fd-type-specific select() */ + int filp_pipe_select_ops; } filp[NR_FILPS]; #define FILP_CLOSED 0 /* filp_mode: associated device closed */ diff --git a/servers/fs/filedes.c b/servers/fs/filedes.c index b0dce3d64..739fdd2ed 100644 --- a/servers/fs/filedes.c +++ b/servers/fs/filedes.c @@ -47,6 +47,9 @@ struct filp **fpt; /* place to return filp slot */ if (f->filp_count == 0) { f->filp_mode = bits; f->filp_pos = 0L; + f->filp_selectors = 0; + f->filp_select_ops = 0; + f->filp_pipe_select_ops = 0; f->filp_flags = 0; *fpt = f; return(OK); diff --git a/servers/fs/fs_timers.h b/servers/fs/fs_timers.h new file mode 100644 index 000000000..b6c53ebe9 --- /dev/null +++ b/servers/fs/fs_timers.h @@ -0,0 +1,3 @@ + +#include + diff --git a/servers/fs/main.c b/servers/fs/main.c index 2832bdf73..12eb70baa 100644 --- a/servers/fs/main.c +++ b/servers/fs/main.c @@ -64,22 +64,29 @@ PUBLIC void main() if (call_nr == HARD_STOP) { do_sync(); sys_exit(0); /* never returns */ - } + } else if(call_nr == SYN_ALARM) { + /* Not a user request; system has expired one of our timers, + * currently only in use for select(). Check it. + */ + fs_expire_timers(m_in.NOTIFY_ARG); + } else if(call_nr == DEV_SELECTED) { + /* device notify()s us of fd that has become usable */ + select_notified(&m_in); + } else { + /* Call the internal function that does the work. */ + if (call_nr < 0 || call_nr >= NCALLS) { + error = ENOSYS; + printf("FS, warning illegal %d system call by %d\n", call_nr, who); + } else { + error = (*call_vec[call_nr])(); + } - /* Call the internal function that does the work. */ - if (call_nr < 0 || call_nr >= NCALLS) { - error = ENOSYS; - printf("FS, warning illegal %d system call by %d\n", call_nr, who); - } else { - error = (*call_vec[call_nr])(); + /* Copy the results back to the user and send reply. */ + if (error != SUSPEND) { reply(who, error); } + if (rdahed_inode != NIL_INODE) { + read_ahead(); /* do block read ahead */ + } } - - /* Copy the results back to the user and send reply. */ - if (error != SUSPEND) { reply(who, error); } - if (rdahed_inode != NIL_INODE) { - read_ahead(); /* do block read ahead */ - } - } } @@ -158,7 +165,7 @@ int result; /* result of the call (usually OK or error #) */ int s; m_out.reply_type = result; s = send(whom, &m_out); - if (s != OK) printf("FS: couldn't send reply: %d\n", s); + if (s != OK) printf("FS: couldn't send reply %d: %d\n", result, s); } diff --git a/servers/fs/pipe.c b/servers/fs/pipe.c index 678676ee0..6d03d60b8 100644 --- a/servers/fs/pipe.c +++ b/servers/fs/pipe.c @@ -18,12 +18,14 @@ #include #include #include +#include #include "dmap.h" #include "file.h" #include "fproc.h" #include "inode.h" #include "param.h" #include "super.h" +#include "select.h" /*===========================================================================* * do_pipe * @@ -82,13 +84,14 @@ PUBLIC int do_pipe() /*===========================================================================* * pipe_check * *===========================================================================*/ -PUBLIC int pipe_check(rip, rw_flag, oflags, bytes, position, canwrite) +PUBLIC int pipe_check(rip, rw_flag, oflags, bytes, position, canwrite, notouch) register struct inode *rip; /* the inode of the pipe */ int rw_flag; /* READING or WRITING */ int oflags; /* flags set by open or fcntl */ register int bytes; /* bytes to be read or written (all chunks) */ register off_t position; /* current file position */ int *canwrite; /* return: number of bytes we can write */ +int notouch; /* check only */ { /* Pipes are a little different. If a process reads from an empty pipe for * which a writer still exists, suspend the reader. If the pipe is empty @@ -106,11 +109,13 @@ int *canwrite; /* return: number of bytes we can write */ if (oflags & O_NONBLOCK) { r = EAGAIN; } else { - suspend(XPIPE); /* block reader */ + if(!notouch) + suspend(XPIPE); /* block reader */ r = SUSPEND; } /* If need be, activate sleeping writers. */ - if (susp_count > 0) release(rip, WRITE, susp_count); + if (susp_count > 0 && !notouch) + release(rip, WRITE, susp_count); } return(r); } @@ -118,7 +123,8 @@ int *canwrite; /* return: number of bytes we can write */ /* Process is writing to a pipe. */ if (find_filp(rip, R_BIT) == NIL_FILP) { /* Tell kernel to generate a SIGPIPE signal. */ - sys_kill((int)(fp - fproc), SIGPIPE); + if(!notouch) + sys_kill((int)(fp - fproc), SIGPIPE); return(EPIPE); } @@ -128,7 +134,8 @@ int *canwrite; /* return: number of bytes we can write */ else if ((oflags & O_NONBLOCK) && bytes > PIPE_SIZE(rip->i_sp->s_block_size)) { if ( (*canwrite = (PIPE_SIZE(rip->i_sp->s_block_size) - position)) > 0) { /* Do a partial write. Need to wakeup reader */ - release(rip, READ, susp_count); + if(!notouch) + release(rip, READ, susp_count); return(1); } else { return(EAGAIN); @@ -143,12 +150,14 @@ int *canwrite; /* return: number of bytes we can write */ return(1); } } - suspend(XPIPE); /* stop writer -- pipe full */ + if(!notouch) + suspend(XPIPE); /* stop writer -- pipe full */ return(SUSPEND); } /* Writing to an empty pipe. Search for suspended reader. */ - if (position == 0) release(rip, READ, susp_count); + if (position == 0 && !notouch) + release(rip, READ, susp_count); } *canwrite = 0; @@ -197,6 +206,25 @@ int count; /* max number of processes to release */ */ register struct fproc *rp; + struct filp *f; + + /* Trying to perform the call also includes SELECTing on it with that + * operation. + */ + if(call_nr == READ || call_nr == WRITE) { + int op; + if(call_nr == READ) + op = SEL_RD; + else + op = SEL_WR; + for(f = &filp[0]; f < &filp[NR_FILPS]; f++) { + if(f->filp_count < 1 || !(f->filp_pipe_select_ops & op) || + f->filp_ino != ip) + continue; + select_callback(f, op); + f->filp_pipe_select_ops &= ~op; + } + } /* Search the proc table. */ for (rp = &fproc[0]; rp < &fproc[NR_PROCS]; rp++) { @@ -215,9 +243,9 @@ int count; /* max number of processes to release */ /*===========================================================================* * revive * *===========================================================================*/ -PUBLIC void revive(proc_nr, bytes) +PUBLIC void revive(proc_nr, returned) int proc_nr; /* process to revive */ -int bytes; /* if hanging on task, how many bytes read */ +int returned; /* if hanging on task, how many bytes read */ { /* Revive a previously blocked process. When a process hangs on tty, this * is the way it is eventually released. @@ -232,8 +260,8 @@ int bytes; /* if hanging on task, how many bytes read */ /* The 'reviving' flag only applies to pipes. Processes waiting for TTY get * a message right away. The revival process is different for TTY and pipes. - * For TTY revival, the work is already done, for pipes it is not: the proc - * must be restarted so it can try again. + * For select and TTY revival, the work is already done, for pipes it is not: + * the proc must be restarted so it can try again. */ task = -rfp->fp_task; if (task == XPIPE || task == XLOCK) { @@ -244,10 +272,12 @@ int bytes; /* if hanging on task, how many bytes read */ rfp->fp_suspended = NOT_SUSPENDED; if (task == XPOPEN) /* process blocked in open or create */ reply(proc_nr, rfp->fp_fd>>8); - else { + else if(task == XSELECT) { + reply(proc_nr, returned); + } else { /* Revive a process suspended on TTY or other device. */ - rfp->fp_nbytes = bytes; /*pretend it wants only what there is*/ - reply(proc_nr, bytes); /* unblock the process */ + rfp->fp_nbytes = returned; /*pretend it wants only what there is*/ + reply(proc_nr, returned); /* unblock the process */ } } } @@ -282,6 +312,10 @@ PUBLIC int do_unpause() case XLOCK: /* process trying to set a lock with FCNTL */ break; + case XSELECT: /* process blocking on select() */ + select_forget(proc_nr); + break; + case XPOPEN: /* process trying to open a fifo */ break; @@ -305,3 +339,46 @@ PUBLIC int do_unpause() reply(proc_nr, EINTR); /* signal interrupted call */ return(OK); } + +/*===========================================================================* + * select_request_pipe * + *===========================================================================*/ +PUBLIC int select_request_pipe(struct filp *f, int *ops, int block) +{ + int orig_ops, r = 0, err, canwrite; + orig_ops = *ops; + if((*ops & SEL_RD)) { + if((err = pipe_check(f->filp_ino, READING, 0, + 1, f->filp_pos, &canwrite, 1)) != SUSPEND) + r |= SEL_RD; + if(err < 0 && err != SUSPEND && (*ops & SEL_ERR)) + r |= SEL_ERR; + } + if((*ops & SEL_WR)) { + if((err = pipe_check(f->filp_ino, WRITING, 0, + 1, f->filp_pos, &canwrite, 1)) != SUSPEND) + r |= SEL_WR; + if(err < 0 && err != SUSPEND && (*ops & SEL_ERR)) + r |= SEL_ERR; + } + + *ops = r; + + if(!r && block) { + f->filp_pipe_select_ops |= orig_ops; + } + + return SEL_OK; +} + +/*===========================================================================* + * select_match_pipe * + *===========================================================================*/ +PUBLIC int select_match_pipe(struct filp *f) +{ + /* recognize either pipe or named pipe (FIFO) */ + if(f && f->filp_ino && (f->filp_ino->i_mode & I_NAMED_PIPE)) + return 1; + return 0; +} + diff --git a/servers/fs/proto.h b/servers/fs/proto.h index 4dfec6e1f..f7e10ba24 100644 --- a/servers/fs/proto.h +++ b/servers/fs/proto.h @@ -1,5 +1,7 @@ /* Function prototypes. */ +#include "timers.h" + /* Structs used in prototypes must be declared as such first. */ struct buf; struct filp; @@ -113,10 +115,13 @@ _PROTOTYPE( struct inode *last_dir, (char *path, char string [NAME_MAX])); _PROTOTYPE( int do_pipe, (void) ); _PROTOTYPE( int do_unpause, (void) ); _PROTOTYPE( int pipe_check, (struct inode *rip, int rw_flag, - int oflags, int bytes, off_t position, int *canwrite)); + int oflags, int bytes, off_t position, int *canwrite, int notouch)); _PROTOTYPE( void release, (struct inode *ip, int call_nr, int count) ); _PROTOTYPE( void revive, (int proc_nr, int bytes) ); _PROTOTYPE( void suspend, (int task) ); +_PROTOTYPE( int select_request_pipe, (struct filp *f, int *ops, int bl) ); +_PROTOTYPE( int select_cancel_pipe, (struct filp *f) ); +_PROTOTYPE( int select_match_pipe, (struct filp *f) ); /* protect.c */ _PROTOTYPE( int do_access, (void) ); @@ -174,4 +179,12 @@ _PROTOTYPE( void zero_block, (struct buf *bp) ); /* select.c */ _PROTOTYPE( int do_select, (void) ); +_PROTOTYPE( int select_callback, (struct filp *, int ops) ); +_PROTOTYPE( void select_forget, (int fproc) ); +_PROTOTYPE( void select_timeout_check, (timer_t *) ); +_PROTOTYPE( int select_notified, (message *) ); +/* timers.c */ +_PROTOTYPE( void fs_set_timer, (timer_t *tp, int delta, tmr_func_t watchdog, int arg)); +_PROTOTYPE( void fs_expire_timers, (clock_t now)); +_PROTOTYPE( void fs_cancel_timer, (timer_t *tp)); diff --git a/servers/fs/read.c b/servers/fs/read.c index e867405d0..9792cd4b0 100644 --- a/servers/fs/read.c +++ b/servers/fs/read.c @@ -21,6 +21,13 @@ #include "param.h" #include "super.h" +#define VVIRCOPY 0 + +#if VVIRCOPY +#define NOVVIRCOPY 0 +#else +#define NOVVIRCOPY 1 +#endif FORWARD _PROTOTYPE( int rw_chunk, (struct inode *rip, off_t position, unsigned off, int chunk, unsigned left, int rw_flag, @@ -79,8 +86,10 @@ int rw_flag; /* READING or WRITING */ * it means something has gone wrong we can't repair now. */ if(copy_queue_used != 0) { - panic(__FILE__,"copy queue size nonzero when entering read_write().", - copy_queue_used); + panic(__FILE__,"start - copy queue size nonzero", copy_queue_used); + } + if(bufs_in_use < 0) { + panic(__FILE__,"start - bufs_in_use negative", bufs_in_use); } /* MM loads segments by putting funny things in upper 10 bits of 'fd'. */ @@ -167,8 +176,9 @@ int rw_flag; /* READING or WRITING */ /* Pipes are a little different. Check. */ if (rip->i_pipe == I_PIPE) { - r = pipe_check(rip,rw_flag,oflags, - m_in.nbytes,position,&partial_cnt); + struct filp *other_end; + r = pipe_check(rip, rw_flag, oflags, + m_in.nbytes, position, &partial_cnt, 0); if (r <= 0) return(r); } @@ -210,7 +220,7 @@ int rw_flag; /* READING or WRITING */ } } -#if 0 +#if VVIRCOPY /* do copying to/from user space */ r2 = rw_chunk_finish(&completed); #endif @@ -221,11 +231,14 @@ int rw_flag; /* READING or WRITING */ if (position > f_size) rip->i_size = position; } } else { - if (rip->i_pipe == I_PIPE && position >= rip->i_size) { - /* Reset pipe pointers. */ - rip->i_size = 0; /* no data left */ - position = 0; /* reset reader(s) */ - if ( (wf = find_filp(rip, W_BIT)) != NIL_FILP) wf->filp_pos =0; + if (rip->i_pipe == I_PIPE) { + if( position >= rip->i_size) { + /* Reset pipe pointers. */ + rip->i_size = 0; /* no data left */ + position = 0; /* reset reader(s) */ + wf = find_filp(rip, W_BIT); + if (wf != NIL_FILP) wf->filp_pos = 0; + } } } f->filp_pos = position; @@ -262,6 +275,12 @@ int rw_flag; /* READING or WRITING */ fp->fp_cum_io_partial = 0; return(cum_io); } + if(copy_queue_used != 0) { + panic(__FILE__,"end - copy queue size nonzero", copy_queue_used); + } + if(bufs_in_use < 0) { + panic(__FILE__,"end - bufs_in_use negative", bufs_in_use); + } return(r); } @@ -332,7 +351,7 @@ int *completed; /* number of bytes copied */ zero_block(bp); } -#if 1 +#if NOVVIRCOPY if (rw_flag == READING) { /* Copy a chunk from the block buffer to user space. */ r = sys_vircopy(FS_PROC_NR, D, (phys_bytes) (bp->b_data+off), @@ -416,7 +435,6 @@ PRIVATE int rw_chunk_finish(int *completed) user->segment = copy_queue[i].user_seg; user->offset = copy_queue[i].user_offset; total += copy_queue[i].chunk; - put_block(copy_queue[i].bp, copy_queue[i].blocktype); } m.m_type = SYS_VIRVCOPY; @@ -427,6 +445,10 @@ PRIVATE int rw_chunk_finish(int *completed) panic(__FILE__,"rw_chunk_finish: virvcopy sendrec failed", r); } + for(i = 0; i < copy_queue_used; i++) { + put_block(copy_queue[i].bp, copy_queue[i].blocktype); + } + *completed = total; copy_queue_used = 0; diff --git a/servers/fs/select.c b/servers/fs/select.c index 5bf065d94..156ef494f 100644 --- a/servers/fs/select.c +++ b/servers/fs/select.c @@ -1,16 +1,653 @@ /* Implement entry point to select system call. * * The entry points into this file are - * do_select: perform the SELECT system call + * do_select: perform the SELECT system call + * select_callback: notify select system of possible fd operation + * select_notified: low-level entry for device notifying select */ + + /* TODO: check if close (pipe?) / exit works; + * some printf()s are serious errors; + * check combinations of cases listen in open group select + * spec (various NULLs and behaviours); + * pty support in tty + * make select cancel disappearing fp's + */ + +#define DEBUG_SELECT 1 + #include "fs.h" +#include "select.h" +#include "file.h" +#include "inode.h" +#include "fs_timers.h" + +#include +#include +#include +#include + +/* max. number of simultaneously pending select() calls */ +#define MAXSELECTS 25 + +PRIVATE struct selectentry { + struct fproc *requestor; /* slot is free iff this is NULL */ + int req_procnr; + fd_set readfds, writefds, errorfds; + fd_set ready_readfds, ready_writefds, ready_errorfds; + fd_set *vir_readfds, *vir_writefds, *vir_errorfds; + struct filp *filps[FD_SETSIZE]; + int type[FD_SETSIZE]; + int nfds, nreadyfds; + clock_t expiry; + timer_t timer; /* if expiry > 0 */ +} selecttab[MAXSELECTS]; + +#define SELFD_FILE 0 +#define SELFD_PIPE 1 +#define SELFD_TTY 2 +#define SELFD_INET 3 +#define SEL_FDS 4 + +FORWARD _PROTOTYPE(int select_reevaluate, (struct filp *fp)); + +FORWARD _PROTOTYPE(int select_request_file, (struct filp *f, int *ops, int block)); +FORWARD _PROTOTYPE(int select_match_file, (struct filp *f)); + +FORWARD _PROTOTYPE(int select_request_tty, (struct filp *f, int *ops, int block)); +FORWARD _PROTOTYPE(int select_match_tty, (struct filp *f)); + +FORWARD _PROTOTYPE(int select_request_inet, (struct filp *f, int *ops, int block)); +FORWARD _PROTOTYPE(int select_match_inet, (struct filp *f)); + +FORWARD _PROTOTYPE(void select_cancel_all, (struct selectentry *e)); +FORWARD _PROTOTYPE(int select_wakeup, (struct selectentry *e)); + +/* The Open Group: + * "The pselect() and select() functions shall support + * regular files, terminal and pseudo-terminal devices, + * STREAMS-based files, FIFOs, pipes, and sockets." + */ + +PRIVATE struct fdtype { + int (*select_request)(struct filp *, int *ops, int block); + int (*select_match)(struct filp *); +} fdtypes[SEL_FDS] = { + /* SELFD_FILE */ + { select_request_file, select_match_file }, + /* SELFD_TTY (also PTY) */ + { select_request_tty, select_match_tty }, + /* SELFD_INET */ + { select_request_inet, select_match_inet }, + /* SELFD_PIPE (pipe(2) pipes and FS FIFOs) */ + { select_request_pipe, select_match_pipe }, +}; + +/* Open Group: + * "File descriptors associated with regular files shall always select true + * for ready to read, ready to write, and error conditions." + */ + +/*===========================================================================* + * select_request_file * + *===========================================================================*/ +PRIVATE int select_request_file(struct filp *f, int *ops, int block) +{ + /* output *ops is input *ops */ + return SEL_OK; +} + +/*===========================================================================* + * select_match_file * + *===========================================================================*/ +PRIVATE int select_match_file(struct filp *file) +{ + if(file && file->filp_ino && (file->filp_ino->i_mode & I_REGULAR)) + return 1; + return 0; +} + +/*===========================================================================* + * select_request_tty * + *===========================================================================*/ +PRIVATE int select_request_tty(struct filp *f, int *ops, int block) +{ + int r, rops; + rops = *ops; + if(block) rops |= SEL_NOTIFY; + *ops = dev_io(DEV_SELECT, f->filp_ino->i_zone[0], rops, NULL, 0, 0, 0); + if(*ops < 0) + return SEL_ERR; + return SEL_OK; +} + +/*===========================================================================* + * select_match_tty * + *===========================================================================*/ +PRIVATE int select_match_tty(struct filp *file) +{ + int major; + if(!(file && file->filp_ino && + (file->filp_ino->i_mode & I_TYPE) == I_CHAR_SPECIAL)) + return 0; + major = (file->filp_ino->i_zone[0] >> MAJOR) & BYTE; + if(major == TTY_MAJOR || major == CTTY_MAJOR) + return 1; + return 0; +} + +/*===========================================================================* + * select_request_inet * + *===========================================================================*/ +PRIVATE int select_request_inet(struct filp *f, int *ops, int block) +{ + int r, rops; + rops = *ops; + if(block) rops |= SEL_NOTIFY; + *ops = dev_io(DEV_SELECT, f->filp_ino->i_zone[0], rops, NULL, 0, 0, 0); + if(*ops < 0) + return SEL_ERR; + return SEL_OK; +} + +/*===========================================================================* + * select_match_inet * + *===========================================================================*/ +PRIVATE int select_match_inet(struct filp *file) +{ + int major; + if(!(file && file->filp_ino && + (file->filp_ino->i_mode & I_TYPE) == I_CHAR_SPECIAL)) + return 0; + major = (file->filp_ino->i_zone[0] >> MAJOR) & BYTE; + if(major == INET_MAJOR) + printf("inet minor: %d\n", + (file->filp_ino->i_zone[0] & BYTE)); + /* return 1; */ + return 0; +} + + +PRIVATE int tab2ops(int fd, struct selectentry *e) +{ + return (FD_ISSET(fd, &e->readfds) ? SEL_RD : 0) | + (FD_ISSET(fd, &e->writefds) ? SEL_WR : 0) | + (FD_ISSET(fd, &e->errorfds) ? SEL_ERR : 0); +} + +PRIVATE void ops2tab(int ops, int fd, struct selectentry *e) +{ + if((ops & SEL_RD) && e->vir_readfds && FD_ISSET(fd, &e->readfds) + && !FD_ISSET(fd, &e->ready_readfds)) { + FD_SET(fd, &e->ready_readfds); + e->nreadyfds++; + } + if((ops & SEL_WR) && e->vir_writefds && FD_ISSET(fd, &e->writefds) + && !FD_ISSET(fd, &e->ready_writefds)) { + FD_SET(fd, &e->ready_writefds); + e->nreadyfds++; + } + if((ops & SEL_ERR) && e->vir_errorfds && FD_ISSET(fd, &e->errorfds) + && !FD_ISSET(fd, &e->ready_errorfds)) { + FD_SET(fd, &e->ready_errorfds); + e->nreadyfds++; + } + + return; +} + +PRIVATE void copy_fdsets(struct selectentry *e) +{ + if(e->vir_readfds) + sys_vircopy(SELF, D, (vir_bytes) &e->ready_readfds, + e->req_procnr, D, (vir_bytes) e->vir_readfds, sizeof(fd_set)); + if(e->vir_writefds) + sys_vircopy(SELF, D, (vir_bytes) &e->ready_writefds, + e->req_procnr, D, (vir_bytes) e->vir_writefds, sizeof(fd_set)); + if(e->vir_errorfds) + sys_vircopy(SELF, D, (vir_bytes) &e->ready_errorfds, + e->req_procnr, D, (vir_bytes) e->vir_errorfds, sizeof(fd_set)); + + return; +} /*===========================================================================* * do_select * *===========================================================================*/ PUBLIC int do_select(void) { + int r, nfds, is_timeout = 1, nonzero_timeout = 0, + fd, s, block = 0; + struct timeval timeout; + nfds = m_in.SEL_NFDS; + + if(nfds < 0 || nfds > FD_SETSIZE) + return EINVAL; + + for(s = 0; s < MAXSELECTS; s++) + if(!selecttab[s].requestor) + break; + + if(s >= MAXSELECTS) + return ENOSPC; + + selecttab[s].req_procnr = who; + selecttab[s].nfds = 0; + selecttab[s].nreadyfds = 0; + memset(selecttab[s].filps, 0, sizeof(selecttab[s].filps)); + + /* defaults */ + FD_ZERO(&selecttab[s].readfds); + FD_ZERO(&selecttab[s].writefds); + FD_ZERO(&selecttab[s].errorfds); + FD_ZERO(&selecttab[s].ready_readfds); + FD_ZERO(&selecttab[s].ready_writefds); + FD_ZERO(&selecttab[s].ready_errorfds); + + selecttab[s].vir_readfds = (fd_set *) m_in.SEL_READFDS; + selecttab[s].vir_writefds = (fd_set *) m_in.SEL_WRITEFDS; + selecttab[s].vir_errorfds = (fd_set *) m_in.SEL_ERRORFDS; + + /* copy args */ + if(selecttab[s].vir_readfds && (r=sys_vircopy(who, D, (vir_bytes) m_in.SEL_READFDS, + SELF, D, (vir_bytes) &selecttab[s].readfds, sizeof(fd_set))) != OK) + return r; + + if(selecttab[s].vir_writefds && (r=sys_vircopy(who, D, (vir_bytes) m_in.SEL_WRITEFDS, + SELF, D, (vir_bytes) &selecttab[s].writefds, sizeof(fd_set))) != OK) + return r; + + if(selecttab[s].vir_errorfds && (r=sys_vircopy(who, D, (vir_bytes) m_in.SEL_ERRORFDS, + SELF, D, (vir_bytes) &selecttab[s].errorfds, sizeof(fd_set))) != OK) + return r; + + if(!m_in.SEL_TIMEOUT) + is_timeout = nonzero_timeout = 0; + else + if((r=sys_vircopy(who, D, (vir_bytes) m_in.SEL_TIMEOUT, + SELF, D, (vir_bytes) &timeout, sizeof(timeout))) != OK) + return r; + + /* No nonsense in the timeval please. */ + if(is_timeout && (timeout.tv_sec < 0 || timeout.tv_usec < 0)) + return EINVAL; + + /* if is_timeout if 0, we block forever. otherwise, if nonzero_timeout + * is 0, we do a poll (don't block). otherwise, we block up to the + * specified time interval. + */ + if(is_timeout && (timeout.tv_sec > 0 || timeout.tv_usec > 0)) + nonzero_timeout = 1; + + if(nonzero_timeout || !is_timeout) + block = 1; + else + block = 0; /* timeout set as (0,0) - this effects a poll */ + + /* no timeout set (yet) */ + selecttab[s].expiry = 0; + + for(fd = 0; fd < nfds; fd++) { + int orig_ops, ops, t, type = -1, r; + struct filp *filp; + + if(!(orig_ops = ops = tab2ops(fd, &selecttab[s]))) + continue; + if(!(filp = selecttab[s].filps[fd] = get_filp(fd))) { + select_cancel_all(&selecttab[s]); + return EBADF; + } + + for(t = 0; t < SEL_FDS; t++) { + if(fdtypes[t].select_match(filp)) { +#if DEBUG_SELECT + printf("select: fd %d is type %d ", fd, t); +#endif + if(type != -1) + printf("select: double match\n"); + type = t; + } + } + + /* Open Group: + * "The pselect() and select() functions shall support + * regular files, terminal and pseudo-terminal devices, + * STREAMS-based files, FIFOs, pipes, and sockets. The + * behavior of pselect() and select() on file descriptors + * that refer to other types of file is unspecified." + * + * If all types are implemented, then this is another + * type of file and we get to do whatever we want. + */ + if(type == -1) + return EBADF; + + selecttab[s].type[fd] = type; + + if((selecttab[s].filps[fd]->filp_select_ops & ops) != ops) { + int wantops; + /* Request the select on this fd. */ +#if DEBUG_SELECT + printf("%p requesting ops %d -> ", + selecttab[s].filps[fd], + selecttab[s].filps[fd]->filp_select_ops); +#endif + wantops = (selecttab[s].filps[fd]->filp_select_ops |= ops); +#if DEBUG_SELECT + printf("%d\n", selecttab[s].filps[fd]->filp_select_ops); +#endif + if((r = fdtypes[type].select_request(filp, + &wantops, block)) != SEL_OK) { + /* error or bogus return code.. backpaddle */ + select_cancel_all(&selecttab[s]); + printf("select: select_request returned error\n"); + return EINVAL; + } + if(wantops) { + if(wantops & ops) { + /* operations that were just requested + * are ready to go right away + */ + ops2tab(wantops, fd, &selecttab[s]); + } + /* if there are any other select()s blocking + * on these operations of this fp, they can + * be awoken too + */ + select_callback(filp, ops); + } +#if DEBUG_SELECT + printf("select request ok; ops returned %d\n", wantops); +#endif + } else { +#if DEBUG_SELECT + printf("select already happening on that filp\n"); +#endif + } + + selecttab[s].nfds = fd+1; + selecttab[s].filps[fd]->filp_selectors++; + +#if DEBUG_SELECT + printf("[fd %d ops: %d] ", fd, ops); +#endif + } + + if(selecttab[s].nreadyfds > 0 || !block) { + /* fd's were found that were ready to go right away, and/or + * we were instructed not to block at all. Must return + * immediately. + */ + copy_fdsets(&selecttab[s]); + select_cancel_all(&selecttab[s]); + selecttab[s].requestor = NULL; + + /* Open Group: + * "Upon successful completion, the pselect() and select() + * functions shall return the total number of bits + * set in the bit masks." + */ + + return selecttab[s].nreadyfds; + } + + /* Convert timeval to ticks and set the timer. If it fails, undo + * all, return error. + */ + if(is_timeout) { + int ticks; + /* Open Group: + * "If the requested timeout interval requires a finer + * granularity than the implementation supports, the + * actual timeout interval shall be rounded up to the next + * supported value." + */ +#define USECPERSEC 1000000 + while(timeout.tv_usec >= USECPERSEC) { + /* this is to avoid overflow with *HZ below */ + timeout.tv_usec -= USECPERSEC; + timeout.tv_sec++; + } + ticks = timeout.tv_sec * HZ + + (timeout.tv_usec * HZ + USECPERSEC-1) / USECPERSEC; + selecttab[s].expiry = ticks; + fs_set_timer(&selecttab[s].timer, ticks, select_timeout_check, s); +#if DEBUG_SELECT + printf("%d: blocking %d ticks\n", s, ticks); +#endif + } + + /* if we're blocking, the table entry is now valid. */ + selecttab[s].requestor = fp; + + /* process now blocked */ + suspend(XSELECT); + return SUSPEND; +} + +PRIVATE void select_cancel_all(struct selectentry *e) +{ + int fd; + + for(fd = 0; fd < e->nfds; fd++) { + struct filp *fp; + fp = e->filps[fd]; + if(!fp) { +#if DEBUG_SELECT + printf("[ fd %d/%d NULL ] ", fd, e->nfds); +#endif + continue; + } + if(fp->filp_selectors < 1) { +#if DEBUG_SELECT + printf("select: %d selectors?!\n", fp->filp_selectors); +#endif + continue; + } + fp->filp_selectors--; + e->filps[fd] = NULL; + select_reevaluate(fp); + } + + if(e->expiry > 0) { +#if DEBUG_SELECT + printf("cancelling timer %d\n", e - selecttab); +#endif + fs_cancel_timer(&e->timer); + e->expiry = 0; + } + + return; +} + +PRIVATE int select_wakeup(struct selectentry *e) +{ + /* Open Group: + * "Upon successful completion, the pselect() and select() + * functions shall return the total number of bits + * set in the bit masks." + */ + revive(e->req_procnr, e->nreadyfds); + return; +} + +PRIVATE int select_reevaluate(struct filp *fp) +{ + int r; + int s, remain_ops = 0, fd, type = -1; + int want_ops; + + if(!fp) { + printf("fs: select: reevalute NULL fp\n"); + return 0; + } + + for(s = 0; s < MAXSELECTS; s++) { + if(!selecttab[s].requestor) + continue; + for(fd = 0; fd < selecttab[s].nfds; fd++) + if(fp == selecttab[s].filps[fd]) { + remain_ops |= tab2ops(fd, &selecttab[s]); + type = selecttab[s].type[fd]; + } + } + + /* If there are any select()s open that want any operations on + * this fd that haven't been satisfied by this callback, then we're + * still in the market for it. + */ + fp->filp_select_ops = remain_ops; +#if DEBUG_SELECT + printf("remaining operations on fp are %d\n", fp->filp_select_ops); +#endif + + return remain_ops; +} + +/*===========================================================================* + * int select_callback * + *===========================================================================*/ +PUBLIC int select_callback(struct filp *fp, int ops) +{ + int s, f, fd, want_ops, remain_ops, type; + + /* We are being notified that file pointer fp is available for + * operations 'ops'. We must re-register the select for + * operations that we are still interested in, if any. + */ + +restart_callback: + want_ops = 0; + type = -1; + for(s = 0; s < MAXSELECTS; s++) { + int wakehim = 0; + if(!selecttab[s].requestor) + continue; + for(fd = 0; fd < selecttab[s].nfds; fd++) { + if(!selecttab[s].filps[fd]) + continue; + if(selecttab[s].filps[fd] == fp) { + int this_want_ops; + this_want_ops = tab2ops(fd, &selecttab[s]); + want_ops |= this_want_ops; + if(this_want_ops & ops) { + /* this select() has been satisfied. */ + ops2tab(ops, fd, &selecttab[s]); + wakehim = 1; + } + type = selecttab[s].type[fd]; + } + } + if(wakehim) { + select_cancel_all(&selecttab[s]); + copy_fdsets(&selecttab[s]); + selecttab[s].requestor = NULL; + select_wakeup(&selecttab[s]); + } + } + + return 0; +} + +/*===========================================================================* + * int select_notified * + *===========================================================================*/ +PUBLIC int select_notified(message *m) +{ + int s, f; + + switch(m->m_source) { + case TTY: +#if DEBUG_SELECT + printf("fs: select: tty notification\n"); +#endif + for(s = 0; s < MAXSELECTS; s++) { + int line, ops; + if(!selecttab[s].requestor) + continue; + for(f = 0; f < selecttab[s].nfds; f++) { + if(!selecttab[s].filps[f] || + !select_match_tty(selecttab[s].filps[f])) + continue; + ops = tab2ops(f, &selecttab[s]); + line = selecttab[s].filps[f]->filp_ino->i_zone[0] & BYTE; + if((line == m->NOTIFY_ARG) && + (m->NOTIFY_FLAGS & ops)) { +#if DEBUG_SELECT + printf("fs: select: tty notification matched\n"); +#endif + select_callback(selecttab[s].filps[f], ops); + } + } + } + break; + default: + printf("fs: select: unrecognized select reply\n"); + } return OK; } +/*===========================================================================* + * int select_forget * + *===========================================================================*/ +PUBLIC void select_forget(int proc) +{ + /* something has happened (e.g. signal delivered that interrupts + * select()). totally forget about the select(). + */ + int s; + + for(s = 0; s < MAXSELECTS; s++) { + if(selecttab[s].requestor && + selecttab[s].req_procnr == proc) { + break; + } + + } + + if(s >= MAXSELECTS) { + printf("select: cancelled select() not found"); + return; + } + + select_cancel_all(&selecttab[s]); + selecttab[s].requestor = NULL; + + return; +} + +/*===========================================================================* + * int select_timeout_check * + *===========================================================================*/ +PUBLIC void select_timeout_check(timer_t *timer) +{ + int s, r; + clock_t now; + + s = tmr_arg(timer)->ta_int; + + if(s < 0 || s >= MAXSELECTS) { + printf("select: bogus slot arg to watchdog %d\n", s); + return; + } + + if(!selecttab[s].requestor) { + printf("select: no requestor in watchdog\n"); + return; + } + + if(selecttab[s].expiry <= 0) { + printf("select: strange expiry value in watchdog\n", s); + return; + } + + selecttab[s].expiry = 0; + copy_fdsets(&selecttab[s]); + select_cancel_all(&selecttab[s]); + selecttab[s].requestor = NULL; + select_wakeup(&selecttab[s]); + + return; +} + diff --git a/servers/fs/select.h b/servers/fs/select.h new file mode 100644 index 000000000..6fef6110e --- /dev/null +++ b/servers/fs/select.h @@ -0,0 +1,10 @@ + +#ifndef _FS_SELECT_H +#define _FS_SELECT_H 1 + +/* return codes for select_request_* and select_cancel_* */ +#define SEL_OK 0 /* ready */ +#define SEL_ERROR 1 /* failed */ + +#endif +