From 97f65b001294338abed02a7b132d6be6517b3f1d Mon Sep 17 00:00:00 2001 From: mdw Date: Fri, 14 May 1999 21:01:28 +0000 Subject: [PATCH] Integrated `select' handling bits from the background resolver project. --- Makefile.am | 11 ++- conn.c | 193 +++++++++++++++++++++++++++++++++++++++++++ conn.h | 111 +++++++++++++++++++++++++ lbuf.c | 269 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ lbuf.h | 205 +++++++++++++++++++++++++++++++++++++++++++++ sel.c | 250 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ sel.h | 234 ++++++++++++++++++++++++++++++++++++++++++++++++++++ selbuf.c | 155 ++++++++++++++++++++++++++++++++++ selbuf.h | 113 +++++++++++++++++++++++++ 9 files changed, 1538 insertions(+), 3 deletions(-) create mode 100644 conn.c create mode 100644 conn.h create mode 100644 lbuf.c create mode 100644 lbuf.h create mode 100644 sel.c create mode 100644 sel.h create mode 100644 selbuf.c create mode 100644 selbuf.h 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 -- 2.11.0