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