1
2
3
4
5
6
7
8
9
10#include "qemu/osdep.h"
11#include <glusterfs/api/glfs.h>
12#include "block/block_int.h"
13#include "qapi/error.h"
14#include "qapi/qmp/qerror.h"
15#include "qemu/uri.h"
16#include "qemu/error-report.h"
17#include "qemu/cutils.h"
18
19#define GLUSTER_OPT_FILENAME "filename"
20#define GLUSTER_OPT_VOLUME "volume"
21#define GLUSTER_OPT_PATH "path"
22#define GLUSTER_OPT_TYPE "type"
23#define GLUSTER_OPT_SERVER_PATTERN "server."
24#define GLUSTER_OPT_HOST "host"
25#define GLUSTER_OPT_PORT "port"
26#define GLUSTER_OPT_TO "to"
27#define GLUSTER_OPT_IPV4 "ipv4"
28#define GLUSTER_OPT_IPV6 "ipv6"
29#define GLUSTER_OPT_SOCKET "socket"
30#define GLUSTER_OPT_DEBUG "debug"
31#define GLUSTER_DEFAULT_PORT 24007
32#define GLUSTER_DEBUG_DEFAULT 4
33#define GLUSTER_DEBUG_MAX 9
34#define GLUSTER_OPT_LOGFILE "logfile"
35#define GLUSTER_LOGFILE_DEFAULT "-"
36
37#define GERR_INDEX_HINT "hint: check in 'server' array index '%d'\n"
38
39typedef struct GlusterAIOCB {
40 int64_t size;
41 int ret;
42 Coroutine *coroutine;
43 AioContext *aio_context;
44} GlusterAIOCB;
45
46typedef struct BDRVGlusterState {
47 struct glfs *glfs;
48 struct glfs_fd *fd;
49 char *logfile;
50 bool supports_seek_data;
51 int debug;
52} BDRVGlusterState;
53
54typedef struct BDRVGlusterReopenState {
55 struct glfs *glfs;
56 struct glfs_fd *fd;
57} BDRVGlusterReopenState;
58
59
60typedef struct GlfsPreopened {
61 char *volume;
62 glfs_t *fs;
63 int ref;
64} GlfsPreopened;
65
66typedef struct ListElement {
67 QLIST_ENTRY(ListElement) list;
68 GlfsPreopened saved;
69} ListElement;
70
71static QLIST_HEAD(glfs_list, ListElement) glfs_list;
72
73static QemuOptsList qemu_gluster_create_opts = {
74 .name = "qemu-gluster-create-opts",
75 .head = QTAILQ_HEAD_INITIALIZER(qemu_gluster_create_opts.head),
76 .desc = {
77 {
78 .name = BLOCK_OPT_SIZE,
79 .type = QEMU_OPT_SIZE,
80 .help = "Virtual disk size"
81 },
82 {
83 .name = BLOCK_OPT_PREALLOC,
84 .type = QEMU_OPT_STRING,
85 .help = "Preallocation mode (allowed values: off, full)"
86 },
87 {
88 .name = GLUSTER_OPT_DEBUG,
89 .type = QEMU_OPT_NUMBER,
90 .help = "Gluster log level, valid range is 0-9",
91 },
92 {
93 .name = GLUSTER_OPT_LOGFILE,
94 .type = QEMU_OPT_STRING,
95 .help = "Logfile path of libgfapi",
96 },
97 { }
98 }
99};
100
101static QemuOptsList runtime_opts = {
102 .name = "gluster",
103 .head = QTAILQ_HEAD_INITIALIZER(runtime_opts.head),
104 .desc = {
105 {
106 .name = GLUSTER_OPT_FILENAME,
107 .type = QEMU_OPT_STRING,
108 .help = "URL to the gluster image",
109 },
110 {
111 .name = GLUSTER_OPT_DEBUG,
112 .type = QEMU_OPT_NUMBER,
113 .help = "Gluster log level, valid range is 0-9",
114 },
115 {
116 .name = GLUSTER_OPT_LOGFILE,
117 .type = QEMU_OPT_STRING,
118 .help = "Logfile path of libgfapi",
119 },
120 { }
121 },
122};
123
124static QemuOptsList runtime_json_opts = {
125 .name = "gluster_json",
126 .head = QTAILQ_HEAD_INITIALIZER(runtime_json_opts.head),
127 .desc = {
128 {
129 .name = GLUSTER_OPT_VOLUME,
130 .type = QEMU_OPT_STRING,
131 .help = "name of gluster volume where VM image resides",
132 },
133 {
134 .name = GLUSTER_OPT_PATH,
135 .type = QEMU_OPT_STRING,
136 .help = "absolute path to image file in gluster volume",
137 },
138 {
139 .name = GLUSTER_OPT_DEBUG,
140 .type = QEMU_OPT_NUMBER,
141 .help = "Gluster log level, valid range is 0-9",
142 },
143 { }
144 },
145};
146
147static QemuOptsList runtime_type_opts = {
148 .name = "gluster_type",
149 .head = QTAILQ_HEAD_INITIALIZER(runtime_type_opts.head),
150 .desc = {
151 {
152 .name = GLUSTER_OPT_TYPE,
153 .type = QEMU_OPT_STRING,
154 .help = "inet|unix",
155 },
156 { }
157 },
158};
159
160static QemuOptsList runtime_unix_opts = {
161 .name = "gluster_unix",
162 .head = QTAILQ_HEAD_INITIALIZER(runtime_unix_opts.head),
163 .desc = {
164 {
165 .name = GLUSTER_OPT_SOCKET,
166 .type = QEMU_OPT_STRING,
167 .help = "socket file path (legacy)",
168 },
169 {
170 .name = GLUSTER_OPT_PATH,
171 .type = QEMU_OPT_STRING,
172 .help = "socket file path (QAPI)",
173 },
174 { }
175 },
176};
177
178static QemuOptsList runtime_inet_opts = {
179 .name = "gluster_inet",
180 .head = QTAILQ_HEAD_INITIALIZER(runtime_inet_opts.head),
181 .desc = {
182 {
183 .name = GLUSTER_OPT_TYPE,
184 .type = QEMU_OPT_STRING,
185 .help = "inet|unix",
186 },
187 {
188 .name = GLUSTER_OPT_HOST,
189 .type = QEMU_OPT_STRING,
190 .help = "host address (hostname/ipv4/ipv6 addresses)",
191 },
192 {
193 .name = GLUSTER_OPT_PORT,
194 .type = QEMU_OPT_STRING,
195 .help = "port number on which glusterd is listening (default 24007)",
196 },
197 {
198 .name = "to",
199 .type = QEMU_OPT_NUMBER,
200 .help = "max port number, not supported by gluster",
201 },
202 {
203 .name = "ipv4",
204 .type = QEMU_OPT_BOOL,
205 .help = "ipv4 bool value, not supported by gluster",
206 },
207 {
208 .name = "ipv6",
209 .type = QEMU_OPT_BOOL,
210 .help = "ipv6 bool value, not supported by gluster",
211 },
212 { }
213 },
214};
215
216static void glfs_set_preopened(const char *volume, glfs_t *fs)
217{
218 ListElement *entry = NULL;
219
220 entry = g_new(ListElement, 1);
221
222 entry->saved.volume = g_strdup(volume);
223
224 entry->saved.fs = fs;
225 entry->saved.ref = 1;
226
227 QLIST_INSERT_HEAD(&glfs_list, entry, list);
228}
229
230static glfs_t *glfs_find_preopened(const char *volume)
231{
232 ListElement *entry = NULL;
233
234 QLIST_FOREACH(entry, &glfs_list, list) {
235 if (strcmp(entry->saved.volume, volume) == 0) {
236 entry->saved.ref++;
237 return entry->saved.fs;
238 }
239 }
240
241 return NULL;
242}
243
244static void glfs_clear_preopened(glfs_t *fs)
245{
246 ListElement *entry = NULL;
247 ListElement *next;
248
249 if (fs == NULL) {
250 return;
251 }
252
253 QLIST_FOREACH_SAFE(entry, &glfs_list, list, next) {
254 if (entry->saved.fs == fs) {
255 if (--entry->saved.ref) {
256 return;
257 }
258
259 QLIST_REMOVE(entry, list);
260
261 glfs_fini(entry->saved.fs);
262 g_free(entry->saved.volume);
263 g_free(entry);
264 }
265 }
266}
267
268static int parse_volume_options(BlockdevOptionsGluster *gconf, char *path)
269{
270 char *p, *q;
271
272 if (!path) {
273 return -EINVAL;
274 }
275
276
277 p = q = path + strspn(path, "/");
278 p += strcspn(p, "/");
279 if (*p == '\0') {
280 return -EINVAL;
281 }
282 gconf->volume = g_strndup(q, p - q);
283
284
285 p += strspn(p, "/");
286 if (*p == '\0') {
287 return -EINVAL;
288 }
289 gconf->path = g_strdup(p);
290 return 0;
291}
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325static int qemu_gluster_parse_uri(BlockdevOptionsGluster *gconf,
326 const char *filename)
327{
328 SocketAddress *gsconf;
329 URI *uri;
330 QueryParams *qp = NULL;
331 bool is_unix = false;
332 int ret = 0;
333
334 uri = uri_parse(filename);
335 if (!uri) {
336 return -EINVAL;
337 }
338
339 gconf->server = g_new0(SocketAddressList, 1);
340 gconf->server->value = gsconf = g_new0(SocketAddress, 1);
341
342
343 if (!uri->scheme || !strcmp(uri->scheme, "gluster")) {
344 gsconf->type = SOCKET_ADDRESS_TYPE_INET;
345 } else if (!strcmp(uri->scheme, "gluster+tcp")) {
346 gsconf->type = SOCKET_ADDRESS_TYPE_INET;
347 } else if (!strcmp(uri->scheme, "gluster+unix")) {
348 gsconf->type = SOCKET_ADDRESS_TYPE_UNIX;
349 is_unix = true;
350 } else if (!strcmp(uri->scheme, "gluster+rdma")) {
351 gsconf->type = SOCKET_ADDRESS_TYPE_INET;
352 warn_report("rdma feature is not supported, falling back to tcp");
353 } else {
354 ret = -EINVAL;
355 goto out;
356 }
357
358 ret = parse_volume_options(gconf, uri->path);
359 if (ret < 0) {
360 goto out;
361 }
362
363 qp = query_params_parse(uri->query);
364 if (qp->n > 1 || (is_unix && !qp->n) || (!is_unix && qp->n)) {
365 ret = -EINVAL;
366 goto out;
367 }
368
369 if (is_unix) {
370 if (uri->server || uri->port) {
371 ret = -EINVAL;
372 goto out;
373 }
374 if (strcmp(qp->p[0].name, "socket")) {
375 ret = -EINVAL;
376 goto out;
377 }
378 gsconf->u.q_unix.path = g_strdup(qp->p[0].value);
379 } else {
380 gsconf->u.inet.host = g_strdup(uri->server ? uri->server : "localhost");
381 if (uri->port) {
382 gsconf->u.inet.port = g_strdup_printf("%d", uri->port);
383 } else {
384 gsconf->u.inet.port = g_strdup_printf("%d", GLUSTER_DEFAULT_PORT);
385 }
386 }
387
388out:
389 if (qp) {
390 query_params_free(qp);
391 }
392 uri_free(uri);
393 return ret;
394}
395
396static struct glfs *qemu_gluster_glfs_init(BlockdevOptionsGluster *gconf,
397 Error **errp)
398{
399 struct glfs *glfs;
400 int ret;
401 int old_errno;
402 SocketAddressList *server;
403 unsigned long long port;
404
405 glfs = glfs_find_preopened(gconf->volume);
406 if (glfs) {
407 return glfs;
408 }
409
410 glfs = glfs_new(gconf->volume);
411 if (!glfs) {
412 goto out;
413 }
414
415 glfs_set_preopened(gconf->volume, glfs);
416
417 for (server = gconf->server; server; server = server->next) {
418 switch (server->value->type) {
419 case SOCKET_ADDRESS_TYPE_UNIX:
420 ret = glfs_set_volfile_server(glfs, "unix",
421 server->value->u.q_unix.path, 0);
422 break;
423 case SOCKET_ADDRESS_TYPE_INET:
424 if (parse_uint_full(server->value->u.inet.port, &port, 10) < 0 ||
425 port > 65535) {
426 error_setg(errp, "'%s' is not a valid port number",
427 server->value->u.inet.port);
428 errno = EINVAL;
429 goto out;
430 }
431 ret = glfs_set_volfile_server(glfs, "tcp",
432 server->value->u.inet.host,
433 (int)port);
434 break;
435 case SOCKET_ADDRESS_TYPE_VSOCK:
436 case SOCKET_ADDRESS_TYPE_FD:
437 default:
438 abort();
439 }
440
441 if (ret < 0) {
442 goto out;
443 }
444 }
445
446 ret = glfs_set_logging(glfs, gconf->logfile, gconf->debug);
447 if (ret < 0) {
448 goto out;
449 }
450
451 ret = glfs_init(glfs);
452 if (ret) {
453 error_setg(errp, "Gluster connection for volume %s, path %s failed"
454 " to connect", gconf->volume, gconf->path);
455 for (server = gconf->server; server; server = server->next) {
456 if (server->value->type == SOCKET_ADDRESS_TYPE_UNIX) {
457 error_append_hint(errp, "hint: failed on socket %s ",
458 server->value->u.q_unix.path);
459 } else {
460 error_append_hint(errp, "hint: failed on host %s and port %s ",
461 server->value->u.inet.host,
462 server->value->u.inet.port);
463 }
464 }
465
466 error_append_hint(errp, "Please refer to gluster logs for more info\n");
467
468
469 if (errno == 0) {
470 errno = EINVAL;
471 }
472
473 goto out;
474 }
475 return glfs;
476
477out:
478 if (glfs) {
479 old_errno = errno;
480 glfs_clear_preopened(glfs);
481 errno = old_errno;
482 }
483 return NULL;
484}
485
486
487
488
489static int qemu_gluster_parse_json(BlockdevOptionsGluster *gconf,
490 QDict *options, Error **errp)
491{
492 QemuOpts *opts;
493 SocketAddress *gsconf = NULL;
494 SocketAddressList *curr = NULL;
495 QDict *backing_options = NULL;
496 Error *local_err = NULL;
497 char *str = NULL;
498 const char *ptr;
499 int i, type, num_servers;
500
501
502 opts = qemu_opts_create(&runtime_json_opts, NULL, 0, &error_abort);
503 qemu_opts_absorb_qdict(opts, options, &local_err);
504 if (local_err) {
505 goto out;
506 }
507
508 num_servers = qdict_array_entries(options, GLUSTER_OPT_SERVER_PATTERN);
509 if (num_servers < 1) {
510 error_setg(&local_err, QERR_MISSING_PARAMETER, "server");
511 goto out;
512 }
513
514 ptr = qemu_opt_get(opts, GLUSTER_OPT_VOLUME);
515 if (!ptr) {
516 error_setg(&local_err, QERR_MISSING_PARAMETER, GLUSTER_OPT_VOLUME);
517 goto out;
518 }
519 gconf->volume = g_strdup(ptr);
520
521 ptr = qemu_opt_get(opts, GLUSTER_OPT_PATH);
522 if (!ptr) {
523 error_setg(&local_err, QERR_MISSING_PARAMETER, GLUSTER_OPT_PATH);
524 goto out;
525 }
526 gconf->path = g_strdup(ptr);
527 qemu_opts_del(opts);
528
529 for (i = 0; i < num_servers; i++) {
530 str = g_strdup_printf(GLUSTER_OPT_SERVER_PATTERN"%d.", i);
531 qdict_extract_subqdict(options, &backing_options, str);
532
533
534 opts = qemu_opts_create(&runtime_type_opts, NULL, 0, &error_abort);
535 qemu_opts_absorb_qdict(opts, backing_options, &local_err);
536 if (local_err) {
537 goto out;
538 }
539
540 ptr = qemu_opt_get(opts, GLUSTER_OPT_TYPE);
541 if (!ptr) {
542 error_setg(&local_err, QERR_MISSING_PARAMETER, GLUSTER_OPT_TYPE);
543 error_append_hint(&local_err, GERR_INDEX_HINT, i);
544 goto out;
545
546 }
547 gsconf = g_new0(SocketAddress, 1);
548 if (!strcmp(ptr, "tcp")) {
549 ptr = "inet";
550 }
551 type = qapi_enum_parse(&SocketAddressType_lookup, ptr, -1, NULL);
552 if (type != SOCKET_ADDRESS_TYPE_INET
553 && type != SOCKET_ADDRESS_TYPE_UNIX) {
554 error_setg(&local_err,
555 "Parameter '%s' may be 'inet' or 'unix'",
556 GLUSTER_OPT_TYPE);
557 error_append_hint(&local_err, GERR_INDEX_HINT, i);
558 goto out;
559 }
560 gsconf->type = type;
561 qemu_opts_del(opts);
562
563 if (gsconf->type == SOCKET_ADDRESS_TYPE_INET) {
564
565 opts = qemu_opts_create(&runtime_inet_opts, NULL, 0, &error_abort);
566 qemu_opts_absorb_qdict(opts, backing_options, &local_err);
567 if (local_err) {
568 goto out;
569 }
570
571 ptr = qemu_opt_get(opts, GLUSTER_OPT_HOST);
572 if (!ptr) {
573 error_setg(&local_err, QERR_MISSING_PARAMETER,
574 GLUSTER_OPT_HOST);
575 error_append_hint(&local_err, GERR_INDEX_HINT, i);
576 goto out;
577 }
578 gsconf->u.inet.host = g_strdup(ptr);
579 ptr = qemu_opt_get(opts, GLUSTER_OPT_PORT);
580 if (!ptr) {
581 error_setg(&local_err, QERR_MISSING_PARAMETER,
582 GLUSTER_OPT_PORT);
583 error_append_hint(&local_err, GERR_INDEX_HINT, i);
584 goto out;
585 }
586 gsconf->u.inet.port = g_strdup(ptr);
587
588
589
590
591 ptr = qemu_opt_get(opts, GLUSTER_OPT_TO);
592 if (ptr) {
593 gsconf->u.inet.has_to = true;
594 }
595 ptr = qemu_opt_get(opts, GLUSTER_OPT_IPV4);
596 if (ptr) {
597 gsconf->u.inet.has_ipv4 = true;
598 }
599 ptr = qemu_opt_get(opts, GLUSTER_OPT_IPV6);
600 if (ptr) {
601 gsconf->u.inet.has_ipv6 = true;
602 }
603 if (gsconf->u.inet.has_to) {
604 error_setg(&local_err, "Parameter 'to' not supported");
605 goto out;
606 }
607 if (gsconf->u.inet.has_ipv4 || gsconf->u.inet.has_ipv6) {
608 error_setg(&local_err, "Parameters 'ipv4/ipv6' not supported");
609 goto out;
610 }
611 qemu_opts_del(opts);
612 } else {
613
614 opts = qemu_opts_create(&runtime_unix_opts, NULL, 0, &error_abort);
615 qemu_opts_absorb_qdict(opts, backing_options, &local_err);
616 if (local_err) {
617 goto out;
618 }
619
620 ptr = qemu_opt_get(opts, GLUSTER_OPT_PATH);
621 if (!ptr) {
622 ptr = qemu_opt_get(opts, GLUSTER_OPT_SOCKET);
623 } else if (qemu_opt_get(opts, GLUSTER_OPT_SOCKET)) {
624 error_setg(&local_err,
625 "Conflicting parameters 'path' and 'socket'");
626 error_append_hint(&local_err, GERR_INDEX_HINT, i);
627 goto out;
628 }
629 if (!ptr) {
630 error_setg(&local_err, QERR_MISSING_PARAMETER,
631 GLUSTER_OPT_PATH);
632 error_append_hint(&local_err, GERR_INDEX_HINT, i);
633 goto out;
634 }
635 gsconf->u.q_unix.path = g_strdup(ptr);
636 qemu_opts_del(opts);
637 }
638
639 if (gconf->server == NULL) {
640 gconf->server = g_new0(SocketAddressList, 1);
641 gconf->server->value = gsconf;
642 curr = gconf->server;
643 } else {
644 curr->next = g_new0(SocketAddressList, 1);
645 curr->next->value = gsconf;
646 curr = curr->next;
647 }
648 gsconf = NULL;
649
650 QDECREF(backing_options);
651 backing_options = NULL;
652 g_free(str);
653 str = NULL;
654 }
655
656 return 0;
657
658out:
659 error_propagate(errp, local_err);
660 qapi_free_SocketAddress(gsconf);
661 qemu_opts_del(opts);
662 g_free(str);
663 QDECREF(backing_options);
664 errno = EINVAL;
665 return -errno;
666}
667
668static struct glfs *qemu_gluster_init(BlockdevOptionsGluster *gconf,
669 const char *filename,
670 QDict *options, Error **errp)
671{
672 int ret;
673 if (filename) {
674 ret = qemu_gluster_parse_uri(gconf, filename);
675 if (ret < 0) {
676 error_setg(errp, "invalid URI");
677 error_append_hint(errp, "Usage: file=gluster[+transport]://"
678 "[host[:port]]volume/path[?socket=...]"
679 "[,file.debug=N]"
680 "[,file.logfile=/path/filename.log]\n");
681 errno = -ret;
682 return NULL;
683 }
684 } else {
685 ret = qemu_gluster_parse_json(gconf, options, errp);
686 if (ret < 0) {
687 error_append_hint(errp, "Usage: "
688 "-drive driver=qcow2,file.driver=gluster,"
689 "file.volume=testvol,file.path=/path/a.qcow2"
690 "[,file.debug=9]"
691 "[,file.logfile=/path/filename.log],"
692 "file.server.0.type=inet,"
693 "file.server.0.host=1.2.3.4,"
694 "file.server.0.port=24007,"
695 "file.server.1.transport=unix,"
696 "file.server.1.path=/var/run/glusterd.socket ..."
697 "\n");
698 errno = -ret;
699 return NULL;
700 }
701
702 }
703
704 return qemu_gluster_glfs_init(gconf, errp);
705}
706
707
708
709
710static void gluster_finish_aiocb(struct glfs_fd *fd, ssize_t ret, void *arg)
711{
712 GlusterAIOCB *acb = (GlusterAIOCB *)arg;
713
714 if (!ret || ret == acb->size) {
715 acb->ret = 0;
716 } else if (ret < 0) {
717 acb->ret = -errno;
718 } else {
719 acb->ret = -EIO;
720 }
721
722 aio_co_schedule(acb->aio_context, acb->coroutine);
723}
724
725static void qemu_gluster_parse_flags(int bdrv_flags, int *open_flags)
726{
727 assert(open_flags != NULL);
728
729 *open_flags |= O_BINARY;
730
731 if (bdrv_flags & BDRV_O_RDWR) {
732 *open_flags |= O_RDWR;
733 } else {
734 *open_flags |= O_RDONLY;
735 }
736
737 if ((bdrv_flags & BDRV_O_NOCACHE)) {
738 *open_flags |= O_DIRECT;
739 }
740}
741
742
743
744
745
746
747
748
749static bool qemu_gluster_test_seek(struct glfs_fd *fd)
750{
751 off_t ret = 0;
752
753#if defined SEEK_HOLE && defined SEEK_DATA
754 off_t eof;
755
756 eof = glfs_lseek(fd, 0, SEEK_END);
757 if (eof < 0) {
758
759 return false;
760 }
761
762
763 ret = glfs_lseek(fd, eof, SEEK_DATA);
764#endif
765
766 return (ret < 0) && (errno == ENXIO);
767}
768
769static int qemu_gluster_open(BlockDriverState *bs, QDict *options,
770 int bdrv_flags, Error **errp)
771{
772 BDRVGlusterState *s = bs->opaque;
773 int open_flags = 0;
774 int ret = 0;
775 BlockdevOptionsGluster *gconf = NULL;
776 QemuOpts *opts;
777 Error *local_err = NULL;
778 const char *filename, *logfile;
779
780 opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort);
781 qemu_opts_absorb_qdict(opts, options, &local_err);
782 if (local_err) {
783 error_propagate(errp, local_err);
784 ret = -EINVAL;
785 goto out;
786 }
787
788 filename = qemu_opt_get(opts, GLUSTER_OPT_FILENAME);
789
790 s->debug = qemu_opt_get_number(opts, GLUSTER_OPT_DEBUG,
791 GLUSTER_DEBUG_DEFAULT);
792 if (s->debug < 0) {
793 s->debug = 0;
794 } else if (s->debug > GLUSTER_DEBUG_MAX) {
795 s->debug = GLUSTER_DEBUG_MAX;
796 }
797
798 gconf = g_new0(BlockdevOptionsGluster, 1);
799 gconf->debug = s->debug;
800 gconf->has_debug = true;
801
802 logfile = qemu_opt_get(opts, GLUSTER_OPT_LOGFILE);
803 s->logfile = g_strdup(logfile ? logfile : GLUSTER_LOGFILE_DEFAULT);
804
805 gconf->logfile = g_strdup(s->logfile);
806 gconf->has_logfile = true;
807
808 s->glfs = qemu_gluster_init(gconf, filename, options, errp);
809 if (!s->glfs) {
810 ret = -errno;
811 goto out;
812 }
813
814#ifdef CONFIG_GLUSTERFS_XLATOR_OPT
815
816
817
818
819
820
821 ret = glfs_set_xlator_option(s->glfs, "*-write-behind",
822 "resync-failed-syncs-after-fsync",
823 "on");
824 if (ret < 0) {
825 error_setg_errno(errp, errno, "Unable to set xlator key/value pair");
826 ret = -errno;
827 goto out;
828 }
829#endif
830
831 qemu_gluster_parse_flags(bdrv_flags, &open_flags);
832
833 s->fd = glfs_open(s->glfs, gconf->path, open_flags);
834 if (!s->fd) {
835 ret = -errno;
836 }
837
838 s->supports_seek_data = qemu_gluster_test_seek(s->fd);
839
840out:
841 qemu_opts_del(opts);
842 qapi_free_BlockdevOptionsGluster(gconf);
843 if (!ret) {
844 return ret;
845 }
846 g_free(s->logfile);
847 if (s->fd) {
848 glfs_close(s->fd);
849 }
850
851 glfs_clear_preopened(s->glfs);
852
853 return ret;
854}
855
856static int qemu_gluster_reopen_prepare(BDRVReopenState *state,
857 BlockReopenQueue *queue, Error **errp)
858{
859 int ret = 0;
860 BDRVGlusterState *s;
861 BDRVGlusterReopenState *reop_s;
862 BlockdevOptionsGluster *gconf;
863 int open_flags = 0;
864
865 assert(state != NULL);
866 assert(state->bs != NULL);
867
868 s = state->bs->opaque;
869
870 state->opaque = g_new0(BDRVGlusterReopenState, 1);
871 reop_s = state->opaque;
872
873 qemu_gluster_parse_flags(state->flags, &open_flags);
874
875 gconf = g_new0(BlockdevOptionsGluster, 1);
876 gconf->debug = s->debug;
877 gconf->has_debug = true;
878 gconf->logfile = g_strdup(s->logfile);
879 gconf->has_logfile = true;
880 reop_s->glfs = qemu_gluster_init(gconf, state->bs->filename, NULL, errp);
881 if (reop_s->glfs == NULL) {
882 ret = -errno;
883 goto exit;
884 }
885
886#ifdef CONFIG_GLUSTERFS_XLATOR_OPT
887 ret = glfs_set_xlator_option(reop_s->glfs, "*-write-behind",
888 "resync-failed-syncs-after-fsync", "on");
889 if (ret < 0) {
890 error_setg_errno(errp, errno, "Unable to set xlator key/value pair");
891 ret = -errno;
892 goto exit;
893 }
894#endif
895
896 reop_s->fd = glfs_open(reop_s->glfs, gconf->path, open_flags);
897 if (reop_s->fd == NULL) {
898
899 ret = -errno;
900 goto exit;
901 }
902
903exit:
904
905 qapi_free_BlockdevOptionsGluster(gconf);
906 return ret;
907}
908
909static void qemu_gluster_reopen_commit(BDRVReopenState *state)
910{
911 BDRVGlusterReopenState *reop_s = state->opaque;
912 BDRVGlusterState *s = state->bs->opaque;
913
914
915
916 if (s->fd) {
917 glfs_close(s->fd);
918 }
919
920 glfs_clear_preopened(s->glfs);
921
922
923 s->fd = reop_s->fd;
924 s->glfs = reop_s->glfs;
925
926 g_free(state->opaque);
927 state->opaque = NULL;
928
929 return;
930}
931
932
933static void qemu_gluster_reopen_abort(BDRVReopenState *state)
934{
935 BDRVGlusterReopenState *reop_s = state->opaque;
936
937 if (reop_s == NULL) {
938 return;
939 }
940
941 if (reop_s->fd) {
942 glfs_close(reop_s->fd);
943 }
944
945 glfs_clear_preopened(reop_s->glfs);
946
947 g_free(state->opaque);
948 state->opaque = NULL;
949
950 return;
951}
952
953#ifdef CONFIG_GLUSTERFS_ZEROFILL
954static coroutine_fn int qemu_gluster_co_pwrite_zeroes(BlockDriverState *bs,
955 int64_t offset,
956 int size,
957 BdrvRequestFlags flags)
958{
959 int ret;
960 GlusterAIOCB acb;
961 BDRVGlusterState *s = bs->opaque;
962
963 acb.size = size;
964 acb.ret = 0;
965 acb.coroutine = qemu_coroutine_self();
966 acb.aio_context = bdrv_get_aio_context(bs);
967
968 ret = glfs_zerofill_async(s->fd, offset, size, gluster_finish_aiocb, &acb);
969 if (ret < 0) {
970 return -errno;
971 }
972
973 qemu_coroutine_yield();
974 return acb.ret;
975}
976#endif
977
978static int qemu_gluster_create(const char *filename,
979 QemuOpts *opts, Error **errp)
980{
981 BlockdevOptionsGluster *gconf;
982 struct glfs *glfs;
983 struct glfs_fd *fd;
984 int ret = 0;
985 PreallocMode prealloc;
986 int64_t total_size = 0;
987 char *tmp = NULL;
988 Error *local_err = NULL;
989
990 gconf = g_new0(BlockdevOptionsGluster, 1);
991 gconf->debug = qemu_opt_get_number_del(opts, GLUSTER_OPT_DEBUG,
992 GLUSTER_DEBUG_DEFAULT);
993 if (gconf->debug < 0) {
994 gconf->debug = 0;
995 } else if (gconf->debug > GLUSTER_DEBUG_MAX) {
996 gconf->debug = GLUSTER_DEBUG_MAX;
997 }
998 gconf->has_debug = true;
999
1000 gconf->logfile = qemu_opt_get_del(opts, GLUSTER_OPT_LOGFILE);
1001 if (!gconf->logfile) {
1002 gconf->logfile = g_strdup(GLUSTER_LOGFILE_DEFAULT);
1003 }
1004 gconf->has_logfile = true;
1005
1006 glfs = qemu_gluster_init(gconf, filename, NULL, errp);
1007 if (!glfs) {
1008 ret = -errno;
1009 goto out;
1010 }
1011
1012 total_size = ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0),
1013 BDRV_SECTOR_SIZE);
1014
1015 tmp = qemu_opt_get_del(opts, BLOCK_OPT_PREALLOC);
1016 prealloc = qapi_enum_parse(&PreallocMode_lookup, tmp, PREALLOC_MODE_OFF,
1017 &local_err);
1018 g_free(tmp);
1019 if (local_err) {
1020 error_propagate(errp, local_err);
1021 ret = -EINVAL;
1022 goto out;
1023 }
1024
1025 fd = glfs_creat(glfs, gconf->path,
1026 O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, S_IRUSR | S_IWUSR);
1027 if (!fd) {
1028 ret = -errno;
1029 goto out;
1030 }
1031
1032 switch (prealloc) {
1033#ifdef CONFIG_GLUSTERFS_FALLOCATE
1034 case PREALLOC_MODE_FALLOC:
1035 if (glfs_fallocate(fd, 0, 0, total_size)) {
1036 error_setg(errp, "Could not preallocate data for the new file");
1037 ret = -errno;
1038 }
1039 break;
1040#endif
1041#ifdef CONFIG_GLUSTERFS_ZEROFILL
1042 case PREALLOC_MODE_FULL:
1043 if (!glfs_ftruncate(fd, total_size)) {
1044 if (glfs_zerofill(fd, 0, total_size)) {
1045 error_setg(errp, "Could not zerofill the new file");
1046 ret = -errno;
1047 }
1048 } else {
1049 error_setg(errp, "Could not resize file");
1050 ret = -errno;
1051 }
1052 break;
1053#endif
1054 case PREALLOC_MODE_OFF:
1055 if (glfs_ftruncate(fd, total_size) != 0) {
1056 ret = -errno;
1057 error_setg(errp, "Could not resize file");
1058 }
1059 break;
1060 default:
1061 ret = -EINVAL;
1062 error_setg(errp, "Unsupported preallocation mode: %s",
1063 PreallocMode_str(prealloc));
1064 break;
1065 }
1066
1067 if (glfs_close(fd) != 0) {
1068 ret = -errno;
1069 }
1070out:
1071 qapi_free_BlockdevOptionsGluster(gconf);
1072 glfs_clear_preopened(glfs);
1073 return ret;
1074}
1075
1076static coroutine_fn int qemu_gluster_co_rw(BlockDriverState *bs,
1077 int64_t sector_num, int nb_sectors,
1078 QEMUIOVector *qiov, int write)
1079{
1080 int ret;
1081 GlusterAIOCB acb;
1082 BDRVGlusterState *s = bs->opaque;
1083 size_t size = nb_sectors * BDRV_SECTOR_SIZE;
1084 off_t offset = sector_num * BDRV_SECTOR_SIZE;
1085
1086 acb.size = size;
1087 acb.ret = 0;
1088 acb.coroutine = qemu_coroutine_self();
1089 acb.aio_context = bdrv_get_aio_context(bs);
1090
1091 if (write) {
1092 ret = glfs_pwritev_async(s->fd, qiov->iov, qiov->niov, offset, 0,
1093 gluster_finish_aiocb, &acb);
1094 } else {
1095 ret = glfs_preadv_async(s->fd, qiov->iov, qiov->niov, offset, 0,
1096 gluster_finish_aiocb, &acb);
1097 }
1098
1099 if (ret < 0) {
1100 return -errno;
1101 }
1102
1103 qemu_coroutine_yield();
1104 return acb.ret;
1105}
1106
1107static int qemu_gluster_truncate(BlockDriverState *bs, int64_t offset,
1108 PreallocMode prealloc, Error **errp)
1109{
1110 int ret;
1111 BDRVGlusterState *s = bs->opaque;
1112
1113 if (prealloc != PREALLOC_MODE_OFF) {
1114 error_setg(errp, "Unsupported preallocation mode '%s'",
1115 PreallocMode_str(prealloc));
1116 return -ENOTSUP;
1117 }
1118
1119 ret = glfs_ftruncate(s->fd, offset);
1120 if (ret < 0) {
1121 ret = -errno;
1122 error_setg_errno(errp, -ret, "Failed to truncate file");
1123 return ret;
1124 }
1125
1126 return 0;
1127}
1128
1129static coroutine_fn int qemu_gluster_co_readv(BlockDriverState *bs,
1130 int64_t sector_num,
1131 int nb_sectors,
1132 QEMUIOVector *qiov)
1133{
1134 return qemu_gluster_co_rw(bs, sector_num, nb_sectors, qiov, 0);
1135}
1136
1137static coroutine_fn int qemu_gluster_co_writev(BlockDriverState *bs,
1138 int64_t sector_num,
1139 int nb_sectors,
1140 QEMUIOVector *qiov)
1141{
1142 return qemu_gluster_co_rw(bs, sector_num, nb_sectors, qiov, 1);
1143}
1144
1145static void qemu_gluster_close(BlockDriverState *bs)
1146{
1147 BDRVGlusterState *s = bs->opaque;
1148
1149 g_free(s->logfile);
1150 if (s->fd) {
1151 glfs_close(s->fd);
1152 s->fd = NULL;
1153 }
1154 glfs_clear_preopened(s->glfs);
1155}
1156
1157static coroutine_fn int qemu_gluster_co_flush_to_disk(BlockDriverState *bs)
1158{
1159 int ret;
1160 GlusterAIOCB acb;
1161 BDRVGlusterState *s = bs->opaque;
1162
1163 acb.size = 0;
1164 acb.ret = 0;
1165 acb.coroutine = qemu_coroutine_self();
1166 acb.aio_context = bdrv_get_aio_context(bs);
1167
1168 ret = glfs_fsync_async(s->fd, gluster_finish_aiocb, &acb);
1169 if (ret < 0) {
1170 ret = -errno;
1171 goto error;
1172 }
1173
1174 qemu_coroutine_yield();
1175 if (acb.ret < 0) {
1176 ret = acb.ret;
1177 goto error;
1178 }
1179
1180 return acb.ret;
1181
1182error:
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196 qemu_gluster_close(bs);
1197 bs->drv = NULL;
1198 return ret;
1199}
1200
1201#ifdef CONFIG_GLUSTERFS_DISCARD
1202static coroutine_fn int qemu_gluster_co_pdiscard(BlockDriverState *bs,
1203 int64_t offset, int size)
1204{
1205 int ret;
1206 GlusterAIOCB acb;
1207 BDRVGlusterState *s = bs->opaque;
1208
1209 acb.size = 0;
1210 acb.ret = 0;
1211 acb.coroutine = qemu_coroutine_self();
1212 acb.aio_context = bdrv_get_aio_context(bs);
1213
1214 ret = glfs_discard_async(s->fd, offset, size, gluster_finish_aiocb, &acb);
1215 if (ret < 0) {
1216 return -errno;
1217 }
1218
1219 qemu_coroutine_yield();
1220 return acb.ret;
1221}
1222#endif
1223
1224static int64_t qemu_gluster_getlength(BlockDriverState *bs)
1225{
1226 BDRVGlusterState *s = bs->opaque;
1227 int64_t ret;
1228
1229 ret = glfs_lseek(s->fd, 0, SEEK_END);
1230 if (ret < 0) {
1231 return -errno;
1232 } else {
1233 return ret;
1234 }
1235}
1236
1237static int64_t qemu_gluster_allocated_file_size(BlockDriverState *bs)
1238{
1239 BDRVGlusterState *s = bs->opaque;
1240 struct stat st;
1241 int ret;
1242
1243 ret = glfs_fstat(s->fd, &st);
1244 if (ret < 0) {
1245 return -errno;
1246 } else {
1247 return st.st_blocks * 512;
1248 }
1249}
1250
1251static int qemu_gluster_has_zero_init(BlockDriverState *bs)
1252{
1253
1254 return 0;
1255}
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269static int find_allocation(BlockDriverState *bs, off_t start,
1270 off_t *data, off_t *hole)
1271{
1272 BDRVGlusterState *s = bs->opaque;
1273
1274 if (!s->supports_seek_data) {
1275 goto exit;
1276 }
1277
1278#if defined SEEK_HOLE && defined SEEK_DATA
1279 off_t offs;
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292 offs = glfs_lseek(s->fd, start, SEEK_DATA);
1293 if (offs < 0) {
1294 return -errno;
1295 }
1296
1297 if (offs < start) {
1298
1299
1300
1301
1302 return -EIO;
1303 }
1304
1305 if (offs > start) {
1306
1307 *hole = start;
1308 *data = offs;
1309 return 0;
1310 }
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331 offs = glfs_lseek(s->fd, start, SEEK_HOLE);
1332 if (offs < 0) {
1333 return -errno;
1334 }
1335
1336 if (offs < start) {
1337
1338
1339
1340
1341 return -EIO;
1342 }
1343
1344 if (offs > start) {
1345
1346
1347
1348
1349
1350
1351 *data = start;
1352 *hole = offs;
1353 return 0;
1354 }
1355
1356
1357 return -EBUSY;
1358#endif
1359
1360exit:
1361 return -ENOTSUP;
1362}
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379static int64_t coroutine_fn qemu_gluster_co_get_block_status(
1380 BlockDriverState *bs, int64_t sector_num, int nb_sectors, int *pnum,
1381 BlockDriverState **file)
1382{
1383 BDRVGlusterState *s = bs->opaque;
1384 off_t start, data = 0, hole = 0;
1385 int64_t total_size;
1386 int ret = -EINVAL;
1387
1388 if (!s->fd) {
1389 return ret;
1390 }
1391
1392 start = sector_num * BDRV_SECTOR_SIZE;
1393 total_size = bdrv_getlength(bs);
1394 if (total_size < 0) {
1395 return total_size;
1396 } else if (start >= total_size) {
1397 *pnum = 0;
1398 return 0;
1399 } else if (start + nb_sectors * BDRV_SECTOR_SIZE > total_size) {
1400 nb_sectors = DIV_ROUND_UP(total_size - start, BDRV_SECTOR_SIZE);
1401 }
1402
1403 ret = find_allocation(bs, start, &data, &hole);
1404 if (ret == -ENXIO) {
1405
1406 *pnum = nb_sectors;
1407 ret = BDRV_BLOCK_ZERO;
1408 } else if (ret < 0) {
1409
1410 *pnum = nb_sectors;
1411 ret = BDRV_BLOCK_DATA;
1412 } else if (data == start) {
1413
1414
1415 *pnum = MIN(nb_sectors, DIV_ROUND_UP(hole - start, BDRV_SECTOR_SIZE));
1416 ret = BDRV_BLOCK_DATA;
1417 } else {
1418
1419 assert(hole == start);
1420 *pnum = MIN(nb_sectors, (data - start) / BDRV_SECTOR_SIZE);
1421 ret = BDRV_BLOCK_ZERO;
1422 }
1423
1424 *file = bs;
1425
1426 return ret | BDRV_BLOCK_OFFSET_VALID | start;
1427}
1428
1429
1430static BlockDriver bdrv_gluster = {
1431 .format_name = "gluster",
1432 .protocol_name = "gluster",
1433 .instance_size = sizeof(BDRVGlusterState),
1434 .bdrv_needs_filename = false,
1435 .bdrv_file_open = qemu_gluster_open,
1436 .bdrv_reopen_prepare = qemu_gluster_reopen_prepare,
1437 .bdrv_reopen_commit = qemu_gluster_reopen_commit,
1438 .bdrv_reopen_abort = qemu_gluster_reopen_abort,
1439 .bdrv_close = qemu_gluster_close,
1440 .bdrv_create = qemu_gluster_create,
1441 .bdrv_getlength = qemu_gluster_getlength,
1442 .bdrv_get_allocated_file_size = qemu_gluster_allocated_file_size,
1443 .bdrv_truncate = qemu_gluster_truncate,
1444 .bdrv_co_readv = qemu_gluster_co_readv,
1445 .bdrv_co_writev = qemu_gluster_co_writev,
1446 .bdrv_co_flush_to_disk = qemu_gluster_co_flush_to_disk,
1447 .bdrv_has_zero_init = qemu_gluster_has_zero_init,
1448#ifdef CONFIG_GLUSTERFS_DISCARD
1449 .bdrv_co_pdiscard = qemu_gluster_co_pdiscard,
1450#endif
1451#ifdef CONFIG_GLUSTERFS_ZEROFILL
1452 .bdrv_co_pwrite_zeroes = qemu_gluster_co_pwrite_zeroes,
1453#endif
1454 .bdrv_co_get_block_status = qemu_gluster_co_get_block_status,
1455 .create_opts = &qemu_gluster_create_opts,
1456};
1457
1458static BlockDriver bdrv_gluster_tcp = {
1459 .format_name = "gluster",
1460 .protocol_name = "gluster+tcp",
1461 .instance_size = sizeof(BDRVGlusterState),
1462 .bdrv_needs_filename = false,
1463 .bdrv_file_open = qemu_gluster_open,
1464 .bdrv_reopen_prepare = qemu_gluster_reopen_prepare,
1465 .bdrv_reopen_commit = qemu_gluster_reopen_commit,
1466 .bdrv_reopen_abort = qemu_gluster_reopen_abort,
1467 .bdrv_close = qemu_gluster_close,
1468 .bdrv_create = qemu_gluster_create,
1469 .bdrv_getlength = qemu_gluster_getlength,
1470 .bdrv_get_allocated_file_size = qemu_gluster_allocated_file_size,
1471 .bdrv_truncate = qemu_gluster_truncate,
1472 .bdrv_co_readv = qemu_gluster_co_readv,
1473 .bdrv_co_writev = qemu_gluster_co_writev,
1474 .bdrv_co_flush_to_disk = qemu_gluster_co_flush_to_disk,
1475 .bdrv_has_zero_init = qemu_gluster_has_zero_init,
1476#ifdef CONFIG_GLUSTERFS_DISCARD
1477 .bdrv_co_pdiscard = qemu_gluster_co_pdiscard,
1478#endif
1479#ifdef CONFIG_GLUSTERFS_ZEROFILL
1480 .bdrv_co_pwrite_zeroes = qemu_gluster_co_pwrite_zeroes,
1481#endif
1482 .bdrv_co_get_block_status = qemu_gluster_co_get_block_status,
1483 .create_opts = &qemu_gluster_create_opts,
1484};
1485
1486static BlockDriver bdrv_gluster_unix = {
1487 .format_name = "gluster",
1488 .protocol_name = "gluster+unix",
1489 .instance_size = sizeof(BDRVGlusterState),
1490 .bdrv_needs_filename = true,
1491 .bdrv_file_open = qemu_gluster_open,
1492 .bdrv_reopen_prepare = qemu_gluster_reopen_prepare,
1493 .bdrv_reopen_commit = qemu_gluster_reopen_commit,
1494 .bdrv_reopen_abort = qemu_gluster_reopen_abort,
1495 .bdrv_close = qemu_gluster_close,
1496 .bdrv_create = qemu_gluster_create,
1497 .bdrv_getlength = qemu_gluster_getlength,
1498 .bdrv_get_allocated_file_size = qemu_gluster_allocated_file_size,
1499 .bdrv_truncate = qemu_gluster_truncate,
1500 .bdrv_co_readv = qemu_gluster_co_readv,
1501 .bdrv_co_writev = qemu_gluster_co_writev,
1502 .bdrv_co_flush_to_disk = qemu_gluster_co_flush_to_disk,
1503 .bdrv_has_zero_init = qemu_gluster_has_zero_init,
1504#ifdef CONFIG_GLUSTERFS_DISCARD
1505 .bdrv_co_pdiscard = qemu_gluster_co_pdiscard,
1506#endif
1507#ifdef CONFIG_GLUSTERFS_ZEROFILL
1508 .bdrv_co_pwrite_zeroes = qemu_gluster_co_pwrite_zeroes,
1509#endif
1510 .bdrv_co_get_block_status = qemu_gluster_co_get_block_status,
1511 .create_opts = &qemu_gluster_create_opts,
1512};
1513
1514
1515
1516
1517
1518
1519
1520static BlockDriver bdrv_gluster_rdma = {
1521 .format_name = "gluster",
1522 .protocol_name = "gluster+rdma",
1523 .instance_size = sizeof(BDRVGlusterState),
1524 .bdrv_needs_filename = true,
1525 .bdrv_file_open = qemu_gluster_open,
1526 .bdrv_reopen_prepare = qemu_gluster_reopen_prepare,
1527 .bdrv_reopen_commit = qemu_gluster_reopen_commit,
1528 .bdrv_reopen_abort = qemu_gluster_reopen_abort,
1529 .bdrv_close = qemu_gluster_close,
1530 .bdrv_create = qemu_gluster_create,
1531 .bdrv_getlength = qemu_gluster_getlength,
1532 .bdrv_get_allocated_file_size = qemu_gluster_allocated_file_size,
1533 .bdrv_truncate = qemu_gluster_truncate,
1534 .bdrv_co_readv = qemu_gluster_co_readv,
1535 .bdrv_co_writev = qemu_gluster_co_writev,
1536 .bdrv_co_flush_to_disk = qemu_gluster_co_flush_to_disk,
1537 .bdrv_has_zero_init = qemu_gluster_has_zero_init,
1538#ifdef CONFIG_GLUSTERFS_DISCARD
1539 .bdrv_co_pdiscard = qemu_gluster_co_pdiscard,
1540#endif
1541#ifdef CONFIG_GLUSTERFS_ZEROFILL
1542 .bdrv_co_pwrite_zeroes = qemu_gluster_co_pwrite_zeroes,
1543#endif
1544 .bdrv_co_get_block_status = qemu_gluster_co_get_block_status,
1545 .create_opts = &qemu_gluster_create_opts,
1546};
1547
1548static void bdrv_gluster_init(void)
1549{
1550 bdrv_register(&bdrv_gluster_rdma);
1551 bdrv_register(&bdrv_gluster_unix);
1552 bdrv_register(&bdrv_gluster_tcp);
1553 bdrv_register(&bdrv_gluster);
1554}
1555
1556block_init(bdrv_gluster_init);
1557