From: mdw Date: Fri, 14 May 1999 21:01:28 +0000 (+0000) Subject: Integrated `select' handling bits from the background resolver project. X-Git-Tag: 2.0.4~326 X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~mdw/git/mLib/commitdiff_plain/97f65b001294338abed02a7b132d6be6517b3f1d?ds=inline Integrated `select' handling bits from the background resolver project. --- diff --git a/Makefile.am b/Makefile.am index 4759694..d143566 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,7 +1,7 @@ ## Process this file with Automake to generate `Makefile.in' ## -*-Makefile-*- ## -## $Id: Makefile.am,v 1.4 1999/05/06 19:51:35 mdw Exp $ +## $Id: Makefile.am,v 1.5 1999/05/14 21:01:28 mdw Exp $ ## ## Building the distribution ## @@ -30,6 +30,9 @@ ##----- Revision history ---------------------------------------------------- ## ## $Log: Makefile.am,v $ +## Revision 1.5 1999/05/14 21:01:28 mdw +## Integrated `select' handling bits from the background resolver project. +## ## Revision 1.4 1999/05/06 19:51:35 mdw ## Reformatted the LGPL notice a little bit. ## @@ -56,7 +59,8 @@ lib_LIBRARIES = libmLib.a pkginclude_HEADERS = \ alloc.h crc32.h dstr.h dynarray.h exc.h mdwopt.h \ - quis.h report.h sub.h sym.h testrig.h trace.h track.h tv.h + quis.h report.h sub.h sym.h testrig.h trace.h track.h \ + conn.h lbuf.h sel.h selbuf.h tv.h ## --- Things to put in the library --- @@ -64,4 +68,5 @@ pkginclude_HEADERS = \ libmLib_a_SOURCES = \ alloc.c crc32.c dstr.c exc.c mdwopt.c quis.c \ - report.c sub.c sym.c testrig.c trace.c track.c tv.c + report.c sub.c sym.c testrig.c trace.c track.c \ + conn.c lbuf.c sel.c selbuf.c tv.c diff --git a/conn.c b/conn.c new file mode 100644 index 0000000..6ce7769 --- /dev/null +++ b/conn.c @@ -0,0 +1,193 @@ +/* -*-c-*- + * + * $Id: conn.c,v 1.1 1999/05/14 21:01:14 mdw Exp $ + * + * Nonblocking connect handling + * + * (c) 1999 Mark Wooding + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This file is part of the mLib utilities library. + * + * mLib is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * mLib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with mLib; if not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +/*----- Revision history --------------------------------------------------* + * + * $Log: conn.c,v $ + * Revision 1.1 1999/05/14 21:01:14 mdw + * Integrated `select' handling bits from the background resolver project. + * + */ + +/*----- Header files ------------------------------------------------------*/ + +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include + +#include "conn.h" +#include "sel.h" + +/*----- Main code ---------------------------------------------------------*/ + +/* --- @conn_connect@ --- * + * + * Arguments: @int fd@ = file descriptor to try to connect + * @unsigned mode@ = what we can do to the file + * @void *p@ = pointer to connection context + * + * Returns: --- + * + * Use: Handles select results for pending connections. + */ + +static void conn_connect(int fd, unsigned mode, void *p) +{ + conn *c = p; + struct sockaddr_in sin; + int sinsz; + + sinsz = sizeof(sin); + if (getpeername(fd, (struct sockaddr *)&sin, &sinsz) < 0) { + int err; + int errsz = sizeof(err); + if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &errsz) == 0) + errno = err; + c->func(-1, c->p); + close(fd); + } else + c->func(fd, c->p); + sel_rmfile(&c->writer); +} + +/* --- @conn_init@ --- * + * + * Arguments: @conn *c@ = pointer to connection block + * @sel_state *s@ = pointer to select state to attach to + * @unsigned long saddr@ = source IP address + * @unsigned short sport@ = source port + * @unsigned long daddr@ = destination IP address + * @unsigned short dport@ = destination port + * @void (*func)(int fd, void *p) = handler function + * @void *p@ = argument for the handler function + * + * Returns: --- + * + * Use: Sets up a nonblocking connect job. + */ + +void conn_init(conn *c, sel_state *s, + unsigned long saddr, + unsigned short sport, + unsigned long daddr, + unsigned long dport, + void (*func)(int /*fd*/, void */*p*/), + void *p) +{ + int fd; + + /* --- Make a socket to do the connecting with --- */ + + c->writer.fd = -1; + if ((fd = socket(PF_INET, SOCK_STREAM, 0)) < 0) + goto fail; + + /* --- Make the socket nonblocking --- */ + + { + int f; + + if ((f = fcntl(fd, F_GETFL)) < 0 || + fcntl(fd, F_SETFL, f | O_NONBLOCK)) + goto fail_close; + } + + /* --- Set up the source address and bind it to the socket --- */ + + { + struct sockaddr_in sin; + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = saddr; + sin.sin_port = sport; + if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)) < 0) + goto fail_close; + } + + /* --- Finally, set up the destination and try the connect --- */ + + { + struct sockaddr_in sin; + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = daddr; + sin.sin_port = dport; + if (connect(fd, (struct sockaddr *)&sin, sizeof(sin)) < 0) { + if (errno != EINPROGRESS) + goto fail_close; + c->func = func; + c->p = p; + sel_initfile(s, &c->writer, fd, SEL_WRITE, conn_connect, c); + sel_addfile(&c->writer); + } else + func(fd, p); + } + + /* --- Everything is set up now --- */ + + return; + + /* --- Something went pear-shaped --- */ + +fail_close: + close(fd); +fail: + func(-1, p); +} + +/* --- @conn_kill@ --- * + * + * Arguments: @conn *c@ = pointer to connection to dispose of + * + * Returns: --- + * + * Use: Disposes of a connection when it's not wanted any more. + */ + +void conn_kill(conn *c) +{ + if (c->writer.fd != -1) { + close(c->writer.fd); + sel_rmfile(&c->writer); + c->writer.fd = -1; + } +} + +/*----- That's all, folks -------------------------------------------------*/ diff --git a/conn.h b/conn.h new file mode 100644 index 0000000..7362b65 --- /dev/null +++ b/conn.h @@ -0,0 +1,111 @@ +/* -*-c-*- + * + * $Id: conn.h,v 1.1 1999/05/14 21:01:14 mdw Exp $ + * + * Nonblocking connect handling + * + * (c) 1999 Mark Wooding + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This file is part of the mLib utilities library. + * + * mLib is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * mLib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with mLib; if not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +/*----- Revision history --------------------------------------------------* + * + * $Log: conn.h,v $ + * Revision 1.1 1999/05/14 21:01:14 mdw + * Integrated `select' handling bits from the background resolver project. + * + */ + +#ifndef CONN_H +#define CONN_H + +#ifdef __cplusplus + extern "C" { +#endif + +/*----- Header files ------------------------------------------------------*/ + +#ifndef SEL_H +# include "sel.h" +#endif + +/*----- Data structures ---------------------------------------------------*/ + +/* --- The nonblocking connect structure --- */ + +typedef struct conn { + sel_file writer; /* Select listener */ + void (*func)(int /*fd*/, void */*p*/); /* Handler function */ + void *p; /* Argument for handler function */ +} conn; + +/*----- Functions provided ------------------------------------------------*/ + +/* --- @conn_init@ --- * + * + * Arguments: @conn *c@ = pointer to connection block + * @sel_state *s@ = pointer to select state to attach to + * @unsigned long saddr@ = source IP address + * @unsigned short sport@ = source port + * @unsigned long daddr@ = destination IP address + * @unsigned short dport@ = destination port + * @void (*func)(int fd, void *p) = handler function + * @void *p@ = argument for the handler function + * + * Returns: --- + * + * Use: Sets up a nonblocking connect job. The source address and + * port can be zero if you don't care. When the connection + * completes, the handler function is called with the connected + * socket as an argument. If the connect fails rather than + * completes, the handler is informed of this by being passed a + * negative file descriptor. In either case, the select job is + * then removed. + */ + +extern void conn_init(conn */*c*/, sel_state */*s*/, + unsigned long /*saddr*/, + unsigned short /*sport*/, + unsigned long /*daddr*/, + unsigned long /*dport*/, + void (*/*func*/)(int /*fd*/, void */*p*/), + void */*p*/); + +/* --- @conn_kill@ --- * + * + * Arguments: @conn *c@ = pointer to connection to dispose of + * + * Returns: --- + * + * Use: Disposes of a connection when it's not wanted any more. The + * connect handler function is not called. + */ + +extern void conn_kill(conn */*c*/); + +/*----- That's all, folks -------------------------------------------------*/ + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/lbuf.c b/lbuf.c new file mode 100644 index 0000000..b81136f --- /dev/null +++ b/lbuf.c @@ -0,0 +1,269 @@ +/* -*-c-*- + * + * $Id: lbuf.c,v 1.1 1999/05/14 21:01:14 mdw Exp $ + * + * Block-to-line buffering + * + * (c) 1999 Straylight/Edgeware + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This file is part of the mLib utilities library. + * + * mLib is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * mLib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with mLib; if not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +/*----- Revision history --------------------------------------------------* + * + * $Log: lbuf.c,v $ + * Revision 1.1 1999/05/14 21:01:14 mdw + * Integrated `select' handling bits from the background resolver project. + * + */ + +/*----- Header files ------------------------------------------------------*/ + +#include +#include +#include + +#include "lbuf.h" + +/*----- Main code ---------------------------------------------------------*/ + +/* --- @lbuf_flush@ --- * + * + * Arguments: @lbuf *b@ = pointer to buffer block + * @char *p@ = pointer to where to start searching + * @size_t len@ = length of new material added + * + * Returns: --- + * + * Use: Flushes any complete lines in a line buffer. New material + * is assumed to have been added starting at @p@. If @p@ is + * null, then the scan starts at the beginning of the buffer, + * and the size of data already in the buffer is used in place + * of @len@. + * + * It is assumed that the buffer is initially enabled. You + * shouldn't be contributing data to a disabled buffer anyway. + * However, the buffer handler may at some point disable itself, + * and @lbuf_flush@ can cope with this eventuality. Any pending + * data is left at the start of the buffer and can be flushed + * out by calling @lbuf_flush(b, 0, 0)@ if the buffer is ever + * re-enabled. + */ + +void lbuf_flush(lbuf *b, char *p, size_t len) +{ + char *l; /* Limit of data in buffer */ + char *q; /* Roving pointer through string */ + char *base; /* Base address of current line */ + int cr; /* Carriage return state */ + + /* --- Initialize variables as necessary --- */ + + if (!p) { + p = b->buf; + cr = 0; + len = b->len; + } else + cr = b->f & lbuf_cr; + + l = p + len; + + /* --- Clear @base@ if I'm discarding an overlong line --- */ + + if (b->len == sizeof(b->buf)) + base = 0; + else + base = b->buf; + + /* --- Now I march through the string --- */ + + for (q = p; q < l; q++) { + + /* --- Quickly discard uninteresting characters --- */ + + if (*q != '\r' && *q != '\n') { + cr = 0; + continue; + } + if (*q == '\r') { + cr = 1; + continue; + } + + /* --- Two choices here --- * + * + * I can either be strict about CRLF line ends, or I can be shoddy + * and allow bare LFs. I'll do the latter, although I oughtn't, + * because it makes testing interactively and with Unix text files + * easier. + */ + +#ifdef STRICT_CRLF + if (!cr) + continue; +#endif + + /* --- I have a positive ID on a linefeed --- * + * + * If I'm interested in this string, report it to my owner. + */ + + if (base) { + if (cr) + q[-1] = 0; /* Exercise: why is this safe? */ + else + *q = 0; + b->func(base, b->p); + if (!(b->f & lbuf_enable)) { + base = q + 1; + break; + } + } + base = q + 1; + cr = 0; + } + + /* --- Sift through the aftermath --- */ + + if (base) { + size_t len = l - base; + if (len == sizeof(b->buf)) { + b->buf[len - 1] = 0; + b->func(base, b->p); + } else if (base != b->buf) + memmove(b->buf, base, len); + b->len = len; + if (cr) + b->f |= lbuf_cr; + else + b->f &= ~lbuf_cr; + } +} + +/* --- @lbuf_close@ --- * + * + * Arguments: @lbuf *b@ = pointer to buffer block + * + * Returns: --- + * + * Use: Empties the buffer of any data currently lurking in it, and + * informs the client that this has happened. It's assumed that + * the buffer is enabled: you shouldn't be reading close events + * on disabled buffers. + */ + +void lbuf_close(lbuf *b) +{ + if (b->len && b->len != sizeof(b->buf)) { + b->buf[b->len] = 0; + b->func(b->buf, b->p); + } + if (b->f & lbuf_enable) + b->func(0, b->p); +} + +/* --- @lbuf_free@ --- * + * + * Arguments: @lbuf *b@ = pointer to buffer block + * @char **p@ = output pointer to free space + * + * Returns: Free buffer size. + * + * Use: Returns the free portion of a line buffer. Data can then be + * written to this portion, and split out into lines by calling + * @lbuf_flush@. + */ + +size_t lbuf_free(lbuf *b, char **p) +{ + /* --- There's a special case to consider --- * + * + * If a line from the file wouldn't fit in the buffer, I truncate it and + * return what would fit. The rest of the line ought to be discarded. + * This condition is signalled by @len = sizeof(buf)@, and means that the + * entire buffer is OK to be trashed. In other cases, @len@ is the amount + * of space currently occupied in the buffer. This special case is the + * reason this routine exists. + */ + + if (b->len == 0 || b->len == sizeof(b->buf)) { + *p = b->buf + b->len; + return (sizeof(b->buf) - b->len); + } else { + *p = b->buf; + return (sizeof(b->buf)); + } +} + +/* --- @lbuf_snarf@ --- * + * + * Arguments: @lbuf *b@ = pointer to buffer block + * @const void *p@ = pointer to input data buffer + * @size_t sz@ = size of data in input buffer + * + * Returns: --- + * + * Use: Snarfs the data from the input buffer and spits it out as + * lines. This interface ignores the complexities of dealing + * with disablement: you should be using @lbuf_free@ to + * contribute data if you want to cope with that. + */ + +void lbuf_snarf(lbuf *b, const void *p, size_t sz) +{ + const char *pp = p; + while (sz) { + size_t bsz; + char *bp; + + bsz = lbuf_free(b, &bp); + if (bsz > sz) + bsz = sz; + memcpy(bp, pp, bsz); + lbuf_flush(b, bp, bsz); + pp += bsz; + sz -= bsz; + } +} + +/* --- @lbuf_init@ --- * + * + * Arguments: @lbuf *b@ = pointer to buffer block + * @void (*func)(char *s, void *p)@ = handler function + * @void *p@ = argument pointer for @func@ + * + * Returns: --- + * + * Use: Initializes a line buffer block. Any recognized lines are + * passed to @func@ for processing. + */ + +void lbuf_init(lbuf *b, + void (*func)(char */*s*/, void */*p*/), + void *p) +{ + b->func = func; + b->p = p; + b->len = 0; + b->f = lbuf_enable; +} + +/*----- That's all, folks -------------------------------------------------*/ diff --git a/lbuf.h b/lbuf.h new file mode 100644 index 0000000..7772885 --- /dev/null +++ b/lbuf.h @@ -0,0 +1,205 @@ +/* -*-c-*- + * + * $Id: lbuf.h,v 1.1 1999/05/14 21:01:14 mdw Exp $ + * + * Block-to-line buffering + * + * (c) 1999 Straylight/Edgeware + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This file is part of the mLib utilities library. + * + * mLib is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * mLib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with mLib; if not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +/*----- Revision history --------------------------------------------------* + * + * $Log: lbuf.h,v $ + * Revision 1.1 1999/05/14 21:01:14 mdw + * Integrated `select' handling bits from the background resolver project. + * + */ + +#ifndef LBUF_H +#define LBUF_H + +#ifdef __cplusplus + extern "C" { +#endif + +/*----- Line buffering ----------------------------------------------------* + * + * The line buffer accepts as input arbitrary-sized lumps of data and + * converts them, by passing them to a client-supplied function, into a + * sequence of lines. It's particularly useful when performing multiplexed + * network I/O. It's not normally acceptable to block while waiting for the + * rest of a text line to arrive, for example. The line buffer stores the + * start of the line until the rest of it arrives later. + * + * A line is a piece of text terminated by either a linefeed or a carriage- + * return/linefeed pair. (The former is there to cope with Unix; the latter + * copes with Internet-format line ends.) + * + * There's a limit to the size of lines that the buffer can cope with. It's + * not hard to remove this limit, but it's probably a bad idea in a lot of + * cases, because it'd allow a remote user to gobble arbitrary amounts of + * your memory. If a line exceeds the limit, it is truncated: the initial + * portion of the line is processed normally, and the remaining portion is + * simply discarded. + * + * Lines extracted from the input data are passed, one at a time, to a + * `handler function', along with a caller-supplied pointer argument to + * provide the handler with some context. The line read is null-terminated + * and does not include the trailing newline characters. It is legal for a + * handler function to modify the string it is passed. However, writing + * beyond the terminating null byte is not allowed. An end-of-file condition + * is signalled to the handler by passing it a null pointer rather than the + * address of a string. + * + * A complexity arises because of the concept of a `disabled' buffer. + * Disablement is really a higher-level concept, but it turns out to be + * important to implement it here. It's useful for a line handler function + * to `disable' itself, so that it doesn't get called any more. For example, + * this might happen if it encouters an error, or when it finishes reading + * everything it wanted to read. The line buffer needs to be `in the loop' + * so that it stops attempting to flush any further lines stored in its + * buffer towards a handler function which isn't ready to accept them. + * Buffers are initially enabled, although higher- level buffering systems + * might well disable them immediately for their own purposes. + */ + +/*----- Header files ------------------------------------------------------*/ + +#include + +/*----- Data structures ---------------------------------------------------*/ + +/* --- The buffer structure --- * + * + * The only thing that's safe to fiddle with in here is the @lbuf_enable@ + * flag. Only higher-level buffering systems should be playing with even + * that. + */ + +typedef struct lbuf { + void (*func)(char */*s*/, void */*p*/); /* Handler function */ + void *p; /* Argument for handler */ + size_t len; /* Length of data in buffer */ + unsigned f; /* Various useful state flags */ + char buf[256]; /* The actual buffer */ +} lbuf; + +enum { + lbuf_cr = 1, /* Read a carriage return */ + lbuf_enable = 2 /* Buffer is currently enabled */ +}; + +/*----- Functions provided ------------------------------------------------*/ + +/* --- @lbuf_flush@ --- * + * + * Arguments: @lbuf *b@ = pointer to buffer block + * @char *p@ = pointer to where to start searching + * @size_t len@ = length of new material added + * + * Returns: --- + * + * Use: Flushes any complete lines in a line buffer. New material + * is assumed to have been added starting at @p@. If @p@ is + * null, then the scan starts at the beginning of the buffer, + * and the size of data already in the buffer is used in place + * of @len@. + * + * It is assumed that the buffer is initially enabled. You + * shouldn't be contributing data to a disabled buffer anyway. + * However, the buffer handler may at some point disable itself, + * and @lbuf_flush@ can cope with this eventuality. Any pending + * data is left at the start of the buffer and can be flushed + * out by calling @lbuf_flush(b, 0, 0)@ if the buffer is ever + * re-enabled. + */ + +extern void lbuf_flush(lbuf */*b*/, char */*p*/, size_t /*len*/); + +/* --- @lbuf_close@ --- * + * + * Arguments: @lbuf *b@ = pointer to buffer block + * + * Returns: --- + * + * Use: Empties the buffer of any data currently lurking in it, and + * informs the client that this has happened. It's assumed that + * the buffer is enabled: you shouldn't be reading close events + * on disabled buffers. + */ + +extern void lbuf_close(lbuf */*b*/); + +/* --- @lbuf_free@ --- * + * + * Arguments: @lbuf *b@ = pointer to buffer block + * @char **p@ = output pointer to free space + * + * Returns: Free buffer size. + * + * Use: Returns the free portion of a line buffer. Data can then be + * written to this portion, and split out into lines by calling + * @lbuf_flush@. + */ + +extern size_t lbuf_free(lbuf */*b*/, char **/*p*/); + +/* --- @lbuf_snarf@ --- * + * + * Arguments: @lbuf *b@ = pointer to buffer block + * @const void *p@ = pointer to input data buffer + * @size_t sz@ = size of data in input buffer + * + * Returns: --- + * + * Use: Snarfs the data from the input buffer and spits it out as + * lines. This interface ignores the complexities of dealing + * with disablement: you should be using @lbuf_free@ to + * contribute data if you want to cope with that. + */ + +extern void lbuf_snarf(lbuf */*b*/, const void */*p*/, size_t /*sz*/); + +/* --- @lbuf_init@ --- * + * + * Arguments: @lbuf *b@ = pointer to buffer block + * @void (*func)(char *s, void *p)@ = handler function + * @void *p@ = argument pointer for @func@ + * + * Returns: --- + * + * Use: Initializes a line buffer block. Any recognized lines are + * passed to @func@ for processing. + */ + +extern void lbuf_init(lbuf */*b*/, + void (*/*func*/)(char */*s*/, void */*p*/), + void */*p*/); + +/*----- That's all, folks -------------------------------------------------*/ + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/sel.c b/sel.c new file mode 100644 index 0000000..3f5493b --- /dev/null +++ b/sel.c @@ -0,0 +1,250 @@ +/* -*-c-*- + * + * $Id: sel.c,v 1.1 1999/05/14 21:01:14 mdw Exp $ + * + * I/O multiplexing support + * + * (c) 1999 Mark Wooding + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This file is part of the mLib utilities library. + * + * mLib is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * mLib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with mLib; if not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +/*----- Revision history --------------------------------------------------* + * + * $Log: sel.c,v $ + * Revision 1.1 1999/05/14 21:01:14 mdw + * Integrated `select' handling bits from the background resolver project. + * + */ + +/*----- Header files ------------------------------------------------------*/ + +#include +#include +#include + +#include +#include +#include + +#include "sel.h" +#include "tv.h" + +/*----- Main code ---------------------------------------------------------*/ + +/* --- @sel_init@ --- * + * + * Arguments: @sel_state *s@ = pointer to a state block to initialize + * + * Returns: --- + * + * Use: Initializes a select state block. + */ + +void sel_init(sel_state *s) +{ + s->files = 0; + s->timers = 0; + FD_ZERO(&s->fd[SEL_READ]); + FD_ZERO(&s->fd[SEL_WRITE]); + FD_ZERO(&s->fd[SEL_EXC]); +} + +/* --- @sel_initfile@ --- * + * + * Arguments: @sel_state *s@ = select state to attach to + * @sel_file *f@ = pointer to a file block to initialize + * @int fd@ = the file descriptor to listen to + * @unsigned mode@ = what to listen for + * @void (*func)(int fd, unsigned mode, void *p)@ = handler + * @void *p@ = argument to pass to handler + * + * Returns: --- + * + * Use: Initializes a file block ready for use. The file block + * isn't added to the list of things to do until a call to + * @sel_addfile@. + */ + +void sel_initfile(sel_state *s, sel_file *f, + int fd, unsigned mode, + void (*func)(int /*fd*/, unsigned /*mode*/, void */*p*/), + void *p) +{ + f->s = s; + f->fd = fd; + f->mode = mode; + f->func = func; + f->p = p; +} + +/* --- @sel_addfile@ --- * + * + * Arguments: @sel_file *f@ = pointer to a file block + * + * Returns: --- + * + * Use: Adds a file block into the list of things to listen to. + */ + +void sel_addfile(sel_file *f) +{ + sel_file **ff = &f->s->files; + + /* --- This little dance looks like line-noise, but it does the job --- */ + + while (*ff && (*ff)->fd > f->fd) + ff = &(*ff)->next; + f->next = *ff; + f->prev = (sel_file *)ff; + if (*ff) + (*ff)->prev = f; + *ff = f; + FD_SET(f->fd, f->s->fd + f->mode); +} + +/* --- @sel_rmfile@ --- * + * + * Arguments: @sel_file *f@ = pointer to a file block + * + * Returns: --- + * + * Use: Removes a file block from the list of things to listen to. + */ + +void sel_rmfile(sel_file *f) +{ + f->prev->next = f->next; + if (f->next) + f->next->prev = f->prev; + FD_CLR(f->fd, f->s->fd + f->mode); +} + +/* --- @sel_addtimer@ --- * + * + * Arguments: @sel_state *s@ = pointer to a state block + * @sel_timer *t@ = pointer to a timer block + * @struct timeval *tv@ = pointer to time to activate + * @void (*func)(struct timeval *tv, void *p)@ = handler + * @void *p@ = argument for handler function + * + * Returns: --- + * + * Use: Registers and sets up a timer. + */ + +void sel_addtimer(sel_state *s, sel_timer *t, + struct timeval *tv, + void (*func)(struct timeval */*tv*/, void */*p*/), + void *p) +{ + sel_timer **tt = &s->timers; + + /* --- Set up the timer block --- */ + + t->tv = *tv; + t->func = func; + t->p = p; + + /* --- More line noise --- */ + + while (*tt && tv_cmp(&(*tt)->tv, tv) > 0) + tt = &(*tt)->next; + t->next = *tt; + t->prev = (sel_timer *)tt; + if (*tt) + (*tt)->prev = t; + *tt = t; +} + +/* --- @sel_rmtimer@ --- * + * + * Arguments: @sel_timer *t@ = pointer to timer block + * + * Returns: --- + * + * Use: Removes a timer from the list of timers. + */ + +void sel_rmtimer(sel_timer *t) +{ + t->prev->next = t->next; + if (t->next) + t->next->prev = t->prev; +} + +/* --- @sel_select@ --- * + * + * Arguments: @sel_state *s@ = pointer to state block + * + * Returns: Zero if all OK, -1 on error. + * + * Use: Does a @select@ call (or equivalent @poll@). + */ + +int sel_select(sel_state *s) +{ + fd_set fd[SEL_MODES]; + struct timeval tv; + int err; + + memcpy(fd, s->fd, sizeof(s->fd)); + if (s->timers) { + struct timeval now; + gettimeofday(&now, 0); + tv_sub(&tv, &now, &s->timers->tv); + err = select(s->files ? s->files->fd + 1 : 0, + fd + SEL_READ, fd + SEL_WRITE, fd + SEL_EXC, + &tv); + gettimeofday(&tv, 0); + } else + err = select(s->files ? s->files->fd + 1 : 0, + fd + SEL_READ, fd + SEL_WRITE, fd + SEL_EXC, + 0); + + if (err < 0) + return (err); + + { + sel_timer *t, *tt; + for (t = s->timers; t && tv_cmp(&t->tv, &tv) <= 0; t = tt) { + tt = t->next; + t->next = t->prev = t; + t->func(&tv, t->p); + } + s->timers = t; + if (t) + t->prev = (sel_timer *)&s->timers; + } + + { + sel_file *f, *ff; + for (f = s->files; f; f = ff) { + ff = f->next; + if (FD_ISSET(f->fd, fd + f->mode)) + f->func(f->fd, f->mode, f->p); + } + } + + return (0); +} + +/*----- That's all, folks -------------------------------------------------*/ diff --git a/sel.h b/sel.h new file mode 100644 index 0000000..3777eed --- /dev/null +++ b/sel.h @@ -0,0 +1,234 @@ +/* -*-c-*- + * + * $Id: sel.h,v 1.1 1999/05/14 21:01:15 mdw Exp $ + * + * I/O multiplexing support + * + * (c) 1999 Mark Wooding + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This file is part of the mLib utilities library. + * + * mLib is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * mLib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with mLib; if not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +/*----- Revision history --------------------------------------------------* + * + * $Log: sel.h,v $ + * Revision 1.1 1999/05/14 21:01:15 mdw + * Integrated `select' handling bits from the background resolver project. + * + */ + +#ifndef SEL_H +#define SEL_H + +#ifdef __cplusplus + extern "C" { +#endif + +/*----- Theory lesson -----------------------------------------------------* + * + * Things which are expecting to do I/O or go off at a certain time are + * called `selectors'. There are two types of selectors: `file selectors' + * wait patiently for a file to become readable or writable; `timeout + * selectors' wait for a certain amount of time to elapse. There is also a + * `multiplexor' which copes with managing all of this stuff. + * + * Multiplexors aren't actually very interesting. You initialize them with + * @sel_init@, and then add and remove selectors as you go. When you want to + * wait for something to happen, call @sel_select@. + * + * A file selector can *either* read *or* write. It can't do both. This is + * because you quite often want to read a socket but not write it; during + * those times you don't want to write, you just don't install a write + * selector. + * + * File selectors are called when their files are available for reading or + * writing as appropriate, and given their file descriptor, the state of the + * file, and a pointer that was registered along with the selector. + * + * File selectors are set up in two phases. First, they're `initialized' + * with @sel_initfile@. An initialized file selector doesn't do anything. + * It needs to be added to a multiplexor using `sel_addfile'. It can later + * be removed using `sel_rmfile'. You can carry on adding and removing as + * you wish. Just don't try adding it twice in a row. + * + * Timeout selectors are called at a certain time. (Actually, they're called + * *after* a certain time.) There's no separate initialization step with + * timouts: you just add them and they work. If you decide you don't want a + * timeout to go off, then you just remove it. (Adding and removing the + * *same* timeout isn't something you do very often. You usually use a + * different expiry time each time.) + */ + +/*----- Header files ------------------------------------------------------*/ + +#include +#include +#include + +/*----- Data structures ---------------------------------------------------*/ + +/* --- Listening for a file --- */ + +typedef struct sel_file { + struct sel_file *next; + struct sel_file *prev; + struct sel_state *s; + int fd; + unsigned mode; + void (*func)(int /*fd*/, unsigned /*mode*/, void */*p*/); + void *p; +} sel_file; + +#define SEL_READ 0 +#define SEL_WRITE 1 +#define SEL_EXC 2 +#define SEL_MODES 3 + +/* --- Waiting for a timeout --- */ + +typedef struct sel_timer { + struct sel_timer *next; + struct sel_timer *prev; + struct timeval tv; + void (*func)(struct timeval *tv, void *p); + void *p; +} sel_timer; + +/* --- A multiplexer --- * + * + * The files are sorted in reverse order of file descriptor number; the + * timers are in normal order of occurrence. Thus, the interesting one + * is always at the front of the list. + */ + +typedef struct sel_state { + struct sel_file *files; + struct sel_timer *timers; + fd_set fd[SEL_MODES]; + struct timeval tv; +} sel_state; + +/*----- Functions provided ------------------------------------------------*/ + +/* --- @sel_init@ --- * + * + * Arguments: @sel_state *s@ = pointer to a state block to initialize + * + * Returns: --- + * + * Use: Initializes a select state block. + */ + +extern void sel_init(sel_state */*s*/); + +/* --- @sel_initfile@ --- * + * + * Arguments: @sel_state *s@ = select state to attach to + * @sel_file *f@ = pointer to a file block to initialize + * @int fd@ = the file descriptor to listen to + * @unsigned mode@ = what to listen for + * @void (*func)(int fd, unsigned mode, void *p)@ = handler + * @void *p@ = argument to pass to handler + * + * Returns: --- + * + * Use: Initializes a file block ready for use. The file block + * isn't added to the list of things to do until a call to + * @sel_addfile@. + */ + +extern void sel_initfile(sel_state */*s*/, sel_file */*f*/, + int /*fd*/, unsigned /*mode*/, + void (*/*func*/)(int /*fd*/, + unsigned /*mode*/, + void */*p*/), + void */*p*/); + +/* --- @sel_addfile@ --- * + * + * Arguments: @sel_file *f@ = pointer to a file block + * + * Returns: --- + * + * Use: Adds a file block into the list of things to listen to. + */ + +extern void sel_addfile(sel_file */*f*/); + +/* --- @sel_rmfile@ --- * + * + * Arguments: @sel_file *f@ = pointer to a file block + * + * Returns: --- + * + * Use: Removes a file block from the list of things to listen to. + */ + +extern void sel_rmfile(sel_file */*f*/); + +/* --- @sel_addtimer@ --- * + * + * Arguments: @sel_state *s@ = pointer to a state block + * @sel_timer *t@ = pointer to a timer block + * @struct timeval *tv@ = pointer to time to activate + * @void (*func)(struct timeval *tv, void *p)@ = handler + * @void *p@ = argument for handler function + * + * Returns: --- + * + * Use: Registers and sets up a timer. + */ + +extern void sel_addtimer(sel_state */*s*/, sel_timer */*t*/, + struct timeval */*tv*/, + void (*/*func*/)(struct timeval */*tv*/, + void */*p*/), + void */*p*/); + +/* --- @sel_rmtimer@ --- * + * + * Arguments: @sel_timer *t@ = pointer to timer block + * + * Returns: --- + * + * Use: Removes a timer from the list of timers. + */ + +extern void sel_rmtimer(sel_timer */*t*/); + +/* --- @sel_select@ --- * + * + * Arguments: @sel_state *s@ = pointer to state block + * + * Returns: Zero if all OK, -1 on error. + * + * Use: Does a @select@ call (or equivalent @poll@). + */ + +extern int sel_select(sel_state */*s*/); + +/*----- That's all, folks -------------------------------------------------*/ + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/selbuf.c b/selbuf.c new file mode 100644 index 0000000..0f07dfc --- /dev/null +++ b/selbuf.c @@ -0,0 +1,155 @@ +/* -*-c-*- + * + * $Id: selbuf.c,v 1.1 1999/05/14 21:01:15 mdw Exp $ + * + * Line-buffering select handler + * + * (c) 1999 Straylight/Edgeware + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This file is part of the mLib utilities library. + * + * mLib is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * mLib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with mLib; if not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +/*----- Revision history --------------------------------------------------* + * + * $Log: selbuf.c,v $ + * Revision 1.1 1999/05/14 21:01:15 mdw + * Integrated `select' handling bits from the background resolver project. + * + */ + +/*----- Header files ------------------------------------------------------*/ + +#include +#include +#include +#include + +#include +#include +#include + +#include "lbuf.h" +#include "sel.h" +#include "selbuf.h" + +/*----- Main code ---------------------------------------------------------*/ + +/* --- @selbuf_enable@ --- * + * + * Arguments: @selbuf *b@ = pointer to buffer block + * + * Returns: --- + * + * Use: Enables a buffer for reading, and emits any queued lines + * to the buffer's owner. + */ + +void selbuf_enable(selbuf *b) +{ + if (!(b->b.f & lbuf_enable)) { + b->b.f |= lbuf_enable; + sel_addfile(&b->reader); + lbuf_flush(&b->b, 0, 0); + } +} + +/* --- @selbuf_disable@ --- * + * + * Arguments: @selbuf *b@ = pointer to a buffer block + * + * Returns: --- + * + * Use: Disables a buffer. It won't be read from until it's + * enabled again. + */ + +void selbuf_disable(selbuf *b) +{ + if (b->b.f & lbuf_enable) { + b->b.f &= ~lbuf_enable; + sel_rmfile(&b->reader); + } +} + +/* --- @selbuf_read@ --- * + * + * Arguments: @int fd@ = file descriptor to read from + * @int mode@ = what we can do to the file + * @void *vp@ = pointer to buffer context + * + * Returns: --- + * + * Use: Acts on the result of a @select@ call. + */ + +static void selbuf_read(int fd, unsigned mode, void *vp) +{ + selbuf *b = vp; + char *p; + size_t sz; + int n; + + sz = lbuf_free(&b->b, &p); +again: + n = read(fd, p, sz); + if (n <= 0) { + switch (errno) { + case EINTR: + goto again; + case EAGAIN: +#if EAGAIN != EWOULDBLOCK + case EWOULDBLOCK: +#endif + return; + default: + lbuf_close(&b->b); + } + } + else + lbuf_flush(&b->b, p, n); +} + +/* --- @selbuf_init@ --- * + * + * Arguments: @selbuf *b@ = pointer to buffer block + * @sel_state *s@ = pointer to select state to attach to + * @int fd@ = file descriptor to listen to + * @void (*func)(char *s, void *p)@ = function to call + * @void *p@ = argument for function + * + * Returns: --- + * + * Use: Initializes a buffer block. + */ + +void selbuf_init(selbuf *b, + sel_state *s, + int fd, + void (*func)(char */*s*/, void */*p*/), + void *p) +{ + lbuf_init(&b->b, func, p); + b->b.f &= ~lbuf_enable; + sel_initfile(s, &b->reader, fd, SEL_READ, selbuf_read, b); + selbuf_enable(b); +} + +/*----- That's all, folks -------------------------------------------------*/ diff --git a/selbuf.h b/selbuf.h new file mode 100644 index 0000000..689d3f7 --- /dev/null +++ b/selbuf.h @@ -0,0 +1,113 @@ +/* -*-c-*- + * + * $Id: selbuf.h,v 1.1 1999/05/14 21:01:15 mdw Exp $ + * + * Line-buffering select handler + * + * (c) 1999 Straylight/Edgeware + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This file is part of the mLib utilities library. + * + * mLib is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * mLib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with mLib; if not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +/*----- Revision history --------------------------------------------------* + * + * $Log: selbuf.h,v $ + * Revision 1.1 1999/05/14 21:01:15 mdw + * Integrated `select' handling bits from the background resolver project. + * + */ + +#ifndef SELBUF_H +#define SELBUF_H + +#ifdef __cplusplus + extern "C" { +#endif + +/*----- Header files ------------------------------------------------------*/ + +#ifndef LBUF_H +# include "lbuf.h" +#endif + +#ifndef SEL_H +# include "sel.h" +#endif + +/*----- Data structures ---------------------------------------------------*/ + +typedef struct selbuf { + sel_file reader; /* File selection object */ + lbuf b; /* Line buffering object */ +} selbuf; + +/*----- Functions provided ------------------------------------------------*/ + +/* --- @selbuf_enable@ --- * + * + * Arguments: @selbuf *b@ = pointer to buffer block + * + * Returns: --- + * + * Use: Enables a buffer for reading, and emits any queued lines + * to the buffer's owner. + */ + +extern void selbuf_enable(selbuf */*b*/); + +/* --- @selbuf_disable@ --- * + * + * Arguments: @selbuf *b@ = pointer to a buffer block + * + * Returns: --- + * + * Use: Disables a buffer. It won't be read from until it's + * enabled again. + */ + +extern void selbuf_disable(selbuf */*b*/); + +/* --- @selbuf_init@ --- * + * + * Arguments: @selbuf *b@ = pointer to buffer block + * @sel_state *s@ = pointer to select state to attach to + * @int fd@ = file descriptor to listen to + * @void (*func)(char *s, void *p)@ = function to call + * @void *p@ = argument for function + * + * Returns: --- + * + * Use: Initializes a buffer block. + */ + +extern void selbuf_init(selbuf */*b*/, + sel_state */*s*/, + int /*fd*/, + void (*/*func*/)(char */*s*/, void */*p*/), + void */*p*/); + +/*----- That's all, folks -------------------------------------------------*/ + +#ifdef __cplusplus + } +#endif + +#endif