/* * Copyright (C) 2013-2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "client_tipc.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "block_device_tipc.h" #include "client.h" #include "client_session.h" #include "client_session_tipc.h" #include "ipc.h" #include "session.h" #include "storage_limits.h" /* macros to help manage debug output */ #define SS_ERR(args...) fprintf(stderr, "ss: " args) #define SS_DBG_IO(args...) \ do { \ } while (0) #if 0 /* this can generate alot of spew on debug builds */ #define SS_INFO(args...) fprintf(stderr, "ss: " args) #else #define SS_INFO(args...) \ do { \ } while (0) #endif static int client_handle_msg(struct ipc_channel_context* ctx, void* msg, size_t msg_size); static void client_disconnect(struct ipc_channel_context* context); static int send_response(struct storage_tipc_client_session* tipc_session, enum storage_err result, struct storage_msg* msg, void* out, size_t out_size); static int send_result(struct storage_tipc_client_session* tipc_session, struct storage_msg* msg, enum storage_err result); static struct storage_op_flags extract_storage_op_flags(uint32_t msg_flags) { return (struct storage_op_flags){ .allow_repaired = msg_flags & STORAGE_MSG_FLAG_FS_REPAIRED_ACK, .complete_transaction = msg_flags & STORAGE_MSG_FLAG_TRANSACT_COMPLETE, .update_checkpoint = msg_flags & STORAGE_MSG_FLAG_TRANSACT_CHECKPOINT, }; } static enum storage_err storage_tipc_file_delete( struct storage_client_session* session, struct storage_msg* msg, struct storage_file_delete_req* req, size_t req_size) { if (req_size < sizeof(*req)) { SS_ERR("%s: invalid request size (%zu)\n", __func__, req_size); return STORAGE_ERR_NOT_VALID; } if ((req->flags & ~STORAGE_FILE_DELETE_MASK) != 0) { SS_ERR("%s: unexpected flags (0x%" PRIx32 ")\n", __func__, req->flags); return STORAGE_ERR_NOT_VALID; } return storage_file_delete(session, req->name, req_size - sizeof(*req), extract_storage_op_flags(msg->flags)); } static enum storage_err storage_tipc_file_move( struct storage_client_session* session, struct storage_msg* msg, struct storage_file_move_req* req, size_t req_size) { if (req_size < sizeof(*req)) { SS_ERR("%s: invalid request size (%zu)\n", __func__, req_size); return STORAGE_ERR_NOT_VALID; } if ((req->flags & ~STORAGE_FILE_MOVE_MASK) != 0) { SS_ERR("invalid move flags 0x%" PRIx32 "\n", req->flags); return STORAGE_ERR_NOT_VALID; } size_t names_combined_len = req_size - sizeof(*req); size_t src_len = req->old_name_len; if (src_len >= names_combined_len) { SS_ERR("%s: invalid src filename length %zu >= %zu\n", __func__, src_len, names_combined_len); return STORAGE_ERR_NOT_VALID; } enum file_create_mode file_create_mode; if (req->flags & STORAGE_FILE_MOVE_CREATE) { file_create_mode = req->flags & STORAGE_FILE_MOVE_CREATE_EXCLUSIVE ? FILE_OPEN_CREATE_EXCLUSIVE : FILE_OPEN_CREATE; } else { file_create_mode = FILE_OPEN_NO_CREATE; } return storage_file_move( session, req->handle, req->flags & STORAGE_FILE_MOVE_OPEN_FILE, req->old_new_name, src_len, req->old_new_name + src_len, names_combined_len - src_len, file_create_mode, extract_storage_op_flags(msg->flags)); } static enum storage_err storage_tipc_file_open( struct storage_tipc_client_session* tipc_session, struct storage_msg* msg, struct storage_file_open_req* req, size_t req_size) { if (req_size < sizeof(*req)) { SS_ERR("%s: invalid request size (%zu)\n", __func__, req_size); return send_result(tipc_session, msg, STORAGE_ERR_NOT_VALID); } if ((req->flags & ~STORAGE_FILE_OPEN_MASK) != 0) { SS_ERR("%s: invalid flags 0x%" PRIx32 "\n", __func__, req->flags); return send_result(tipc_session, msg, STORAGE_ERR_NOT_VALID); } enum file_create_mode file_create_mode; if (req->flags & STORAGE_FILE_OPEN_CREATE) { file_create_mode = req->flags & STORAGE_FILE_OPEN_CREATE_EXCLUSIVE ? FILE_OPEN_CREATE_EXCLUSIVE : FILE_OPEN_CREATE; } else { file_create_mode = FILE_OPEN_NO_CREATE; } struct storage_file_open_resp resp; enum storage_err result = storage_file_open( &tipc_session->session, req->name, req_size - sizeof(*req), file_create_mode, req->flags & STORAGE_FILE_OPEN_TRUNCATE, extract_storage_op_flags(msg->flags), &resp.handle); if (result != STORAGE_NO_ERROR) { return send_result(tipc_session, msg, result); } return send_response(tipc_session, result, msg, &resp, sizeof(resp)); } static enum storage_err storage_tipc_file_close( struct storage_client_session* session, struct storage_msg* msg, struct storage_file_close_req* req, size_t req_size) { if (req_size != sizeof(*req)) { SS_ERR("%s: invalid request size (%zu)\n", __func__, req_size); return STORAGE_ERR_NOT_VALID; } return storage_file_close(session, req->handle, extract_storage_op_flags(msg->flags)); } static enum storage_err storage_tipc_file_write( struct storage_client_session* session, struct storage_msg* msg, struct storage_file_write_req* req, size_t req_size) { if (req_size <= sizeof(*req)) { SS_ERR("%s: invalid request size (%zu)\n", __func__, req_size); return STORAGE_ERR_NOT_VALID; } return storage_file_write(session, req->handle, req->offset, req->data, req_size - sizeof(*req), extract_storage_op_flags(msg->flags)); } static enum storage_err storage_tipc_file_read( struct storage_tipc_client_session* tipc_session, struct storage_msg* msg, struct storage_file_read_req* req, size_t req_size) { if (req_size != sizeof(*req)) { SS_ERR("%s: invalid request size (%zu)\n", __func__, req_size); return send_result(tipc_session, msg, STORAGE_ERR_NOT_VALID); } /* * After getting the args out of req, we still need `*msg`, but won't need * the rest of the buffer anymore. We can reuse that space to hold the * response. */ uint8_t* resp = (uint8_t*)(msg + 1); size_t resp_len = STORAGE_MAX_BUFFER_SIZE - sizeof(*msg); enum storage_err result = storage_file_read( &tipc_session->session, req->handle, req->size, req->offset, extract_storage_op_flags(msg->flags), resp, &resp_len); if (result != STORAGE_NO_ERROR) { return send_result(tipc_session, msg, result); } return send_response(tipc_session, result, msg, resp, resp_len); } struct storage_tipc_file_iter_data { char resp_buf[1024]; size_t buf_used; }; static bool buf_has_space(void* self, size_t max_path_len) { struct storage_tipc_file_iter_data* this = self; /* One extra byte for flags plus one for the path's nul terminator */ size_t max_resp_size = max_path_len + 2; return this->buf_used + max_resp_size <= sizeof(this->resp_buf); } static void write_to_buf(void* self, enum storage_file_list_flag flags, const char* path, size_t path_len) { struct storage_tipc_file_iter_data* this = self; struct storage_file_list_resp* resp = (void*)(this->resp_buf + this->buf_used); resp->flags = flags; this->buf_used++; if (path) { strncpy(resp->name, path, path_len); resp->name[path_len] = '\0'; this->buf_used += path_len + 1; } } static enum storage_err storage_tipc_file_list( struct storage_tipc_client_session* tipc_session, struct storage_msg* msg, struct storage_file_list_req* req, size_t req_size) { if (req_size < sizeof(*req)) { SS_ERR("%s: invalid request size (%zu)\n", __func__, req_size); return send_result(tipc_session, msg, STORAGE_ERR_NOT_VALID); } struct storage_tipc_file_iter_data callback_data = { .buf_used = 0, }; enum storage_err result = storage_file_list( &tipc_session->session, req->max_count, req->flags & STORAGE_FILE_LIST_STATE_MASK, req->name, req_size - sizeof(*req), extract_storage_op_flags(msg->flags), buf_has_space, write_to_buf, &callback_data); if (result != STORAGE_NO_ERROR) { return send_result(tipc_session, msg, result); } return send_response(tipc_session, result, msg, callback_data.resp_buf, callback_data.buf_used); } static enum storage_err storage_tipc_file_get_size( struct storage_tipc_client_session* tipc_session, struct storage_msg* msg, struct storage_file_get_size_req* req, size_t req_size) { if (req_size != sizeof(*req)) { SS_ERR("%s: invalid request size (%zu)\n", __func__, req_size); return send_result(tipc_session, msg, STORAGE_ERR_NOT_VALID); } struct storage_file_get_size_resp resp; enum storage_err result = storage_file_get_size( &tipc_session->session, req->handle, extract_storage_op_flags(msg->flags), &resp.size); if (result != STORAGE_NO_ERROR) { return send_result(tipc_session, msg, result); } return send_response(tipc_session, result, msg, &resp, sizeof(resp)); } static enum storage_err storage_tipc_file_set_size( struct storage_client_session* session, struct storage_msg* msg, struct storage_file_set_size_req* req, size_t req_size) { if (req_size != sizeof(*req)) { SS_ERR("%s: invalid request size (%zu)\n", __func__, req_size); return STORAGE_ERR_NOT_VALID; } return storage_file_set_size(session, req->handle, req->size, extract_storage_op_flags(msg->flags)); } static struct storage_tipc_client_session* chan_context_to_client_session( struct ipc_channel_context* ctx) { assert(ctx != NULL); struct storage_tipc_client_session* tipc_session; tipc_session = containerof(ctx, struct storage_tipc_client_session, chan_ctx); assert(tipc_session->session.magic == STORAGE_CLIENT_SESSION_MAGIC); return tipc_session; } static struct client_port_context* port_context_to_client_port_context( struct ipc_port_context* context) { assert(context != NULL); return containerof(context, struct client_port_context, client_ctx); } static void client_channel_ops_init(struct ipc_channel_ops* ops) { ops->on_handle_msg = client_handle_msg; ops->on_disconnect = client_disconnect; } static struct ipc_channel_context* client_connect( struct ipc_port_context* parent_ctx, const uuid_t* peer_uuid, handle_t chan_handle) { struct client_port_context* client_port_context; struct storage_tipc_client_session* client_tipc_session; client_port_context = port_context_to_client_port_context(parent_ctx); client_tipc_session = calloc(1, sizeof(*client_tipc_session)); if (client_tipc_session == NULL) { SS_ERR("out of memory allocating client session\n"); return NULL; } struct storage_client_session* client_session = &client_tipc_session->session; storage_client_session_init(client_session, client_port_context->tr_state, peer_uuid); client_channel_ops_init(&client_tipc_session->chan_ctx.ops); return &client_tipc_session->chan_ctx; } static void client_disconnect(struct ipc_channel_context* context) { struct storage_tipc_client_session* tipc_session = chan_context_to_client_session(context); storage_client_session_destroy(&tipc_session->session); OPENSSL_cleanse(tipc_session, sizeof(struct storage_tipc_client_session)); free(tipc_session); } static int send_response(struct storage_tipc_client_session* tipc_session, enum storage_err result, struct storage_msg* msg, void* out, size_t out_size) { size_t resp_buf_count = 1; if (result == STORAGE_NO_ERROR && out != NULL && out_size != 0) { ++resp_buf_count; } struct iovec resp_bufs[2]; msg->cmd |= STORAGE_RESP_BIT; msg->flags = 0; msg->size = sizeof(struct storage_msg) + out_size; msg->result = result; resp_bufs[0].iov_base = msg; resp_bufs[0].iov_len = sizeof(struct storage_msg); if (resp_buf_count == 2) { resp_bufs[1].iov_base = out; resp_bufs[1].iov_len = out_size; } struct ipc_msg resp_ipc_msg = { .iov = resp_bufs, .num_iov = resp_buf_count, }; return send_msg(tipc_session->chan_ctx.common.handle, &resp_ipc_msg); } static int send_result(struct storage_tipc_client_session* tipc_session, struct storage_msg* msg, enum storage_err result) { return send_response(tipc_session, result, msg, NULL, 0); } static int client_handle_msg(struct ipc_channel_context* ctx, void* msg_buf, size_t msg_size) { struct storage_tipc_client_session* tipc_session; struct storage_client_session* session; struct storage_msg* msg = msg_buf; size_t payload_len; enum storage_err result; void* payload; tipc_session = chan_context_to_client_session(ctx); session = &tipc_session->session; if (msg_size < sizeof(struct storage_msg)) { SS_ERR("%s: invalid message of size (%zu)\n", __func__, msg_size); struct storage_msg err_msg = {.cmd = STORAGE_RESP_MSG_ERR}; send_result(tipc_session, &err_msg, STORAGE_ERR_NOT_VALID); return ERR_NOT_VALID; /* would force to close connection */ } payload_len = msg_size - sizeof(struct storage_msg); payload = msg->payload; switch (msg->cmd) { case STORAGE_FILE_DELETE: result = storage_tipc_file_delete(session, msg, payload, payload_len); break; case STORAGE_FILE_MOVE: result = storage_tipc_file_move(session, msg, payload, payload_len); break; case STORAGE_FILE_OPEN: return storage_tipc_file_open(tipc_session, msg, payload, payload_len); case STORAGE_FILE_CLOSE: result = storage_tipc_file_close(session, msg, payload, payload_len); break; case STORAGE_FILE_WRITE: result = storage_tipc_file_write(session, msg, payload, payload_len); break; case STORAGE_FILE_READ: return storage_tipc_file_read(tipc_session, msg, payload, payload_len); case STORAGE_FILE_LIST: return storage_tipc_file_list(tipc_session, msg, payload, payload_len); case STORAGE_FILE_GET_SIZE: return storage_tipc_file_get_size(tipc_session, msg, payload, payload_len); case STORAGE_FILE_SET_SIZE: result = storage_tipc_file_set_size(session, msg, payload, payload_len); break; case STORAGE_END_TRANSACTION: result = storage_transaction_end(session, extract_storage_op_flags(msg->flags)); break; default: SS_ERR("%s: unsupported command 0x%" PRIx32 "\n", __func__, msg->cmd); result = STORAGE_ERR_UNIMPLEMENTED; break; } return send_result(tipc_session, msg, result); } int client_create_port(struct tipc_hset* hset, struct ipc_port_context* client_ctx, const char* port_name) { int ret; uint32_t flags = IPC_PORT_ALLOW_TA_CONNECT; #if TEST_BUILD flags |= IPC_PORT_ALLOW_NS_CONNECT; #endif /* start accepting client connections */ client_ctx->ops.on_connect = client_connect; ret = ipc_port_create(hset, client_ctx, port_name, 1, STORAGE_MAX_BUFFER_SIZE, flags); if (ret < 0) { SS_ERR("%s: failure initializing client port (%d)\n", __func__, ret); return ret; } return 0; }