Revert "semver: 1.0 -> 1"
[m6w6/libmemcached] / src / libmemcachedprotocol / handler.c
1 /*
2 +--------------------------------------------------------------------+
3 | libmemcached - C/C++ Client Library for memcached |
4 +--------------------------------------------------------------------+
5 | Redistribution and use in source and binary forms, with or without |
6 | modification, are permitted under the terms of the BSD license. |
7 | You should have received a copy of the license in a bundled file |
8 | named LICENSE; in case you did not receive a copy you can review |
9 | the terms online at: https://opensource.org/licenses/BSD-3-Clause |
10 +--------------------------------------------------------------------+
11 | Copyright (c) 2006-2014 Brian Aker https://datadifferential.com/ |
12 | Copyright (c) 2020 Michael Wallner <mike@php.net> |
13 +--------------------------------------------------------------------+
14 */
15
16 #include "libmemcachedprotocol/common.h"
17
18 #include <stdlib.h>
19 #include <sys/types.h>
20 #include <errno.h>
21 #include <stdbool.h>
22 #include <string.h>
23 #if HAVE_STRINGS_H
24 # include <strings.h>
25 #endif
26 #include <ctype.h>
27 #include <stdio.h>
28
29 #include "p9y/socket.hpp"
30
31 /*
32 ** **********************************************************************
33 ** INTERNAL INTERFACE
34 ** **********************************************************************
35 */
36
37 /**
38 * The default function to receive data from the client. This function
39 * just wraps the recv function to receive from a socket.
40 * See man -s3socket recv for more information.
41 *
42 * @param cookie cookie indentifying a client, not used
43 * @param sock socket to read from
44 * @param buf the destination buffer
45 * @param nbytes the number of bytes to read
46 * @return the number of bytes transferred of -1 upon error
47 */
48 static ssize_t default_recv(const void *cookie, memcached_socket_t sock, void *buf, size_t nbytes) {
49 (void) cookie;
50 return recv(sock, buf, nbytes, 0);
51 }
52
53 /**
54 * The default function to send data to the server. This function
55 * just wraps the send function to send through a socket.
56 * See man -s3socket send for more information.
57 *
58 * @param cookie cookie indentifying a client, not used
59 * @param sock socket to send to
60 * @param buf the source buffer
61 * @param nbytes the number of bytes to send
62 * @return the number of bytes transferred of -1 upon error
63 */
64 static ssize_t default_send(const void *cookie, memcached_socket_t fd, const void *buf,
65 size_t nbytes) {
66 (void) cookie;
67 return send(fd, buf, nbytes, MSG_NOSIGNAL);
68 }
69
70 /**
71 * Try to drain the output buffers without blocking
72 *
73 * @param client the client to drain
74 * @return false if an error occured (connection should be shut down)
75 * true otherwise (please note that there may be more data to
76 * left in the buffer to send)
77 */
78 static bool drain_output(struct memcached_protocol_client_st *client) {
79 if (client->is_verbose) {
80 fprintf(stderr, "%s:%d %s mute:%d output:%s length:%d\n", __FILE__, __LINE__, __func__,
81 (int) client->mute, client->output ? "yes" : "no",
82 client->output ? (int) (client->output->nbytes - client->output->offset) : 0);
83 }
84
85 /* Do we have pending data to send? */
86 while (client->output) {
87 ssize_t len =
88 client->root->send(client, client->sock, client->output->data + client->output->offset,
89 client->output->nbytes - client->output->offset);
90
91 if (len == -1) {
92 if (get_socket_errno() == EWOULDBLOCK) {
93 return true;
94 } else if (get_socket_errno() != EINTR) {
95 client->error = get_socket_errno();
96 return false;
97 }
98 } else {
99 client->output->offset += (size_t) len;
100 if (client->output->offset == client->output->nbytes) {
101 /* This was the complete buffer */
102 struct chunk_st *old = client->output;
103 client->output = client->output->next;
104 if (client->output == NULL) {
105 client->output_tail = NULL;
106 }
107 cache_free(client->root->buffer_cache, old);
108 }
109 }
110 }
111
112 return true;
113 }
114
115 /**
116 * Allocate an output buffer and chain it into the output list
117 *
118 * @param client the client that needs the buffer
119 * @return pointer to the new chunk if the allocation succeeds, NULL otherwise
120 */
121 static struct chunk_st *allocate_output_chunk(struct memcached_protocol_client_st *client) {
122 struct chunk_st *ret = cache_alloc(client->root->buffer_cache);
123
124 if (ret == NULL) {
125 return NULL;
126 }
127
128 ret->offset = ret->nbytes = 0;
129 ret->next = NULL;
130 ret->size = CHUNK_BUFFERSIZE;
131 ret->data = (void *) (ret + 1);
132 if (client->output == NULL) {
133 client->output = client->output_tail = ret;
134 } else {
135 client->output_tail->next = ret;
136 client->output_tail = ret;
137 }
138
139 return ret;
140 }
141
142 /**
143 * Spool data into the send-buffer for a client.
144 *
145 * @param client the client to spool the data for
146 * @param data the data to spool
147 * @param length the number of bytes of data to spool
148 * @return PROTOCOL_BINARY_RESPONSE_SUCCESS if success,
149 * PROTOCOL_BINARY_RESPONSE_ENOMEM if we failed to allocate memory
150 */
151 static protocol_binary_response_status spool_output(struct memcached_protocol_client_st *client,
152 const void *data, size_t length) {
153 if (client->is_verbose) {
154 fprintf(stderr, "%s:%d %s mute:%d length:%d\n", __FILE__, __LINE__, __func__,
155 (int) client->mute, (int) length);
156 }
157
158 if (client->mute) {
159 return PROTOCOL_BINARY_RESPONSE_SUCCESS;
160 }
161
162 size_t offset = 0;
163
164 struct chunk_st *chunk = client->output;
165 while (offset < length) {
166 if (chunk == NULL || (chunk->size - chunk->nbytes) == 0) {
167 if ((chunk = allocate_output_chunk(client)) == NULL) {
168 return PROTOCOL_BINARY_RESPONSE_ENOMEM;
169 }
170 }
171
172 size_t bulk = length - offset;
173 if (bulk > chunk->size - chunk->nbytes) {
174 bulk = chunk->size - chunk->nbytes;
175 }
176
177 memcpy(chunk->data + chunk->nbytes, data, bulk);
178 chunk->nbytes += bulk;
179 offset += bulk;
180 }
181
182 return PROTOCOL_BINARY_RESPONSE_SUCCESS;
183 }
184
185 /**
186 * Try to determine the protocol used on this connection.
187 * If the first byte contains the magic byte PROTOCOL_BINARY_REQ we should
188 * be using the binary protocol on the connection. I implemented the support
189 * for the ASCII protocol by wrapping into the simple interface (aka v1),
190 * so the implementors needs to provide an implementation of that interface
191 *
192 */
193 static memcached_protocol_event_t determine_protocol(struct memcached_protocol_client_st *client,
194 ssize_t *length, void **endptr) {
195 if (*client->root->input_buffer == (uint8_t) PROTOCOL_BINARY_REQ) {
196 if (client->is_verbose) {
197 fprintf(stderr, "%s:%d PROTOCOL: memcached_binary_protocol_process_data\n", __FILE__,
198 __LINE__);
199 }
200 client->work = memcached_binary_protocol_process_data;
201 } else if (client->root->callback->interface_version == 1) {
202 if (client->is_verbose) {
203 fprintf(stderr, "%s:%d PROTOCOL: memcached_ascii_protocol_process_data\n", __FILE__,
204 __LINE__);
205 }
206
207 /*
208 * The ASCII protocol can only be used if the implementors provide
209 * an implementation for the version 1 of the interface..
210 *
211 * @todo I should allow the implementors to provide an implementation
212 * for version 0 and 1 at the same time and set the preferred
213 * interface to use...
214 */
215 client->work = memcached_ascii_protocol_process_data;
216 } else {
217 if (client->is_verbose) {
218 fprintf(stderr, "%s:%d PROTOCOL: Unsupported protocol\n", __FILE__, __LINE__);
219 }
220
221 /* Let's just output a warning the way it is supposed to look like
222 * in the ASCII protocol...
223 */
224 const char *err = "CLIENT_ERROR: Unsupported protocol\r\n";
225 client->root->spool(client, err, strlen(err));
226 client->root->drain(client);
227
228 return MEMCACHED_PROTOCOL_ERROR_EVENT; /* Unsupported protocol */
229 }
230
231 return client->work(client, length, endptr);
232 }
233
234 /*
235 ** **********************************************************************
236 ** * PUBLIC INTERFACE
237 ** * See protocol_handler.h for function description
238 ** **********************************************************************
239 */
240 struct memcached_protocol_st *memcached_protocol_create_instance(void) {
241 struct memcached_protocol_st *ret = calloc(1, sizeof(*ret));
242 if (ret) {
243 ret->recv = default_recv;
244 ret->send = default_send;
245 ret->drain = drain_output;
246 ret->spool = spool_output;
247 ret->input_buffer_size = 1 * 1024 * 1024;
248 ret->input_buffer = malloc(ret->input_buffer_size);
249 if (ret->input_buffer == NULL) {
250 free(ret);
251 ret = NULL;
252
253 return NULL;
254 }
255
256 ret->buffer_cache =
257 cache_create("protocol_handler", CHUNK_BUFFERSIZE + sizeof(struct chunk_st), 0, NULL, NULL);
258 if (ret->buffer_cache == NULL) {
259 free(ret->input_buffer);
260 free(ret);
261 ret = NULL;
262 }
263 }
264
265 return ret;
266 }
267
268 void memcached_protocol_destroy_instance(struct memcached_protocol_st *instance) {
269 cache_destroy(instance->buffer_cache);
270 free(instance->input_buffer);
271 free(instance);
272 }
273
274 struct memcached_protocol_client_st *
275 memcached_protocol_create_client(struct memcached_protocol_st *instance, memcached_socket_t sock) {
276 struct memcached_protocol_client_st *ret = calloc(1, sizeof(memcached_protocol_client_st));
277 if (ret) {
278 ret->root = instance;
279 ret->sock = sock;
280 ret->work = determine_protocol;
281 }
282
283 return ret;
284 }
285
286 void memcached_protocol_client_destroy(struct memcached_protocol_client_st *client) {
287 free(client);
288 }
289
290 void memcached_protocol_client_set_verbose(struct memcached_protocol_client_st *client, bool arg) {
291 if (client) {
292 client->is_verbose = arg;
293 }
294 }
295
296 memcached_protocol_event_t
297 memcached_protocol_client_work(struct memcached_protocol_client_st *client) {
298 /* Try to send data and read from the socket */
299 bool more_data = true;
300 do {
301 ssize_t len = client->root->recv(client, client->sock,
302 client->root->input_buffer + client->input_buffer_offset,
303 client->root->input_buffer_size - client->input_buffer_offset);
304
305 if (len > 0) {
306 /* Do we have the complete packet? */
307 if (client->input_buffer_offset > 0) {
308 memcpy(client->root->input_buffer, client->input_buffer, client->input_buffer_offset);
309 len += (ssize_t) client->input_buffer_offset;
310
311 /* @todo use buffer-cache! */
312 free(client->input_buffer);
313 client->input_buffer_offset = 0;
314 }
315
316 void *endptr;
317 memcached_protocol_event_t events = client->work(client, &len, &endptr);
318 if (events == MEMCACHED_PROTOCOL_ERROR_EVENT) {
319 return MEMCACHED_PROTOCOL_ERROR_EVENT;
320 }
321
322 if (len > 0) {
323 /* save the data for later on */
324 /* @todo use buffer-cache */
325 client->input_buffer = malloc((size_t) len);
326 if (client->input_buffer == NULL) {
327 client->error = ENOMEM;
328 return MEMCACHED_PROTOCOL_ERROR_EVENT;
329 }
330 memcpy(client->input_buffer, endptr, (size_t) len);
331 client->input_buffer_offset = (size_t) len;
332 more_data = false;
333 }
334 } else if (len == 0) {
335 /* Connection closed */
336 drain_output(client);
337 return MEMCACHED_PROTOCOL_ERROR_EVENT;
338 } else {
339 if (get_socket_errno() != EWOULDBLOCK) {
340 client->error = get_socket_errno();
341 /* mark this client as terminated! */
342 return MEMCACHED_PROTOCOL_ERROR_EVENT;
343 }
344 more_data = false;
345 }
346 } while (more_data);
347
348 if (!drain_output(client)) {
349 return MEMCACHED_PROTOCOL_ERROR_EVENT;
350 }
351
352 memcached_protocol_event_t ret = MEMCACHED_PROTOCOL_READ_EVENT;
353 if (client->output) {
354 ret |= MEMCACHED_PROTOCOL_READ_EVENT;
355 }
356
357 return ret;
358 }