File: network_write.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 (205 lines) | stat: -rw-r--r-- 4,947 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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
#include <sys/socket.h>

#include <assert.h>
#include <errno.h>
#include <limits.h>
#include <signal.h>
#include <stdint.h>
#include <stdlib.h>

#include "events.h"
#include "mpool.h"
#include "warnp.h"

#include "network.h"

/**
 * POSIX.1-2008 requires that MSG_NOSIGNAL be defined as a flag for send(2)
 * which has the effect of preventing SIGPIPE from being raised when writing
 * to a descriptor which has been shut down.  Unfortunately there are some
 * platforms which are not POSIX.1-2008 compliant; we provide a workaround
 * (-DPOSIXFAIL_MSG_NOSIGNAL) which instead blocks the SIGPIPE signal on such
 * platforms.
 *
 * (This workaround could be used automatically, but requiring that it be
 * explicitly enabled helps to get platforms fixed.)
 */
#ifdef POSIXFAIL_MSG_NOSIGNAL
#ifndef MSG_NOSIGNAL
#define MSG_NOSIGNAL 0
#endif
#endif

struct network_write_cookie {
	int (* callback)(void *, ssize_t);
	void * cookie;
	int fd;
	const uint8_t * buf;
	size_t buflen;
	size_t minlen;
	size_t bufpos;
};

MPOOL(network_write_cookie, struct network_write_cookie, 16);

/* Invoke the callback, clean up, and return the callback's status. */
static int
docallback(struct network_write_cookie * C, ssize_t nbytes)
{
	int rc;

	/* Invoke the callback. */
	rc = (C->callback)(C->cookie, nbytes);

	/* Clean up. */
	mpool_network_write_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_write_cookie * C = cookie;
	size_t oplen;
	ssize_t len;
#ifdef POSIXFAIL_MSG_NOSIGNAL
	void (* oldsig)(int);
	int saved_errno;
#endif

	/* If we don't have MSG_NOSIGNAL, ignore SIGPIPE. */
#ifdef POSIXFAIL_MSG_NOSIGNAL
	if ((oldsig = signal(SIGPIPE, SIG_IGN)) == SIG_ERR) {
		warnp("signal(SIGPIPE)");
		goto failed;
	}
#endif

	/* Attempt to read/write data to/from the buffer. */
	oplen = C->buflen - C->bufpos;
	len = send(C->fd, C->buf + C->bufpos, oplen, MSG_NOSIGNAL);

	/* We should never see a send length of zero. */
	assert(len != 0);

	/* If we ignored SIGPIPE, restore the old handler. */
#ifdef POSIXFAIL_MSG_NOSIGNAL
	/* Save errno in case it gets clobbered by signal(). */
	saved_errno = errno;

	if (signal(SIGPIPE, oldsig) == SIG_ERR) {
		warnp("signal(SIGPIPE)");
		goto failed;
	}

	/* Restore saved errno. */
	errno = saved_errno;
#endif

	/* 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;
	}

	/* 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_WRITE))
		goto failed;

	/* Callback was reset. */
	return (0);

failed:
	/* Invoke the callback with a failure status and return. */
	return (docallback(C, -1));
}

/**
 * network_write(fd, buf, buflen, minwrite, callback, cookie):
 * Asynchronously write up to ${buflen} bytes of data from ${buf} to ${fd}.
 * When at least ${minwrite} bytes have been written or on error, invoke
 * ${callback}(${cookie}, lenwrit), where lenwrit is -1 on error and the
 * number of bytes written (between ${minwrite} and ${buflen} inclusive)
 * otherwise.  Return a cookie which can be passed to network_write_cancel() in
 * order to cancel the write.
 */
void *
network_write(int fd, const uint8_t * buf, size_t buflen, size_t minwrite,
    int (* callback)(void *, ssize_t), void * cookie)
{
	struct network_write_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_write_cookie_malloc()) == NULL)
		goto err0;
	C->callback = callback;
	C->cookie = cookie;
	C->fd = fd;
	C->buf = buf;
	C->buflen = buflen;
	C->minlen = minwrite;
	C->bufpos = 0;

	/* Register a callback for network readiness. */
	if (events_network_register(callback_buf, C, C->fd,
	    EVENTS_NETWORK_OP_WRITE))
		goto err1;

	/* Success! */
	return (C);

err1:
	mpool_network_write_cookie_free(C);
err0:
	/* Failure! */
	return (NULL);
}

/**
 * network_write_cancel(cookie):
 * Cancel the buffer write for which the cookie ${cookie} was returned by
 * network_write().  Do not invoke the callback associated with the write.
 */
void
network_write_cancel(void * cookie)
{
	struct network_write_cookie * C = cookie;

	/* Kill the network event. */
	events_network_cancel(C->fd, EVENTS_NETWORK_OP_WRITE);

	/* Free the cookie. */
	mpool_network_write_cookie_free(C);
}