1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include <assert.h>
18#include <errno.h>
19#include <fcntl.h>
20#include <linux/ublock.h>
21#include <stdint.h>
22#include <stdio.h>
23#include <stdlib.h>
24#include <sys/types.h>
25#include <ublock/ublock.h>
26
27#define CONTROL_FILE "/dev/ublockctl"
28
29struct ublock_ctx {
30	struct ublock_ops *ops;
31
32	uint32_t index;
33	uint64_t size;
34	uint32_t max_buf;
35
36	char *in_buf;
37	char *out_buf;
38
39	int flags;
40	int fd;
41	int fails;
42};
43
44#define CTX_INITED  0x1
45#define CTX_READY   0x2
46#define CTX_RUNNING 0x4
47
48#define MAX_BUF 65536
49
50#define MAX_FAILURES 10
51
52static inline void ublock_succeed(struct ublock_ctx *ub_ctx)
53{
54	assert(ub_ctx);
55
56	ub_ctx->fails = 0;
57}
58
59static void ublock_fail(struct ublock_ctx *ub_ctx)
60{
61	assert(ub_ctx);
62
63	ub_ctx->fails++;
64	if (ub_ctx->fails > MAX_FAILURES)
65		ublock_stop(ub_ctx);
66}
67
68static int ublock_handle_init(struct ublock_ctx *ub_ctx,
69                              const void *in, size_t in_len,
70                              void *out, size_t *out_len)
71{
72	const struct ublock_init_in *in_h;
73	struct ublock_init_out *out_h;
74
75	assert(ub_ctx);
76	assert(in);
77	assert(out);
78
79	if (in_len != sizeof(*in_h))
80		return -EPROTO;
81
82	in_h = (const struct ublock_init_in *)in;
83
84	if (in_h->version != UBLOCK_VERSION)
85		return -EPROTO;
86
87	out_h = (struct ublock_init_out *)out;
88	out_h->version = UBLOCK_VERSION;
89	out_h->size = ub_ctx->size;
90	if (in_h->max_buf < MAX_BUF)
91		ub_ctx->max_buf = in_h->max_buf;
92	else
93		ub_ctx->max_buf = MAX_BUF;
94	out_h->max_buf = ub_ctx->max_buf;
95
96	*out_len = sizeof(*out_h);
97
98	ub_ctx->index = in_h->index;
99	ub_ctx->flags |= CTX_INITED;
100
101	return 0;
102}
103
104static int ublock_handle_ready(struct ublock_ctx *ub_ctx,
105                               const void *in, size_t in_len,
106                               void *out, size_t *out_len)
107{
108	struct ublock_ready_out *out_h;
109
110	assert(ub_ctx);
111	assert(in);
112	assert(out);
113
114	if (in_len != sizeof(struct ublock_ready_in))
115		return -EPROTO;
116
117	*out_len = sizeof(struct ublock_ready_out);
118
119	ub_ctx->flags |= CTX_READY;
120
121	return 0;
122}
123
124static int ublock_handle_read(struct ublock_ctx *ub_ctx,
125                              const void *in, size_t in_len,
126                              void *out, size_t *out_len)
127{
128	const struct ublock_read_in *in_h;
129	struct ublock_read_out *out_h;
130	char *out_buf;
131
132	assert(ub_ctx);
133	assert(in);
134	assert(out);
135
136	if (in_len != sizeof(*in_h))
137		return -EPROTO;
138
139	in_h = (const struct ublock_read_in *)in;
140
141	out_h = (struct ublock_read_out *)out;
142	out_buf = (char *)(out_h + 1);
143
144	out_h->status = (ub_ctx->ops->read)(out_buf, in_h->length, in_h->offset);
145
146	if (out_h->status >= 0)
147		*out_len = sizeof(*out_h) + in_h->length;
148	else
149		*out_len = sizeof(*out_h);
150
151	return 0;
152}
153
154static int ublock_handle_write(struct ublock_ctx *ub_ctx,
155                               const void *in, size_t in_len,
156                               void *out, size_t *out_len)
157{
158	const struct ublock_write_in *in_h;
159	const char *in_buf;
160	struct ublock_write_out *out_h;
161
162	assert(ub_ctx);
163	assert(in);
164	assert(out);
165
166	if (in_len < sizeof(*in_h))
167		return -EPROTO;
168
169	in_h = (const struct ublock_write_in *)in;
170	in_buf = (const char*)(in_h + 1);
171
172	out_h = (struct ublock_write_out *)out;
173	*out_len = sizeof(*out_h);
174
175	out_h->status = (ub_ctx->ops->write)(in_buf, in_h->length, in_h->offset);
176
177	return 0;
178}
179
180static int ublock_handle_request(struct ublock_ctx *ub_ctx,
181                                 const void *in, size_t in_len,
182                                 void *out, size_t *out_len)
183{
184	const struct ublock_in_header *in_h;
185	const void *in_buf;
186	size_t in_buf_len;
187
188	struct ublock_out_header *out_h;
189	void *out_buf;
190	size_t out_buf_len;
191
192	int result;
193	int (*handle_fn)(struct ublock_ctx *, const void *, size_t, void *, size_t *);
194
195	assert(ub_ctx);
196	assert(in);
197	assert(out);
198
199	if (in_len < sizeof(*in_h))
200		return -EPROTO;
201
202	in_h = (const struct ublock_in_header *)in;
203	in_buf = in_h + 1;
204	in_buf_len = in_len - sizeof(*in_h);
205
206	out_h = (struct ublock_out_header *)out;
207	out_buf = out_h + 1;
208
209	switch (in_h->opcode) {
210	case UBLOCK_INIT_IN:
211		out_h->opcode = UBLOCK_INIT_OUT;
212		handle_fn = &ublock_handle_init;
213		break;
214	case UBLOCK_READY_IN:
215		out_h->opcode = UBLOCK_READY_OUT;
216		handle_fn = &ublock_handle_ready;
217		break;
218	case UBLOCK_READ_IN:
219		out_h->opcode = UBLOCK_READ_OUT;
220		handle_fn = &ublock_handle_read;
221		break;
222	case UBLOCK_WRITE_IN:
223		out_h->opcode = UBLOCK_WRITE_OUT;
224		handle_fn = &ublock_handle_write;
225		break;
226	default:
227		return -EPROTO;
228	}
229
230	out_h->seq = in_h->seq;
231	result = (handle_fn)(ub_ctx, in_buf, in_buf_len, out_buf, &out_buf_len);
232	*out_len = sizeof(*out_h) + out_buf_len;
233
234	return result;
235}
236
237static int ublock_do_request(struct ublock_ctx *ub_ctx,
238			     void *in_buf, size_t in_size,
239			     void *out_buf, size_t out_size)
240{
241	size_t out_len;
242	ssize_t in_len, out_wrote;
243	int result;
244
245	assert(ub_ctx);
246	assert(in_buf);
247	assert(out_buf);
248
249	in_len = read(ub_ctx->fd, in_buf, in_size);
250	if (in_len < 0)
251		return -EPROTO;
252
253	result = ublock_handle_request(ub_ctx, in_buf, in_len, out_buf, &out_len);
254
255	assert(out_len <= out_size);
256
257	out_wrote = write(ub_ctx->fd, out_buf, out_len);
258
259	if (out_wrote < out_len)
260		return -EPROTO;
261
262	if (result)
263		ublock_fail(ub_ctx);
264	else
265		ublock_succeed(ub_ctx);
266
267	return result;
268}
269
270#define EXIT_READY 1
271#define EXIT_STOPPED 2
272#define EXIT_FAIL 4
273
274static int ublock_loop(struct ublock_ctx *ub_ctx, int exit_cond)
275{
276	size_t in_len, out_len;
277	int result;
278
279	result = 0;
280	while (((exit_cond & EXIT_READY) && (!(ub_ctx->flags & CTX_READY))) ||
281	       ((exit_cond & EXIT_STOPPED) && (ub_ctx->flags & CTX_RUNNING)) ||
282	       ((exit_cond & EXIT_FAIL) && (result))) {
283		result = ublock_do_request(ub_ctx,
284		                           ub_ctx->in_buf, ub_ctx->max_buf,
285		                           ub_ctx->out_buf, ub_ctx->max_buf);
286		if (result)
287			return result;
288	}
289
290	return 0;
291}
292
293int ublock_run(struct ublock_ctx *ub_ctx)
294{
295	if (!ub_ctx)
296		return -EFAULT;
297
298	if (!(ub_ctx->flags & CTX_INITED))
299		return -EINVAL;
300
301	ub_ctx->flags |= CTX_RUNNING;
302	ublock_loop(ub_ctx, EXIT_STOPPED);
303
304	return 0;
305}
306
307void ublock_stop(struct ublock_ctx *ub_ctx)
308{
309	if (!ub_ctx)
310		return;
311
312	if (!(ub_ctx->flags & CTX_INITED))
313		return;
314
315	ub_ctx->flags &= ~CTX_RUNNING;
316}
317
318int ublock_index(struct ublock_ctx *ub_ctx)
319{
320	if (!ub_ctx)
321		return -EFAULT;
322
323	if (!(ub_ctx->flags & CTX_INITED))
324		return -EINVAL;
325
326	return ub_ctx->index;
327}
328
329static inline size_t ublock_init_buf_size(void)
330{
331	size_t in_size = sizeof(struct ublock_in_header) +
332			 sizeof(struct ublock_init_in);
333	size_t out_size = sizeof(struct ublock_out_header) +
334			  sizeof(struct ublock_init_out);
335
336	return (in_size > out_size) ? in_size : out_size;
337}
338
339int ublock_init(struct ublock_ctx **ub_ctx_out, struct ublock_ops *ops,
340		uint64_t dev_size)
341{
342	struct ublock_ctx *ub_ctx;
343	char *in_buf, *out_buf;
344	size_t size;
345	int result;
346
347	if (!ub_ctx_out || !ops)
348		return -EFAULT;
349
350	in_buf = out_buf = NULL;
351
352	ub_ctx = malloc(sizeof(struct ublock_ctx));
353	if (!ub_ctx) {
354		result = -ENOMEM;
355		goto error;
356	}
357
358	size = ublock_init_buf_size();
359	in_buf = malloc(size);
360	out_buf = malloc(size);
361	if (!(in_buf && out_buf)) {
362		result = -ENOMEM;
363		goto error;
364	}
365
366	ub_ctx->ops = ops;
367	ub_ctx->size = dev_size;
368	ub_ctx->max_buf = 0;
369	ub_ctx->flags = 0;
370
371	ub_ctx->fd = open(CONTROL_FILE, O_RDWR);
372	if (ub_ctx->fd < 0) {
373		result = -ENOENT;
374		goto error;
375	}
376
377	result = ublock_do_request(ub_ctx, in_buf, size, out_buf, size);
378	if (result) {
379		result = -EPROTO;
380		goto error;
381	}
382	if (!ub_ctx->flags & CTX_INITED) {
383		result = -EPROTO;
384		goto error;
385	}
386
387	free(in_buf);
388	in_buf = NULL;
389	free(out_buf);
390	out_buf = NULL;
391
392	ub_ctx->in_buf = malloc(ub_ctx->max_buf);
393	ub_ctx->out_buf = malloc(ub_ctx->max_buf);
394	if (!(ub_ctx->in_buf && ub_ctx->out_buf)) {
395		result = -ENOMEM;
396		goto error;
397	}
398
399	ublock_loop(ub_ctx, EXIT_READY);
400
401	*ub_ctx_out = ub_ctx;
402
403	return 0;
404
405error:
406	if (ub_ctx) {
407		if (ub_ctx->in_buf)
408			free(ub_ctx->in_buf);
409		if (ub_ctx->out_buf)
410			free(ub_ctx->out_buf);
411		if (ub_ctx->fd)
412			close(ub_ctx->fd);
413		free(ub_ctx);
414	}
415	if (in_buf)
416		free(in_buf);
417	if (out_buf)
418		free(out_buf);
419
420	return result;
421}
422
423void ublock_destroy(struct ublock_ctx *ub_ctx)
424{
425	if (!ub_ctx)
426		return;
427
428	close(ub_ctx->fd);
429	free(ub_ctx);
430}
431