/*
* 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);
}