1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
|
#include <sys/socket.h>
#include <assert.h>
#include <errno.h>
#include <limits.h>
#include <stdint.h>
#include <stdlib.h>
#include "events.h"
#include "mpool.h"
#include "network.h"
struct network_read_cookie {
int (* callback)(void *, ssize_t);
void * cookie;
int fd;
uint8_t * buf;
size_t buflen;
size_t minlen;
size_t bufpos;
};
MPOOL(network_read_cookie, struct network_read_cookie, 16);
/* Invoke the callback, clean up, and return the callback's status. */
static int
docallback(struct network_read_cookie * C, ssize_t nbytes)
{
int rc;
/* Invoke the callback. */
rc = (C->callback)(C->cookie, nbytes);
/* Clean up. */
mpool_network_read_cookie_free(C);
/* Return the callback's status. */
return (rc);
}
/* The socket is ready for reading/writing. */
static int
callback_buf(void * cookie)
{
struct network_read_cookie * C = cookie;
size_t oplen;
ssize_t len;
/* Attempt to read/write data to/from the buffer. */
oplen = C->buflen - C->bufpos;
len = recv(C->fd, C->buf + C->bufpos, oplen, 0);
/* Failure? */
if (len == -1) {
/* Was it really an error, or just a try-again? */
if ((errno == EAGAIN) ||
#if EAGAIN != EWOULDBLOCK
(errno == EWOULDBLOCK) ||
#endif
(errno == EINTR))
goto tryagain;
/* Something went wrong. */
goto failed;
} else if (len == 0) {
/* The socket was shut down by the remote host. */
goto eof;
}
/* We processed some data. */
C->bufpos += (size_t)len;
/* Do we need to keep going? */
if (C->bufpos < C->minlen)
goto tryagain;
/* Sanity-check: buffer position must fit into a ssize_t. */
assert(C->bufpos <= SSIZE_MAX);
/* Invoke the callback and return. */
return (docallback(C, (ssize_t)C->bufpos));
tryagain:
/* Reset the event. */
if (events_network_register(callback_buf, C, C->fd,
EVENTS_NETWORK_OP_READ))
goto failed;
/* Callback was reset. */
return (0);
eof:
/* Invoke the callback with an EOF status and return. */
return (docallback(C, 0));
failed:
/* Invoke the callback with a failure status and return. */
return (docallback(C, -1));
}
/**
* network_read(fd, buf, buflen, minread, callback, cookie):
* Asynchronously read up to ${buflen} bytes of data from ${fd} into ${buf}.
* When at least ${minread} bytes have been read or on error, invoke
* ${callback}(${cookie}, lenread), where lenread is 0 on EOF or -1 on error,
* and the number of bytes read (between ${minread} and ${buflen} inclusive)
* otherwise. Return a cookie which can be passed to network_read_cancel() in
* order to cancel the read.
*/
void *
network_read(int fd, uint8_t * buf, size_t buflen, size_t minread,
int (* callback)(void *, ssize_t), void * cookie)
{
struct network_read_cookie * C;
/* Make sure buflen is non-zero. */
assert(buflen != 0);
/* Sanity-check: # bytes must fit into a ssize_t. */
assert(buflen <= SSIZE_MAX);
/* Bake a cookie. */
if ((C = mpool_network_read_cookie_malloc()) == NULL)
goto err0;
C->callback = callback;
C->cookie = cookie;
C->fd = fd;
C->buf = buf;
C->buflen = buflen;
C->minlen = minread;
C->bufpos = 0;
/* Register a callback for network readiness. */
if (events_network_register(callback_buf, C, C->fd,
EVENTS_NETWORK_OP_READ))
goto err1;
/* Success! */
return (C);
err1:
mpool_network_read_cookie_free(C);
err0:
/* Failure! */
return (NULL);
}
/**
* network_read_cancel(cookie):
* Cancel the buffer read for which the cookie ${cookie} was returned by
* network_read(). Do not invoke the callback associated with the read.
*/
void
network_read_cancel(void * cookie)
{
struct network_read_cookie * C = cookie;
/* Kill the network event. */
events_network_cancel(C->fd, EVENTS_NETWORK_OP_READ);
/* Free the cookie. */
mpool_network_read_cookie_free(C);
}
|