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 +--------------------------------------------------------------------+
16 #include "libmemcachedprotocol/common.h"
19 #include <sys/types.h>
29 #include <sys/types.h>
30 #ifdef HAVE_SYS_SOCKET_H
31 # include <sys/socket.h>
35 ** **********************************************************************
37 ** **********************************************************************
41 * The default function to receive data from the client. This function
42 * just wraps the recv function to receive from a socket.
43 * See man -s3socket recv for more information.
45 * @param cookie cookie indentifying a client, not used
46 * @param sock socket to read from
47 * @param buf the destination buffer
48 * @param nbytes the number of bytes to read
49 * @return the number of bytes transferred of -1 upon error
51 static ssize_t
default_recv(const void *cookie
, memcached_socket_t sock
, void *buf
, size_t nbytes
) {
53 return recv(sock
, buf
, nbytes
, 0);
57 * The default function to send data to the server. This function
58 * just wraps the send function to send through a socket.
59 * See man -s3socket send for more information.
61 * @param cookie cookie indentifying a client, not used
62 * @param sock socket to send to
63 * @param buf the source buffer
64 * @param nbytes the number of bytes to send
65 * @return the number of bytes transferred of -1 upon error
67 static ssize_t
default_send(const void *cookie
, memcached_socket_t fd
, const void *buf
,
70 return send(fd
, buf
, nbytes
, MSG_NOSIGNAL
);
74 * Try to drain the output buffers without blocking
76 * @param client the client to drain
77 * @return false if an error occured (connection should be shut down)
78 * true otherwise (please note that there may be more data to
79 * left in the buffer to send)
81 static bool drain_output(struct memcached_protocol_client_st
*client
) {
82 if (client
->is_verbose
) {
83 fprintf(stderr
, "%s:%d %s mute:%d output:%s length:%d\n", __FILE__
, __LINE__
, __func__
,
84 (int) client
->mute
, client
->output
? "yes" : "no",
85 client
->output
? (int) (client
->output
->nbytes
- client
->output
->offset
) : 0);
88 /* Do we have pending data to send? */
89 while (client
->output
) {
91 client
->root
->send(client
, client
->sock
, client
->output
->data
+ client
->output
->offset
,
92 client
->output
->nbytes
- client
->output
->offset
);
95 if (get_socket_errno() == EWOULDBLOCK
) {
97 } else if (get_socket_errno() != EINTR
) {
98 client
->error
= get_socket_errno();
102 client
->output
->offset
+= (size_t) len
;
103 if (client
->output
->offset
== client
->output
->nbytes
) {
104 /* This was the complete buffer */
105 struct chunk_st
*old
= client
->output
;
106 client
->output
= client
->output
->next
;
107 if (client
->output
== NULL
) {
108 client
->output_tail
= NULL
;
110 cache_free(client
->root
->buffer_cache
, old
);
119 * Allocate an output buffer and chain it into the output list
121 * @param client the client that needs the buffer
122 * @return pointer to the new chunk if the allocation succeeds, NULL otherwise
124 static struct chunk_st
*allocate_output_chunk(struct memcached_protocol_client_st
*client
) {
125 struct chunk_st
*ret
= cache_alloc(client
->root
->buffer_cache
);
131 ret
->offset
= ret
->nbytes
= 0;
133 ret
->size
= CHUNK_BUFFERSIZE
;
134 ret
->data
= (void *) (ret
+ 1);
135 if (client
->output
== NULL
) {
136 client
->output
= client
->output_tail
= ret
;
138 client
->output_tail
->next
= ret
;
139 client
->output_tail
= ret
;
146 * Spool data into the send-buffer for a client.
148 * @param client the client to spool the data for
149 * @param data the data to spool
150 * @param length the number of bytes of data to spool
151 * @return PROTOCOL_BINARY_RESPONSE_SUCCESS if success,
152 * PROTOCOL_BINARY_RESPONSE_ENOMEM if we failed to allocate memory
154 static protocol_binary_response_status
spool_output(struct memcached_protocol_client_st
*client
,
155 const void *data
, size_t length
) {
156 if (client
->is_verbose
) {
157 fprintf(stderr
, "%s:%d %s mute:%d length:%d\n", __FILE__
, __LINE__
, __func__
,
158 (int) client
->mute
, (int) length
);
162 return PROTOCOL_BINARY_RESPONSE_SUCCESS
;
167 struct chunk_st
*chunk
= client
->output
;
168 while (offset
< length
) {
169 if (chunk
== NULL
|| (chunk
->size
- chunk
->nbytes
) == 0) {
170 if ((chunk
= allocate_output_chunk(client
)) == NULL
) {
171 return PROTOCOL_BINARY_RESPONSE_ENOMEM
;
175 size_t bulk
= length
- offset
;
176 if (bulk
> chunk
->size
- chunk
->nbytes
) {
177 bulk
= chunk
->size
- chunk
->nbytes
;
180 memcpy(chunk
->data
+ chunk
->nbytes
, data
, bulk
);
181 chunk
->nbytes
+= bulk
;
185 return PROTOCOL_BINARY_RESPONSE_SUCCESS
;
189 * Try to determine the protocol used on this connection.
190 * If the first byte contains the magic byte PROTOCOL_BINARY_REQ we should
191 * be using the binary protocol on the connection. I implemented the support
192 * for the ASCII protocol by wrapping into the simple interface (aka v1),
193 * so the implementors needs to provide an implementation of that interface
196 static memcached_protocol_event_t
determine_protocol(struct memcached_protocol_client_st
*client
,
197 ssize_t
*length
, void **endptr
) {
198 if (*client
->root
->input_buffer
== (uint8_t) PROTOCOL_BINARY_REQ
) {
199 if (client
->is_verbose
) {
200 fprintf(stderr
, "%s:%d PROTOCOL: memcached_binary_protocol_process_data\n", __FILE__
,
203 client
->work
= memcached_binary_protocol_process_data
;
204 } else if (client
->root
->callback
->interface_version
== 1) {
205 if (client
->is_verbose
) {
206 fprintf(stderr
, "%s:%d PROTOCOL: memcached_ascii_protocol_process_data\n", __FILE__
,
211 * The ASCII protocol can only be used if the implementors provide
212 * an implementation for the version 1 of the interface..
214 * @todo I should allow the implementors to provide an implementation
215 * for version 0 and 1 at the same time and set the preferred
216 * interface to use...
218 client
->work
= memcached_ascii_protocol_process_data
;
220 if (client
->is_verbose
) {
221 fprintf(stderr
, "%s:%d PROTOCOL: Unsupported protocol\n", __FILE__
, __LINE__
);
224 /* Let's just output a warning the way it is supposed to look like
225 * in the ASCII protocol...
227 const char *err
= "CLIENT_ERROR: Unsupported protocol\r\n";
228 client
->root
->spool(client
, err
, strlen(err
));
229 client
->root
->drain(client
);
231 return MEMCACHED_PROTOCOL_ERROR_EVENT
; /* Unsupported protocol */
234 return client
->work(client
, length
, endptr
);
238 ** **********************************************************************
239 ** * PUBLIC INTERFACE
240 ** * See protocol_handler.h for function description
241 ** **********************************************************************
243 struct memcached_protocol_st
*memcached_protocol_create_instance(void) {
244 struct memcached_protocol_st
*ret
= calloc(1, sizeof(*ret
));
246 ret
->recv
= default_recv
;
247 ret
->send
= default_send
;
248 ret
->drain
= drain_output
;
249 ret
->spool
= spool_output
;
250 ret
->input_buffer_size
= 1 * 1024 * 1024;
251 ret
->input_buffer
= malloc(ret
->input_buffer_size
);
252 if (ret
->input_buffer
== NULL
) {
260 cache_create("protocol_handler", CHUNK_BUFFERSIZE
+ sizeof(struct chunk_st
), 0, NULL
, NULL
);
261 if (ret
->buffer_cache
== NULL
) {
262 free(ret
->input_buffer
);
271 void memcached_protocol_destroy_instance(struct memcached_protocol_st
*instance
) {
272 cache_destroy(instance
->buffer_cache
);
273 free(instance
->input_buffer
);
277 struct memcached_protocol_client_st
*
278 memcached_protocol_create_client(struct memcached_protocol_st
*instance
, memcached_socket_t sock
) {
279 struct memcached_protocol_client_st
*ret
= calloc(1, sizeof(memcached_protocol_client_st
));
281 ret
->root
= instance
;
283 ret
->work
= determine_protocol
;
289 void memcached_protocol_client_destroy(struct memcached_protocol_client_st
*client
) {
293 void memcached_protocol_client_set_verbose(struct memcached_protocol_client_st
*client
, bool arg
) {
295 client
->is_verbose
= arg
;
299 memcached_protocol_event_t
300 memcached_protocol_client_work(struct memcached_protocol_client_st
*client
) {
301 /* Try to send data and read from the socket */
302 bool more_data
= true;
304 ssize_t len
= client
->root
->recv(client
, client
->sock
,
305 client
->root
->input_buffer
+ client
->input_buffer_offset
,
306 client
->root
->input_buffer_size
- client
->input_buffer_offset
);
309 /* Do we have the complete packet? */
310 if (client
->input_buffer_offset
> 0) {
311 memcpy(client
->root
->input_buffer
, client
->input_buffer
, client
->input_buffer_offset
);
312 len
+= (ssize_t
) client
->input_buffer_offset
;
314 /* @todo use buffer-cache! */
315 free(client
->input_buffer
);
316 client
->input_buffer_offset
= 0;
320 memcached_protocol_event_t events
= client
->work(client
, &len
, &endptr
);
321 if (events
== MEMCACHED_PROTOCOL_ERROR_EVENT
) {
322 return MEMCACHED_PROTOCOL_ERROR_EVENT
;
326 /* save the data for later on */
327 /* @todo use buffer-cache */
328 client
->input_buffer
= malloc((size_t) len
);
329 if (client
->input_buffer
== NULL
) {
330 client
->error
= ENOMEM
;
331 return MEMCACHED_PROTOCOL_ERROR_EVENT
;
333 memcpy(client
->input_buffer
, endptr
, (size_t) len
);
334 client
->input_buffer_offset
= (size_t) len
;
337 } else if (len
== 0) {
338 /* Connection closed */
339 drain_output(client
);
340 return MEMCACHED_PROTOCOL_ERROR_EVENT
;
342 if (get_socket_errno() != EWOULDBLOCK
) {
343 client
->error
= get_socket_errno();
344 /* mark this client as terminated! */
345 return MEMCACHED_PROTOCOL_ERROR_EVENT
;
351 if (!drain_output(client
)) {
352 return MEMCACHED_PROTOCOL_ERROR_EVENT
;
355 memcached_protocol_event_t ret
= MEMCACHED_PROTOCOL_READ_EVENT
;
356 if (client
->output
) {
357 ret
|= MEMCACHED_PROTOCOL_READ_EVENT
;