/* * */ #include #include #include #include #include #include #include #include #include #include #include "memcache-lite-private.h" MC(state_t) *memcache_initialize() { struct sigaction action; MC(state_t) *state = malloc(sizeof(MC(state_t))); if (state == NULL) return NULL; state->socket = -1; state->last_errno = 0; state->error_has_occurred = ERROR_NONE; state->error_message[0] = '\0'; state->path_to_socket[0] = '\0'; if (MC(buffer_initialize)(&state->read) != RETURN_SUCCESS) { free(state); return NULL; } if (MC(buffer_initialize)(&state->write) != RETURN_SUCCESS) { free(state); return NULL; } /* one event is all we'll do at once */ io_setup(1, &state->io_context); state->write.io_context = &state->io_context; state->read.io_context = &state->io_context; /* Setup SIGPIPE handler */ // TODO: add support to enable/disable SIGPIPE handling //action.sa_sigaction = MC(sigpipe_action); action.sa_handler = SIG_IGN; sigemptyset(&action.sa_mask); sigaction(SIGPIPE, &action, &state->sigpipe_original); return state; } /* does not free */ MC(return_t) memcache_destroy(MC(state_t) *state) { if (state == NULL) FAILURE(ERROR_NULL, "state"); if (state->socket >= 0) close(state->socket); MC(buffer_destroy)(&state->read); MC(buffer_destroy)(&state->write); /* Error handling not as important here... (TODO) */ io_destroy(state->io_context); /* Restore SIGPIPE handler */ sigaction(SIGPIPE, &state->sigpipe_original, NULL); free(state); return RETURN_SUCCESS; } MC(return_t) memcache_connect(MC(state_t) *state, const char* path_to_socket) { MC(return_t) ret = ERROR_NONE; /* TODO: add multiple hosts */ if (state == NULL) FAILURE(ERROR_NULL, "state"); if (path_to_socket == NULL) FAILURE(ERROR_NULL, "path_to_socket"); if (state->connected) close(state->socket); state->connected = 0; strncpy(state->path_to_socket, path_to_socket, PATH_MAX); ret = MC(io_connect_unix)(state->path_to_socket, &state->socket); if (ret != ERROR_NONE) FAILURE(ret, "cannot connect to UNIX socket"); /* We are connected */ state->connected = 1; return RETURN_SUCCESS; } MC(return_t) memcache_reconnect(MC(state_t) *state) { MC(return_t) ret = ERROR_NONE; if (state == NULL) FAILURE(ERROR_NULL, "state"); state->connected = 0; close(state->socket); ret = MC(io_connect_unix)(state->path_to_socket, &state->socket); if (ret != ERROR_NONE) FAILURE(ret, "cannot connect to UNIX socket"); /* We are connected */ state->connected = 1; return RETURN_SUCCESS; } MC(return_t) memcache_connected(MC(state_t) *state) { if (state == NULL) FAILURE(ERROR_NULL, "state"); return state->connected; } MC(return_t) memcache_get(MC(state_t) *state, const char *key, MC(result_t) *result) { /* GET \r\n */ char command[MC_GET_LEN + MEMCACHE_MAX_KEY_LEN + MC_EOL_LEN + 1/*\0*/]; char response_header[MAX_RESPONSE_HEADER_LEN + 1]; int copied = 0; MC(return_t) ret = ERROR_NONE; if (!state->connected) FAILURE(ERROR_SOCKET, "not connected"); if (state == NULL) FAILURE(ERROR_NULL, "state"); if (result == NULL) FAILURE(ERROR_NULL, "result"); if (key == NULL) FAILURE(ERROR_NULL, "key"); copied = snprintf(command, sizeof(command), "%s%s%s", MC_GET, key, MC_EOL); if (copied >= sizeof(command)) FAILURE(ERROR_TOOBIG, "command"); /* TODO just write to buffer then do one write */ ret = MC(io_write)(state, command, copied); if (ret != RETURN_SUCCESS) FAILURE(ret, "cannot write to io"); ret = MC(io_write_sync)(state); /* TODO: do reconnect, failover code here */ if (ret != RETURN_SUCCESS) FAILURE(ret, "cannot write to io"); // read response header ret = MC(io_readn_until)(state, '\n', response_header, sizeof(response_header)); /* TODO: handle IO error properly */ if (ret != RETURN_SUCCESS) FAILURE(ret, "cannot read response header"); // parse response header if (sscanf(response_header, "VALUE %"MEMCACHE_MAX_KEY_LEN_S"s %hd %u\r\n", result->key, &result->flags, &result->size) != 3) { if (!strcmp(response_header, "END\r\n")) { FAILURE(ERROR_NONE, "no match"); } FAILURE(ERROR_BUG, "unknown server response!"); } DLOG("reading key: %s %u\n", result->key, result->size); if (result->size >= MEMCACHE_BUFFER_MAX_SIZE) { // TODO: trash data ... MC(io_readn)(state, NULL, result->size + sizeof("\r\nEND\r\n")); FAILURE(ERROR_TOOBIG, "server sent too large a payload"); } // read bytes result->data = (char *) malloc(result->size); if (result->data == NULL) FAILURE(ERROR_MEM, "malloc()"); ret = MC(io_readn)(state, result->data, result->size); if (ret != RETURN_SUCCESS) { /* TODO: handle IO error properly */ free(result->data); FAILURE(ret, "cannot read response result"); } // eat closing: \r\nEND\r\n MC(io_readn)(state, NULL, sizeof("\r\nEND\r\n")); if (ret != RETURN_SUCCESS) { /* TODO: handle IO error properly */ free(result->data); FAILURE(ret, "read END"); } result->found = 1; return RETURN_SUCCESS; } MC(return_t) memcache_set(MC(state_t) *state, const char *key, const char *data, unsigned int flags, time_t expiration, size_t length) { MC(return_t) ret = ERROR_NONE; if (!state->connected) FAILURE(ERROR_SOCKET, "not connected"); ret = MC(storage)(state, "set", key, data, flags, expiration, length); if (ret != RETURN_SUCCESS) FAILURE(ret, "storage command failed"); return RETURN_SUCCESS; } MC(return_t) memcache_add(MC(state_t) *state, const char *key, const char *data, unsigned int flags, time_t expiration, size_t length) { MC(return_t) ret = ERROR_NONE; if (!state->connected) FAILURE(ERROR_SOCKET, "not connected"); ret = MC(storage)(state, "add", key, data, flags, expiration, length); if (ret != RETURN_SUCCESS) FAILURE(ret, "storage command failed"); return RETURN_SUCCESS; } MC(return_t) memcache_replace(MC(state_t) *state, const char *key, const char *data, unsigned int flags, time_t expiration, size_t length) { MC(return_t) ret = ERROR_NONE; if (!state->connected) FAILURE(ERROR_SOCKET, "not connected"); ret = MC(storage)(state, "replace", key, data, flags, expiration, length); if (ret != RETURN_SUCCESS) FAILURE(ret, "storage command failed"); return RETURN_SUCCESS; } MC(return_t) memcache_del(MC(state_t) *state, const char *key, time_t when) { char response_header[MS_NOT_FOUND_LEN + MC_EOL_LEN + 1]; MC(return_t) ret = ERROR_NONE; if (!state->connected) FAILURE(ERROR_SOCKET, "not connected"); ret = MC(command)(state, "delete %s %u\r\n", key, (unsigned int) when); if (ret != RETURN_SUCCESS) FAILURE(ret, "cannot write command"); ret = MC(io_write_sync)(state); /* TODO: do reconnect, failover code here */ if (ret != RETURN_SUCCESS) FAILURE(ret, "cannot write_sync"); /* read response header */ ret = MC(io_readn_until)(state, '\n', response_header, sizeof(response_header)); /* TODO: handle IO error properly */ if (ret != RETURN_SUCCESS) FAILURE(ret, "cannot read response header"); // parse response header if (!strncmp(response_header, MS_DELETED, MS_DELETED_LEN)) return RETURN_SUCCESS; /* TODO: make these return something useful.... */ if (!strncmp(response_header, MS_NOT_FOUND, MS_NOT_FOUND_LEN)) FAILURE(ERROR_NOTFOUND, "NOT_FOUND"); return RETURN_FAILURE; } /**** private ****/ void MC(sigpipe_action)(int signal, siginfo_t *info, void *state_ptr) { MC(state_t) *state = (MC(state_t) *) state_ptr; /* Only indicate lack of connectedness */ state->connected = 0; /* TODO: add support to call original if this isn't for the * socket we want. */ } MC(return_t) MC(command)(MC(state_t) *state, const char *fmt, ...) { va_list arguments; char command[300]; /* magic size - later do something better */ int length = 0; MC(return_t) ret = ERROR_NONE; va_start(arguments, fmt); length = vsnprintf(command, sizeof(command), fmt, arguments); ret = MC(io_write)(state, command, length); if (ret != RETURN_SUCCESS) FAILURE(ret, "cannot write command"); return RETURN_SUCCESS; } MC(return_t) MC(storage)(MC(state_t) *state, const char *cmd, const char *key, const char *data, unsigned int flags, time_t expiration, size_t length) { char response_header[MS_NOT_STORED_LEN + 1]; MC(return_t) ret = ERROR_NONE; if (key == NULL) FAILURE(ERROR_NULL, "key"); if (cmd == NULL) FAILURE(ERROR_NULL, "cmd"); if (data == NULL && length > 0) FAILURE(ERROR_NULL, "data"); if (strlen(key) > MEMCACHE_MAX_KEY_LEN) FAILURE(ERROR_TOOBIG, "key"); ret = MC(command)(state, "%s %s %u %u %u\r\n", cmd, key, flags, (unsigned int) expiration, length); if (ret != RETURN_SUCCESS) FAILURE(ret, "cannot write command"); /* Send the payload */ if (data != NULL) { ret = MC(io_write)(state, data, length); if (ret != RETURN_SUCCESS) FAILURE(ret, "cannot write data"); } ret = MC(io_write)(state, MC_EOL, MC_EOL_LEN); if (ret != RETURN_SUCCESS) FAILURE(ret, "cannot write eol"); ret = MC(io_write_sync)(state); /* TODO: do reconnect, failover code here */ if (ret != RETURN_SUCCESS) FAILURE(ret, "cannot write_sync"); /* read response header */ ret = MC(io_readn_until)(state, '\n', response_header, sizeof(response_header)); /* TODO: handle IO error properly */ if (ret != RETURN_SUCCESS) FAILURE(ret, "cannot read response header"); // parse response header if (!strncmp(response_header, MS_NOT_STORED, MS_NOT_STORED_LEN)) return RETURN_FAILURE; if (!strncmp(response_header, MS_STORED, MS_STORED_LEN)) return RETURN_SUCCESS; return RETURN_FAILURE; }