| /* |
| * Copyright (C) 2010 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 <assert.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <linux/ublock.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <sys/types.h> |
| #include <ublock/ublock.h> |
| |
| #define CONTROL_FILE "/dev/ublockctl" |
| |
| struct ublock_ctx { |
| struct ublock_ops *ops; |
| |
| uint32_t index; |
| uint64_t size; |
| uint32_t max_buf; |
| |
| char *in_buf; |
| char *out_buf; |
| |
| int flags; |
| int fd; |
| int fails; |
| }; |
| |
| #define CTX_INITED 0x1 |
| #define CTX_READY 0x2 |
| #define CTX_RUNNING 0x4 |
| |
| #define MAX_BUF 65536 |
| |
| #define MAX_FAILURES 10 |
| |
| static inline void ublock_succeed(struct ublock_ctx *ub_ctx) |
| { |
| assert(ub_ctx); |
| |
| ub_ctx->fails = 0; |
| } |
| |
| static void ublock_fail(struct ublock_ctx *ub_ctx) |
| { |
| assert(ub_ctx); |
| |
| ub_ctx->fails++; |
| if (ub_ctx->fails > MAX_FAILURES) |
| ublock_stop(ub_ctx); |
| } |
| |
| static int ublock_handle_init(struct ublock_ctx *ub_ctx, |
| const void *in, size_t in_len, |
| void *out, size_t *out_len) |
| { |
| const struct ublock_init_in *in_h; |
| struct ublock_init_out *out_h; |
| |
| assert(ub_ctx); |
| assert(in); |
| assert(out); |
| |
| if (in_len != sizeof(*in_h)) |
| return -EPROTO; |
| |
| in_h = (const struct ublock_init_in *)in; |
| |
| if (in_h->version != UBLOCK_VERSION) |
| return -EPROTO; |
| |
| out_h = (struct ublock_init_out *)out; |
| out_h->version = UBLOCK_VERSION; |
| out_h->size = ub_ctx->size; |
| if (in_h->max_buf < MAX_BUF) |
| ub_ctx->max_buf = in_h->max_buf; |
| else |
| ub_ctx->max_buf = MAX_BUF; |
| out_h->max_buf = ub_ctx->max_buf; |
| |
| *out_len = sizeof(*out_h); |
| |
| ub_ctx->index = in_h->index; |
| ub_ctx->flags |= CTX_INITED; |
| |
| return 0; |
| } |
| |
| static int ublock_handle_ready(struct ublock_ctx *ub_ctx, |
| const void *in, size_t in_len, |
| void *out, size_t *out_len) |
| { |
| struct ublock_ready_out *out_h; |
| |
| assert(ub_ctx); |
| assert(in); |
| assert(out); |
| |
| if (in_len != sizeof(struct ublock_ready_in)) |
| return -EPROTO; |
| |
| *out_len = sizeof(struct ublock_ready_out); |
| |
| ub_ctx->flags |= CTX_READY; |
| |
| return 0; |
| } |
| |
| static int ublock_handle_read(struct ublock_ctx *ub_ctx, |
| const void *in, size_t in_len, |
| void *out, size_t *out_len) |
| { |
| const struct ublock_read_in *in_h; |
| struct ublock_read_out *out_h; |
| char *out_buf; |
| |
| assert(ub_ctx); |
| assert(in); |
| assert(out); |
| |
| if (in_len != sizeof(*in_h)) |
| return -EPROTO; |
| |
| in_h = (const struct ublock_read_in *)in; |
| |
| out_h = (struct ublock_read_out *)out; |
| out_buf = (char *)(out_h + 1); |
| |
| out_h->status = (ub_ctx->ops->read)(out_buf, in_h->length, in_h->offset); |
| |
| if (out_h->status >= 0) |
| *out_len = sizeof(*out_h) + in_h->length; |
| else |
| *out_len = sizeof(*out_h); |
| |
| return 0; |
| } |
| |
| static int ublock_handle_write(struct ublock_ctx *ub_ctx, |
| const void *in, size_t in_len, |
| void *out, size_t *out_len) |
| { |
| const struct ublock_write_in *in_h; |
| const char *in_buf; |
| struct ublock_write_out *out_h; |
| |
| assert(ub_ctx); |
| assert(in); |
| assert(out); |
| |
| if (in_len < sizeof(*in_h)) |
| return -EPROTO; |
| |
| in_h = (const struct ublock_write_in *)in; |
| in_buf = (const char*)(in_h + 1); |
| |
| out_h = (struct ublock_write_out *)out; |
| *out_len = sizeof(*out_h); |
| |
| out_h->status = (ub_ctx->ops->write)(in_buf, in_h->length, in_h->offset); |
| |
| return 0; |
| } |
| |
| static int ublock_handle_request(struct ublock_ctx *ub_ctx, |
| const void *in, size_t in_len, |
| void *out, size_t *out_len) |
| { |
| const struct ublock_in_header *in_h; |
| const void *in_buf; |
| size_t in_buf_len; |
| |
| struct ublock_out_header *out_h; |
| void *out_buf; |
| size_t out_buf_len; |
| |
| int result; |
| int (*handle_fn)(struct ublock_ctx *, const void *, size_t, void *, size_t *); |
| |
| assert(ub_ctx); |
| assert(in); |
| assert(out); |
| |
| if (in_len < sizeof(*in_h)) |
| return -EPROTO; |
| |
| in_h = (const struct ublock_in_header *)in; |
| in_buf = in_h + 1; |
| in_buf_len = in_len - sizeof(*in_h); |
| |
| out_h = (struct ublock_out_header *)out; |
| out_buf = out_h + 1; |
| |
| switch (in_h->opcode) { |
| case UBLOCK_INIT_IN: |
| out_h->opcode = UBLOCK_INIT_OUT; |
| handle_fn = &ublock_handle_init; |
| break; |
| case UBLOCK_READY_IN: |
| out_h->opcode = UBLOCK_READY_OUT; |
| handle_fn = &ublock_handle_ready; |
| break; |
| case UBLOCK_READ_IN: |
| out_h->opcode = UBLOCK_READ_OUT; |
| handle_fn = &ublock_handle_read; |
| break; |
| case UBLOCK_WRITE_IN: |
| out_h->opcode = UBLOCK_WRITE_OUT; |
| handle_fn = &ublock_handle_write; |
| break; |
| default: |
| return -EPROTO; |
| } |
| |
| out_h->seq = in_h->seq; |
| result = (handle_fn)(ub_ctx, in_buf, in_buf_len, out_buf, &out_buf_len); |
| *out_len = sizeof(*out_h) + out_buf_len; |
| |
| return result; |
| } |
| |
| static int ublock_do_request(struct ublock_ctx *ub_ctx, |
| void *in_buf, size_t in_size, |
| void *out_buf, size_t out_size) |
| { |
| size_t out_len; |
| ssize_t in_len, out_wrote; |
| int result; |
| |
| assert(ub_ctx); |
| assert(in_buf); |
| assert(out_buf); |
| |
| in_len = read(ub_ctx->fd, in_buf, in_size); |
| if (in_len < 0) |
| return -EPROTO; |
| |
| result = ublock_handle_request(ub_ctx, in_buf, in_len, out_buf, &out_len); |
| |
| assert(out_len <= out_size); |
| |
| out_wrote = write(ub_ctx->fd, out_buf, out_len); |
| |
| if (out_wrote < out_len) |
| return -EPROTO; |
| |
| if (result) |
| ublock_fail(ub_ctx); |
| else |
| ublock_succeed(ub_ctx); |
| |
| return result; |
| } |
| |
| #define EXIT_READY 1 |
| #define EXIT_STOPPED 2 |
| #define EXIT_FAIL 4 |
| |
| static int ublock_loop(struct ublock_ctx *ub_ctx, int exit_cond) |
| { |
| size_t in_len, out_len; |
| int result; |
| |
| result = 0; |
| while (((exit_cond & EXIT_READY) && (!(ub_ctx->flags & CTX_READY))) || |
| ((exit_cond & EXIT_STOPPED) && (ub_ctx->flags & CTX_RUNNING)) || |
| ((exit_cond & EXIT_FAIL) && (result))) { |
| result = ublock_do_request(ub_ctx, |
| ub_ctx->in_buf, ub_ctx->max_buf, |
| ub_ctx->out_buf, ub_ctx->max_buf); |
| if (result) |
| return result; |
| } |
| |
| return 0; |
| } |
| |
| int ublock_run(struct ublock_ctx *ub_ctx) |
| { |
| if (!ub_ctx) |
| return -EFAULT; |
| |
| if (!(ub_ctx->flags & CTX_INITED)) |
| return -EINVAL; |
| |
| ub_ctx->flags |= CTX_RUNNING; |
| ublock_loop(ub_ctx, EXIT_STOPPED); |
| |
| return 0; |
| } |
| |
| void ublock_stop(struct ublock_ctx *ub_ctx) |
| { |
| if (!ub_ctx) |
| return; |
| |
| if (!(ub_ctx->flags & CTX_INITED)) |
| return; |
| |
| ub_ctx->flags &= ~CTX_RUNNING; |
| } |
| |
| int ublock_index(struct ublock_ctx *ub_ctx) |
| { |
| if (!ub_ctx) |
| return -EFAULT; |
| |
| if (!(ub_ctx->flags & CTX_INITED)) |
| return -EINVAL; |
| |
| return ub_ctx->index; |
| } |
| |
| static inline size_t ublock_init_buf_size(void) |
| { |
| size_t in_size = sizeof(struct ublock_in_header) + |
| sizeof(struct ublock_init_in); |
| size_t out_size = sizeof(struct ublock_out_header) + |
| sizeof(struct ublock_init_out); |
| |
| return (in_size > out_size) ? in_size : out_size; |
| } |
| |
| int ublock_init(struct ublock_ctx **ub_ctx_out, struct ublock_ops *ops, |
| uint64_t dev_size) |
| { |
| struct ublock_ctx *ub_ctx; |
| char *in_buf, *out_buf; |
| size_t size; |
| int result; |
| |
| if (!ub_ctx_out || !ops) |
| return -EFAULT; |
| |
| in_buf = out_buf = NULL; |
| |
| ub_ctx = malloc(sizeof(struct ublock_ctx)); |
| if (!ub_ctx) { |
| result = -ENOMEM; |
| goto error; |
| } |
| |
| size = ublock_init_buf_size(); |
| in_buf = malloc(size); |
| out_buf = malloc(size); |
| if (!(in_buf && out_buf)) { |
| result = -ENOMEM; |
| goto error; |
| } |
| |
| ub_ctx->ops = ops; |
| ub_ctx->size = dev_size; |
| ub_ctx->max_buf = 0; |
| ub_ctx->flags = 0; |
| |
| ub_ctx->fd = open(CONTROL_FILE, O_RDWR); |
| if (ub_ctx->fd < 0) { |
| result = -ENOENT; |
| goto error; |
| } |
| |
| result = ublock_do_request(ub_ctx, in_buf, size, out_buf, size); |
| if (result) { |
| result = -EPROTO; |
| goto error; |
| } |
| if (!ub_ctx->flags & CTX_INITED) { |
| result = -EPROTO; |
| goto error; |
| } |
| |
| free(in_buf); |
| in_buf = NULL; |
| free(out_buf); |
| out_buf = NULL; |
| |
| ub_ctx->in_buf = malloc(ub_ctx->max_buf); |
| ub_ctx->out_buf = malloc(ub_ctx->max_buf); |
| if (!(ub_ctx->in_buf && ub_ctx->out_buf)) { |
| result = -ENOMEM; |
| goto error; |
| } |
| |
| ublock_loop(ub_ctx, EXIT_READY); |
| |
| *ub_ctx_out = ub_ctx; |
| |
| return 0; |
| |
| error: |
| if (ub_ctx) { |
| if (ub_ctx->in_buf) |
| free(ub_ctx->in_buf); |
| if (ub_ctx->out_buf) |
| free(ub_ctx->out_buf); |
| if (ub_ctx->fd) |
| close(ub_ctx->fd); |
| free(ub_ctx); |
| } |
| if (in_buf) |
| free(in_buf); |
| if (out_buf) |
| free(out_buf); |
| |
| return result; |
| } |
| |
| void ublock_destroy(struct ublock_ctx *ub_ctx) |
| { |
| if (!ub_ctx) |
| return; |
| |
| close(ub_ctx->fd); |
| free(ub_ctx); |
| } |