File: network_read.c

package info (click to toggle)
spiped 1.6.4-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,328 kB
  • sloc: ansic: 11,951; sh: 1,081; makefile: 629; perl: 121
file content (164 lines) | stat: -rw-r--r-- 3,770 bytes parent folder | download | duplicates (2)
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);
}