-# Makefile for the UCW Library (c) 1997--2009 Martin Mares <mj@ucw.cz>
+# Makefile for the UCW Library (c) 1997--2010 Martin Mares <mj@ucw.cz>
DIRS+=ucw
LIBUCW=$(o)/ucw/libucw.pc
prime primetable random timer randomkey \
bit-ffs bit-fls \
url \
- mainloop exitstatus runcmd sighandler \
+ mainloop main-block \
+ exitstatus runcmd sighandler \
lizard lizard-safe adler32 \
md5 sha1 sha1-hmac \
base64 base224 \
--- /dev/null
+/*
+ * UCW Library -- Main Loop: Block I/O
+ *
+ * (c) 2004--2010 Martin Mares <mj@ucw.cz>
+ *
+ * This software may be freely distributed and used according to the terms
+ * of the GNU Lesser General Public License.
+ */
+
+#define LOCAL_DEBUG
+
+#include "ucw/lib.h"
+#include "ucw/mainloop.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+
+static void
+block_io_timer_expired(struct main_timer *tm)
+{
+ struct main_block_io *bio = tm->data;
+ timer_del(&bio->timer);
+ if (bio->error_handler)
+ bio->error_handler(bio, MFERR_TIMEOUT);
+}
+
+void
+block_io_add(struct main_block_io *bio, int fd)
+{
+ bio->file.fd = fd;
+ file_add(&bio->file);
+ bio->timer.handler = block_io_timer_expired;
+ bio->timer.data = bio;
+}
+
+void
+block_io_del(struct main_block_io *bio)
+{
+ timer_del(&bio->timer);
+ file_del(&bio->file);
+}
+
+static int
+block_io_read_handler(struct main_file *fi)
+{
+ struct main_block_io *bio = (struct main_block_io *) fi;
+
+ while (bio->rpos < bio->rlen)
+ {
+ int l = read(fi->fd, bio->rbuf + bio->rpos, bio->rlen - bio->rpos);
+ DBG("BIO: FD %d: read %d", fi->fd, l);
+ if (l < 0)
+ {
+ if (errno != EINTR && errno != EAGAIN && bio->error_handler)
+ bio->error_handler(bio, MFERR_READ);
+ return 0;
+ }
+ else if (!l)
+ break;
+ bio->rpos += l;
+ }
+ DBG("BIO: FD %d done read %d of %d", fi->fd, bio->rpos, bio->rlen);
+ fi->read_handler = NULL;
+ file_chg(fi);
+ bio->read_done(bio);
+ return 1;
+}
+
+static int
+block_io_write_handler(struct main_file *fi)
+{
+ struct main_block_io *bio = (struct main_block_io *) fi;
+
+ while (bio->wpos < bio->wlen)
+ {
+ int l = write(fi->fd, bio->wbuf + bio->wpos, bio->wlen - bio->wpos);
+ DBG("BIO: FD %d: write %d", fi->fd, l);
+ if (l < 0)
+ {
+ if (errno != EINTR && errno != EAGAIN && bio->error_handler)
+ bio->error_handler(bio, MFERR_WRITE);
+ return 0;
+ }
+ bio->wpos += l;
+ }
+ DBG("BIO: FD %d done write %d", fi->fd, bio->wpos);
+ fi->write_handler = NULL;
+ file_chg(fi);
+ bio->write_done(bio);
+ return 1;
+}
+
+void
+block_io_read(struct main_block_io *bio, void *buf, uns len)
+{
+ ASSERT(bio->file.n.next);
+ if (len)
+ {
+ bio->file.read_handler = block_io_read_handler;
+ bio->rbuf = buf;
+ bio->rpos = 0;
+ bio->rlen = len;
+ }
+ else
+ {
+ bio->file.read_handler = NULL;
+ bio->rbuf = NULL;
+ bio->rpos = bio->rlen = 0;
+ }
+ file_chg(&bio->file);
+}
+
+void
+block_io_write(struct main_block_io *bio, void *buf, uns len)
+{
+ ASSERT(bio->file.n.next);
+ if (len)
+ {
+ bio->file.write_handler = block_io_write_handler;
+ bio->wbuf = buf;
+ bio->wpos = 0;
+ bio->wlen = len;
+ }
+ else
+ {
+ bio->file.write_handler = NULL;
+ bio->wbuf = NULL;
+ bio->wpos = bio->wlen = 0;
+ }
+ file_chg(&bio->file);
+}
timer_add(tm, 0);
}
-static void
-file_timer_expired(struct main_timer *tm)
-{
- struct main_file *fi = tm->data;
- timer_del(&fi->timer);
- if (fi->error_handler)
- fi->error_handler(fi, MFERR_TIMEOUT);
-}
-
void
file_add(struct main_file *fi)
{
DBG("MAIN: Adding file %p (fd=%d)", fi, fi->fd);
ASSERT(!fi->n.next);
clist_add_tail(&m->file_list, &fi->n);
- fi->timer.handler = file_timer_expired;
- fi->timer.data = fi;
m->file_cnt++;
m->poll_table_obsolete = 1;
if (fcntl(fi->fd, F_SETFL, O_NONBLOCK) < 0)
DBG("MAIN: Deleting file %p (fd=%d)", fi, fi->fd);
ASSERT(fi->n.next);
- timer_del(&fi->timer);
clist_remove(&fi->n);
m->file_cnt--;
m->poll_table_obsolete = 1;
fi->n.next = fi->n.prev = NULL;
}
-static int
-file_read_handler(struct main_file *fi)
-{
- while (fi->rpos < fi->rlen)
- {
- int l = read(fi->fd, fi->rbuf + fi->rpos, fi->rlen - fi->rpos);
- DBG("MAIN: FD %d: read %d", fi->fd, l);
- if (l < 0)
- {
- if (errno != EINTR && errno != EAGAIN && fi->error_handler)
- fi->error_handler(fi, MFERR_READ);
- return 0;
- }
- else if (!l)
- break;
- fi->rpos += l;
- }
- DBG("MAIN: FD %d done read %d of %d", fi->fd, fi->rpos, fi->rlen);
- fi->read_handler = NULL;
- file_chg(fi);
- fi->read_done(fi);
- return 1;
-}
-
-static int
-file_write_handler(struct main_file *fi)
-{
- while (fi->wpos < fi->wlen)
- {
- int l = write(fi->fd, fi->wbuf + fi->wpos, fi->wlen - fi->wpos);
- DBG("MAIN: FD %d: write %d", fi->fd, l);
- if (l < 0)
- {
- if (errno != EINTR && errno != EAGAIN && fi->error_handler)
- fi->error_handler(fi, MFERR_WRITE);
- return 0;
- }
- fi->wpos += l;
- }
- DBG("MAIN: FD %d done write %d", fi->fd, fi->wpos);
- fi->write_handler = NULL;
- file_chg(fi);
- fi->write_done(fi);
- return 1;
-}
-
-void
-file_read(struct main_file *fi, void *buf, uns len)
-{
- ASSERT(fi->n.next);
- if (len)
- {
- fi->read_handler = file_read_handler;
- fi->rbuf = buf;
- fi->rpos = 0;
- fi->rlen = len;
- }
- else
- {
- fi->read_handler = NULL;
- fi->rbuf = NULL;
- fi->rpos = fi->rlen = 0;
- }
- file_chg(fi);
-}
-
-void
-file_write(struct main_file *fi, void *buf, uns len)
-{
- ASSERT(fi->n.next);
- if (len)
- {
- fi->write_handler = file_write_handler;
- fi->wbuf = buf;
- fi->wpos = 0;
- fi->wlen = len;
- }
- else
- {
- fi->write_handler = NULL;
- fi->wbuf = NULL;
- fi->wpos = fi->wlen = 0;
- }
- file_chg(fi);
-}
-
-void
-file_set_timeout(struct main_file *fi, timestamp_t expires)
-{
- ASSERT(fi->n.next);
- timer_add(&fi->timer, expires);
-}
-
void
file_close_all(void)
{
}
msg(L_DEBUG, "\tActive files:");
CLIST_FOR_EACH(struct main_file *, fi, m->file_list)
- msg(L_DEBUG, "\t\t%p (fd %d, rh %p, wh %p, eh %p, expires %lld, data %p)",
- fi, fi->fd, fi->read_handler, fi->write_handler, fi->error_handler,
- (long long)(fi->timer.expires ? fi->timer.expires - m->now : 999999), fi->data);
+ msg(L_DEBUG, "\t\t%p (fd %d, rh %p, wh %p, data %p)",
+ fi, fi->fd, fi->read_handler, fi->write_handler, fi->data);
+ // FIXME: Can we display status of block_io requests somehow?
msg(L_DEBUG, "\tActive hooks:");
CLIST_FOR_EACH(struct main_hook *, ho, m->hook_done_list)
msg(L_DEBUG, "\t\t%p (func %p, data %p)", ho, ho->handler, ho->data);
#ifdef TEST
static struct main_process mp;
-static struct main_file fin, fout;
+static struct main_block_io fin, fout;
static struct main_hook hook;
static struct main_timer tm;
static struct main_signal sg;
static byte rb[16];
-static void dread(struct main_file *fi)
+static void dread(struct main_block_io *bio)
{
- if (fi->rpos < fi->rlen)
+ if (bio->rpos < bio->rlen)
{
msg(L_INFO, "Read EOF");
- file_del(fi);
+ block_io_del(bio);
}
else
{
msg(L_INFO, "Read done");
- file_read(fi, rb, sizeof(rb));
+ block_io_read(bio, rb, sizeof(rb));
}
}
-static void derror(struct main_file *fi, int cause)
+static void derror(struct main_block_io *bio, int cause)
{
msg(L_INFO, "Error: %m !!! (cause %d)", cause);
- file_del(fi);
+ block_io_del(bio);
}
-static void dwrite(struct main_file *fi UNUSED)
+static void dwrite(struct main_block_io *bio UNUSED)
{
msg(L_INFO, "Write done");
}
log_init(NULL);
main_init();
- fin.fd = 0;
fin.read_done = dread;
fin.error_handler = derror;
- file_add(&fin);
- file_read(&fin, rb, sizeof(rb));
+ block_io_add(&fin, 0);
+ block_io_read(&fin, rb, sizeof(rb));
- fout.fd = 1;
fout.write_done = dwrite;
fout.error_handler = derror;
- file_add(&fout);
- file_write(&fout, "Hello, world!\n", 14);
+ block_io_add(&fout, 1);
+ block_io_write(&fout, "Hello, world!\n", 14);
hook.handler = dhook;
hook_add(&hook);
int fd; /* [*] File descriptor */
int (*read_handler)(struct main_file *fi); /* [*] To be called when ready for reading/writing; must call file_chg() afterwards */
int (*write_handler)(struct main_file *fi);
- void (*error_handler)(struct main_file *fi, int cause); /* [*] Handler to call on errors */
void *data; /* [*] Data for use by the handlers */
- byte *rbuf; /* Read/write pointers for use by file_read/write */
- uns rpos, rlen;
- byte *wbuf;
- uns wpos, wlen;
- void (*read_done)(struct main_file *fi); /* [*] Called when file_read is finished; rpos < rlen if EOF */
- void (*write_done)(struct main_file *fi); /* [*] Called when file_write is finished */
- struct main_timer timer;
struct pollfd *pollfd;
};
-/**
- * Specifies when or why an error happened. This is passed to the error handler.
- * `errno` is still set to the original source of error. The only exception
- * is `MFERR_TIMEOUT`, in which case `errno` is not set and the only possible
- * cause of it is timeout on the file descriptor (see @file_set_timeout).
- **/
-enum main_file_err_cause {
- MFERR_READ,
- MFERR_WRITE,
- MFERR_TIMEOUT
-};
-
/**
* Inserts a <<struct_main_file,`main_file`>> structure into the mainloop to be
* watched for activity. You can call this at any time, even inside a handler
**/
void file_del(struct main_file *fi);
/**
- * Asks the mainloop to read @len bytes of data from @fi into @buf.
+ * Closes all file descriptors known to mainloop. Often used between fork()
+ * and exec().
+ **/
+void file_close_all(void);
+
+struct main_block_io {
+ struct main_file file;
+ byte *rbuf; /* Read/write pointers for use by file_read/write */
+ uns rpos, rlen;
+ byte *wbuf;
+ uns wpos, wlen;
+ void (*read_done)(struct main_block_io *bio); /* [*] Called when file_read is finished; rpos < rlen if EOF */
+ void (*write_done)(struct main_block_io *bio); /* [*] Called when file_write is finished */
+ void (*error_handler)(struct main_block_io *bio, int cause); /* [*] Handler to call on errors */
+ struct main_timer timer;
+ void *data; /* [*] Data for use by the handlers */
+};
+
+void block_io_add(struct main_block_io *bio, int fd);
+void block_io_del(struct main_block_io *bio);
+
+/**
+ * Specifies when or why an error happened. This is passed to the error handler.
+ * `errno` is still set to the original source of error. The only exception
+ * is `MFERR_TIMEOUT`, in which case `errno` is not set and the only possible
+ * cause of it is timeout on the file descriptor (see @file_set_timeout).
+ **/
+enum block_io_err_cause {
+ MFERR_READ,
+ MFERR_WRITE,
+ MFERR_TIMEOUT
+};
+
+/**
+ * Asks the mainloop to read @len bytes of data from @bio into @buf.
* It cancels any previous unfinished read requested this way and overwrites
* `read_handler`.
*
* You can use a call with zero @len to cancel current read, but all read data
* will be thrown away.
**/
-void file_read(struct main_file *fi, void *buf, uns len);
+void block_io_read(struct main_block_io *bio, void *buf, uns len);
/**
- * Requests that the mainloop writes @len bytes of data from @buf to @fi.
+ * Requests that the mainloop writes @len bytes of data from @buf to @bio.
* Cancels any previous unfinished write and overwrites `write_handler`.
*
* When it is written, write_done() handler is called.
* If you call it with zero @len, it will cancel the previous write, but note
* some data may already be written.
**/
-void file_write(struct main_file *fi, void *buf, uns len);
+void block_io_write(struct main_block_io *bio, void *buf, uns len);
/**
- * Sets a timer for a file @fi. If the timer is not overwritten or disabled
+ * Sets a timer for a file @bio. If the timer is not overwritten or disabled
* until @expires, the file timeouts and error_handler() is called with
- * <<enum_main_file_err_cause,`MFERR_TIMEOUT`>>.
+ * <<enum_block_io_err_cause,`MFERR_TIMEOUT`>>.
*
* The mainloop does not disable or reset it, when something happens, it just
* bundles a timer with the file. If you want to watch for inactivity, it is
*
* The @expires parameter is absolute (add <<var_main_now,`main_now`>> if you
* need relative). The call and overwrites previously set timeout. Value of `0`
- * disables the timeout (the <<enum_main_file_err_cause,`MFERR_TIMEOUT`>> will
+ * disables the timeout (the <<enum_block_io_err_cause,`MFERR_TIMEOUT`>> will
* not trigger).
*
* The use-cases for this are mainly sockets or pipes, when:
* - You want to enforce answer in a given time (for example authentication).
* - You give maximum time for a whole connection.
**/
-void file_set_timeout(struct main_file *fi, timestamp_t expires);
-/**
- * Closes all file descriptors known to mainloop. Often used between fork()
- * and exec().
- **/
-void file_close_all(void);
+void block_io_set_timeout(struct main_block_io *bio, timestamp_t expires);
/***
* [[hooks]]
* Loop hooks
* ----------
*
- * The hooks are called whenever the mainloop perform an iteration.
+ * The hooks are called whenever the mainloop performs an iteration.
* You can shutdown the mainloop from within them or request an iteration
* to happen without sleeping (just poll, no waiting for events).
***/