chiark / gitweb /
util: Provide async_linebuf_read
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Tue, 23 Sep 2014 23:33:52 +0000 (00:33 +0100)
committerIan Jackson <ijackson@chiark.greenend.org.uk>
Mon, 29 Sep 2014 15:20:02 +0000 (16:20 +0100)
polypath is going to want to read output from the interface and
address reporting script.

Signed-off-by: Ian Jackson <ijackson@chiark.greenend.org.uk>
util.c
util.h

diff --git a/util.c b/util.c
index fdf1522f8ef28ea74845f5537adc9da138c1f7de..b7e1d41e7f651cf8a9ccdddae8ca70ab13b4446f 100644 (file)
--- a/util.c
+++ b/util.c
@@ -596,3 +596,71 @@ int iaddr_socklen(const union iaddr *ia)
     default:       abort();
     }
 }
+
+enum async_linebuf_result
+async_linebuf_read(struct pollfd *pfd, struct buffer_if *buf,
+                  const char **emsg_out)
+{
+    int revents=pfd->revents;
+
+#define BAD(m) do{ *emsg_out=(m); return async_linebuf_broken; }while(0)
+#define BADBIT(b) \
+    if (!(revents & b)) ; else BAD(#b)
+    BADBIT(POLLERR);
+    BADBIT(POLLHUP);
+    BADBIT(POLLNVAL);
+#undef BADBIT
+
+    if (!(revents & POLLIN))
+       return async_linebuf_nothing;
+
+    /* Data structure: A line which has been returned to the user is
+     * stored in buf at base before start.  But we retain the usual
+     * buffer meaning of len.  So the buffer has valid but used data
+     * from base to start, and valid but waiting data from start to
+     * start+len. */
+
+    BUF_ASSERT_USED(buf);
+
+    /* firstly, eat any previous */
+    if (buf->start != buf->base) {
+       memmove(buf->base,buf->start,buf->size);
+       buf->start=buf->base;
+    }
+
+    uint8_t *searched=buf->base;
+    uint8_t *dataend=buf->base+buf->size;
+    for (;;) {
+       char *newline=memchr(searched,'\n',dataend-searched);
+       if (newline) {
+           *newline=0;
+           buf->start=newline+1;
+           buf->size=dataend-buf->start;
+           return async_linebuf_ok;
+       }
+       searched=dataend;
+       ssize_t space=(buf->start+buf->alloclen)-searched;
+       if (!space) BAD("input line too long");
+       ssize_t r=read(pfd->fd,searched,space);
+       if (r==0) {
+           *searched=0;
+           *emsg_out=buf->size?"no newline at eof":0;
+           buf->start=searched+1;
+           buf->size=0;
+           return async_linebuf_eof;
+       }
+       if (r<0) {
+           if (errno==EINTR)
+               continue;
+           if (iswouldblock(errno))
+               return async_linebuf_nothing;
+           BAD(strerror(errno));
+       }
+       if (memchr(searched,0,r)) BAD("nul in input data");
+       assert(r<=space);
+       dataend+=r;
+       buf->size+=r;
+    }
+
+#undef BAD
+}
diff --git a/util.h b/util.h
index a3e3e1199dde3b83fa1497b34a2324314004be4a..e6b586d46d52e1df44f48a12918a9855dd7b7511 100644 (file)
--- a/util.h
+++ b/util.h
@@ -63,6 +63,45 @@ int iaddr_socklen(const union iaddr *ia);
 void text2iaddr(const item_t *item, uint16_t port, union iaddr *ia,
                const char *desc);
 
+
+/*----- line-buffered asynch input -----*/
+
+enum async_linebuf_result {
+    async_linebuf_nothing,
+    async_linebuf_ok,
+    async_linebuf_eof,
+    async_linebuf_broken,
+};
+
+enum async_linebuf_result
+async_linebuf_read(struct pollfd *pfd, struct buffer_if *buf,
+                  const char **emsg_out);
+   /* Implements reading whole lines, asynchronously.  Use like
+    * this:
+    *   - set up the fd, which should be readable, O_NONBLOCK
+    *   - set up and initialise buffer, which should be big enough
+    *     for one lines plus its trailing newline, and be empty
+    *     with start==base
+    *   - in your beforepoll_fn, be interested in POLLIN
+    *   - in your afterpoll_fn, repeatedly call this function
+    *     until it doesn't return `nothing'
+    *   - after you're done, simply close fd and free or reset buf
+    * State on exit depends on return value:
+    *   nothing:  don't touch buf, return from beforepoll
+    *   ok:       buf->base contains a input line
+    *              as a nul-terminated string (\n replaced by \0);
+    *             *emsg_out==0.
+    *   eof:      buf->base contains any partial
+    *              (non-newline-terminated) line;
+    *             *emsg_out!=0 iff there was such a line.
+    *   broken:   *emsg_out is the error message describing the problem.
+    *             this may be stored in buf
+    *             buf contents is undefined
+    * While using this function, do not look at buf->start or ->size.
+    */
+
+/*----- some handy macros -----*/
+
 #define MINMAX(ae,be,op) ({                    \
        typeof((ae)) a=(ae);                    \
        typeof((be)) b=(be);                    \