From 64a516de3ba97568c54ab285f729cb5604525968 Mon Sep 17 00:00:00 2001
From: Dan Egnor <egnor@ofb.net>
Date: Sun, 3 Dec 2000 03:01:45 +0000
Subject: [PATCH] ians stuff

---
 oop-read.h | 237 +++++++++++++++++++++++++++
 read-fd.c  | 103 ++++++++++++
 read-mem.c | 157 ++++++++++++++++++
 read.c     | 474 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 971 insertions(+)
 create mode 100644 oop-read.h
 create mode 100644 read-fd.c
 create mode 100644 read-mem.c
 create mode 100644 read.c

diff --git a/oop-read.h b/oop-read.h
new file mode 100644
index 0000000..af98079
--- /dev/null
+++ b/oop-read.h
@@ -0,0 +1,237 @@
+/* oop-read.h, liboop, copyright 2000 Ian Jackson
+
+   This is free software; you can redistribute it and/or modify it under the
+   terms of the GNU Lesser General Public License, version 2.1 or later.
+   See the file COPYING for details. */
+
+#ifndef OOP_READ_H
+#define OOP_READ_H
+
+#include "oop.h"
+
+/* ------------------------------------------------------------------------- */
+/* Generic interface for readable bytestreams */
+
+typedef struct oop_readable oop_readable;
+
+typedef void *oop_readable_call(oop_source*, oop_readable*, void*);
+
+struct oop_readable {
+  int (*on_readable)(struct oop_readable*, oop_readable_call*, void*);
+   /* Calls back as soon as any data available.  Only one on_read can
+    * be set for any oop_readable. */
+  void (*on_cancel)(struct oop_readable*);
+  ssize_t (*try_read)(struct oop_readable*, void *buffer, size_t length);
+   /* Just like read(2), but never gives EINTR, but may give EAGAIN.
+    * Never cancels, never blocks. */
+  int (*delete_tidy)(struct oop_readable*); /* resets any things done by _new */
+  void (*delete_kill)(struct oop_readable*); /* just frees etc.; use eg after fork */
+};
+
+/* ------------------------------------------------------------------------- */
+/* Interpret an fd as a readable bytestream           */
+/* simple wrapper around fcntl, oop->on_fd and read() */
+
+oop_readable *oop_readable_fd(oop_source*, int fd);
+/* side-effect on fd is to make it nonblocking.
+ * delete_tidy resets blocking. */
+
+int oop_fd_nonblock(int fd, int nonblock);
+/* Utility function.  Returns 0 if OK, errno value if it fails. */
+
+
+/* ------------------------------------------------------------------------- */
+/* Interpret a block of memory as a readable bytestream */
+/* Is always ready for reading, of course.              */
+
+oop_readable *oop_readable_mem(oop_source*, const void *data, size_t length);
+/* Stores a pointer to data, rather than copying it. */
+
+
+/* ------------------------------------------------------------------------- */
+/* Record-structured `cooked' reading from any readable bytestream */
+
+/*
+ * Input stream is treated as series of records.
+ *
+ * If no delimiter is specified (_DELIM_NONE) then the records are
+ * of fixed size (sz arg to oop_rd_read); otherwise file is sequence of
+ * pairs {record data, delimiter string}.
+ *
+ * Records may end early under some circumstances:
+ *  - with _SHORTREC_SOONEST, record boundary always `interpreted'
+ *    whereever input would block.  Note that streams don't usually
+ *    guarantee position of blocking boundaries.  Use this with
+ *    _DELIM_NONE only if record boundaries are not important.
+ *  - with _SHORTREC_EOFONLY or _BUFFULL, at end of file a partial
+ *    record is always OK, so missing delimiter at EOF, or short last
+ *    fixed-length record, is fine;
+ *  - with _SHORTREC_BUFFULL, if the sz is exceeded by the record
+ *    length - in this case the record is split in two (or more), the
+ *    first (strictly: all but last) of which will be passed to ifok
+ *    with no delimiter and event type _RD_BUFFULL, the last with the
+ *    delimiter attached (if _DELIM_KEEP) and event _RD_OK.
+ *
+ * If, with delimited records, the delimiter doesn't appear within the
+ * sz, and _BUFFULL or _SOONEST are not specified, then iferr is
+ * called with _RD_BUFFULL.  Likewise, if the final record is too
+ * short (for fixed-size records) or missing its delimiter (for
+ * delimited ones) then without _SHORTREC_BUFFULL or
+ * _SHORTREC_EOFONLY, iferr is called with _RD_PARTREC.
+ *
+ * Reading will continue until EOF or an error.  ifok may be called
+ * any number of times with data!=0, and then there will be either one
+ * further call to ifok with data==0, or alternatively one call to
+ * iferr.
+ *
+ * You can call _rd_cancel at any point (eg in a callback) to prevent
+ * further calls to your callbacks.
+ *
+ * An oop_read may read ahead as much as it likes in the stream any
+ * time after the first call to _rd_read.  This can be prevented by
+ * calling _rd_bufcontrol with a non-0 debuf argument; if called
+ * before the first _rd_read then debuf will not be called, and the
+ * oop_read will not read ahead `unnecessarily' (see below).  If
+ * called afterwards, then any buffered data will be presented to the
+ * debuf callback, once, and then matters are as above.  If
+ * _rd_bufcontrol is called with 0 for debuf then buffering is
+ * (re)-enabled.
+ *
+ * `unnecessary' readahead: with no delimiter, the readahead will
+ * always be less than the record size (sz argument to _rd_read); with
+ * a delimiter, it will be less than the maximum record size if any
+ * except that we won't read past the end of a read(2) return if the
+ * delimiter is in the returned data.  If styles and record sizes are
+ * mixed then the readahead point will of course not go backwards, but
+ * apart from that the most recent style and record size will apply.
+ *
+ * Calling _rd_delete will discard any read ahead data !
+ *
+ * ifok and iferr may be the same function; the sets of arguments
+ * passed to it then will be unambiguous.
+ *
+ * With _NUL_DISCARD, any null bytes in the input still count against
+ * the maximum record size, even though they are not included in the
+ * record size returned.
+ */
+ 
+typedef struct oop_read oop_read;
+
+typedef enum { /* If you change these, also change eventstrings in read.c */
+  OOP_RD_OK,
+  OOP_RD_EOF,     /* EOF; data==0                                            */
+  OOP_RD_PARTREC, /* partial record at EOF; data!=0                          */
+  OOP_RD_LONG,    /* too much data before delimiter; data==0 if error        */
+  OOP_RD_NUL,     /* nul byte in data, with _NUL_FORBID; data==0             */
+  OOP_RD_SYSTEM   /* system error, look in errnoval, data may be !=0         */
+} oop_rd_event;
+
+typedef enum {
+  OOP_RD_BUFCTL_QUERY,  /* return amount of read-ahead data */
+  OOP_RD_BUFCTL_ENABLE, /* enable, return 0 */
+  OOP_RD_BUFCTL_DISABLE,/* disable but keep any already read, return that amt */
+  OOP_RD_BUFCTL_FLUSH   /* disable and discard, return amount discarded */
+} oop_rd_bufctl_op;
+size_t oop_rd_bufctl(oop_read*, oop_rd_bufctl_op op);
+
+typedef enum {
+  OOP_RD_DELIM_NONE,  /* there is no delimiter specified */
+  OOP_RD_DELIM_STRIP, /* strip the delimiter */
+  OOP_RD_DELIM_KEEP   /* keep the delimiter */
+} oop_rd_delim;
+
+typedef enum {
+  OOP_RD_NUL_FORBID,  /* bad for general-purpose data files ! */
+  OOP_RD_NUL_DISCARD, /* bad for general-purpose data files ! */
+  OOP_RD_NUL_PERMIT
+} oop_rd_nul;
+
+typedef enum {             /* record may end early without error if:       */
+  OOP_RD_SHORTREC_FORBID,  /*   never (both conditions above are an error) */
+  OOP_RD_SHORTREC_EOF,     /*   EOF                                        */
+  OOP_RD_SHORTREC_LONG,    /*   EOF or record too long                     */
+  OOP_RD_SHORTREC_SOONEST  /*   any data read at all                       */
+} oop_rd_shortrec;
+
+typedef struct {
+  oop_rd_delim delim_mode; /* if _DELIM_NONE, delim=='\0',             */
+  char delim;              /*  otherwise delim must be valid           */
+  oop_rd_nul nul_mode; /* applies to data content, not to any in delim */
+  oop_rd_shortrec shortrec_mode;
+} oop_rd_style;
+
+typedef void *oop_rd_call(oop_source*, oop_read*,
+			  oop_rd_event, const char *errmsg, int errnoval,
+			  const char *data, size_t recsz, void*);
+/*
+ * When called as `ifok':
+ *  _result indicates why the record ended early (or OK if it didn't);
+ *  data is 0 iff no record was read because EOF
+ *  errmsg==0, errnoval==0
+ *
+ * When called as `iferr':
+ *  _result indicates the error (and is not _OK); if it is _SYSTEM
+ *  then errmsg is strerror(errnoval), otherwise errmsg is from
+ *  library and errnoval is 0.  Errors in a record do NOT cause any
+ *  data to be discarded, though some may be passed to the iferr call;
+ *  if data==0 then calling oop_rd_read again with the same style may
+ *  produce the same error again.
+ *
+ * data will always be nul-terminated, may also contain nuls unless
+ * _NUL_FORBID specified.  recsz does not include the trailing nul; if
+ * _DELIM_STRIP then it doesn't include the (now-stripped) delimiter,
+ * but if _DELIM_KEEP then if there was a delimiter it is included in
+ * recsz.
+ *
+ * Any data allocated by oop_read, and errmsg if set, is valid only
+ * during this call - you must copy it !  (Also invalidated by
+ * _rd_delete, but not by _rd_cancel.)
+ */
+
+const char *oop_rd_errmsg(oop_read *rd, oop_rd_event event, int errnoval,
+			  const oop_rd_style *style);
+/* style is a hint; it may be NUL.  The returned value is valid only
+ * until this event call finishes (as if it had come from
+ * oop_call_rd).  Will never return NULL.
+ */
+
+oop_read *oop_rd_new(oop_source*, oop_readable *ra, char *buf, size_t bufsz);
+/* buf may be 0, in which case a buffer will be allocated internally
+ * (and should then not be touched at all while the oop_read exists).
+ * bufsz is the actual size of buf, or 0 if buf==0. */
+void oop_rd_delete(oop_read*);
+
+oop_read *oop_rd_new_fd(oop_source*, int fd, char *buf, size_t bufsz);
+/* Uses oop_readable_fd first. */
+
+int oop_rd_delete_tidy(oop_read*);
+void oop_rd_delete_kill(oop_read*);
+/* Also call the delete_tidy or delete_kill methods of the underlying
+ * readable.  Make sure to use these if you use oop_rd_new_fd. */
+
+/* predefined styles:                               DELIM    NUL    SHORTREC */
+extern const oop_rd_style OOP_RD_STYLE_GETLINE[1];/*STRIP \n FORBID ATEOF    */
+extern const oop_rd_style OOP_RD_STYLE_BLOCK[1]; /* NONE     ALLOW  FIXED    */
+extern const oop_rd_style OOP_RD_STYLE_IMMED[1]; /* NONE     ALLOW  SOONEST  */
+/* these are all 1-element arrays so you don't have to say &... */
+
+int oop_rd_read(oop_read*, const oop_rd_style *style, size_t maxrecsz,
+		oop_rd_call *ifok, void*,
+		oop_rd_call *iferr, void*);
+/* The data passed to ifok is only valid for that call to ifok (also
+ * invalidated by _rd_delete, but not by _rd_cancel.).  maxrecsz is
+ * the maximum value of recsz which will be passed to ifok or iferr,
+ * or 0 for no limit.
+ *
+ * NB that if a caller-supplied buffer is being used then its size
+ * should be at least 1 larger than maxrecsz; otherwise the value of
+ * maxrecsz actually used will be reduced.
+ *
+ * Errors imply _rd_cancel.
+ *
+ * Only one _rd_read at a time per oop_read.
+ */
+
+void oop_rd_cancel(oop_read*);
+
+#endif
diff --git a/read-fd.c b/read-fd.c
new file mode 100644
index 0000000..50e2a8e
--- /dev/null
+++ b/read-fd.c
@@ -0,0 +1,103 @@
+/* read-fd.c, liboop, copyright 2000 Ian jackson
+   
+   This is free software; you can redistribute it and/or modify it under the
+   terms of the GNU Lesser General Public License, version 2.1 or later.
+   See the file COPYING for details. */
+
+#include "oop.h"
+#include "oop-read.h"
+
+#include <assert.h>
+#include <limits.h>
+#include <errno.h>
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+typedef struct {
+  oop_readable ra;
+  oop_source *oop;
+  int fd;
+  oop_readable_call *call;
+  void *opaque;
+} rafd_intern;
+
+static void *process(oop_source *oop, int fd, oop_event event, void *rafd_void) {
+  rafd_intern *rafd= rafd_void;
+
+  assert(event == OOP_READ);
+  assert(fd == rafd->fd);
+  assert(oop == rafd->oop);
+
+  return
+    rafd->call(oop,&rafd->ra,rafd->opaque);
+}
+
+static void on_cancel(struct oop_readable *ra) {
+  rafd_intern *rafd= (void*)ra;
+
+  rafd->oop->cancel_fd(rafd->oop,rafd->fd,OOP_READ);
+}
+  
+static int on_read(oop_readable *ra, oop_readable_call *call, void *opaque) {
+  rafd_intern *rafd= (void*)ra;
+
+  rafd->call= call;
+  rafd->opaque= opaque;
+
+  return
+    rafd->oop->on_fd(rafd->oop,rafd->fd,OOP_READ,process,rafd), 0; /* fixme */
+}
+
+static ssize_t try_read(oop_readable *ra, void *buffer, size_t length) {
+  rafd_intern *rafd= (void*)ra;
+  ssize_t nread;
+
+  for (;;) {
+    nread= read(rafd->fd,buffer,length);
+    if (nread != -1) break;
+    if (errno != EINTR) return nread;
+  }
+
+  assert(nread >= 0);
+  return nread;
+}
+
+static void delete_kill(struct oop_readable *ra) {
+  oop_free(ra);
+}
+
+static int delete_tidy(struct oop_readable *ra) {
+  rafd_intern *rafd= (void*)ra;
+  int err;
+
+  err= oop_fd_nonblock(rafd->fd,0);
+  delete_kill(ra);
+  return err;
+}
+
+static const oop_readable functions= {
+  on_read, on_cancel, try_read, delete_tidy, delete_kill
+};
+
+oop_readable *oop_readable_fd(oop_source *oop, int fd) {
+  rafd_intern *rafd;
+
+  rafd= oop_malloc(sizeof(*rafd));  if (!rafd) return 0;
+
+  rafd->ra= functions;
+  rafd->oop= oop;
+  rafd->fd= fd;
+
+  if (oop_fd_nonblock(fd,1)) { oop_free(rafd); return 0; }
+  return (oop_readable*)rafd;
+}
+
+int oop_fd_nonblock(int fd, int nonblock) {
+  int flags;
+  
+  flags= fcntl(fd, F_GETFL);  if (flags == -1) return errno;
+  if (nonblock) flags |= O_NONBLOCK; else flags &= ~O_NONBLOCK;
+  return fcntl(fd, F_SETFL, flags) ? errno : 0;
+}
diff --git a/read-mem.c b/read-mem.c
new file mode 100644
index 0000000..3146ed4
--- /dev/null
+++ b/read-mem.c
@@ -0,0 +1,157 @@
+/* read-mem.c, liboop, copyright 2000 Ian jackson
+   
+   This is free software; you can redistribute it and/or modify it under the
+   terms of the GNU Lesser General Public License, version 2.1 or later.
+   See the file COPYING for details. */
+
+#include "oop.h"
+#include "oop-read.h"
+
+#include <assert.h>
+#include <string.h>
+
+#include <unistd.h>
+#include <limits.h>
+
+typedef struct {
+  oop_readable ra;
+  oop_source *oop;
+  int processing;
+  enum { state_cancelled, state_active, state_dying } state;
+  const char *data;
+  size_t remaining;
+  oop_readable_call *call;
+  void *opaque;
+} ram_intern;
+
+static void *process(oop_source *oop, struct timeval when, void *ram_void);
+
+static int set_time(ram_intern *ram) {
+  int err;
+  err=
+    (ram->oop->on_time(ram->oop,OOP_TIME_NOW,process,ram), 0); /* fixme */
+  if (err) return err;
+
+  ram->processing= 1;
+  return 0;
+}
+
+static void *process(oop_source *oop, struct timeval when, void *ram_void) {
+  ram_intern *ram= ram_void;
+  void *ret;
+  int err;
+
+  assert(oop == ram->oop);
+  assert(ram->processing);
+
+  ret= OOP_CONTINUE;
+
+  while (ram->state == state_active && ret == OOP_CONTINUE) {
+    ret= ram->call(oop,&ram->ra,ram->opaque);
+  }
+
+  switch (ram->state) {
+
+  case state_active:
+    err= set_time(ram);
+    if (err)
+      assert(!"must not lose flow of control");
+         /* AAARGH! No way to avoid this I think.  Happens when:
+	  *  - program calls on_read which works, setting immediate callback;
+	  *  - process calls the application's function, which returns
+	  *    OOP_HALT or some such, but without calling on_cancel;
+	  *  Now we have to set another immediate callback.
+	  *  If this fails and we were to ignore it then:
+	  *  - program reenters event loop, expecting to deal with the rest
+	  *    of the oop_readable_mem data.  But we've lost the flow
+	  *    of control and the callback never happens, so
+	  *    oop_sys_run or whatever would (lyingly) exit straight
+	  *    away with OOP_CONTINUE.
+	  *  Alternatively we could ignore the application's request
+	  *  to abort the event loop, which seems just as bad.
+	  */
+    break;
+
+  case state_cancelled:
+    ram->processing= 0;
+    break;
+
+  case state_dying:
+    oop_free(ram);
+    break;
+  }
+  
+  return ret;
+}
+
+static int on_read(oop_readable *ra, oop_readable_call *call, void *opaque) {
+  ram_intern *ram= (void*)ra;
+
+  assert(ram->state != state_dying);
+  ram->state= state_active;
+  ram->call= call;
+  ram->opaque= opaque;
+
+  if (ram->processing)
+    return 0;
+
+  return
+    set_time(ram);
+}
+
+static void on_cancel(struct oop_readable *ra) {
+  ram_intern *ram= (void*)ra;
+
+  assert(ram->state != state_dying);
+  ram->state= state_cancelled;
+}
+
+static ssize_t try_read(oop_readable *ra, void *buffer, size_t length) {
+  ram_intern *ram= (void*)ra;
+
+  if (length > SSIZE_MAX)
+    length= SSIZE_MAX;
+
+  if (length > ram->remaining)
+    length= ram->remaining;
+
+  memcpy(buffer,ram->data,length);
+  ram->data += length;
+  ram->remaining -= length;
+
+  return length;
+}
+
+static void delete_kill(struct oop_readable *ra) {
+  ram_intern *ram= (void*)ra;
+
+  assert(ram->state != state_dying);
+  ram->state= state_dying;
+  if (!ram->processing)
+    oop_free(ram);
+}
+
+static int delete_tidy(struct oop_readable *ra) {
+  delete_kill(ra);
+  return 0;
+}
+
+static const oop_readable functions= {
+  on_read, on_cancel, try_read, delete_tidy, delete_kill
+};
+
+oop_readable *oop_readable_mem(oop_source *oop, const void *data, size_t length) {
+  ram_intern *ram;
+
+  ram= oop_malloc(sizeof(*ram));  if (!ram) return 0;
+
+  ram->ra= functions;
+  ram->oop= oop;
+  ram->processing= 0;
+  ram->state= state_cancelled;
+
+  ram->data= data;
+  ram->remaining= length;
+
+  return (oop_readable*)ram;
+}
diff --git a/read.c b/read.c
new file mode 100644
index 0000000..3ddd204
--- /dev/null
+++ b/read.c
@@ -0,0 +1,474 @@
+/* read.c, liboop, copyright 2000 Ian jackson
+   
+   This is free software; you can redistribute it and/or modify it under the
+   terms of the GNU Lesser General Public License, version 2.1 or later.
+   See the file COPYING for details. */
+
+#include "oop.h"
+#include "oop-read.h"
+
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <limits.h>
+
+#define MIN(a,b) ((a)<(b) ? (a) : (b))
+
+static void *on_time(oop_source*, struct timeval, void*);
+static void *on_readable(oop_source*, oop_readable*, void*);
+static void *on_process(oop_source*, oop_read*, int try_read);
+
+static int set_time_ifbuf(oop_source *oop, oop_read *rd);
+static void cancel_time(oop_source *oop, oop_read *rd);
+
+const oop_rd_style OOP_RD_STYLE_GETLINE[]= {{
+  OOP_RD_DELIM_STRIP,'\n', OOP_RD_NUL_FORBID, OOP_RD_SHORTREC_EOF,
+}};
+const oop_rd_style OOP_RD_STYLE_BLOCK[]= {{
+  OOP_RD_DELIM_NONE, 0,    OOP_RD_NUL_PERMIT, OOP_RD_SHORTREC_EOF,
+}};
+const oop_rd_style OOP_RD_STYLE_IMMED[]= {{
+  OOP_RD_DELIM_NONE, 0,    OOP_RD_NUL_PERMIT, OOP_RD_SHORTREC_SOONEST,
+}};
+
+struct oop_read {
+  /* set at creation time: */
+  oop_source *oop;
+  oop_readable *ra;
+  char *userbuf;
+  /* persistent state */
+  oop_rd_bufctl_op readahead; /* _ENABLE or _DISABLE */
+  char *allocbuf;
+  size_t alloc, used, discard;
+  size_t neednotcheck; /* data we've already searched for delimiter */
+  int displacedchar; /* >=0, first unused */
+  /* arguments to oop_rd_read */
+  oop_rd_style style;
+  size_t maxrecsz;
+  oop_rd_call *call_ok, *call_err;
+  void *data_ok, *data_err;
+};
+
+/* Buffer is structured like this if displacedchar>=0 and delim found:
+ *
+ *              done stuff,    displaced readahead - read     unused
+ *              we've called  delimiter| but not yet          buffer
+ *              back for              || returned             space
+ *              ddddddddddddddddddddddDOaaaaaaaaaaaaaaaaaaa____________
+ *              <------- discard ----->
+ *              <----------------------- used ------------>
+ *              <------------------------------------- alloc --------->
+ *
+ * If displacedchar>=0 then the the first character of readahead has
+ * been displaced by a nul byte and is stored in displacedchar.  If
+ * _DELIM_STRIP and the delimiter is found then the nul overwrites the
+ * delimiter.
+ *
+ *               Buffer when full   {this,max}                  may need
+ * DELIM found?  <-recval->      recdata  buffer required       readahead
+ *  NONE  n/a    ddddddddddOaaa_ recsz    recdata+1 == recsz+1  maxrecsz
+ *  KEEP  Yes    dddddddddDOaaa_ recsz    recdata+1 == recsz+1  maxrecsz
+ *  KEEP  No     ddddddddddOaaa_ recsz    recdata+1 == recsz+1  maxrecsz
+ *  STRIP Yes    dddddddddd0aaaa recsz+1  recdata   == recsz+1  maxrecsz+1
+ *  STRIP No     ddddddddddOaaaa recsz    recdata+1 == recsz+1  maxrecsz+1
+ *
+ * Key:  d = data to be returned
+ *       D = delimiter, being returned
+ *       a = readahead, not to be returned
+ *       O = readahead character displaced by a nul
+ *       0 = delimiter replaced by a nul
+ *       _ = unused
+ */
+
+static const char *const eventstrings_nl[]= {
+  "INTERNAL ERROR (_nl _OK) please report",
+  "End of file",
+  "Missing newline at end of file",
+  "Line too long",
+  "Nul byte",
+  "Nul byte, in line which is also too long",
+  "INTERNAL ERROR (_nl _SYSTEM) please report"
+};
+
+static const char *const eventstrings_other[]= {
+  "Record read successfully",
+  "End of file",
+  "Incomplete record at end of file",
+  "Record too long",
+  "Nul byte",
+  "Nul byte in record which is also too long",
+  "System error"
+};
+
+oop_read *oop_rd_new(oop_source *oop, oop_readable *ra, char *buf, size_t bufsz) {
+  oop_read *rd= 0;
+
+  assert(buf ? bufsz>=2 : !bufsz);
+
+  rd= oop_malloc(sizeof(*rd));  if (!rd) goto x_fail;
+  rd->oop= oop;
+  rd->ra= ra;
+  rd->userbuf= buf;
+  rd->readahead= OOP_RD_BUFCTL_ENABLE;
+  rd->allocbuf= 0;
+  rd->used= 0;
+  rd->alloc= buf ? bufsz : 0;
+  rd->neednotcheck= 0;
+  rd->displacedchar= -1;
+  rd->style= *OOP_RD_STYLE_IMMED;
+
+  return rd;
+
+x_fail:
+  oop_free(rd);
+  return 0;
+}
+
+static int set_time_ifbuf(oop_source *oop, oop_read *rd) {
+  if (rd->used > rd->discard)
+    return oop->on_time(oop,OOP_TIME_NOW,on_time,rd), 0; /* fixme */
+  return 0;
+}
+static void cancel_time(oop_source *oop, oop_read *rd) {
+  oop->cancel_time(oop,OOP_TIME_NOW,on_time,rd);
+}
+static int set_read(oop_source *oop, oop_read *rd) {
+  return rd->ra->on_readable(rd->ra,on_readable,rd), 0; /* fixme */
+}
+static void cancel_read(oop_source *oop, oop_read *rd) {
+  rd->ra->on_cancel(rd->ra);
+}
+
+int oop_rd_read(oop_read *rd, const oop_rd_style *style, size_t maxrecsz,
+		oop_rd_call *ifok, void *data_ok,
+		oop_rd_call *iferr, void *data_err) {
+  oop_source *oop= rd->oop;
+  int er;
+
+  cancel_time(oop,rd);
+  cancel_read(oop,rd);
+
+  if (style->delim_mode == OOP_RD_DELIM_NONE ||
+      rd->style.delim_mode == OOP_RD_DELIM_NONE ||
+      style->delim != rd->style.delim)
+    rd->neednotcheck= 0;
+
+  rd->style= *style;
+  rd->maxrecsz= maxrecsz;
+  rd->call_ok= ifok; rd->data_ok= data_ok;
+  rd->call_err= iferr; rd->data_err= data_err;
+
+  er= set_read(oop,rd);        if (er) return er;
+  er= set_time_ifbuf(oop,rd);  if (er) return er;
+  return 0;
+}
+
+void oop_rd_delete(oop_read *rd) {
+  rd->ra->on_cancel(rd->ra);
+  oop_free(rd->allocbuf);
+  oop_free(rd);
+}
+
+void oop_rd_cancel(oop_read *rd) {
+  cancel_time(rd->oop,rd);
+  cancel_read(rd->oop,rd);
+}
+
+const char *oop_rd_errmsg(oop_read *rd, oop_rd_event event, int errnoval,
+			  const oop_rd_style *style) {
+  if (event == OOP_RD_SYSTEM)
+    return strerror(errnoval);
+  else if (style && style->delim_mode != OOP_RD_DELIM_NONE
+	   && style->delim == '\n')
+    return eventstrings_nl[event];
+  else
+    return eventstrings_other[event];
+}
+
+static void *on_readable(oop_source *oop, oop_readable *ra, void *rd_void) {
+  oop_read *rd= rd_void;
+
+  assert(oop == rd->oop);
+  assert(ra == rd->ra);
+  return on_process(oop,rd,1);
+}
+
+static void *on_time(oop_source *oop, struct timeval when, void *rd_void) {
+  oop_read *rd= rd_void;
+
+  assert(oop == rd->oop);
+  return on_process(oop,rd,0);
+}
+
+static size_t calc_dataspace(oop_read *rd) {
+  if (rd->style.delim_mode == OOP_RD_DELIM_STRIP) {
+    return rd->alloc;
+  } else {
+    return rd->alloc ? rd->alloc-1 : 0;
+  }
+}
+
+static void *on_process(oop_source *oop, oop_read *rd, int try_read) {
+  oop_rd_event event;
+  int evkind; /* 0=none, -1=error, 1=something */
+  int errnoval, nread, cancelnow;
+  oop_rd_call *call;
+  char *buf, *delimp;
+  const char *errmsg;
+  size_t maxrecsz; /* like in arg to oop_rd_read, but 0 -> large val */
+  size_t maxbufreqd; /* maximum buffer we might possibly want to alloc */
+  size_t readahead; /* max amount of data we might want to readahead */
+  size_t want; /* amount we want to allocate or data we want to read */
+  size_t dataspace; /* amount of buffer we can usefully fill with data */
+  size_t thisrecsz; /* length of the record we've found */
+  size_t thisrecdata; /* length of data representing the record */
+  void *call_data;
+
+  cancel_time(oop,rd);
+
+  if (rd->userbuf) {
+    buf= rd->userbuf;
+  } else {
+    buf= rd->allocbuf;
+  }
+  
+  if (rd->discard) {
+    rd->used -= rd->discard;
+    rd->neednotcheck -= rd->discard;
+    memmove(buf, buf + rd->discard, rd->used);
+    rd->discard= 0;
+  }
+  if (rd->displacedchar >= 0) {
+    assert(rd->used > 0);
+    buf[0]= rd->displacedchar;
+    rd->displacedchar= -1;
+  }
+
+  maxrecsz= rd->maxrecsz ? rd->maxrecsz : INT_MAX / 5 /* allows +20 and *4 */;
+  maxbufreqd= maxrecsz+1;
+
+  if (rd->userbuf && maxbufreqd > rd->alloc) {
+    maxrecsz -= (maxbufreqd - rd->alloc);
+    maxbufreqd= rd->alloc;
+  }
+
+  if (rd->style.delim_mode == OOP_RD_DELIM_STRIP) {
+    readahead= maxrecsz+1;
+  } else {
+    readahead= maxrecsz;
+  }
+
+  for (;;) {
+    evkind= 0;
+    event= -1;
+    thisrecdata= thisrecsz= 0;
+    errnoval= 0;
+
+    assert(rd->used <= rd->alloc);
+    dataspace= calc_dataspace(rd);
+    
+    if (/* delimiter already in buffer, within max record data ? */
+	rd->style.delim_mode != OOP_RD_DELIM_NONE &&
+	(delimp= memchr(buf + rd->neednotcheck, rd->style.delim,
+			MIN(rd->used, readahead) - rd->neednotcheck))) {
+      
+      thisrecsz= (delimp - buf);
+      thisrecdata= thisrecsz+1;
+      if (rd->style.delim_mode == OOP_RD_DELIM_KEEP)
+	thisrecsz= thisrecdata;
+      event= OOP_RD_OK;
+      evkind= +1;
+
+    } else if (rd->used >= readahead) {
+      
+      thisrecsz= thisrecdata= maxrecsz;
+      evkind= +1;
+
+      if (rd->style.delim_mode == OOP_RD_DELIM_NONE) {
+	event= OOP_RD_OK;
+      } else {
+	event= OOP_RD_LONG;
+	if (rd->style.shortrec_mode < OOP_RD_SHORTREC_LONG) {
+	  evkind= -1;
+	  thisrecsz= thisrecdata= 0;
+	}
+      }
+
+    } else if (/* want to return ASAP, and we have something ? */
+	       rd->style.shortrec_mode == OOP_RD_SHORTREC_SOONEST &&
+	       rd->used > 0 && rd->alloc >= 2) {
+      
+      thisrecdata= rd->used;
+      if (thisrecdata == rd->alloc) thisrecdata--;
+      thisrecsz= thisrecdata;
+      event= OOP_RD_OK;
+      evkind= +1;
+
+    }
+
+    want= 0;
+    if (evkind && thisrecdata && thisrecsz >= rd->alloc) {
+      /* Need to make space for the trailing nul */
+      want= rd->alloc+1;
+    } else if (!evkind && !rd->userbuf &&
+	       rd->used >= dataspace && rd->alloc < maxbufreqd) {
+      /* Need to make space to read more data */
+      want= rd->alloc + 20;
+      want <<= 2;
+      want= MIN(want, maxbufreqd);
+    }
+
+    if (want) {
+      assert(!rd->userbuf);
+      assert(want <= maxbufreqd);
+
+      buf= oop_realloc(rd->allocbuf,want);
+      if (!buf) {
+	event= OOP_RD_SYSTEM;
+	evkind= -1;
+	errnoval= ENOMEM;
+	thisrecsz= thisrecdata= 0;
+	break;
+      }
+      rd->allocbuf= buf;
+      rd->alloc= want;
+    }
+
+    if (evkind) break; /* OK, process it then */
+
+    if (!try_read) return OOP_CONTINUE; /* But we weren't told it was ready. */
+
+    dataspace= calc_dataspace(rd);
+    want= MIN(dataspace, readahead);
+    assert(rd->used < want);
+
+    nread= rd->ra->try_read(rd->ra, buf+rd->used, want-rd->used);
+    if (errno == EAGAIN) return OOP_CONTINUE;
+
+    if (nread > 0) {
+      rd->neednotcheck= rd->used;
+      rd->used += nread;
+      continue;
+    }
+
+    if (nread < 0) { /* read error */
+
+      event= OOP_RD_SYSTEM;
+      evkind= -1;
+      errnoval= errno;
+      thisrecsz= thisrecdata= rd->used;
+      break;
+
+    } else {
+
+      if (rd->used) {
+	event= OOP_RD_PARTREC;
+	evkind= (rd->style.shortrec_mode == OOP_RD_SHORTREC_FORBID) ? -1 : +1;
+	thisrecsz= thisrecdata= rd->used;
+      } else {
+	event= OOP_RD_EOF;
+	evkind= +1;
+      }
+      break;
+
+    }
+  }
+
+  /* OK, we have an event of some kind */
+
+  /* Nul byte handling */
+  if (thisrecsz > 0 && rd->style.nul_mode != OOP_RD_NUL_PERMIT) {
+    size_t checked;
+    char *nul, *notnul;
+    
+    for (checked=0;
+	 (nul= memchr(buf+checked,0,thisrecsz-checked));
+	 ) {
+      if (rd->style.nul_mode == OOP_RD_NUL_FORBID) {
+	event= OOP_RD_NUL;
+	evkind= -1;
+	thisrecdata= thisrecsz= 0;
+	break;
+      }
+      assert(rd->style.nul_mode == OOP_RD_NUL_DISCARD);
+      for (notnul= nul+1;
+	   notnul < buf+thisrecsz && notnul == '\0';
+	   notnul++);
+      thisrecsz-= (notnul-nul);
+      checked= nul-buf;
+      memmove(nul,notnul,thisrecsz-checked);
+    }
+  }
+
+  /* Checks that all is well */
+
+  assert(evkind);
+  assert(thisrecsz <= thisrecdata);
+  assert(!rd->maxrecsz || thisrecsz <= rd->maxrecsz);
+  assert(thisrecdata <= rd->used);
+
+  rd->discard= thisrecdata;
+
+  cancelnow= (evkind < 0) || (event == OOP_RD_EOF);
+
+  if (!cancelnow) {
+    errnoval= set_time_ifbuf(oop,rd);
+    if (errnoval) {
+      event= OOP_RD_SYSTEM;
+      evkind= -1;
+      cancelnow= 1;
+      thisrecsz= thisrecdata= 0;
+      rd->discard= 0;
+    }
+  }
+
+  if (evkind < 0) {
+    call= rd->call_err;
+    call_data= rd->data_err;
+    errmsg= oop_rd_errmsg(rd,event,errnoval,&rd->style);
+  } else {
+    call= rd->call_ok;
+    call_data= rd->data_ok;
+    errmsg= 0;
+  }
+
+  if (thisrecdata) {
+    /* We have to fill in a nul byte. */
+    assert(thisrecsz < rd->alloc);
+    if (thisrecsz == thisrecdata && thisrecsz < rd->used)
+      rd->displacedchar= (unsigned char)buf[thisrecdata];
+    buf[thisrecsz]= 0;
+  }
+
+  if (cancelnow)
+    oop_rd_cancel(rd);
+
+  return
+    call(oop,rd, event,errmsg,errnoval,
+	 (thisrecdata ? buf : 0), thisrecsz, call_data);
+}
+
+oop_read *oop_rd_new_fd(oop_source *oop, int fd, char *buf, size_t bufsz) {
+  oop_readable *ra;
+  oop_read *rd;
+
+  ra= oop_readable_fd(oop,fd);
+  if (!ra) return 0;
+
+  rd= oop_rd_new(oop,ra,buf,bufsz);
+  if (!rd) { ra->delete_tidy(ra); return 0; }
+
+  return rd;
+}
+
+int oop_rd_delete_tidy(oop_read *rd) {
+  oop_readable *ra= rd->ra;
+  oop_rd_delete(rd);
+  return ra->delete_tidy(ra);
+}
+
+void oop_rd_delete_kill(oop_read *rd) {
+  oop_readable *ra= rd->ra;
+  oop_rd_delete(rd);
+  ra->delete_kill(ra);
+}  
-- 
GitLab