* [Qemu-devel] [RFC v7 0/5] GlusterFS support in QEMU - v7
@ 2012-09-17 15:21 Bharata B Rao
2012-09-17 15:22 ` [Qemu-devel] [PATCH v7 1/5] sockets: Make inet_parse() non static Bharata B Rao
` (4 more replies)
0 siblings, 5 replies; 24+ messages in thread
From: Bharata B Rao @ 2012-09-17 15:21 UTC (permalink / raw)
To: qemu-devel
Cc: Kevin Wolf, Anthony Liguori, Anand Avati, Vijay Bellur,
Stefan Hajnoczi, Amar Tumballi, Markus Armbruster, Blue Swirl,
Avi Kivity, Paolo Bonzini
Hi,
This is the v7 of the patchset to support GlusterFS backend from QEMU.
Changes in v7
-------------
- Added a few more patches to the patchset that are needed by gluster block
driver: inet_parse and qemu_aio_count fixes.
- URI specification of gluster drive on QEMU cmdline has been changed.
Updated parser routines and registering multiple block driver for
each format now.
- Fixed a few bugs: qcow2 file creation on gluster backend, AIO completion
handling, aio_cancel etc
- Addressed review comments from v6.
- Using inet_parse() to parse inet addresses instead of a separate
parser in gluster driver.
- Support for .bdrv_get_allocated_file_size.
- Graceful recovery when gluster thread fails to notify QEMU thread on
IO completion.
Previous versions can be found here:
v6 - http://lists.nongnu.org/archive/html/qemu-devel/2012-08/msg01536.html
v5 - http://lists.nongnu.org/archive/html/qemu-devel/2012-08/msg01023.html
v4 - http://lists.nongnu.org/archive/html/qemu-devel/2012-08/msg00147.html
v3 - http://lists.nongnu.org/archive/html/qemu-devel/2012-07/msg03322.html
v2 - http://lists.nongnu.org/archive/html/qemu-devel/2012-07/msg02718.html
v1 - http://lists.nongnu.org/archive/html/qemu-devel/2012-06/msg01745.html
Regards,
Bharata.
^ permalink raw reply [flat|nested] 24+ messages in thread
* [Qemu-devel] [PATCH v7 1/5] sockets: Make inet_parse() non static.
2012-09-17 15:21 [Qemu-devel] [RFC v7 0/5] GlusterFS support in QEMU - v7 Bharata B Rao
@ 2012-09-17 15:22 ` Bharata B Rao
2012-09-18 12:47 ` Kevin Wolf
2012-09-17 15:23 ` [Qemu-devel] [PATCH v7 2/5] sockets: Change inet_parse() to accept address specification without port Bharata B Rao
` (3 subsequent siblings)
4 siblings, 1 reply; 24+ messages in thread
From: Bharata B Rao @ 2012-09-17 15:22 UTC (permalink / raw)
To: qemu-devel
Cc: Kevin Wolf, Anthony Liguori, Anand Avati, Vijay Bellur,
Stefan Hajnoczi, Amar Tumballi, Markus Armbruster, Blue Swirl,
Avi Kivity, Paolo Bonzini
sockets: Make inet_parse() non static.
From: Bharata B Rao <bharata@linux.vnet.ibm.com>
Make inet_parse() non-static so that other subsystems like gluster
can use it to parse inet addresses. As a pre-requisite, define and
globalize the qemu_inet_opts.
Signed-off-by: Bharata B Rao <bharata@linux.vnet.ibm.com>
---
Makefile.objs | 2 +-
qemu-config.c | 31 +++++++++++++++++++++++++++++++
qemu-sockets.c | 42 ++++++------------------------------------
qemu_socket.h | 1 +
4 files changed, 39 insertions(+), 37 deletions(-)
diff --git a/Makefile.objs b/Makefile.objs
index 4412757..447371f 100644
--- a/Makefile.objs
+++ b/Makefile.objs
@@ -230,7 +230,7 @@ universal-obj-y += $(qapi-obj-y)
qga-obj-y = qga/ qemu-ga.o module.o
qga-obj-$(CONFIG_WIN32) += oslib-win32.o
-qga-obj-$(CONFIG_POSIX) += oslib-posix.o qemu-sockets.o qemu-option.o
+qga-obj-$(CONFIG_POSIX) += oslib-posix.o qemu-config.o qemu-sockets.o qemu-option.o
vl.o: QEMU_CFLAGS+=$(GPROF_CFLAGS)
diff --git a/qemu-config.c b/qemu-config.c
index eba977e..d3d36b6 100644
--- a/qemu-config.c
+++ b/qemu-config.c
@@ -646,6 +646,36 @@ QemuOptsList qemu_boot_opts = {
},
};
+QemuOptsList qemu_inet_opts = {
+ .name = "inet",
+ .head = QTAILQ_HEAD_INITIALIZER(qemu_inet_opts.head),
+ .desc = {
+ {
+ .name = "path",
+ .type = QEMU_OPT_STRING,
+ },{
+ .name = "host",
+ .type = QEMU_OPT_STRING,
+ },{
+ .name = "port",
+ .type = QEMU_OPT_STRING,
+ },{
+ .name = "to",
+ .type = QEMU_OPT_NUMBER,
+ },{
+ .name = "ipv4",
+ .type = QEMU_OPT_BOOL,
+ },{
+ .name = "ipv6",
+ .type = QEMU_OPT_BOOL,
+ },{
+ .name = "block",
+ .type = QEMU_OPT_BOOL,
+ },
+ { /* end if list */ }
+ },
+};
+
static QemuOptsList *vm_config_groups[32] = {
&qemu_drive_opts,
&qemu_chardev_opts,
@@ -662,6 +692,7 @@ static QemuOptsList *vm_config_groups[32] = {
&qemu_boot_opts,
&qemu_iscsi_opts,
&qemu_sandbox_opts,
+ &qemu_inet_opts,
NULL,
};
diff --git a/qemu-sockets.c b/qemu-sockets.c
index 361d890..c330fc5 100644
--- a/qemu-sockets.c
+++ b/qemu-sockets.c
@@ -24,6 +24,7 @@
#include "qemu_socket.h"
#include "qemu-common.h" /* for qemu_isdigit */
+#include "qemu-config.h"
#ifndef AI_ADDRCONFIG
# define AI_ADDRCONFIG 0
@@ -31,37 +32,6 @@
static const int on=1, off=0;
-/* used temporarely until all users are converted to QemuOpts */
-static QemuOptsList dummy_opts = {
- .name = "dummy",
- .head = QTAILQ_HEAD_INITIALIZER(dummy_opts.head),
- .desc = {
- {
- .name = "path",
- .type = QEMU_OPT_STRING,
- },{
- .name = "host",
- .type = QEMU_OPT_STRING,
- },{
- .name = "port",
- .type = QEMU_OPT_STRING,
- },{
- .name = "to",
- .type = QEMU_OPT_NUMBER,
- },{
- .name = "ipv4",
- .type = QEMU_OPT_BOOL,
- },{
- .name = "ipv6",
- .type = QEMU_OPT_BOOL,
- },{
- .name = "block",
- .type = QEMU_OPT_BOOL,
- },
- { /* end if list */ }
- },
-};
-
static int inet_getport(struct addrinfo *e)
{
struct sockaddr_in *i4;
@@ -407,7 +377,7 @@ err:
}
/* compatibility wrapper */
-static int inet_parse(QemuOpts *opts, const char *str)
+int inet_parse(QemuOpts *opts, const char *str)
{
const char *optstr, *h;
char addr[64];
@@ -469,7 +439,7 @@ int inet_listen(const char *str, char *ostr, int olen,
char *optstr;
int sock = -1;
- opts = qemu_opts_create(&dummy_opts, NULL, 0, NULL);
+ opts = qemu_opts_create(qemu_find_opts("inet"), NULL, 0, NULL);
if (inet_parse(opts, str) == 0) {
sock = inet_listen_opts(opts, port_offset, errp);
if (sock != -1 && ostr) {
@@ -498,7 +468,7 @@ int inet_connect(const char *str, bool block, bool *in_progress, Error **errp)
QemuOpts *opts;
int sock = -1;
- opts = qemu_opts_create(&dummy_opts, NULL, 0, NULL);
+ opts = qemu_opts_create(qemu_find_opts("inet"), NULL, 0, NULL);
if (inet_parse(opts, str) == 0) {
if (block) {
qemu_opt_set(opts, "block", "on");
@@ -597,7 +567,7 @@ int unix_listen(const char *str, char *ostr, int olen)
char *path, *optstr;
int sock, len;
- opts = qemu_opts_create(&dummy_opts, NULL, 0, NULL);
+ opts = qemu_opts_create(qemu_find_opts("inet"), NULL, 0, NULL);
optstr = strchr(str, ',');
if (optstr) {
@@ -625,7 +595,7 @@ int unix_connect(const char *path)
QemuOpts *opts;
int sock;
- opts = qemu_opts_create(&dummy_opts, NULL, 0, NULL);
+ opts = qemu_opts_create(qemu_find_opts("inet"), NULL, 0, NULL);
qemu_opt_set(opts, "path", path);
sock = unix_connect_opts(opts);
qemu_opts_del(opts);
diff --git a/qemu_socket.h b/qemu_socket.h
index 30ae6af..bd4d281 100644
--- a/qemu_socket.h
+++ b/qemu_socket.h
@@ -46,6 +46,7 @@ int inet_connect_opts(QemuOpts *opts, bool *in_progress, Error **errp);
int inet_connect(const char *str, bool block, bool *in_progress, Error **errp);
int inet_dgram_opts(QemuOpts *opts);
const char *inet_strfamily(int family);
+int inet_parse(QemuOpts *opts, const char *str);
int unix_listen_opts(QemuOpts *opts);
int unix_listen(const char *path, char *ostr, int olen);
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [Qemu-devel] [PATCH v7 2/5] sockets: Change inet_parse() to accept address specification without port
2012-09-17 15:21 [Qemu-devel] [RFC v7 0/5] GlusterFS support in QEMU - v7 Bharata B Rao
2012-09-17 15:22 ` [Qemu-devel] [PATCH v7 1/5] sockets: Make inet_parse() non static Bharata B Rao
@ 2012-09-17 15:23 ` Bharata B Rao
2012-09-18 13:22 ` Kevin Wolf
2012-09-17 15:24 ` [Qemu-devel] [PATCH v7 3/5] aio: Fix qemu_aio_wait() to maintain correct walking_handlers count Bharata B Rao
` (2 subsequent siblings)
4 siblings, 1 reply; 24+ messages in thread
From: Bharata B Rao @ 2012-09-17 15:23 UTC (permalink / raw)
To: qemu-devel
Cc: Kevin Wolf, Anthony Liguori, Anand Avati, Vijay Bellur,
Stefan Hajnoczi, Amar Tumballi, Markus Armbruster, Blue Swirl,
Avi Kivity, Paolo Bonzini
sockets: Change inet_parse() to accept address specification without port
From: Bharata B Rao <bharata@linux.vnet.ibm.com>
inet_parse() expects address:port. Change it to work without explicit port
specification. In addition, don't depend solely on the return value of
sscanf but also consider the value obtained for %n directive used in sscanf.
This ensures that the scanning of malformed inet address isn't flagged as
success.
Signed-off-by: Bharata B Rao <bharata@linux.vnet.ibm.com>
---
qemu-sockets.c | 32 +++++++++++++++++++++++++-------
1 files changed, 25 insertions(+), 7 deletions(-)
diff --git a/qemu-sockets.c b/qemu-sockets.c
index c330fc5..627269f 100644
--- a/qemu-sockets.c
+++ b/qemu-sockets.c
@@ -376,26 +376,35 @@ err:
return -1;
}
-/* compatibility wrapper */
+/*
+ * sscanf could return positive return value indicating successful
+ * match and assignment even when it doesn't parse the input as per
+ * the format specified. Hence we are depending on %n to really determine if
+ * inputs were successfully scanned. Checking retval for EOF isn't strictly
+ * necessary, but just being extra careful.
+ */
int inet_parse(QemuOpts *opts, const char *str)
{
const char *optstr, *h;
char addr[64];
- char port[33];
- int pos;
+ char port[33] = {'\0'}; /* port is optional */
+ int ret, pos, addr_pos = -1, port_pos = -1;
/* parse address */
if (str[0] == ':') {
/* no host given */
addr[0] = '\0';
- if (1 != sscanf(str,":%32[^,]%n",port,&pos)) {
+ ret = sscanf(str, ":%32[^,]%n", port, &port_pos);
+ if (port_pos == -1 || ret == EOF) {
fprintf(stderr, "%s: portonly parse error (%s)\n",
__FUNCTION__, str);
return -1;
}
} else if (str[0] == '[') {
/* IPv6 addr */
- if (2 != sscanf(str,"[%64[^]]]:%32[^,]%n",addr,port,&pos)) {
+ ret = sscanf(str, "[%64[^]]]%n:%32[^,]%n", addr, &addr_pos,
+ port, &port_pos);
+ if (addr_pos == -1 || ret == EOF) {
fprintf(stderr, "%s: ipv6 parse error (%s)\n",
__FUNCTION__, str);
return -1;
@@ -403,7 +412,9 @@ int inet_parse(QemuOpts *opts, const char *str)
qemu_opt_set(opts, "ipv6", "on");
} else if (qemu_isdigit(str[0])) {
/* IPv4 addr */
- if (2 != sscanf(str,"%64[0-9.]:%32[^,]%n",addr,port,&pos)) {
+ ret = sscanf(str, "%64[0-9.]%n:%32[^,]%n", addr, &addr_pos,
+ port, &port_pos);
+ if (addr_pos == -1 || ret == EOF) {
fprintf(stderr, "%s: ipv4 parse error (%s)\n",
__FUNCTION__, str);
return -1;
@@ -411,7 +422,9 @@ int inet_parse(QemuOpts *opts, const char *str)
qemu_opt_set(opts, "ipv4", "on");
} else {
/* hostname */
- if (2 != sscanf(str,"%64[^:]:%32[^,]%n",addr,port,&pos)) {
+ ret = sscanf(str, "%64[^:]%n:%32[^,]%n", addr, &addr_pos,
+ port, &port_pos);
+ if (addr_pos == -1 || ret == EOF) {
fprintf(stderr, "%s: hostname parse error (%s)\n",
__FUNCTION__, str);
return -1;
@@ -421,6 +434,11 @@ int inet_parse(QemuOpts *opts, const char *str)
qemu_opt_set(opts, "port", port);
/* parse options */
+ if (port_pos != -1) {
+ pos = port_pos;
+ } else {
+ pos = addr_pos;
+ }
optstr = str + pos;
h = strstr(optstr, ",to=");
if (h)
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [Qemu-devel] [PATCH v7 3/5] aio: Fix qemu_aio_wait() to maintain correct walking_handlers count
2012-09-17 15:21 [Qemu-devel] [RFC v7 0/5] GlusterFS support in QEMU - v7 Bharata B Rao
2012-09-17 15:22 ` [Qemu-devel] [PATCH v7 1/5] sockets: Make inet_parse() non static Bharata B Rao
2012-09-17 15:23 ` [Qemu-devel] [PATCH v7 2/5] sockets: Change inet_parse() to accept address specification without port Bharata B Rao
@ 2012-09-17 15:24 ` Bharata B Rao
2012-09-17 15:25 ` [Qemu-devel] [PATCH v7 4/5] configure: Add a config option for GlusterFS as block backend Bharata B Rao
2012-09-17 15:26 ` [Qemu-devel] [PATCH v7 5/5] block: Support GlusterFS as a QEMU " Bharata B Rao
4 siblings, 0 replies; 24+ messages in thread
From: Bharata B Rao @ 2012-09-17 15:24 UTC (permalink / raw)
To: qemu-devel
Cc: Kevin Wolf, Anthony Liguori, Anand Avati, Vijay Bellur,
Stefan Hajnoczi, Amar Tumballi, Markus Armbruster, Blue Swirl,
Avi Kivity, Paolo Bonzini
aio: Fix qemu_aio_wait() to maintain correct walking_handlers count
From: Paolo Bonzini <pbonzini@redhat.com>
Fix qemu_aio_wait() to ensure that registered aio handlers don't get
deleted when they are still active. This is ensured by maintaning the
right count of walking_handlers.
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
Signed-off-by: Bharata B Rao <bharata@linux.vnet.ibm.com>
---
aio.c | 8 ++++----
1 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/aio.c b/aio.c
index 0a9eb10..99b8b72 100644
--- a/aio.c
+++ b/aio.c
@@ -119,7 +119,7 @@ bool qemu_aio_wait(void)
return true;
}
- walking_handlers = 1;
+ walking_handlers++;
FD_ZERO(&rdfds);
FD_ZERO(&wrfds);
@@ -147,7 +147,7 @@ bool qemu_aio_wait(void)
}
}
- walking_handlers = 0;
+ walking_handlers--;
/* No AIO operations? Get us out of here */
if (!busy) {
@@ -159,7 +159,7 @@ bool qemu_aio_wait(void)
/* if we have any readable fds, dispatch event */
if (ret > 0) {
- walking_handlers = 1;
+ walking_handlers++;
/* we have to walk very carefully in case
* qemu_aio_set_fd_handler is called while we're walking */
@@ -187,7 +187,7 @@ bool qemu_aio_wait(void)
}
}
- walking_handlers = 0;
+ walking_handlers--;
}
return true;
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [Qemu-devel] [PATCH v7 4/5] configure: Add a config option for GlusterFS as block backend
2012-09-17 15:21 [Qemu-devel] [RFC v7 0/5] GlusterFS support in QEMU - v7 Bharata B Rao
` (2 preceding siblings ...)
2012-09-17 15:24 ` [Qemu-devel] [PATCH v7 3/5] aio: Fix qemu_aio_wait() to maintain correct walking_handlers count Bharata B Rao
@ 2012-09-17 15:25 ` Bharata B Rao
2012-09-17 15:26 ` [Qemu-devel] [PATCH v7 5/5] block: Support GlusterFS as a QEMU " Bharata B Rao
4 siblings, 0 replies; 24+ messages in thread
From: Bharata B Rao @ 2012-09-17 15:25 UTC (permalink / raw)
To: qemu-devel
Cc: Kevin Wolf, Anthony Liguori, Anand Avati, Vijay Bellur,
Stefan Hajnoczi, Amar Tumballi, Markus Armbruster, Blue Swirl,
Avi Kivity, Paolo Bonzini
configure: Add a config option for GlusterFS as block backend
From: Bharata B Rao <bharata@linux.vnet.ibm.com>
GlusterFS support in QEMU depends on libgfapi, libgfrpc and
libgfxdr provided by GlusterFS.
Signed-off-by: Bharata B Rao <bharata@linux.vnet.ibm.com>
---
configure | 35 +++++++++++++++++++++++++++++++++++
1 files changed, 35 insertions(+), 0 deletions(-)
diff --git a/configure b/configure
index 30be784..85f4fb6 100755
--- a/configure
+++ b/configure
@@ -221,6 +221,7 @@ want_tools="yes"
libiscsi=""
coroutine=""
seccomp=""
+glusterfs=""
# parse CC options first
for opt do
@@ -877,6 +878,10 @@ for opt do
;;
--disable-seccomp) seccomp="no"
;;
+ --disable-glusterfs) glusterfs="no"
+ ;;
+ --enable-glusterfs) glusterfs="yes"
+ ;;
*) echo "ERROR: unknown option $opt"; show_help="yes"
;;
esac
@@ -1169,6 +1174,8 @@ echo " --disable-seccomp disable seccomp support"
echo " --enable-seccomp enables seccomp support"
echo " --with-coroutine=BACKEND coroutine backend. Supported options:"
echo " gthread, ucontext, sigaltstack, windows"
+echo " --enable-glusterfs enable GlusterFS backend"
+echo " --disable-glusterfs disable GlusterFS backend"
echo ""
echo "NOTE: The object files are built at the place where configure is launched"
exit 1
@@ -2343,6 +2350,29 @@ EOF
fi
fi
+##########################################
+# glusterfs probe
+if test "$glusterfs" != "no" ; then
+ cat > $TMPC <<EOF
+#include <glusterfs/api/glfs.h>
+int main(void) {
+ (void) glfs_new("volume");
+ return 0;
+}
+EOF
+ glusterfs_libs="-lgfapi -lgfrpc -lgfxdr"
+ if compile_prog "" "$glusterfs_libs" ; then
+ glusterfs=yes
+ libs_tools="$glusterfs_libs $libs_tools"
+ libs_softmmu="$glusterfs_libs $libs_softmmu"
+ else
+ if test "$glusterfs" = "yes" ; then
+ feature_not_found "GlusterFS backend support"
+ fi
+ glusterfs=no
+ fi
+fi
+
#
# Check for xxxat() functions when we are building linux-user
# emulator. This is done because older glibc versions don't
@@ -3180,6 +3210,7 @@ echo "libiscsi support $libiscsi"
echo "build guest agent $guest_agent"
echo "seccomp support $seccomp"
echo "coroutine backend $coroutine_backend"
+echo "GlusterFS support $glusterfs"
if test "$sdl_too_old" = "yes"; then
echo "-> Your SDL version is too old - please upgrade to have SDL support"
@@ -3522,6 +3553,10 @@ if test "$has_environ" = "yes" ; then
echo "CONFIG_HAS_ENVIRON=y" >> $config_host_mak
fi
+if test "$glusterfs" = "yes" ; then
+ echo "CONFIG_GLUSTERFS=y" >> $config_host_mak
+fi
+
# USB host support
case "$usb" in
linux)
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [Qemu-devel] [PATCH v7 5/5] block: Support GlusterFS as a QEMU block backend.
2012-09-17 15:21 [Qemu-devel] [RFC v7 0/5] GlusterFS support in QEMU - v7 Bharata B Rao
` (3 preceding siblings ...)
2012-09-17 15:25 ` [Qemu-devel] [PATCH v7 4/5] configure: Add a config option for GlusterFS as block backend Bharata B Rao
@ 2012-09-17 15:26 ` Bharata B Rao
2012-09-18 14:01 ` Kevin Wolf
4 siblings, 1 reply; 24+ messages in thread
From: Bharata B Rao @ 2012-09-17 15:26 UTC (permalink / raw)
To: qemu-devel
Cc: Kevin Wolf, Anthony Liguori, Anand Avati, Vijay Bellur,
Stefan Hajnoczi, Amar Tumballi, Markus Armbruster, Blue Swirl,
Avi Kivity, Paolo Bonzini
block: Support GlusterFS as a QEMU block backend.
From: Bharata B Rao <bharata@linux.vnet.ibm.com>
This patch adds gluster as the new block backend in QEMU. This gives
QEMU the ability to boot VM images from gluster volumes. Its already
possible to boot from VM images on gluster volumes using FUSE mount, but
this patchset provides the ability to boot VM images from gluster volumes
by by-passing the FUSE layer in gluster. This is made possible by
using libgfapi routines to perform IO on gluster volumes directly.
VM Image on gluster volume is specified like this:
file=gluster[+transport]://[server[:port]]/volname/image[?socket=...]
'gluster' is the protocol.
'transport' specifies the transport type used to connect to gluster
management daemon (glusterd). Valid transport types are
tcp, unix and rdma. If the transport type isn't specified, then tcp
type is assumed.
'server' specifies the server where the volume file specification for
the given volume resides. This can be either hostname or ipv4 address
or ipv6 address. ipv6 address needs to be with in square brackets [ ].
If transport type is 'unix', then server field is ignored, but the
'socket' field needs to be populated with the path to unix domain
socket.
'port' is the port number on which glusterd is listening. This is optional
and if not specified, QEMU will send 0 which will make gluster to use the
default port. port is ignored for unix type of transport.
'volname' is the name of the gluster volume which contains the VM image.
'image' is the path to the actual VM image that resides on gluster volume.
Examples:
file=gluster://1.2.3.4/testvol/a.img
file=gluster+tcp://1.2.3.4/testvol/a.img
file=gluster+tcp://1.2.3.4:24007/testvol/dir/a.img
file=gluster+tcp://[1:2:3:4:5:6:7:8]/testvol/dir/a.img
file=gluster+tcp://[1:2:3:4:5:6:7:8]:24007/testvol/dir/a.img
file=gluster+tcp://server.domain.com:24007/testvol/dir/a.img
file=gluster+unix:///testvol/dir/a.img?socket=/tmp/glusterd.socket
file=gluster+rdma://1.2.3.4:24007/testvol/a.img
Signed-off-by: Bharata B Rao <bharata@linux.vnet.ibm.com>
---
block/Makefile.objs | 1
block/gluster.c | 694 +++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 695 insertions(+), 0 deletions(-)
create mode 100644 block/gluster.c
diff --git a/block/Makefile.objs b/block/Makefile.objs
index b5754d3..a1ae67f 100644
--- a/block/Makefile.objs
+++ b/block/Makefile.objs
@@ -9,3 +9,4 @@ block-obj-$(CONFIG_POSIX) += raw-posix.o
block-obj-$(CONFIG_LIBISCSI) += iscsi.o
block-obj-$(CONFIG_CURL) += curl.o
block-obj-$(CONFIG_RBD) += rbd.o
+block-obj-$(CONFIG_GLUSTERFS) += gluster.o
diff --git a/block/gluster.c b/block/gluster.c
new file mode 100644
index 0000000..0de3286
--- /dev/null
+++ b/block/gluster.c
@@ -0,0 +1,694 @@
+/*
+ * GlusterFS backend for QEMU
+ *
+ * Copyright (C) 2012 Bharata B Rao <bharata@linux.vnet.ibm.com>
+ *
+ * Pipe handling mechanism in AIO implementation is derived from
+ * block/rbd.c. Hence,
+ *
+ * Copyright (C) 2010-2011 Christian Brunner <chb@muc.de>,
+ * Josh Durgin <josh.durgin@dreamhost.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2. See
+ * the COPYING file in the top-level directory.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+#include <glusterfs/api/glfs.h>
+#include "block_int.h"
+#include "qemu_socket.h"
+
+typedef struct GlusterAIOCB {
+ BlockDriverAIOCB common;
+ int64_t size;
+ int ret;
+ bool *finished;
+ QEMUBH *bh;
+} GlusterAIOCB;
+
+typedef struct BDRVGlusterState {
+ struct glfs *glfs;
+ int fds[2];
+ struct glfs_fd *fd;
+ int qemu_aio_count;
+ int event_reader_pos;
+ GlusterAIOCB *event_acb;
+} BDRVGlusterState;
+
+#define GLUSTER_FD_READ 0
+#define GLUSTER_FD_WRITE 1
+
+#define GLUSTER_TRANSPORT_DEFAULT "gluster://"
+#define GLUSTER_TRANSPORT_DEFAULT_SZ strlen(GLUSTER_TRANSPORT_DEFAULT)
+#define GLUSTER_TRANSPORT_TCP "gluster+tcp://"
+#define GLUSTER_TRANSPORT_TCP_SZ strlen(GLUSTER_TRANSPORT_TCP)
+#define GLUSTER_TRANSPORT_UNIX "gluster+unix://"
+#define GLUSTER_TRANSPORT_UNIX_SZ strlen(GLUSTER_TRANSPORT_UNIX)
+#define GLUSTER_TRANSPORT_RDMA "gluster+rdma://"
+#define GLUSTER_TRANSPORT_RDMA_SZ strlen(GLUSTER_TRANSPORT_RDMA)
+
+typedef struct GlusterURI {
+ char *server;
+ int port;
+ char *volname;
+ char *image;
+ char *transport;
+ bool is_unix;
+} GlusterURI;
+
+static void qemu_gluster_uri_free(GlusterURI *uri)
+{
+ g_free(uri->server);
+ g_free(uri->volname);
+ g_free(uri->image);
+ g_free(uri->transport);
+ g_free(uri);
+}
+
+static int parse_socket(GlusterURI *uri, char *socket)
+{
+ char *token, *saveptr;
+
+ if (!socket) {
+ return 0;
+ }
+ token = strtok_r(socket, "=", &saveptr);
+ if (!token || strcmp(token, "socket")) {
+ return -EINVAL;
+ }
+ token = strtok_r(NULL, "=", &saveptr);
+ if (!token) {
+ return -EINVAL;
+ }
+ uri->server = g_strdup(token);
+ uri->is_unix = true;
+ return 0;
+}
+
+static int parse_gluster_spec(GlusterURI *uri, char *spec)
+{
+ char *token, *saveptr;
+ int ret;
+ QemuOpts *opts;
+ char *p, *q;
+
+ /* transport */
+ p = spec;
+ if (!strncmp(p, GLUSTER_TRANSPORT_DEFAULT, GLUSTER_TRANSPORT_DEFAULT_SZ)) {
+ uri->transport = g_strdup("tcp");
+ p += GLUSTER_TRANSPORT_DEFAULT_SZ;
+ } else if (!strncmp(p, GLUSTER_TRANSPORT_TCP, GLUSTER_TRANSPORT_TCP_SZ)) {
+ uri->transport = g_strdup("tcp");
+ p += GLUSTER_TRANSPORT_TCP_SZ;
+ } else if (!strncmp(p, GLUSTER_TRANSPORT_UNIX, GLUSTER_TRANSPORT_UNIX_SZ)) {
+ uri->transport = g_strdup("unix");
+ p += GLUSTER_TRANSPORT_UNIX_SZ;
+ } else if (!strncmp(p, GLUSTER_TRANSPORT_RDMA, GLUSTER_TRANSPORT_RDMA_SZ)) {
+ uri->transport = g_strdup("rdma");
+ p += GLUSTER_TRANSPORT_RDMA_SZ;
+ } else {
+ return -EINVAL;
+ }
+ q = p;
+
+ /* server */
+ if (!strcmp(uri->transport, "unix")) {
+ if (!uri->is_unix) {
+ return -EINVAL;
+ }
+ } else {
+ if (uri->is_unix) {
+ return -EINVAL;
+ }
+ p = strchr(p, '/');
+ if (!p) {
+ return -EINVAL;
+ }
+ *p++ = '\0';
+ opts = qemu_opts_create(qemu_find_opts("inet"), NULL, 0, NULL);
+ ret = inet_parse(opts, q);
+ if (!ret) {
+ uri->server = g_strdup(qemu_opt_get(opts, "host"));
+ uri->port = strtoul(qemu_opt_get(opts, "port"), NULL, 0);
+ if (uri->port < 0) {
+ ret = -EINVAL;
+ }
+ }
+ qemu_opts_del(opts);
+ if (ret < 0) {
+ return -EINVAL;
+ }
+ }
+
+ /* volname */
+ token = strtok_r(p, "/", &saveptr);
+ if (!token) {
+ return -EINVAL;
+ }
+ uri->volname = g_strdup(token);
+
+ /* image */
+ token = strtok_r(NULL, "?", &saveptr);
+ if (!token) {
+ return -EINVAL;
+ }
+ uri->image = g_strdup(token);
+ return 0;
+}
+
+/*
+ * file=gluster[+transport]://[server[:port]]/volname/image[?socket=...]
+ *
+ * 'gluster' is the protocol.
+ *
+ * 'transport' specifies the transport type used to connect to gluster
+ * management daemon (glusterd). Valid transport types are
+ * tcp, unix and rdma. If the transport type isn't specified, then tcp
+ * type is assumed.
+ *
+ * 'server' specifies the server where the volume file specification for
+ * the given volume resides. This can be either hostname or ipv4 address
+ * or ipv6 address. ipv6 address needs to be with in square brackets [ ].
+ * If transport type is 'unix', then server field is ignored, but the
+ * 'socket' field needs to be populated with the path to unix domain
+ * socket.
+ *
+ * 'port' is the port number on which glusterd is listening. This is optional
+ * and if not specified, QEMU will send 0 which will make gluster to use the
+ * default port. port is ignored for unix type of transport.
+ *
+ * 'volname' is the name of the gluster volume which contains the VM image.
+ *
+ * 'image' is the path to the actual VM image that resides on gluster volume.
+ *
+ * Examples:
+ *
+ * file=gluster://1.2.3.4/testvol/a.img
+ * file=gluster+tcp://1.2.3.4/testvol/a.img
+ * file=gluster+tcp://1.2.3.4:24007/testvol/dir/a.img
+ * file=gluster+tcp://[1:2:3:4:5:6:7:8]/testvol/dir/a.img
+ * file=gluster+tcp://[1:2:3:4:5:6:7:8]:24007/testvol/dir/a.img
+ * file=gluster+tcp://server.domain.com:24007/testvol/dir/a.img
+ * file=gluster+unix:///testvol/dir/a.img?socket=/tmp/glusterd.socket
+ * file=gluster+rdma://1.2.3.4:24007/testvol/a.img
+ */
+static int qemu_gluster_parseuri(GlusterURI *uri, const char *filename)
+{
+ char *token, *saveptr;
+ char *p, *q, *gluster_spec = NULL;
+ int ret = -EINVAL;
+
+ p = q = g_strdup(filename);
+
+ /* Extract server, volname and image */
+ token = strtok_r(p, "?", &saveptr);
+ if (!token) {
+ goto out;
+ }
+ gluster_spec = g_strdup(token);
+
+ /* socket */
+ token = strtok_r(NULL, "?", &saveptr);
+ ret = parse_socket(uri, token);
+ if (ret < 0) {
+ goto out;
+ }
+
+ /* Flag error for extra options */
+ token = strtok_r(NULL, "?", &saveptr);
+ if (token) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ret = parse_gluster_spec(uri, gluster_spec);
+ if (ret < 0) {
+ goto out;
+ }
+ ret = 0;
+out:
+ g_free(q);
+ g_free(gluster_spec);
+ return ret;
+}
+
+static struct glfs *qemu_gluster_init(GlusterURI *uri, const char *filename)
+{
+ struct glfs *glfs = NULL;
+ int ret;
+
+ ret = qemu_gluster_parseuri(uri, filename);
+ if (ret < 0) {
+ error_report("Usage: file=gluster[+transport]://[server[:port]]/"
+ "volname/image[?socket=...]");
+ errno = -ret;
+ goto out;
+ }
+
+ glfs = glfs_new(uri->volname);
+ if (!glfs) {
+ goto out;
+ }
+
+ ret = glfs_set_volfile_server(glfs, uri->transport, uri->server, uri->port);
+ if (ret < 0) {
+ goto out;
+ }
+
+ /*
+ * TODO: Use GF_LOG_ERROR instead of hard code value of 4 here when
+ * GlusterFS exports it in a header.
+ */
+ ret = glfs_set_logging(glfs, "-", 4);
+ if (ret < 0) {
+ goto out;
+ }
+
+ ret = glfs_init(glfs);
+ if (ret) {
+ error_report("Gluster connection failed for server=%s port=%d "
+ "volume=%s image=%s transport=%s\n", uri->server, uri->port,
+ uri->volname, uri->image, uri->transport);
+ goto out;
+ }
+ return glfs;
+
+out:
+ if (glfs) {
+ glfs_fini(glfs);
+ }
+ return NULL;
+}
+
+static void qemu_gluster_complete_aio(GlusterAIOCB *acb, BDRVGlusterState *s)
+{
+ int ret;
+ bool *finished = acb->finished;
+ BlockDriverCompletionFunc *cb = acb->common.cb;
+ void *opaque = acb->common.opaque;
+
+ if (!acb->ret || acb->ret == acb->size) {
+ ret = 0; /* Success */
+ } else if (acb->ret < 0) {
+ ret = acb->ret; /* Read/Write failed */
+ } else {
+ ret = -EIO; /* Partial read/write - fail it */
+ }
+
+ s->qemu_aio_count--;
+ qemu_aio_release(acb);
+ cb(opaque, ret);
+ if (finished) {
+ *finished = true;
+ }
+}
+
+static void qemu_gluster_aio_event_reader(void *opaque)
+{
+ BDRVGlusterState *s = opaque;
+ ssize_t ret;
+
+ do {
+ char *p = (char *)&s->event_acb;
+
+ ret = read(s->fds[GLUSTER_FD_READ], p + s->event_reader_pos,
+ sizeof(s->event_acb) - s->event_reader_pos);
+ if (ret > 0) {
+ s->event_reader_pos += ret;
+ if (s->event_reader_pos == sizeof(s->event_acb)) {
+ s->event_reader_pos = 0;
+ qemu_gluster_complete_aio(s->event_acb, s);
+ }
+ }
+ } while (ret < 0 && errno == EINTR);
+}
+
+static int qemu_gluster_aio_flush_cb(void *opaque)
+{
+ BDRVGlusterState *s = opaque;
+
+ return (s->qemu_aio_count > 0);
+}
+
+static int qemu_gluster_open(BlockDriverState *bs, const char *filename,
+ int bdrv_flags)
+{
+ BDRVGlusterState *s = bs->opaque;
+ int open_flags = 0;
+ int ret = 0;
+ GlusterURI *uri = g_malloc0(sizeof(GlusterURI));
+
+ s->glfs = qemu_gluster_init(uri, filename);
+ if (!s->glfs) {
+ ret = -errno;
+ goto out;
+ }
+
+ open_flags |= O_BINARY;
+ open_flags &= ~O_ACCMODE;
+ if (bdrv_flags & BDRV_O_RDWR) {
+ open_flags |= O_RDWR;
+ } else {
+ open_flags |= O_RDONLY;
+ }
+
+ if ((bdrv_flags & BDRV_O_NOCACHE)) {
+ open_flags |= O_DIRECT;
+ }
+
+ s->fd = glfs_open(s->glfs, uri->image, open_flags);
+ if (!s->fd) {
+ ret = -errno;
+ goto out;
+ }
+
+ ret = qemu_pipe(s->fds);
+ if (ret < 0) {
+ ret = -errno;
+ goto out;
+ }
+ fcntl(s->fds[GLUSTER_FD_READ], F_SETFL, O_NONBLOCK);
+ qemu_aio_set_fd_handler(s->fds[GLUSTER_FD_READ],
+ qemu_gluster_aio_event_reader, NULL, qemu_gluster_aio_flush_cb, s);
+
+out:
+ qemu_gluster_uri_free(uri);
+ if (!ret) {
+ return ret;
+ }
+ if (s->fd) {
+ glfs_close(s->fd);
+ }
+ if (s->glfs) {
+ glfs_fini(s->glfs);
+ }
+ return ret;
+}
+
+static int qemu_gluster_create(const char *filename,
+ QEMUOptionParameter *options)
+{
+ struct glfs *glfs;
+ struct glfs_fd *fd;
+ int ret = 0;
+ int64_t total_size = 0;
+ GlusterURI *uri = g_malloc0(sizeof(GlusterURI));
+
+ glfs = qemu_gluster_init(uri, filename);
+ if (!glfs) {
+ ret = -errno;
+ goto out;
+ }
+
+ while (options && options->name) {
+ if (!strcmp(options->name, BLOCK_OPT_SIZE)) {
+ total_size = options->value.n / BDRV_SECTOR_SIZE;
+ }
+ options++;
+ }
+
+ fd = glfs_creat(glfs, uri->image,
+ O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, S_IRUSR | S_IWUSR);
+ if (!fd) {
+ ret = -errno;
+ } else {
+ if (glfs_ftruncate(fd, total_size * BDRV_SECTOR_SIZE) != 0) {
+ ret = -errno;
+ }
+ if (glfs_close(fd) != 0) {
+ ret = -errno;
+ }
+ }
+out:
+ qemu_gluster_uri_free(uri);
+ if (glfs) {
+ glfs_fini(glfs);
+ }
+ return ret;
+}
+
+static void qemu_gluster_aio_cancel(BlockDriverAIOCB *blockacb)
+{
+ GlusterAIOCB *acb = (GlusterAIOCB *)blockacb;
+ bool finished = false;
+
+ acb->finished = &finished;
+ while (!finished) {
+ qemu_aio_wait();
+ }
+}
+
+static AIOPool gluster_aio_pool = {
+ .aiocb_size = sizeof(GlusterAIOCB),
+ .cancel = qemu_gluster_aio_cancel,
+};
+
+static int qemu_gluster_send_pipe(BDRVGlusterState *s, GlusterAIOCB *acb)
+{
+ int ret = 0;
+
+ while (1) {
+ int fd = s->fds[GLUSTER_FD_WRITE];
+
+ ret = write(fd, (void *)&acb, sizeof(acb));
+ if (ret >= 0) {
+ break;
+ }
+ if (errno == EINTR) {
+ continue;
+ }
+ if (errno != EAGAIN) {
+ break;
+ }
+ }
+ return ret;
+}
+
+static void gluster_finish_aiocb(struct glfs_fd *fd, ssize_t ret, void *arg)
+{
+ GlusterAIOCB *acb = (GlusterAIOCB *)arg;
+ BlockDriverState *bs = acb->common.bs;
+ BDRVGlusterState *s = bs->opaque;
+
+ acb->ret = ret;
+ if (qemu_gluster_send_pipe(s, acb) < 0) {
+ /*
+ * Gluster AIO callback thread failed to notify the waiting
+ * QEMU thread about IO completion.
+ *
+ * Complete this IO request and make the disk inaccessible for
+ * subsequent reads and writes.
+ */
+ error_report("Gluster failed to notify QEMU about IO completion");
+
+ qemu_mutex_lock_iothread(); /* We are in gluster thread context */
+ acb->common.cb(acb->common.opaque, -EIO);
+ qemu_aio_release(acb);
+ s->qemu_aio_count--;
+ close(s->fds[GLUSTER_FD_READ]);
+ close(s->fds[GLUSTER_FD_WRITE]);
+ qemu_aio_set_fd_handler(s->fds[GLUSTER_FD_READ], NULL, NULL, NULL,
+ NULL);
+ bs->drv = NULL; /* Make the disk inaccessible */
+ qemu_mutex_unlock_iothread();
+ }
+}
+
+static BlockDriverAIOCB *qemu_gluster_aio_rw(BlockDriverState *bs,
+ int64_t sector_num, QEMUIOVector *qiov, int nb_sectors,
+ BlockDriverCompletionFunc *cb, void *opaque, int write)
+{
+ int ret;
+ GlusterAIOCB *acb;
+ BDRVGlusterState *s = bs->opaque;
+ size_t size;
+ off_t offset;
+
+ offset = sector_num * BDRV_SECTOR_SIZE;
+ size = nb_sectors * BDRV_SECTOR_SIZE;
+ s->qemu_aio_count++;
+
+ acb = qemu_aio_get(&gluster_aio_pool, bs, cb, opaque);
+ acb->size = size;
+ acb->ret = 0;
+ acb->finished = NULL;
+
+ if (write) {
+ ret = glfs_pwritev_async(s->fd, qiov->iov, qiov->niov, offset, 0,
+ &gluster_finish_aiocb, acb);
+ } else {
+ ret = glfs_preadv_async(s->fd, qiov->iov, qiov->niov, offset, 0,
+ &gluster_finish_aiocb, acb);
+ }
+
+ if (ret < 0) {
+ goto out;
+ }
+ return &acb->common;
+
+out:
+ s->qemu_aio_count--;
+ qemu_aio_release(acb);
+ return NULL;
+}
+
+static BlockDriverAIOCB *qemu_gluster_aio_readv(BlockDriverState *bs,
+ int64_t sector_num, QEMUIOVector *qiov, int nb_sectors,
+ BlockDriverCompletionFunc *cb, void *opaque)
+{
+ return qemu_gluster_aio_rw(bs, sector_num, qiov, nb_sectors, cb, opaque, 0);
+}
+
+static BlockDriverAIOCB *qemu_gluster_aio_writev(BlockDriverState *bs,
+ int64_t sector_num, QEMUIOVector *qiov, int nb_sectors,
+ BlockDriverCompletionFunc *cb, void *opaque)
+{
+ return qemu_gluster_aio_rw(bs, sector_num, qiov, nb_sectors, cb, opaque, 1);
+}
+
+static BlockDriverAIOCB *qemu_gluster_aio_flush(BlockDriverState *bs,
+ BlockDriverCompletionFunc *cb, void *opaque)
+{
+ int ret;
+ GlusterAIOCB *acb;
+ BDRVGlusterState *s = bs->opaque;
+
+ acb = qemu_aio_get(&gluster_aio_pool, bs, cb, opaque);
+ acb->size = 0;
+ acb->ret = 0;
+ acb->finished = NULL;
+ s->qemu_aio_count++;
+
+ ret = glfs_fsync_async(s->fd, &gluster_finish_aiocb, acb);
+ if (ret < 0) {
+ goto out;
+ }
+ return &acb->common;
+
+out:
+ s->qemu_aio_count--;
+ qemu_aio_release(acb);
+ return NULL;
+}
+
+static int64_t qemu_gluster_getlength(BlockDriverState *bs)
+{
+ BDRVGlusterState *s = bs->opaque;
+ int64_t ret;
+
+ ret = glfs_lseek(s->fd, 0, SEEK_END);
+ if (ret < 0) {
+ return -errno;
+ } else {
+ return ret;
+ }
+}
+
+static int64_t qemu_gluster_allocated_file_size(BlockDriverState *bs)
+{
+ BDRVGlusterState *s = bs->opaque;
+ struct stat st;
+ int ret;
+
+ ret = glfs_fstat(s->fd, &st);
+ if (ret < 0) {
+ return -errno;
+ } else {
+ return st.st_blocks * 512;
+ }
+}
+
+static void qemu_gluster_close(BlockDriverState *bs)
+{
+ BDRVGlusterState *s = bs->opaque;
+
+ close(s->fds[GLUSTER_FD_READ]);
+ close(s->fds[GLUSTER_FD_WRITE]);
+ qemu_aio_set_fd_handler(s->fds[GLUSTER_FD_READ], NULL, NULL, NULL, NULL);
+
+ if (s->fd) {
+ glfs_close(s->fd);
+ s->fd = NULL;
+ }
+ glfs_fini(s->glfs);
+}
+
+static QEMUOptionParameter qemu_gluster_create_options[] = {
+ {
+ .name = BLOCK_OPT_SIZE,
+ .type = OPT_SIZE,
+ .help = "Virtual disk size"
+ },
+ { NULL }
+};
+
+static BlockDriver bdrv_gluster = {
+ .format_name = "gluster",
+ .protocol_name = "gluster",
+ .instance_size = sizeof(BDRVGlusterState),
+ .bdrv_file_open = qemu_gluster_open,
+ .bdrv_close = qemu_gluster_close,
+ .bdrv_create = qemu_gluster_create,
+ .bdrv_getlength = qemu_gluster_getlength,
+ .bdrv_get_allocated_file_size = qemu_gluster_allocated_file_size,
+ .bdrv_aio_readv = qemu_gluster_aio_readv,
+ .bdrv_aio_writev = qemu_gluster_aio_writev,
+ .bdrv_aio_flush = qemu_gluster_aio_flush,
+ .create_options = qemu_gluster_create_options,
+};
+
+static BlockDriver bdrv_gluster_tcp = {
+ .format_name = "gluster",
+ .protocol_name = "gluster+tcp",
+ .instance_size = sizeof(BDRVGlusterState),
+ .bdrv_file_open = qemu_gluster_open,
+ .bdrv_close = qemu_gluster_close,
+ .bdrv_create = qemu_gluster_create,
+ .bdrv_getlength = qemu_gluster_getlength,
+ .bdrv_get_allocated_file_size = qemu_gluster_allocated_file_size,
+ .bdrv_aio_readv = qemu_gluster_aio_readv,
+ .bdrv_aio_writev = qemu_gluster_aio_writev,
+ .bdrv_aio_flush = qemu_gluster_aio_flush,
+ .create_options = qemu_gluster_create_options,
+};
+
+static BlockDriver bdrv_gluster_unix = {
+ .format_name = "gluster",
+ .protocol_name = "gluster+unix",
+ .instance_size = sizeof(BDRVGlusterState),
+ .bdrv_file_open = qemu_gluster_open,
+ .bdrv_close = qemu_gluster_close,
+ .bdrv_create = qemu_gluster_create,
+ .bdrv_getlength = qemu_gluster_getlength,
+ .bdrv_get_allocated_file_size = qemu_gluster_allocated_file_size,
+ .bdrv_aio_readv = qemu_gluster_aio_readv,
+ .bdrv_aio_writev = qemu_gluster_aio_writev,
+ .bdrv_aio_flush = qemu_gluster_aio_flush,
+ .create_options = qemu_gluster_create_options,
+};
+
+static BlockDriver bdrv_gluster_rdma = {
+ .format_name = "gluster",
+ .protocol_name = "gluster+rdma",
+ .instance_size = sizeof(BDRVGlusterState),
+ .bdrv_file_open = qemu_gluster_open,
+ .bdrv_close = qemu_gluster_close,
+ .bdrv_create = qemu_gluster_create,
+ .bdrv_getlength = qemu_gluster_getlength,
+ .bdrv_get_allocated_file_size = qemu_gluster_allocated_file_size,
+ .bdrv_aio_readv = qemu_gluster_aio_readv,
+ .bdrv_aio_writev = qemu_gluster_aio_writev,
+ .bdrv_aio_flush = qemu_gluster_aio_flush,
+ .create_options = qemu_gluster_create_options,
+};
+
+static void bdrv_gluster_init(void)
+{
+ bdrv_register(&bdrv_gluster_rdma);
+ bdrv_register(&bdrv_gluster_unix);
+ bdrv_register(&bdrv_gluster_tcp);
+ bdrv_register(&bdrv_gluster);
+}
+
+block_init(bdrv_gluster_init);
^ permalink raw reply related [flat|nested] 24+ messages in thread
* Re: [Qemu-devel] [PATCH v7 1/5] sockets: Make inet_parse() non static.
2012-09-17 15:22 ` [Qemu-devel] [PATCH v7 1/5] sockets: Make inet_parse() non static Bharata B Rao
@ 2012-09-18 12:47 ` Kevin Wolf
2012-09-18 12:57 ` Paolo Bonzini
0 siblings, 1 reply; 24+ messages in thread
From: Kevin Wolf @ 2012-09-18 12:47 UTC (permalink / raw)
To: bharata
Cc: Anthony Liguori, Anand Avati, Vijay Bellur, Stefan Hajnoczi,
Amar Tumballi, qemu-devel, Markus Armbruster, Blue Swirl,
Avi Kivity, Paolo Bonzini
Am 17.09.2012 17:22, schrieb Bharata B Rao:
> sockets: Make inet_parse() non static.
>
> From: Bharata B Rao <bharata@linux.vnet.ibm.com>
>
> Make inet_parse() non-static so that other subsystems like gluster
> can use it to parse inet addresses. As a pre-requisite, define and
> globalize the qemu_inet_opts.
>
> Signed-off-by: Bharata B Rao <bharata@linux.vnet.ibm.com>
> ---
>
> Makefile.objs | 2 +-
> qemu-config.c | 31 +++++++++++++++++++++++++++++++
> qemu-sockets.c | 42 ++++++------------------------------------
> qemu_socket.h | 1 +
> 4 files changed, 39 insertions(+), 37 deletions(-)
Making the option list global sounds fine, but I don't think it should
be moved to qemu-config.c or added to vm_config_groups, which is
generally used for command line options and config file entries. At
least I can't imagine a command line option -inet would be very useful.
Kevin
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [Qemu-devel] [PATCH v7 1/5] sockets: Make inet_parse() non static.
2012-09-18 12:47 ` Kevin Wolf
@ 2012-09-18 12:57 ` Paolo Bonzini
2012-09-20 5:57 ` Bharata B Rao
0 siblings, 1 reply; 24+ messages in thread
From: Paolo Bonzini @ 2012-09-18 12:57 UTC (permalink / raw)
To: Kevin Wolf
Cc: Anthony Liguori, Anand Avati, Vijay Bellur, Stefan Hajnoczi,
Amar Tumballi, qemu-devel, Markus Armbruster, Blue Swirl,
Avi Kivity, bharata
Il 18/09/2012 14:47, Kevin Wolf ha scritto:
>> > Makefile.objs | 2 +-
>> > qemu-config.c | 31 +++++++++++++++++++++++++++++++
>> > qemu-sockets.c | 42 ++++++------------------------------------
>> > qemu_socket.h | 1 +
>> > 4 files changed, 39 insertions(+), 37 deletions(-)
> Making the option list global sounds fine, but I don't think it should
> be moved to qemu-config.c or added to vm_config_groups, which is
> generally used for command line options and config file entries. At
> least I can't imagine a command line option -inet would be very useful.
Yes. Also, I'll send a pull-request for
http://patchwork.ozlabs.org/patch/180237/ soon, so you might as well
take that patch.
Paolo
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [Qemu-devel] [PATCH v7 2/5] sockets: Change inet_parse() to accept address specification without port
2012-09-17 15:23 ` [Qemu-devel] [PATCH v7 2/5] sockets: Change inet_parse() to accept address specification without port Bharata B Rao
@ 2012-09-18 13:22 ` Kevin Wolf
2012-09-18 13:31 ` Paolo Bonzini
0 siblings, 1 reply; 24+ messages in thread
From: Kevin Wolf @ 2012-09-18 13:22 UTC (permalink / raw)
To: bharata
Cc: Anthony Liguori, Anand Avati, Vijay Bellur, Stefan Hajnoczi,
Amar Tumballi, qemu-devel, Markus Armbruster, Blue Swirl,
Avi Kivity, Paolo Bonzini
Am 17.09.2012 17:23, schrieb Bharata B Rao:
> sockets: Change inet_parse() to accept address specification without port
>
> From: Bharata B Rao <bharata@linux.vnet.ibm.com>
>
> inet_parse() expects address:port. Change it to work without explicit port
> specification. In addition, don't depend solely on the return value of
Things like "in addition" in a commit message are almost always a sign
that the patch should be split in two.
> sscanf but also consider the value obtained for %n directive used in sscanf.
> This ensures that the scanning of malformed inet address isn't flagged as
> success.
Can you give an example string that would be falsely accepted? To me the
old checks look fine (even though the new ones are a little bit easier
to read, so even if they don't fix anything, they might be worth doing).
Anyway, it does look correct.
Kevin
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [Qemu-devel] [PATCH v7 2/5] sockets: Change inet_parse() to accept address specification without port
2012-09-18 13:22 ` Kevin Wolf
@ 2012-09-18 13:31 ` Paolo Bonzini
2012-09-18 14:08 ` Kevin Wolf
0 siblings, 1 reply; 24+ messages in thread
From: Paolo Bonzini @ 2012-09-18 13:31 UTC (permalink / raw)
To: Kevin Wolf
Cc: Anthony Liguori, Anand Avati, Vijay Bellur, Stefan Hajnoczi,
Amar Tumballi, qemu-devel, Markus Armbruster, Blue Swirl,
Avi Kivity, bharata
Il 18/09/2012 15:22, Kevin Wolf ha scritto:
> Am 17.09.2012 17:23, schrieb Bharata B Rao:
>> sockets: Change inet_parse() to accept address specification without port
>>
>> From: Bharata B Rao <bharata@linux.vnet.ibm.com>
>>
>> inet_parse() expects address:port. Change it to work without explicit port
>> specification. In addition, don't depend solely on the return value of
>
> Things like "in addition" in a commit message are almost always a sign
> that the patch should be split in two.
>
>> sscanf but also consider the value obtained for %n directive used in sscanf.
>> This ensures that the scanning of malformed inet address isn't flagged as
>> success.
>
> Can you give an example string that would be falsely accepted? To me the
> old checks look fine (even though the new ones are a little bit easier
> to read, so even if they don't fix anything, they might be worth doing).
"localhost" would fail to be parsed:
- if (2 != sscanf(str,"%64[^:]:%32[^,]%n",addr,port,&pos)) {
+ ret = sscanf(str, "%64[^:]%n:%32[^,]%n", addr, &addr_pos,
+ port, &port_pos);
+ if (addr_pos == -1 || ret == EOF) {
because the : in the format string would not match and sscanf would
return 1.
However, is it correct to set the port unconditionally to an empty
string? Your usecase makes sense, but perhaps the default port be
passed as an extra parameter to inet_parse instead.
Paolo
> Anyway, it does look correct.
>
> Kevin
>
>
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [Qemu-devel] [PATCH v7 5/5] block: Support GlusterFS as a QEMU block backend.
2012-09-17 15:26 ` [Qemu-devel] [PATCH v7 5/5] block: Support GlusterFS as a QEMU " Bharata B Rao
@ 2012-09-18 14:01 ` Kevin Wolf
2012-09-20 6:41 ` Bharata B Rao
0 siblings, 1 reply; 24+ messages in thread
From: Kevin Wolf @ 2012-09-18 14:01 UTC (permalink / raw)
To: bharata
Cc: Anthony Liguori, Anand Avati, Vijay Bellur, Stefan Hajnoczi,
Amar Tumballi, qemu-devel, Markus Armbruster, Blue Swirl,
Avi Kivity, Paolo Bonzini
Am 17.09.2012 17:26, schrieb Bharata B Rao:
> block: Support GlusterFS as a QEMU block backend.
>
> From: Bharata B Rao <bharata@linux.vnet.ibm.com>
>
> This patch adds gluster as the new block backend in QEMU. This gives
> QEMU the ability to boot VM images from gluster volumes. Its already
> possible to boot from VM images on gluster volumes using FUSE mount, but
> this patchset provides the ability to boot VM images from gluster volumes
> by by-passing the FUSE layer in gluster. This is made possible by
> using libgfapi routines to perform IO on gluster volumes directly.
>
> VM Image on gluster volume is specified like this:
>
> file=gluster[+transport]://[server[:port]]/volname/image[?socket=...]
>
> 'gluster' is the protocol.
>
> 'transport' specifies the transport type used to connect to gluster
> management daemon (glusterd). Valid transport types are
> tcp, unix and rdma. If the transport type isn't specified, then tcp
> type is assumed.
>
> 'server' specifies the server where the volume file specification for
> the given volume resides. This can be either hostname or ipv4 address
> or ipv6 address. ipv6 address needs to be with in square brackets [ ].
> If transport type is 'unix', then server field is ignored, but the
> 'socket' field needs to be populated with the path to unix domain
> socket.
>
> 'port' is the port number on which glusterd is listening. This is optional
> and if not specified, QEMU will send 0 which will make gluster to use the
> default port. port is ignored for unix type of transport.
>
> 'volname' is the name of the gluster volume which contains the VM image.
>
> 'image' is the path to the actual VM image that resides on gluster volume.
>
> Examples:
>
> file=gluster://1.2.3.4/testvol/a.img
> file=gluster+tcp://1.2.3.4/testvol/a.img
> file=gluster+tcp://1.2.3.4:24007/testvol/dir/a.img
> file=gluster+tcp://[1:2:3:4:5:6:7:8]/testvol/dir/a.img
> file=gluster+tcp://[1:2:3:4:5:6:7:8]:24007/testvol/dir/a.img
> file=gluster+tcp://server.domain.com:24007/testvol/dir/a.img
> file=gluster+unix:///testvol/dir/a.img?socket=/tmp/glusterd.socket
> file=gluster+rdma://1.2.3.4:24007/testvol/a.img
>
> Signed-off-by: Bharata B Rao <bharata@linux.vnet.ibm.com>
> ---
>
> block/Makefile.objs | 1
> block/gluster.c | 694 +++++++++++++++++++++++++++++++++++++++++++++++++++
> 2 files changed, 695 insertions(+), 0 deletions(-)
> create mode 100644 block/gluster.c
>
>
> diff --git a/block/Makefile.objs b/block/Makefile.objs
> index b5754d3..a1ae67f 100644
> --- a/block/Makefile.objs
> +++ b/block/Makefile.objs
> @@ -9,3 +9,4 @@ block-obj-$(CONFIG_POSIX) += raw-posix.o
> block-obj-$(CONFIG_LIBISCSI) += iscsi.o
> block-obj-$(CONFIG_CURL) += curl.o
> block-obj-$(CONFIG_RBD) += rbd.o
> +block-obj-$(CONFIG_GLUSTERFS) += gluster.o
> diff --git a/block/gluster.c b/block/gluster.c
> new file mode 100644
> index 0000000..0de3286
> --- /dev/null
> +++ b/block/gluster.c
> @@ -0,0 +1,694 @@
> +/*
> + * GlusterFS backend for QEMU
> + *
> + * Copyright (C) 2012 Bharata B Rao <bharata@linux.vnet.ibm.com>
> + *
> + * Pipe handling mechanism in AIO implementation is derived from
> + * block/rbd.c. Hence,
> + *
> + * Copyright (C) 2010-2011 Christian Brunner <chb@muc.de>,
> + * Josh Durgin <josh.durgin@dreamhost.com>
> + *
> + * This work is licensed under the terms of the GNU GPL, version 2. See
> + * the COPYING file in the top-level directory.
> + *
> + * Contributions after 2012-01-13 are licensed under the terms of the
> + * GNU GPL, version 2 or (at your option) any later version.
> + */
> +#include <glusterfs/api/glfs.h>
> +#include "block_int.h"
> +#include "qemu_socket.h"
> +
> +typedef struct GlusterAIOCB {
> + BlockDriverAIOCB common;
> + int64_t size;
> + int ret;
> + bool *finished;
> + QEMUBH *bh;
> +} GlusterAIOCB;
> +
> +typedef struct BDRVGlusterState {
> + struct glfs *glfs;
> + int fds[2];
> + struct glfs_fd *fd;
> + int qemu_aio_count;
> + int event_reader_pos;
> + GlusterAIOCB *event_acb;
> +} BDRVGlusterState;
> +
> +#define GLUSTER_FD_READ 0
> +#define GLUSTER_FD_WRITE 1
> +
> +#define GLUSTER_TRANSPORT_DEFAULT "gluster://"
> +#define GLUSTER_TRANSPORT_DEFAULT_SZ strlen(GLUSTER_TRANSPORT_DEFAULT)
> +#define GLUSTER_TRANSPORT_TCP "gluster+tcp://"
> +#define GLUSTER_TRANSPORT_TCP_SZ strlen(GLUSTER_TRANSPORT_TCP)
> +#define GLUSTER_TRANSPORT_UNIX "gluster+unix://"
> +#define GLUSTER_TRANSPORT_UNIX_SZ strlen(GLUSTER_TRANSPORT_UNIX)
> +#define GLUSTER_TRANSPORT_RDMA "gluster+rdma://"
> +#define GLUSTER_TRANSPORT_RDMA_SZ strlen(GLUSTER_TRANSPORT_RDMA)
> +
> +typedef struct GlusterURI {
> + char *server;
> + int port;
> + char *volname;
> + char *image;
> + char *transport;
> + bool is_unix;
> +} GlusterURI;
> +
> +static void qemu_gluster_uri_free(GlusterURI *uri)
> +{
> + g_free(uri->server);
> + g_free(uri->volname);
> + g_free(uri->image);
> + g_free(uri->transport);
> + g_free(uri);
> +}
> +
> +static int parse_socket(GlusterURI *uri, char *socket)
> +{
> + char *token, *saveptr;
> +
> + if (!socket) {
> + return 0;
> + }
> + token = strtok_r(socket, "=", &saveptr);
> + if (!token || strcmp(token, "socket")) {
> + return -EINVAL;
> + }
> + token = strtok_r(NULL, "=", &saveptr);
> + if (!token) {
> + return -EINVAL;
> + }
> + uri->server = g_strdup(token);
> + uri->is_unix = true;
> + return 0;
> +}
> +
> +static int parse_gluster_spec(GlusterURI *uri, char *spec)
> +{
> + char *token, *saveptr;
> + int ret;
> + QemuOpts *opts;
> + char *p, *q;
> +
> + /* transport */
> + p = spec;
> + if (!strncmp(p, GLUSTER_TRANSPORT_DEFAULT, GLUSTER_TRANSPORT_DEFAULT_SZ)) {
> + uri->transport = g_strdup("tcp");
> + p += GLUSTER_TRANSPORT_DEFAULT_SZ;
> + } else if (!strncmp(p, GLUSTER_TRANSPORT_TCP, GLUSTER_TRANSPORT_TCP_SZ)) {
> + uri->transport = g_strdup("tcp");
> + p += GLUSTER_TRANSPORT_TCP_SZ;
> + } else if (!strncmp(p, GLUSTER_TRANSPORT_UNIX, GLUSTER_TRANSPORT_UNIX_SZ)) {
> + uri->transport = g_strdup("unix");
> + p += GLUSTER_TRANSPORT_UNIX_SZ;
> + } else if (!strncmp(p, GLUSTER_TRANSPORT_RDMA, GLUSTER_TRANSPORT_RDMA_SZ)) {
Would look a bit nicer with strstart() form cutils.c instead of strncmp().
> + uri->transport = g_strdup("rdma");
> + p += GLUSTER_TRANSPORT_RDMA_SZ;
> + } else {
> + return -EINVAL;
> + }
> + q = p;
> +
> + /* server */
> + if (!strcmp(uri->transport, "unix")) {
> + if (!uri->is_unix) {
> + return -EINVAL;
> + }
> + } else {
> + if (uri->is_unix) {
> + return -EINVAL;
> + }
> + p = strchr(p, '/');
> + if (!p) {
> + return -EINVAL;
> + }
> + *p++ = '\0';
> + opts = qemu_opts_create(qemu_find_opts("inet"), NULL, 0, NULL);
> + ret = inet_parse(opts, q);
> + if (!ret) {
> + uri->server = g_strdup(qemu_opt_get(opts, "host"));
> + uri->port = strtoul(qemu_opt_get(opts, "port"), NULL, 0);
> + if (uri->port < 0) {
> + ret = -EINVAL;
> + }
> + }
> + qemu_opts_del(opts);
> + if (ret < 0) {
> + return -EINVAL;
> + }
> + }
> +
> + /* volname */
> + token = strtok_r(p, "/", &saveptr);
> + if (!token) {
> + return -EINVAL;
> + }
> + uri->volname = g_strdup(token);
> +
> + /* image */
> + token = strtok_r(NULL, "?", &saveptr);
> + if (!token) {
> + return -EINVAL;
> + }
> + uri->image = g_strdup(token);
> + return 0;
> +}
> +
> +/*
> + * file=gluster[+transport]://[server[:port]]/volname/image[?socket=...]
> + *
> + * 'gluster' is the protocol.
> + *
> + * 'transport' specifies the transport type used to connect to gluster
> + * management daemon (glusterd). Valid transport types are
> + * tcp, unix and rdma. If the transport type isn't specified, then tcp
> + * type is assumed.
> + *
> + * 'server' specifies the server where the volume file specification for
> + * the given volume resides. This can be either hostname or ipv4 address
> + * or ipv6 address. ipv6 address needs to be with in square brackets [ ].
> + * If transport type is 'unix', then server field is ignored, but the
> + * 'socket' field needs to be populated with the path to unix domain
> + * socket.
> + *
> + * 'port' is the port number on which glusterd is listening. This is optional
> + * and if not specified, QEMU will send 0 which will make gluster to use the
> + * default port. port is ignored for unix type of transport.
> + *
> + * 'volname' is the name of the gluster volume which contains the VM image.
> + *
> + * 'image' is the path to the actual VM image that resides on gluster volume.
> + *
> + * Examples:
> + *
> + * file=gluster://1.2.3.4/testvol/a.img
> + * file=gluster+tcp://1.2.3.4/testvol/a.img
> + * file=gluster+tcp://1.2.3.4:24007/testvol/dir/a.img
> + * file=gluster+tcp://[1:2:3:4:5:6:7:8]/testvol/dir/a.img
> + * file=gluster+tcp://[1:2:3:4:5:6:7:8]:24007/testvol/dir/a.img
> + * file=gluster+tcp://server.domain.com:24007/testvol/dir/a.img
> + * file=gluster+unix:///testvol/dir/a.img?socket=/tmp/glusterd.socket
> + * file=gluster+rdma://1.2.3.4:24007/testvol/a.img
> + */
> +static int qemu_gluster_parseuri(GlusterURI *uri, const char *filename)
> +{
> + char *token, *saveptr;
> + char *p, *q, *gluster_spec = NULL;
> + int ret = -EINVAL;
> +
> + p = q = g_strdup(filename);
Neither p nor q are changed, so one variable would be enough.
> +
> + /* Extract server, volname and image */
> + token = strtok_r(p, "?", &saveptr);
> + if (!token) {
> + goto out;
> + }
> + gluster_spec = g_strdup(token);
> +
> + /* socket */
> + token = strtok_r(NULL, "?", &saveptr);
> + ret = parse_socket(uri, token);
> + if (ret < 0) {
> + goto out;
> + }
The is_unix thing feels a bit backwards. You set it whenever an option
is present, and fail if a protocol other than gluster+unix:// is used.
Wouldn't it make more sense to set it depending on the protocol and then
check in the option parser if is_unix == true when there is a 'socket'
option? Otherwise adding non-unix options later is going to become hard.
The rest looks good now.
Kevin
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [Qemu-devel] [PATCH v7 2/5] sockets: Change inet_parse() to accept address specification without port
2012-09-18 13:31 ` Paolo Bonzini
@ 2012-09-18 14:08 ` Kevin Wolf
2012-09-20 6:30 ` Bharata B Rao
0 siblings, 1 reply; 24+ messages in thread
From: Kevin Wolf @ 2012-09-18 14:08 UTC (permalink / raw)
To: Paolo Bonzini
Cc: Anthony Liguori, Anand Avati, Vijay Bellur, Stefan Hajnoczi,
Amar Tumballi, qemu-devel, Markus Armbruster, Blue Swirl,
Avi Kivity, bharata
Am 18.09.2012 15:31, schrieb Paolo Bonzini:
> Il 18/09/2012 15:22, Kevin Wolf ha scritto:
>> Am 17.09.2012 17:23, schrieb Bharata B Rao:
>>> sockets: Change inet_parse() to accept address specification without port
>>>
>>> From: Bharata B Rao <bharata@linux.vnet.ibm.com>
>>>
>>> inet_parse() expects address:port. Change it to work without explicit port
>>> specification. In addition, don't depend solely on the return value of
>>
>> Things like "in addition" in a commit message are almost always a sign
>> that the patch should be split in two.
>>
>>> sscanf but also consider the value obtained for %n directive used in sscanf.
>>> This ensures that the scanning of malformed inet address isn't flagged as
>>> success.
>>
>> Can you give an example string that would be falsely accepted? To me the
>> old checks look fine (even though the new ones are a little bit easier
>> to read, so even if they don't fix anything, they might be worth doing).
>
> "localhost" would fail to be parsed:
>
> - if (2 != sscanf(str,"%64[^:]:%32[^,]%n",addr,port,&pos)) {
> + ret = sscanf(str, "%64[^:]%n:%32[^,]%n", addr, &addr_pos,
> + port, &port_pos);
> + if (addr_pos == -1 || ret == EOF) {
>
> because the : in the format string would not match and sscanf would
> return 1.
Yes, that's the part with making the port optional.
Bharata also claims that "scanning of malformed inet address" could
falsely succeed before, which I can't see (but which I suspect is what
the first two hunks of the patch are meant to address).
> However, is it correct to set the port unconditionally to an empty
> string? Your usecase makes sense, but perhaps the default port be
> passed as an extra parameter to inet_parse instead.
I thought about this, too, but didn't care enough to mention it. Now
that we're two, yes, I'd like adding a default port parameter.
Kevin
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [Qemu-devel] [PATCH v7 1/5] sockets: Make inet_parse() non static.
2012-09-18 12:57 ` Paolo Bonzini
@ 2012-09-20 5:57 ` Bharata B Rao
0 siblings, 0 replies; 24+ messages in thread
From: Bharata B Rao @ 2012-09-20 5:57 UTC (permalink / raw)
To: Paolo Bonzini
Cc: Kevin Wolf, Anthony Liguori, Anand Avati, Vijay Bellur,
Stefan Hajnoczi, Amar Tumballi, qemu-devel, Markus Armbruster,
Blue Swirl, Avi Kivity
On Tue, Sep 18, 2012 at 02:57:59PM +0200, Paolo Bonzini wrote:
> Il 18/09/2012 14:47, Kevin Wolf ha scritto:
> >> > Makefile.objs | 2 +-
> >> > qemu-config.c | 31 +++++++++++++++++++++++++++++++
> >> > qemu-sockets.c | 42 ++++++------------------------------------
> >> > qemu_socket.h | 1 +
> >> > 4 files changed, 39 insertions(+), 37 deletions(-)
> > Making the option list global sounds fine, but I don't think it should
> > be moved to qemu-config.c or added to vm_config_groups, which is
> > generally used for command line options and config file entries. At
> > least I can't imagine a command line option -inet would be very useful.
>
> Yes. Also, I'll send a pull-request for
> http://patchwork.ozlabs.org/patch/180237/ soon, so you might as well
> take that patch.
Ok, this patch is good enough for gluster usecase. I will include this in my
v8 post if its not upstream by that time.
Regards,
Bharata.
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [Qemu-devel] [PATCH v7 2/5] sockets: Change inet_parse() to accept address specification without port
2012-09-18 14:08 ` Kevin Wolf
@ 2012-09-20 6:30 ` Bharata B Rao
2012-09-20 6:51 ` Kevin Wolf
0 siblings, 1 reply; 24+ messages in thread
From: Bharata B Rao @ 2012-09-20 6:30 UTC (permalink / raw)
To: Kevin Wolf
Cc: Anthony Liguori, Anand Avati, Vijay Bellur, Stefan Hajnoczi,
Amar Tumballi, qemu-devel, Markus Armbruster, Blue Swirl,
Avi Kivity, Paolo Bonzini
On Tue, Sep 18, 2012 at 04:08:43PM +0200, Kevin Wolf wrote:
> Am 18.09.2012 15:31, schrieb Paolo Bonzini:
> > Il 18/09/2012 15:22, Kevin Wolf ha scritto:
> >> Am 17.09.2012 17:23, schrieb Bharata B Rao:
> >>> sockets: Change inet_parse() to accept address specification without port
> >>>
> >>> From: Bharata B Rao <bharata@linux.vnet.ibm.com>
> >>>
> >>> inet_parse() expects address:port. Change it to work without explicit port
> >>> specification. In addition, don't depend solely on the return value of
> >>
> >> Things like "in addition" in a commit message are almost always a sign
> >> that the patch should be split in two.
Both kind of go together. Not depending on return value of sscanf gives us
the ability to have the port as optional parameter. Will rephrase the patch
description accordingly.
> >>
> >>> sscanf but also consider the value obtained for %n directive used in sscanf.
> >>> This ensures that the scanning of malformed inet address isn't flagged as
> >>> success.
> >>
> >> Can you give an example string that would be falsely accepted? To me the
> >> old checks look fine (even though the new ones are a little bit easier
> >> to read, so even if they don't fix anything, they might be worth doing).
> >
> > "localhost" would fail to be parsed:
> >
> > - if (2 != sscanf(str,"%64[^:]:%32[^,]%n",addr,port,&pos)) {
> > + ret = sscanf(str, "%64[^:]%n:%32[^,]%n", addr, &addr_pos,
> > + port, &port_pos);
> > + if (addr_pos == -1 || ret == EOF) {
> >
> > because the : in the format string would not match and sscanf would
> > return 1.
>
> Yes, that's the part with making the port optional.
>
> Bharata also claims that "scanning of malformed inet address" could
> falsely succeed before, which I can't see (but which I suspect is what
> the first two hunks of the patch are meant to address).
For malformed ipv6 address like "[1:2:3:4:5", sccanf in inet_parse
returns 1 (which means 1 input item successfully matched and assigned)
The current inet_parse code would eventually fail it since it checks for
return value of 2, but when I am making port optional, I can't depend
on return value of 1 or 2 since sscanf can return 1 for such incomplete ipv6
addresses too. Note that in the above case, though sscanf returned 1, the
pos argument remains unchanged indicating that it couldn't really parse
any input correctly.
So in summary, when I said scanning of malformed inet address succeeded
earlier, I should have been more specific by saying that sscanf in inet_parse
could return success for malformed ipv6 strings.
>
> > However, is it correct to set the port unconditionally to an empty
> > string? Your usecase makes sense, but perhaps the default port be
> > passed as an extra parameter to inet_parse instead.
>
> I thought about this, too, but didn't care enough to mention it. Now
> that we're two, yes, I'd like adding a default port parameter.
So you are saying that lets change inet_parse to look like this:
int inet_parse(QemuOpts *opts, const char *str, int port)
and if @str didn't specify a port explicitly, use @port to populate
the port option in @opts ? Other callers of inet_parse are inet_listen and
inet_connect. What should be the default port values from these callers ?
Regards,
Bharata.
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [Qemu-devel] [PATCH v7 5/5] block: Support GlusterFS as a QEMU block backend.
2012-09-18 14:01 ` Kevin Wolf
@ 2012-09-20 6:41 ` Bharata B Rao
2012-09-20 7:53 ` Paolo Bonzini
0 siblings, 1 reply; 24+ messages in thread
From: Bharata B Rao @ 2012-09-20 6:41 UTC (permalink / raw)
To: Kevin Wolf
Cc: Anthony Liguori, Anand Avati, Vijay Bellur, Stefan Hajnoczi,
Amar Tumballi, qemu-devel, Markus Armbruster, Blue Swirl,
Avi Kivity, Paolo Bonzini
On Tue, Sep 18, 2012 at 04:01:58PM +0200, Kevin Wolf wrote:
> > +
> > +#define GLUSTER_TRANSPORT_DEFAULT "gluster://"
> > +#define GLUSTER_TRANSPORT_DEFAULT_SZ strlen(GLUSTER_TRANSPORT_DEFAULT)
> > +#define GLUSTER_TRANSPORT_TCP "gluster+tcp://"
> > +#define GLUSTER_TRANSPORT_TCP_SZ strlen(GLUSTER_TRANSPORT_TCP)
> > +#define GLUSTER_TRANSPORT_UNIX "gluster+unix://"
> > +#define GLUSTER_TRANSPORT_UNIX_SZ strlen(GLUSTER_TRANSPORT_UNIX)
> > +#define GLUSTER_TRANSPORT_RDMA "gluster+rdma://"
> > +#define GLUSTER_TRANSPORT_RDMA_SZ strlen(GLUSTER_TRANSPORT_RDMA)
> > +
> > +
> > +static int parse_gluster_spec(GlusterURI *uri, char *spec)
> > +{
> > + char *token, *saveptr;
> > + int ret;
> > + QemuOpts *opts;
> > + char *p, *q;
> > +
> > + /* transport */
> > + p = spec;
> > + if (!strncmp(p, GLUSTER_TRANSPORT_DEFAULT, GLUSTER_TRANSPORT_DEFAULT_SZ)) {
> > + uri->transport = g_strdup("tcp");
> > + p += GLUSTER_TRANSPORT_DEFAULT_SZ;
> > + } else if (!strncmp(p, GLUSTER_TRANSPORT_TCP, GLUSTER_TRANSPORT_TCP_SZ)) {
> > + uri->transport = g_strdup("tcp");
> > + p += GLUSTER_TRANSPORT_TCP_SZ;
> > + } else if (!strncmp(p, GLUSTER_TRANSPORT_UNIX, GLUSTER_TRANSPORT_UNIX_SZ)) {
> > + uri->transport = g_strdup("unix");
> > + p += GLUSTER_TRANSPORT_UNIX_SZ;
> > + } else if (!strncmp(p, GLUSTER_TRANSPORT_RDMA, GLUSTER_TRANSPORT_RDMA_SZ)) {
>
> Would look a bit nicer with strstart() form cutils.c instead of strncmp().
strstart() works with const char pointers, but I have char pointers here
which I need to modify.
> > +static int qemu_gluster_parseuri(GlusterURI *uri, const char *filename)
> > +{
> > + char *token, *saveptr;
> > + char *p, *q, *gluster_spec = NULL;
> > + int ret = -EINVAL;
> > +
> > + p = q = g_strdup(filename);
>
> Neither p nor q are changed, so one variable would be enough.
Right, will fix.
>
> > +
> > + /* Extract server, volname and image */
> > + token = strtok_r(p, "?", &saveptr);
> > + if (!token) {
> > + goto out;
> > + }
> > + gluster_spec = g_strdup(token);
> > +
> > + /* socket */
> > + token = strtok_r(NULL, "?", &saveptr);
> > + ret = parse_socket(uri, token);
> > + if (ret < 0) {
> > + goto out;
> > + }
>
> The is_unix thing feels a bit backwards. You set it whenever an option
> is present, and fail if a protocol other than gluster+unix:// is used.
>
> Wouldn't it make more sense to set it depending on the protocol and then
> check in the option parser if is_unix == true when there is a 'socket'
> option? Otherwise adding non-unix options later is going to become hard.
I see your point, will change this.
>
> The rest looks good now.
Thanks for the review.
Regards,
Bharata.
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [Qemu-devel] [PATCH v7 2/5] sockets: Change inet_parse() to accept address specification without port
2012-09-20 6:30 ` Bharata B Rao
@ 2012-09-20 6:51 ` Kevin Wolf
0 siblings, 0 replies; 24+ messages in thread
From: Kevin Wolf @ 2012-09-20 6:51 UTC (permalink / raw)
To: bharata
Cc: Anthony Liguori, Anand Avati, Vijay Bellur, Stefan Hajnoczi,
Amar Tumballi, qemu-devel, Markus Armbruster, Blue Swirl,
Avi Kivity, Paolo Bonzini
Am 20.09.2012 08:30, schrieb Bharata B Rao:
> On Tue, Sep 18, 2012 at 04:08:43PM +0200, Kevin Wolf wrote:
>> Am 18.09.2012 15:31, schrieb Paolo Bonzini:
>>> Il 18/09/2012 15:22, Kevin Wolf ha scritto:
>>>> Am 17.09.2012 17:23, schrieb Bharata B Rao:
>>>>> sockets: Change inet_parse() to accept address specification without port
>>>>>
>>>>> From: Bharata B Rao <bharata@linux.vnet.ibm.com>
>>>>>
>>>>> inet_parse() expects address:port. Change it to work without explicit port
>>>>> specification. In addition, don't depend solely on the return value of
>>>>
>>>> Things like "in addition" in a commit message are almost always a sign
>>>> that the patch should be split in two.
>
> Both kind of go together. Not depending on return value of sscanf gives us
> the ability to have the port as optional parameter. Will rephrase the patch
> description accordingly.
>
>>>>
>>>>> sscanf but also consider the value obtained for %n directive used in sscanf.
>>>>> This ensures that the scanning of malformed inet address isn't flagged as
>>>>> success.
>>>>
>>>> Can you give an example string that would be falsely accepted? To me the
>>>> old checks look fine (even though the new ones are a little bit easier
>>>> to read, so even if they don't fix anything, they might be worth doing).
>>>
>>> "localhost" would fail to be parsed:
>>>
>>> - if (2 != sscanf(str,"%64[^:]:%32[^,]%n",addr,port,&pos)) {
>>> + ret = sscanf(str, "%64[^:]%n:%32[^,]%n", addr, &addr_pos,
>>> + port, &port_pos);
>>> + if (addr_pos == -1 || ret == EOF) {
>>>
>>> because the : in the format string would not match and sscanf would
>>> return 1.
>>
>> Yes, that's the part with making the port optional.
>>
>> Bharata also claims that "scanning of malformed inet address" could
>> falsely succeed before, which I can't see (but which I suspect is what
>> the first two hunks of the patch are meant to address).
>
> For malformed ipv6 address like "[1:2:3:4:5", sccanf in inet_parse
> returns 1 (which means 1 input item successfully matched and assigned)
> The current inet_parse code would eventually fail it since it checks for
> return value of 2, but when I am making port optional, I can't depend
> on return value of 1 or 2 since sscanf can return 1 for such incomplete ipv6
> addresses too. Note that in the above case, though sscanf returned 1, the
> pos argument remains unchanged indicating that it couldn't really parse
> any input correctly.
>
> So in summary, when I said scanning of malformed inet address succeeded
> earlier, I should have been more specific by saying that sscanf in inet_parse
> could return success for malformed ipv6 strings.
Ah, so you're talking about a potential problem after making the port
optional, not about a real, existing bug in the code as it is today?
Please rephrase the commit message then; actually I wouldn't even care
to talk about the reason for the scanf changes, they are obviously
required for making the port optional.
>>> However, is it correct to set the port unconditionally to an empty
>>> string? Your usecase makes sense, but perhaps the default port be
>>> passed as an extra parameter to inet_parse instead.
>>
>> I thought about this, too, but didn't care enough to mention it. Now
>> that we're two, yes, I'd like adding a default port parameter.
>
> So you are saying that lets change inet_parse to look like this:
>
> int inet_parse(QemuOpts *opts, const char *str, int port)
>
> and if @str didn't specify a port explicitly, use @port to populate
> the port option in @opts ? Other callers of inet_parse are inet_listen and
> inet_connect. What should be the default port values from these callers ?
Yes, but make it 'int default_port'.
You could make default_port = -1 retain the current behaviour, i.e. port
is required in str (document this in a comment before inet_listen). This
would probably be the right thing to pass for existing callers.
Kevin
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [Qemu-devel] [PATCH v7 5/5] block: Support GlusterFS as a QEMU block backend.
2012-09-20 6:41 ` Bharata B Rao
@ 2012-09-20 7:53 ` Paolo Bonzini
2012-09-20 8:20 ` Paolo Bonzini
0 siblings, 1 reply; 24+ messages in thread
From: Paolo Bonzini @ 2012-09-20 7:53 UTC (permalink / raw)
To: bharata
Cc: Kevin Wolf, Anthony Liguori, Anand Avati, Vijay Bellur,
Stefan Hajnoczi, Amar Tumballi, qemu-devel, Markus Armbruster,
Blue Swirl, Avi Kivity
Il 20/09/2012 08:41, Bharata B Rao ha scritto:
>> > Would look a bit nicer with strstart() form cutils.c instead of strncmp().
> strstart() works with const char pointers, but I have char pointers here
> which I need to modify.
You can pass a char* to a function that accepts const char*. In your
case, the last argument to strstart would be NULL.
Paolo
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [Qemu-devel] [PATCH v7 5/5] block: Support GlusterFS as a QEMU block backend.
2012-09-20 7:53 ` Paolo Bonzini
@ 2012-09-20 8:20 ` Paolo Bonzini
2012-09-20 9:12 ` Bharata B Rao
0 siblings, 1 reply; 24+ messages in thread
From: Paolo Bonzini @ 2012-09-20 8:20 UTC (permalink / raw)
Cc: Kevin Wolf, Anthony Liguori, Anand Avati, Vijay Bellur,
Stefan Hajnoczi, Amar Tumballi, qemu-devel, Markus Armbruster,
Blue Swirl, Avi Kivity, bharata
Il 20/09/2012 09:53, Paolo Bonzini ha scritto:
>>>> Would look a bit nicer with strstart() form cutils.c instead of strncmp().
>> > strstart() works with const char pointers, but I have char pointers here
>> > which I need to modify.
> You can pass a char* to a function that accepts const char*. In your
> case, the last argument to strstart would be NULL.
As you pointed out on IRC, you meant the last argument. I don't think
it would be a problem to cast that from char ** to const char **.
Perhaps it would be cleaner to make qemu_gluster_parseuri and
parse_gluster_spec accept a const char *. You can replace strtok_r +
g_strdup with strspn/strcspn followed by g_strndup.
BTW, here the second strtok_r needs to stop at "&".
> +static int parse_socket(GlusterURI *uri, char *socket)
> +{
> + char *token, *saveptr;
> +
> + if (!socket) {
> + return 0;
> + }
> + token = strtok_r(socket, "=", &saveptr);
> + if (!token || strcmp(token, "socket")) {
> + return -EINVAL;
> + }
> + token = strtok_r(NULL, "=", &saveptr);
> + if (!token) {
> + return -EINVAL;
> + }
And the same for the second strtok_r here too:
> + /* socket */
> + token = strtok_r(NULL, "?", &saveptr);
> + ret = parse_socket(uri, token);
> + if (ret < 0) {
> + goto out;
> + }
> +
> + /* Flag error for extra options */
> + token = strtok_r(NULL, "?", &saveptr);
> + if (token) {
> + ret = -EINVAL;
> + goto out;
> + }
> +
Paolo
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [Qemu-devel] [PATCH v7 5/5] block: Support GlusterFS as a QEMU block backend.
2012-09-20 8:20 ` Paolo Bonzini
@ 2012-09-20 9:12 ` Bharata B Rao
2012-09-20 9:14 ` Paolo Bonzini
2012-09-20 9:34 ` Kevin Wolf
0 siblings, 2 replies; 24+ messages in thread
From: Bharata B Rao @ 2012-09-20 9:12 UTC (permalink / raw)
To: Paolo Bonzini
Cc: Kevin Wolf, Anthony Liguori, Anand Avati, Vijay Bellur,
Stefan Hajnoczi, Amar Tumballi, qemu-devel, Markus Armbruster,
Blue Swirl, Avi Kivity
On Thu, Sep 20, 2012 at 10:20:33AM +0200, Paolo Bonzini wrote:
> Il 20/09/2012 09:53, Paolo Bonzini ha scritto:
> >>>> Would look a bit nicer with strstart() form cutils.c instead of strncmp().
> >> > strstart() works with const char pointers, but I have char pointers here
> >> > which I need to modify.
> > You can pass a char* to a function that accepts const char*. In your
> > case, the last argument to strstart would be NULL.
>
> As you pointed out on IRC, you meant the last argument. I don't think
> it would be a problem to cast that from char ** to const char **.
>
> Perhaps it would be cleaner to make qemu_gluster_parseuri and
> parse_gluster_spec accept a const char *. You can replace strtok_r +
> g_strdup with strspn/strcspn followed by g_strndup.
I feel the current approach of using the combination of strncmp, strtok_r
and g_strdup should be good enough.
But if you feel and insist that the right way to do this is to use the
combination of strstart, strspn and g_strndup, I could give it a try.
Regards,
Bharata.
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [Qemu-devel] [PATCH v7 5/5] block: Support GlusterFS as a QEMU block backend.
2012-09-20 9:12 ` Bharata B Rao
@ 2012-09-20 9:14 ` Paolo Bonzini
2012-09-20 9:34 ` Kevin Wolf
1 sibling, 0 replies; 24+ messages in thread
From: Paolo Bonzini @ 2012-09-20 9:14 UTC (permalink / raw)
To: bharata
Cc: Kevin Wolf, Anthony Liguori, Anand Avati, Vijay Bellur,
Stefan Hajnoczi, Amar Tumballi, qemu-devel, Markus Armbruster,
Blue Swirl, Avi Kivity
Il 20/09/2012 11:12, Bharata B Rao ha scritto:
>> > Perhaps it would be cleaner to make qemu_gluster_parseuri and
>> > parse_gluster_spec accept a const char *. You can replace strtok_r +
>> > g_strdup with strspn/strcspn followed by g_strndup.
> I feel the current approach of using the combination of strncmp, strtok_r
> and g_strdup should be good enough.
>
> But if you feel and insist that the right way to do this is to use the
> combination of strstart, strspn and g_strndup, I could give it a try.
I won't insist on anything :) but please check the delimiters as
mentioned in the other message.
Paolo
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [Qemu-devel] [PATCH v7 5/5] block: Support GlusterFS as a QEMU block backend.
2012-09-20 9:12 ` Bharata B Rao
2012-09-20 9:14 ` Paolo Bonzini
@ 2012-09-20 9:34 ` Kevin Wolf
2012-09-20 15:08 ` Paolo Bonzini
1 sibling, 1 reply; 24+ messages in thread
From: Kevin Wolf @ 2012-09-20 9:34 UTC (permalink / raw)
To: bharata
Cc: Anthony Liguori, Anand Avati, Vijay Bellur, Stefan Hajnoczi,
Amar Tumballi, qemu-devel, Markus Armbruster, Blue Swirl,
Avi Kivity, Paolo Bonzini
Am 20.09.2012 11:12, schrieb Bharata B Rao:
> On Thu, Sep 20, 2012 at 10:20:33AM +0200, Paolo Bonzini wrote:
>> Il 20/09/2012 09:53, Paolo Bonzini ha scritto:
>>>>>> Would look a bit nicer with strstart() form cutils.c instead of strncmp().
>>>>> strstart() works with const char pointers, but I have char pointers here
>>>>> which I need to modify.
>>> You can pass a char* to a function that accepts const char*. In your
>>> case, the last argument to strstart would be NULL.
>>
>> As you pointed out on IRC, you meant the last argument. I don't think
>> it would be a problem to cast that from char ** to const char **.
>>
>> Perhaps it would be cleaner to make qemu_gluster_parseuri and
>> parse_gluster_spec accept a const char *. You can replace strtok_r +
>> g_strdup with strspn/strcspn followed by g_strndup.
>
> I feel the current approach of using the combination of strncmp, strtok_r
> and g_strdup should be good enough.
Now that Paolo agreed that there wouldn't be a problem with casting,
let's use strstart with a cast to (const char**) for the third
parameter. Using strtok_r is okay with me.
Kevin
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [Qemu-devel] [PATCH v7 5/5] block: Support GlusterFS as a QEMU block backend.
2012-09-20 9:34 ` Kevin Wolf
@ 2012-09-20 15:08 ` Paolo Bonzini
2012-09-21 3:50 ` Bharata B Rao
0 siblings, 1 reply; 24+ messages in thread
From: Paolo Bonzini @ 2012-09-20 15:08 UTC (permalink / raw)
To: Kevin Wolf
Cc: Anthony Liguori, Anand Avati, Vijay Bellur, Stefan Hajnoczi,
Amar Tumballi, qemu-devel, Markus Armbruster, Blue Swirl,
Avi Kivity, bharata
[-- Attachment #1: Type: text/plain, Size: 1856 bytes --]
Il 20/09/2012 11:34, Kevin Wolf ha scritto:
> Am 20.09.2012 11:12, schrieb Bharata B Rao:
>> On Thu, Sep 20, 2012 at 10:20:33AM +0200, Paolo Bonzini wrote:
>>> Il 20/09/2012 09:53, Paolo Bonzini ha scritto:
>>>>>>> Would look a bit nicer with strstart() form cutils.c instead of strncmp().
>>>>>> strstart() works with const char pointers, but I have char pointers here
>>>>>> which I need to modify.
>>>> You can pass a char* to a function that accepts const char*. In your
>>>> case, the last argument to strstart would be NULL.
>>>
>>> As you pointed out on IRC, you meant the last argument. I don't think
>>> it would be a problem to cast that from char ** to const char **.
>>>
>>> Perhaps it would be cleaner to make qemu_gluster_parseuri and
>>> parse_gluster_spec accept a const char *. You can replace strtok_r +
>>> g_strdup with strspn/strcspn followed by g_strndup.
>>
>> I feel the current approach of using the combination of strncmp, strtok_r
>> and g_strdup should be good enough.
>
> Now that Paolo agreed that there wouldn't be a problem with casting,
> let's use strstart with a cast to (const char**) for the third
> parameter. Using strtok_r is okay with me.
Just shooting around a possibility: why reinvent the wheel poorly if we
can use a full-blown URI parsing library? The libxml2 one is very good
and easy to use.
It is also pretty much self-contained and has hardly seen a commit in 3
years, so we can even make it completely self-contained and keep it in
tree. See attachment, which also includes some functions taken from
libvirt to parse query parameters.
(I know this is controversial and would rather add a build dependency,
but at the same time it is tempting not to).
Paolo
ps: to test the attachment: gcc -I/usr/include/libxml2
-I/usr/include/glib-2.0 -I/usr/lib64/glib-2.0/include uri.c -lglib-2.0
&& ./a.out
[-- Attachment #2: uri.c --]
[-- Type: text/x-c, Size: 59119 bytes --]
/**
* uri.c: set of generic URI related routines
*
* Reference: RFCs 3986, 2732 and 2373
*
* Copyright (C) 1998-2003 Daniel Veillard. All Rights Reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* DANIEL VEILLARD BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* Except as contained in this notice, the name of Daniel Veillard shall not
* be used in advertising or otherwise to promote the sale, use or other
* dealings in this Software without prior written authorization from him.
*
* daniel@veillard.com
*
**
*
* Copyright (C) 2007, 2009-2010 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Authors:
* Richard W.M. Jones <rjones@redhat.com>
*
*/
#include <glib.h>
#include <string.h>
#include <stdio.h>
#include "uri.h"
static void uri_clean(URI *uri);
/*
* Old rule from 2396 used in legacy handling code
* alpha = lowalpha | upalpha
*/
#define IS_ALPHA(x) (IS_LOWALPHA(x) || IS_UPALPHA(x))
/*
* lowalpha = "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" |
* "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" |
* "u" | "v" | "w" | "x" | "y" | "z"
*/
#define IS_LOWALPHA(x) (((x) >= 'a') && ((x) <= 'z'))
/*
* upalpha = "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" |
* "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" | "S" | "T" |
* "U" | "V" | "W" | "X" | "Y" | "Z"
*/
#define IS_UPALPHA(x) (((x) >= 'A') && ((x) <= 'Z'))
#ifdef IS_DIGIT
#undef IS_DIGIT
#endif
/*
* digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
*/
#define IS_DIGIT(x) (((x) >= '0') && ((x) <= '9'))
/*
* alphanum = alpha | digit
*/
#define IS_ALPHANUM(x) (IS_ALPHA(x) || IS_DIGIT(x))
/*
* mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
*/
#define IS_MARK(x) (((x) == '-') || ((x) == '_') || ((x) == '.') || \
((x) == '!') || ((x) == '~') || ((x) == '*') || ((x) == '\'') || \
((x) == '(') || ((x) == ')'))
/*
* unwise = "{" | "}" | "|" | "\" | "^" | "`"
*/
#define IS_UNWISE(p) \
(((*(p) == '{')) || ((*(p) == '}')) || ((*(p) == '|')) || \
((*(p) == '\\')) || ((*(p) == '^')) || ((*(p) == '[')) || \
((*(p) == ']')) || ((*(p) == '`')))
/*
* reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | "," |
* "[" | "]"
*/
#define IS_RESERVED(x) (((x) == ';') || ((x) == '/') || ((x) == '?') || \
((x) == ':') || ((x) == '@') || ((x) == '&') || ((x) == '=') || \
((x) == '+') || ((x) == '$') || ((x) == ',') || ((x) == '[') || \
((x) == ']'))
/*
* unreserved = alphanum | mark
*/
#define IS_UNRESERVED(x) (IS_ALPHANUM(x) || IS_MARK(x))
/*
* Skip to next pointer char, handle escaped sequences
*/
#define NEXT(p) ((*p == '%')? p += 3 : p++)
/*
* Productions from the spec.
*
* authority = server | reg_name
* reg_name = 1*( unreserved | escaped | "$" | "," |
* ";" | ":" | "@" | "&" | "=" | "+" )
*
* path = [ abs_path | opaque_part ]
*/
/************************************************************************
* *
* RFC 3986 parser *
* *
************************************************************************/
#define ISA_DIGIT(p) ((*(p) >= '0') && (*(p) <= '9'))
#define ISA_ALPHA(p) (((*(p) >= 'a') && (*(p) <= 'z')) || \
((*(p) >= 'A') && (*(p) <= 'Z')))
#define ISA_HEXDIG(p) \
(ISA_DIGIT(p) || ((*(p) >= 'a') && (*(p) <= 'f')) || \
((*(p) >= 'A') && (*(p) <= 'F')))
/*
* sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
* / "*" / "+" / "," / ";" / "="
*/
#define ISA_SUB_DELIM(p) \
(((*(p) == '!')) || ((*(p) == '$')) || ((*(p) == '&')) || \
((*(p) == '(')) || ((*(p) == ')')) || ((*(p) == '*')) || \
((*(p) == '+')) || ((*(p) == ',')) || ((*(p) == ';')) || \
((*(p) == '=')) || ((*(p) == '\'')))
/*
* gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"
*/
#define ISA_GEN_DELIM(p) \
(((*(p) == ':')) || ((*(p) == '/')) || ((*(p) == '?')) || \
((*(p) == '#')) || ((*(p) == '[')) || ((*(p) == ']')) || \
((*(p) == '@')))
/*
* reserved = gen-delims / sub-delims
*/
#define ISA_RESERVED(p) (ISA_GEN_DELIM(p) || (ISA_SUB_DELIM(p)))
/*
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
*/
#define ISA_UNRESERVED(p) \
((ISA_ALPHA(p)) || (ISA_DIGIT(p)) || ((*(p) == '-')) || \
((*(p) == '.')) || ((*(p) == '_')) || ((*(p) == '~')))
/*
* pct-encoded = "%" HEXDIG HEXDIG
*/
#define ISA_PCT_ENCODED(p) \
((*(p) == '%') && (ISA_HEXDIG(p + 1)) && (ISA_HEXDIG(p + 2)))
/*
* pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
*/
#define ISA_PCHAR(p) \
(ISA_UNRESERVED(p) || ISA_PCT_ENCODED(p) || ISA_SUB_DELIM(p) || \
((*(p) == ':')) || ((*(p) == '@')))
/**
* rfc3986_parse_scheme:
* @uri: pointer to an URI structure
* @str: pointer to the string to analyze
*
* Parse an URI scheme
*
* ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
*
* Returns 0 or the error code
*/
static int
rfc3986_parse_scheme(URI *uri, const char **str) {
const char *cur;
if (str == NULL)
return(-1);
cur = *str;
if (!ISA_ALPHA(cur))
return(2);
cur++;
while (ISA_ALPHA(cur) || ISA_DIGIT(cur) ||
(*cur == '+') || (*cur == '-') || (*cur == '.')) cur++;
if (uri != NULL) {
if (uri->scheme != NULL) g_free(uri->scheme);
uri->scheme = g_strndup(*str, cur - *str);
}
*str = cur;
return(0);
}
/**
* rfc3986_parse_fragment:
* @uri: pointer to an URI structure
* @str: pointer to the string to analyze
*
* Parse the query part of an URI
*
* fragment = *( pchar / "/" / "?" )
* NOTE: the strict syntax as defined by 3986 does not allow '[' and ']'
* in the fragment identifier but this is used very broadly for
* xpointer scheme selection, so we are allowing it here to not break
* for example all the DocBook processing chains.
*
* Returns 0 or the error code
*/
static int
rfc3986_parse_fragment(URI *uri, const char **str)
{
const char *cur;
if (str == NULL)
return (-1);
cur = *str;
while ((ISA_PCHAR(cur)) || (*cur == '/') || (*cur == '?') ||
(*cur == '[') || (*cur == ']') ||
((uri != NULL) && (uri->cleanup & 1) && (IS_UNWISE(cur))))
NEXT(cur);
if (uri != NULL) {
if (uri->fragment != NULL)
g_free(uri->fragment);
if (uri->cleanup & 2)
uri->fragment = g_strndup(*str, cur - *str);
else
uri->fragment = uri_string_unescape(*str, cur - *str, NULL);
}
*str = cur;
return (0);
}
/**
* rfc3986_parse_query:
* @uri: pointer to an URI structure
* @str: pointer to the string to analyze
*
* Parse the query part of an URI
*
* query = *uric
*
* Returns 0 or the error code
*/
static int
rfc3986_parse_query(URI *uri, const char **str)
{
const char *cur;
if (str == NULL)
return (-1);
cur = *str;
while ((ISA_PCHAR(cur)) || (*cur == '/') || (*cur == '?') ||
((uri != NULL) && (uri->cleanup & 1) && (IS_UNWISE(cur))))
NEXT(cur);
if (uri != NULL) {
if (uri->query != NULL)
g_free (uri->query);
uri->query = g_strndup (*str, cur - *str);
}
*str = cur;
return (0);
}
/**
* rfc3986_parse_port:
* @uri: pointer to an URI structure
* @str: the string to analyze
*
* Parse a port part and fills in the appropriate fields
* of the @uri structure
*
* port = *DIGIT
*
* Returns 0 or the error code
*/
static int
rfc3986_parse_port(URI *uri, const char **str)
{
const char *cur = *str;
if (ISA_DIGIT(cur)) {
if (uri != NULL)
uri->port = 0;
while (ISA_DIGIT(cur)) {
if (uri != NULL)
uri->port = uri->port * 10 + (*cur - '0');
cur++;
}
*str = cur;
return(0);
}
return(1);
}
/**
* rfc3986_parse_user_info:
* @uri: pointer to an URI structure
* @str: the string to analyze
*
* Parse an user informations part and fills in the appropriate fields
* of the @uri structure
*
* userinfo = *( unreserved / pct-encoded / sub-delims / ":" )
*
* Returns 0 or the error code
*/
static int
rfc3986_parse_user_info(URI *uri, const char **str)
{
const char *cur;
cur = *str;
while (ISA_UNRESERVED(cur) || ISA_PCT_ENCODED(cur) ||
ISA_SUB_DELIM(cur) || (*cur == ':'))
NEXT(cur);
if (*cur == '@') {
if (uri != NULL) {
if (uri->user != NULL) g_free(uri->user);
if (uri->cleanup & 2)
uri->user = g_strndup(*str, cur - *str);
else
uri->user = uri_string_unescape(*str, cur - *str, NULL);
}
*str = cur;
return(0);
}
return(1);
}
/**
* rfc3986_parse_dec_octet:
* @str: the string to analyze
*
* dec-octet = DIGIT ; 0-9
* / %x31-39 DIGIT ; 10-99
* / "1" 2DIGIT ; 100-199
* / "2" %x30-34 DIGIT ; 200-249
* / "25" %x30-35 ; 250-255
*
* Skip a dec-octet.
*
* Returns 0 if found and skipped, 1 otherwise
*/
static int
rfc3986_parse_dec_octet(const char **str) {
const char *cur = *str;
if (!(ISA_DIGIT(cur)))
return(1);
if (!ISA_DIGIT(cur+1))
cur++;
else if ((*cur != '0') && (ISA_DIGIT(cur + 1)) && (!ISA_DIGIT(cur+2)))
cur += 2;
else if ((*cur == '1') && (ISA_DIGIT(cur + 1)) && (ISA_DIGIT(cur + 2)))
cur += 3;
else if ((*cur == '2') && (*(cur + 1) >= '0') &&
(*(cur + 1) <= '4') && (ISA_DIGIT(cur + 2)))
cur += 3;
else if ((*cur == '2') && (*(cur + 1) == '5') &&
(*(cur + 2) >= '0') && (*(cur + 1) <= '5'))
cur += 3;
else
return(1);
*str = cur;
return(0);
}
/**
* rfc3986_parse_host:
* @uri: pointer to an URI structure
* @str: the string to analyze
*
* Parse an host part and fills in the appropriate fields
* of the @uri structure
*
* host = IP-literal / IPv4address / reg-name
* IP-literal = "[" ( IPv6address / IPvFuture ) "]"
* IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet
* reg-name = *( unreserved / pct-encoded / sub-delims )
*
* Returns 0 or the error code
*/
static int
rfc3986_parse_host(URI *uri, const char **str)
{
const char *cur = *str;
const char *host;
host = cur;
/*
* IPv6 and future adressing scheme are enclosed between brackets
*/
if (*cur == '[') {
cur++;
while ((*cur != ']') && (*cur != 0))
cur++;
if (*cur != ']')
return(1);
cur++;
goto found;
}
/*
* try to parse an IPv4
*/
if (ISA_DIGIT(cur)) {
if (rfc3986_parse_dec_octet(&cur) != 0)
goto not_ipv4;
if (*cur != '.')
goto not_ipv4;
cur++;
if (rfc3986_parse_dec_octet(&cur) != 0)
goto not_ipv4;
if (*cur != '.')
goto not_ipv4;
if (rfc3986_parse_dec_octet(&cur) != 0)
goto not_ipv4;
if (*cur != '.')
goto not_ipv4;
if (rfc3986_parse_dec_octet(&cur) != 0)
goto not_ipv4;
goto found;
not_ipv4:
cur = *str;
}
/*
* then this should be a hostname which can be empty
*/
while (ISA_UNRESERVED(cur) || ISA_PCT_ENCODED(cur) || ISA_SUB_DELIM(cur))
NEXT(cur);
found:
if (uri != NULL) {
if (uri->authority != NULL) g_free(uri->authority);
uri->authority = NULL;
if (uri->server != NULL) g_free(uri->server);
if (cur != host) {
if (uri->cleanup & 2)
uri->server = g_strndup(host, cur - host);
else
uri->server = uri_string_unescape(host, cur - host, NULL);
} else
uri->server = NULL;
}
*str = cur;
return(0);
}
/**
* rfc3986_parse_authority:
* @uri: pointer to an URI structure
* @str: the string to analyze
*
* Parse an authority part and fills in the appropriate fields
* of the @uri structure
*
* authority = [ userinfo "@" ] host [ ":" port ]
*
* Returns 0 or the error code
*/
static int
rfc3986_parse_authority(URI *uri, const char **str)
{
const char *cur;
int ret;
cur = *str;
/*
* try to parse an userinfo and check for the trailing @
*/
ret = rfc3986_parse_user_info(uri, &cur);
if ((ret != 0) || (*cur != '@'))
cur = *str;
else
cur++;
ret = rfc3986_parse_host(uri, &cur);
if (ret != 0) return(ret);
if (*cur == ':') {
cur++;
ret = rfc3986_parse_port(uri, &cur);
if (ret != 0) return(ret);
}
*str = cur;
return(0);
}
/**
* rfc3986_parse_segment:
* @str: the string to analyze
* @forbid: an optional forbidden character
* @empty: allow an empty segment
*
* Parse a segment and fills in the appropriate fields
* of the @uri structure
*
* segment = *pchar
* segment-nz = 1*pchar
* segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" )
* ; non-zero-length segment without any colon ":"
*
* Returns 0 or the error code
*/
static int
rfc3986_parse_segment(const char **str, char forbid, int empty)
{
const char *cur;
cur = *str;
if (!ISA_PCHAR(cur)) {
if (empty)
return(0);
return(1);
}
while (ISA_PCHAR(cur) && (*cur != forbid))
NEXT(cur);
*str = cur;
return (0);
}
/**
* rfc3986_parse_path_ab_empty:
* @uri: pointer to an URI structure
* @str: the string to analyze
*
* Parse an path absolute or empty and fills in the appropriate fields
* of the @uri structure
*
* path-abempty = *( "/" segment )
*
* Returns 0 or the error code
*/
static int
rfc3986_parse_path_ab_empty(URI *uri, const char **str)
{
const char *cur;
int ret;
cur = *str;
while (*cur == '/') {
cur++;
ret = rfc3986_parse_segment(&cur, 0, 1);
if (ret != 0) return(ret);
}
if (uri != NULL) {
if (uri->path != NULL) g_free(uri->path);
if (*str != cur) {
if (uri->cleanup & 2)
uri->path = g_strndup(*str, cur - *str);
else
uri->path = uri_string_unescape(*str, cur - *str, NULL);
} else {
uri->path = NULL;
}
}
*str = cur;
return (0);
}
/**
* rfc3986_parse_path_absolute:
* @uri: pointer to an URI structure
* @str: the string to analyze
*
* Parse an path absolute and fills in the appropriate fields
* of the @uri structure
*
* path-absolute = "/" [ segment-nz *( "/" segment ) ]
*
* Returns 0 or the error code
*/
static int
rfc3986_parse_path_absolute(URI *uri, const char **str)
{
const char *cur;
int ret;
cur = *str;
if (*cur != '/')
return(1);
cur++;
ret = rfc3986_parse_segment(&cur, 0, 0);
if (ret == 0) {
while (*cur == '/') {
cur++;
ret = rfc3986_parse_segment(&cur, 0, 1);
if (ret != 0) return(ret);
}
}
if (uri != NULL) {
if (uri->path != NULL) g_free(uri->path);
if (cur != *str) {
if (uri->cleanup & 2)
uri->path = g_strndup(*str, cur - *str);
else
uri->path = uri_string_unescape(*str, cur - *str, NULL);
} else {
uri->path = NULL;
}
}
*str = cur;
return (0);
}
/**
* rfc3986_parse_path_rootless:
* @uri: pointer to an URI structure
* @str: the string to analyze
*
* Parse an path without root and fills in the appropriate fields
* of the @uri structure
*
* path-rootless = segment-nz *( "/" segment )
*
* Returns 0 or the error code
*/
static int
rfc3986_parse_path_rootless(URI *uri, const char **str)
{
const char *cur;
int ret;
cur = *str;
ret = rfc3986_parse_segment(&cur, 0, 0);
if (ret != 0) return(ret);
while (*cur == '/') {
cur++;
ret = rfc3986_parse_segment(&cur, 0, 1);
if (ret != 0) return(ret);
}
if (uri != NULL) {
if (uri->path != NULL) g_free(uri->path);
if (cur != *str) {
if (uri->cleanup & 2)
uri->path = g_strndup(*str, cur - *str);
else
uri->path = uri_string_unescape(*str, cur - *str, NULL);
} else {
uri->path = NULL;
}
}
*str = cur;
return (0);
}
/**
* rfc3986_parse_path_no_scheme:
* @uri: pointer to an URI structure
* @str: the string to analyze
*
* Parse an path which is not a scheme and fills in the appropriate fields
* of the @uri structure
*
* path-noscheme = segment-nz-nc *( "/" segment )
*
* Returns 0 or the error code
*/
static int
rfc3986_parse_path_no_scheme(URI *uri, const char **str)
{
const char *cur;
int ret;
cur = *str;
ret = rfc3986_parse_segment(&cur, ':', 0);
if (ret != 0) return(ret);
while (*cur == '/') {
cur++;
ret = rfc3986_parse_segment(&cur, 0, 1);
if (ret != 0) return(ret);
}
if (uri != NULL) {
if (uri->path != NULL) g_free(uri->path);
if (cur != *str) {
if (uri->cleanup & 2)
uri->path = g_strndup(*str, cur - *str);
else
uri->path = uri_string_unescape(*str, cur - *str, NULL);
} else {
uri->path = NULL;
}
}
*str = cur;
return (0);
}
/**
* rfc3986_parse_hier_part:
* @uri: pointer to an URI structure
* @str: the string to analyze
*
* Parse an hierarchical part and fills in the appropriate fields
* of the @uri structure
*
* hier-part = "//" authority path-abempty
* / path-absolute
* / path-rootless
* / path-empty
*
* Returns 0 or the error code
*/
static int
rfc3986_parse_hier_part(URI *uri, const char **str)
{
const char *cur;
int ret;
cur = *str;
if ((*cur == '/') && (*(cur + 1) == '/')) {
cur += 2;
ret = rfc3986_parse_authority(uri, &cur);
if (ret != 0) return(ret);
ret = rfc3986_parse_path_ab_empty(uri, &cur);
if (ret != 0) return(ret);
*str = cur;
return(0);
} else if (*cur == '/') {
ret = rfc3986_parse_path_absolute(uri, &cur);
if (ret != 0) return(ret);
} else if (ISA_PCHAR(cur)) {
ret = rfc3986_parse_path_rootless(uri, &cur);
if (ret != 0) return(ret);
} else {
/* path-empty is effectively empty */
if (uri != NULL) {
if (uri->path != NULL) g_free(uri->path);
uri->path = NULL;
}
}
*str = cur;
return (0);
}
/**
* rfc3986_parse_relative_ref:
* @uri: pointer to an URI structure
* @str: the string to analyze
*
* Parse an URI string and fills in the appropriate fields
* of the @uri structure
*
* relative-ref = relative-part [ "?" query ] [ "#" fragment ]
* relative-part = "//" authority path-abempty
* / path-absolute
* / path-noscheme
* / path-empty
*
* Returns 0 or the error code
*/
static int
rfc3986_parse_relative_ref(URI *uri, const char *str) {
int ret;
if ((*str == '/') && (*(str + 1) == '/')) {
str += 2;
ret = rfc3986_parse_authority(uri, &str);
if (ret != 0) return(ret);
ret = rfc3986_parse_path_ab_empty(uri, &str);
if (ret != 0) return(ret);
} else if (*str == '/') {
ret = rfc3986_parse_path_absolute(uri, &str);
if (ret != 0) return(ret);
} else if (ISA_PCHAR(str)) {
ret = rfc3986_parse_path_no_scheme(uri, &str);
if (ret != 0) return(ret);
} else {
/* path-empty is effectively empty */
if (uri != NULL) {
if (uri->path != NULL) g_free(uri->path);
uri->path = NULL;
}
}
if (*str == '?') {
str++;
ret = rfc3986_parse_query(uri, &str);
if (ret != 0) return(ret);
}
if (*str == '#') {
str++;
ret = rfc3986_parse_fragment(uri, &str);
if (ret != 0) return(ret);
}
if (*str != 0) {
uri_clean(uri);
return(1);
}
return(0);
}
/**
* rfc3986_parse:
* @uri: pointer to an URI structure
* @str: the string to analyze
*
* Parse an URI string and fills in the appropriate fields
* of the @uri structure
*
* scheme ":" hier-part [ "?" query ] [ "#" fragment ]
*
* Returns 0 or the error code
*/
static int
rfc3986_parse(URI *uri, const char *str) {
int ret;
ret = rfc3986_parse_scheme(uri, &str);
if (ret != 0) return(ret);
if (*str != ':') {
return(1);
}
str++;
ret = rfc3986_parse_hier_part(uri, &str);
if (ret != 0) return(ret);
if (*str == '?') {
str++;
ret = rfc3986_parse_query(uri, &str);
if (ret != 0) return(ret);
}
if (*str == '#') {
str++;
ret = rfc3986_parse_fragment(uri, &str);
if (ret != 0) return(ret);
}
if (*str != 0) {
uri_clean(uri);
return(1);
}
return(0);
}
/**
* rfc3986_parse_uri_reference:
* @uri: pointer to an URI structure
* @str: the string to analyze
*
* Parse an URI reference string and fills in the appropriate fields
* of the @uri structure
*
* URI-reference = URI / relative-ref
*
* Returns 0 or the error code
*/
static int
rfc3986_parse_uri_reference(URI *uri, const char *str) {
int ret;
if (str == NULL)
return(-1);
uri_clean(uri);
/*
* Try first to parse absolute refs, then fallback to relative if
* it fails.
*/
ret = rfc3986_parse(uri, str);
if (ret != 0) {
uri_clean(uri);
ret = rfc3986_parse_relative_ref(uri, str);
if (ret != 0) {
uri_clean(uri);
return(ret);
}
}
return(0);
}
/**
* uri_parse:
* @str: the URI string to analyze
*
* Parse an URI based on RFC 3986
*
* URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ]
*
* Returns a newly built URI or NULL in case of error
*/
URI *
uri_parse(const char *str) {
URI *uri;
int ret;
if (str == NULL)
return(NULL);
uri = uri_new();
if (uri != NULL) {
ret = rfc3986_parse_uri_reference(uri, str);
if (ret) {
uri_free(uri);
return(NULL);
}
}
return(uri);
}
/**
* uri_parse_into:
* @uri: pointer to an URI structure
* @str: the string to analyze
*
* Parse an URI reference string based on RFC 3986 and fills in the
* appropriate fields of the @uri structure
*
* URI-reference = URI / relative-ref
*
* Returns 0 or the error code
*/
int
uri_parse_into(URI *uri, const char *str) {
return(rfc3986_parse_uri_reference(uri, str));
}
/**
* uri_parse_raw:
* @str: the URI string to analyze
* @raw: if 1 unescaping of URI pieces are disabled
*
* Parse an URI but allows to keep intact the original fragments.
*
* URI-reference = URI / relative-ref
*
* Returns a newly built URI or NULL in case of error
*/
URI *
uri_parse_raw(const char *str, int raw) {
URI *uri;
int ret;
if (str == NULL)
return(NULL);
uri = uri_new();
if (uri != NULL) {
if (raw) {
uri->cleanup |= 2;
}
ret = uri_parse_into(uri, str);
if (ret) {
uri_free(uri);
return(NULL);
}
}
return(uri);
}
/************************************************************************
* *
* Generic URI structure functions *
* *
************************************************************************/
/**
* uri_new:
*
* Simply creates an empty URI
*
* Returns the new structure or NULL in case of error
*/
URI *
uri_new(void) {
URI *ret;
ret = (URI *) g_malloc(sizeof(URI));
memset(ret, 0, sizeof(URI));
return(ret);
}
/**
* realloc2n:
*
* Function to handle properly a reallocation when saving an URI
* Also imposes some limit on the length of an URI string output
*/
static char *
realloc2n(char *ret, int *max) {
char *temp;
int tmp;
tmp = *max * 2;
temp = g_realloc(ret, (tmp + 1));
*max = tmp;
return(temp);
}
/**
* uri_to_string:
* @uri: pointer to an URI
*
* Save the URI as an escaped string
*
* Returns a new string (to be deallocated by caller)
*/
char *
uri_to_string(URI *uri) {
char *ret = NULL;
char *temp;
const char *p;
int len;
int max;
if (uri == NULL) return(NULL);
max = 80;
ret = g_malloc(max + 1);
len = 0;
if (uri->scheme != NULL) {
p = uri->scheme;
while (*p != 0) {
if (len >= max) {
temp = realloc2n(ret, &max);
if (temp == NULL) goto mem_error;
ret = temp;
}
ret[len++] = *p++;
}
if (len >= max) {
temp = realloc2n(ret, &max);
if (temp == NULL) goto mem_error;
ret = temp;
}
ret[len++] = ':';
}
if (uri->opaque != NULL) {
p = uri->opaque;
while (*p != 0) {
if (len + 3 >= max) {
temp = realloc2n(ret, &max);
if (temp == NULL) goto mem_error;
ret = temp;
}
if (IS_RESERVED(*(p)) || IS_UNRESERVED(*(p)))
ret[len++] = *p++;
else {
int val = *(unsigned char *)p++;
int hi = val / 0x10, lo = val % 0x10;
ret[len++] = '%';
ret[len++] = hi + (hi > 9? 'A'-10 : '0');
ret[len++] = lo + (lo > 9? 'A'-10 : '0');
}
}
} else {
if (uri->server != NULL) {
if (len + 3 >= max) {
temp = realloc2n(ret, &max);
if (temp == NULL) goto mem_error;
ret = temp;
}
ret[len++] = '/';
ret[len++] = '/';
if (uri->user != NULL) {
p = uri->user;
while (*p != 0) {
if (len + 3 >= max) {
temp = realloc2n(ret, &max);
if (temp == NULL) goto mem_error;
ret = temp;
}
if ((IS_UNRESERVED(*(p))) ||
((*(p) == ';')) || ((*(p) == ':')) ||
((*(p) == '&')) || ((*(p) == '=')) ||
((*(p) == '+')) || ((*(p) == '$')) ||
((*(p) == ',')))
ret[len++] = *p++;
else {
int val = *(unsigned char *)p++;
int hi = val / 0x10, lo = val % 0x10;
ret[len++] = '%';
ret[len++] = hi + (hi > 9? 'A'-10 : '0');
ret[len++] = lo + (lo > 9? 'A'-10 : '0');
}
}
if (len + 3 >= max) {
temp = realloc2n(ret, &max);
if (temp == NULL) goto mem_error;
ret = temp;
}
ret[len++] = '@';
}
p = uri->server;
while (*p != 0) {
if (len >= max) {
temp = realloc2n(ret, &max);
if (temp == NULL) goto mem_error;
ret = temp;
}
ret[len++] = *p++;
}
if (uri->port > 0) {
if (len + 10 >= max) {
temp = realloc2n(ret, &max);
if (temp == NULL) goto mem_error;
ret = temp;
}
len += snprintf(&ret[len], max - len, ":%d", uri->port);
}
} else if (uri->authority != NULL) {
if (len + 3 >= max) {
temp = realloc2n(ret, &max);
if (temp == NULL) goto mem_error;
ret = temp;
}
ret[len++] = '/';
ret[len++] = '/';
p = uri->authority;
while (*p != 0) {
if (len + 3 >= max) {
temp = realloc2n(ret, &max);
if (temp == NULL) goto mem_error;
ret = temp;
}
if ((IS_UNRESERVED(*(p))) ||
((*(p) == '$')) || ((*(p) == ',')) || ((*(p) == ';')) ||
((*(p) == ':')) || ((*(p) == '@')) || ((*(p) == '&')) ||
((*(p) == '=')) || ((*(p) == '+')))
ret[len++] = *p++;
else {
int val = *(unsigned char *)p++;
int hi = val / 0x10, lo = val % 0x10;
ret[len++] = '%';
ret[len++] = hi + (hi > 9? 'A'-10 : '0');
ret[len++] = lo + (lo > 9? 'A'-10 : '0');
}
}
} else if (uri->scheme != NULL) {
if (len + 3 >= max) {
temp = realloc2n(ret, &max);
if (temp == NULL) goto mem_error;
ret = temp;
}
ret[len++] = '/';
ret[len++] = '/';
}
if (uri->path != NULL) {
p = uri->path;
/*
* the colon in file:///d: should not be escaped or
* Windows accesses fail later.
*/
if ((uri->scheme != NULL) &&
(p[0] == '/') &&
(((p[1] >= 'a') && (p[1] <= 'z')) ||
((p[1] >= 'A') && (p[1] <= 'Z'))) &&
(p[2] == ':') &&
(!strcmp(uri->scheme, "file"))) {
if (len + 3 >= max) {
temp = realloc2n(ret, &max);
if (temp == NULL) goto mem_error;
ret = temp;
}
ret[len++] = *p++;
ret[len++] = *p++;
ret[len++] = *p++;
}
while (*p != 0) {
if (len + 3 >= max) {
temp = realloc2n(ret, &max);
if (temp == NULL) goto mem_error;
ret = temp;
}
if ((IS_UNRESERVED(*(p))) || ((*(p) == '/')) ||
((*(p) == ';')) || ((*(p) == '@')) || ((*(p) == '&')) ||
((*(p) == '=')) || ((*(p) == '+')) || ((*(p) == '$')) ||
((*(p) == ',')))
ret[len++] = *p++;
else {
int val = *(unsigned char *)p++;
int hi = val / 0x10, lo = val % 0x10;
ret[len++] = '%';
ret[len++] = hi + (hi > 9? 'A'-10 : '0');
ret[len++] = lo + (lo > 9? 'A'-10 : '0');
}
}
}
if (uri->query != NULL) {
if (len + 1 >= max) {
temp = realloc2n(ret, &max);
if (temp == NULL) goto mem_error;
ret = temp;
}
ret[len++] = '?';
p = uri->query;
while (*p != 0) {
if (len + 1 >= max) {
temp = realloc2n(ret, &max);
if (temp == NULL) goto mem_error;
ret = temp;
}
ret[len++] = *p++;
}
}
}
if (uri->fragment != NULL) {
if (len + 3 >= max) {
temp = realloc2n(ret, &max);
if (temp == NULL) goto mem_error;
ret = temp;
}
ret[len++] = '#';
p = uri->fragment;
while (*p != 0) {
if (len + 3 >= max) {
temp = realloc2n(ret, &max);
if (temp == NULL) goto mem_error;
ret = temp;
}
if ((IS_UNRESERVED(*(p))) || (IS_RESERVED(*(p))))
ret[len++] = *p++;
else {
int val = *(unsigned char *)p++;
int hi = val / 0x10, lo = val % 0x10;
ret[len++] = '%';
ret[len++] = hi + (hi > 9? 'A'-10 : '0');
ret[len++] = lo + (lo > 9? 'A'-10 : '0');
}
}
}
if (len >= max) {
temp = realloc2n(ret, &max);
if (temp == NULL) goto mem_error;
ret = temp;
}
ret[len] = 0;
return(ret);
mem_error:
g_free(ret);
return(NULL);
}
/**
* uri_clean:
* @uri: pointer to an URI
*
* Make sure the URI struct is free of content
*/
static void
uri_clean(URI *uri) {
if (uri == NULL) return;
if (uri->scheme != NULL) g_free(uri->scheme);
uri->scheme = NULL;
if (uri->server != NULL) g_free(uri->server);
uri->server = NULL;
if (uri->user != NULL) g_free(uri->user);
uri->user = NULL;
if (uri->path != NULL) g_free(uri->path);
uri->path = NULL;
if (uri->fragment != NULL) g_free(uri->fragment);
uri->fragment = NULL;
if (uri->opaque != NULL) g_free(uri->opaque);
uri->opaque = NULL;
if (uri->authority != NULL) g_free(uri->authority);
uri->authority = NULL;
if (uri->query != NULL) g_free(uri->query);
uri->query = NULL;
}
/**
* uri_free:
* @uri: pointer to an URI
*
* Free up the URI struct
*/
void
uri_free(URI *uri) {
uri_clean(uri);
g_free(uri);
}
/************************************************************************
* *
* Helper functions *
* *
************************************************************************/
/**
* normalize_uri_path:
* @path: pointer to the path string
*
* Applies the 5 normalization steps to a path string--that is, RFC 2396
* Section 5.2, steps 6.c through 6.g.
*
* Normalization occurs directly on the string, no new allocation is done
*
* Returns 0 or an error code
*/
static int
normalize_uri_path(char *path) {
char *cur, *out;
if (path == NULL)
return(-1);
/* Skip all initial "/" chars. We want to get to the beginning of the
* first non-empty segment.
*/
cur = path;
while (cur[0] == '/')
++cur;
if (cur[0] == '\0')
return(0);
/* Keep everything we've seen so far. */
out = cur;
/*
* Analyze each segment in sequence for cases (c) and (d).
*/
while (cur[0] != '\0') {
/*
* c) All occurrences of "./", where "." is a complete path segment,
* are removed from the buffer string.
*/
if ((cur[0] == '.') && (cur[1] == '/')) {
cur += 2;
/* '//' normalization should be done at this point too */
while (cur[0] == '/')
cur++;
continue;
}
/*
* d) If the buffer string ends with "." as a complete path segment,
* that "." is removed.
*/
if ((cur[0] == '.') && (cur[1] == '\0'))
break;
/* Otherwise keep the segment. */
while (cur[0] != '/') {
if (cur[0] == '\0')
goto done_cd;
(out++)[0] = (cur++)[0];
}
/* nomalize // */
while ((cur[0] == '/') && (cur[1] == '/'))
cur++;
(out++)[0] = (cur++)[0];
}
done_cd:
out[0] = '\0';
/* Reset to the beginning of the first segment for the next sequence. */
cur = path;
while (cur[0] == '/')
++cur;
if (cur[0] == '\0')
return(0);
/*
* Analyze each segment in sequence for cases (e) and (f).
*
* e) All occurrences of "<segment>/../", where <segment> is a
* complete path segment not equal to "..", are removed from the
* buffer string. Removal of these path segments is performed
* iteratively, removing the leftmost matching pattern on each
* iteration, until no matching pattern remains.
*
* f) If the buffer string ends with "<segment>/..", where <segment>
* is a complete path segment not equal to "..", that
* "<segment>/.." is removed.
*
* To satisfy the "iterative" clause in (e), we need to collapse the
* string every time we find something that needs to be removed. Thus,
* we don't need to keep two pointers into the string: we only need a
* "current position" pointer.
*/
while (1) {
char *segp, *tmp;
/* At the beginning of each iteration of this loop, "cur" points to
* the first character of the segment we want to examine.
*/
/* Find the end of the current segment. */
segp = cur;
while ((segp[0] != '/') && (segp[0] != '\0'))
++segp;
/* If this is the last segment, we're done (we need at least two
* segments to meet the criteria for the (e) and (f) cases).
*/
if (segp[0] == '\0')
break;
/* If the first segment is "..", or if the next segment _isn't_ "..",
* keep this segment and try the next one.
*/
++segp;
if (((cur[0] == '.') && (cur[1] == '.') && (segp == cur+3))
|| ((segp[0] != '.') || (segp[1] != '.')
|| ((segp[2] != '/') && (segp[2] != '\0')))) {
cur = segp;
continue;
}
/* If we get here, remove this segment and the next one and back up
* to the previous segment (if there is one), to implement the
* "iteratively" clause. It's pretty much impossible to back up
* while maintaining two pointers into the buffer, so just compact
* the whole buffer now.
*/
/* If this is the end of the buffer, we're done. */
if (segp[2] == '\0') {
cur[0] = '\0';
break;
}
/* Valgrind complained, strcpy(cur, segp + 3); */
/* string will overlap, do not use strcpy */
tmp = cur;
segp += 3;
while ((*tmp++ = *segp++) != 0)
;
/* If there are no previous segments, then keep going from here. */
segp = cur;
while ((segp > path) && ((--segp)[0] == '/'))
;
if (segp == path)
continue;
/* "segp" is pointing to the end of a previous segment; find it's
* start. We need to back up to the previous segment and start
* over with that to handle things like "foo/bar/../..". If we
* don't do this, then on the first pass we'll remove the "bar/..",
* but be pointing at the second ".." so we won't realize we can also
* remove the "foo/..".
*/
cur = segp;
while ((cur > path) && (cur[-1] != '/'))
--cur;
}
out[0] = '\0';
/*
* g) If the resulting buffer string still begins with one or more
* complete path segments of "..", then the reference is
* considered to be in error. Implementations may handle this
* error by retaining these components in the resolved path (i.e.,
* treating them as part of the final URI), by removing them from
* the resolved path (i.e., discarding relative levels above the
* root), or by avoiding traversal of the reference.
*
* We discard them from the final path.
*/
if (path[0] == '/') {
cur = path;
while ((cur[0] == '/') && (cur[1] == '.') && (cur[2] == '.')
&& ((cur[3] == '/') || (cur[3] == '\0')))
cur += 3;
if (cur != path) {
out = path;
while (cur[0] != '\0')
(out++)[0] = (cur++)[0];
out[0] = 0;
}
}
return(0);
}
static int is_hex(char c) {
if (((c >= '0') && (c <= '9')) ||
((c >= 'a') && (c <= 'f')) ||
((c >= 'A') && (c <= 'F')))
return(1);
return(0);
}
/**
* uri_string_unescape:
* @str: the string to unescape
* @len: the length in bytes to unescape (or <= 0 to indicate full string)
* @target: optional destination buffer
*
* Unescaping routine, but does not check that the string is an URI. The
* output is a direct unsigned char translation of %XX values (no encoding)
* Note that the length of the result can only be smaller or same size as
* the input string.
*
* Returns a copy of the string, but unescaped, will return NULL only in case
* of error
*/
char *
uri_string_unescape(const char *str, int len, char *target) {
char *ret, *out;
const char *in;
if (str == NULL)
return(NULL);
if (len <= 0) len = strlen(str);
if (len < 0) return(NULL);
if (target == NULL) {
ret = g_malloc(len + 1);
} else
ret = target;
in = str;
out = ret;
while(len > 0) {
if ((len > 2) && (*in == '%') && (is_hex(in[1])) && (is_hex(in[2]))) {
in++;
if ((*in >= '0') && (*in <= '9'))
*out = (*in - '0');
else if ((*in >= 'a') && (*in <= 'f'))
*out = (*in - 'a') + 10;
else if ((*in >= 'A') && (*in <= 'F'))
*out = (*in - 'A') + 10;
in++;
if ((*in >= '0') && (*in <= '9'))
*out = *out * 16 + (*in - '0');
else if ((*in >= 'a') && (*in <= 'f'))
*out = *out * 16 + (*in - 'a') + 10;
else if ((*in >= 'A') && (*in <= 'F'))
*out = *out * 16 + (*in - 'A') + 10;
in++;
len -= 3;
out++;
} else {
*out++ = *in++;
len--;
}
}
*out = 0;
return(ret);
}
/**
* uri_string_escape:
* @str: string to escape
* @list: exception list string of chars not to escape
*
* This routine escapes a string to hex, ignoring reserved characters (a-z)
* and the characters in the exception list.
*
* Returns a new escaped string or NULL in case of error.
*/
char *
uri_string_escape(const char *str, const char *list) {
char *ret, ch;
char *temp;
const char *in;
int len, out;
if (str == NULL)
return(NULL);
if (str[0] == 0)
return(g_strdup(str));
len = strlen(str);
if (!(len > 0)) return(NULL);
len += 20;
ret = g_malloc(len);
in = str;
out = 0;
while(*in != 0) {
if (len - out <= 3) {
temp = realloc2n(ret, &len);
ret = temp;
}
ch = *in;
if ((ch != '@') && (!IS_UNRESERVED(ch)) && (!strchr(list, ch))) {
unsigned char val;
ret[out++] = '%';
val = ch >> 4;
if (val <= 9)
ret[out++] = '0' + val;
else
ret[out++] = 'A' + val - 0xA;
val = ch & 0xF;
if (val <= 9)
ret[out++] = '0' + val;
else
ret[out++] = 'A' + val - 0xA;
in++;
} else {
ret[out++] = *in++;
}
}
ret[out] = 0;
return(ret);
}
/************************************************************************
* *
* Public functions *
* *
************************************************************************/
/**
* uri_resolve:
* @URI: the URI instance found in the document
* @base: the base value
*
* Computes he final URI of the reference done by checking that
* the given URI is valid, and building the final URI using the
* base URI. This is processed according to section 5.2 of the
* RFC 2396
*
* 5.2. Resolving Relative References to Absolute Form
*
* Returns a new URI string (to be freed by the caller) or NULL in case
* of error.
*/
char *
uri_resolve(const char *uri, const char *base) {
char *val = NULL;
int ret, len, indx, cur, out;
URI *ref = NULL;
URI *bas = NULL;
URI *res = NULL;
/*
* 1) The URI reference is parsed into the potential four components and
* fragment identifier, as described in Section 4.3.
*
* NOTE that a completely empty URI is treated by modern browsers
* as a reference to "." rather than as a synonym for the current
* URI. Should we do that here?
*/
if (uri == NULL)
ret = -1;
else {
if (*uri) {
ref = uri_new();
if (ref == NULL)
goto done;
ret = uri_parse_into(ref, uri);
}
else
ret = 0;
}
if (ret != 0)
goto done;
if ((ref != NULL) && (ref->scheme != NULL)) {
/*
* The URI is absolute don't modify.
*/
val = g_strdup(uri);
goto done;
}
if (base == NULL)
ret = -1;
else {
bas = uri_new();
if (bas == NULL)
goto done;
ret = uri_parse_into(bas, base);
}
if (ret != 0) {
if (ref)
val = uri_to_string(ref);
goto done;
}
if (ref == NULL) {
/*
* the base fragment must be ignored
*/
if (bas->fragment != NULL) {
g_free(bas->fragment);
bas->fragment = NULL;
}
val = uri_to_string(bas);
goto done;
}
/*
* 2) If the path component is empty and the scheme, authority, and
* query components are undefined, then it is a reference to the
* current document and we are done. Otherwise, the reference URI's
* query and fragment components are defined as found (or not found)
* within the URI reference and not inherited from the base URI.
*
* NOTE that in modern browsers, the parsing differs from the above
* in the following aspect: the query component is allowed to be
* defined while still treating this as a reference to the current
* document.
*/
res = uri_new();
if (res == NULL)
goto done;
if ((ref->scheme == NULL) && (ref->path == NULL) &&
((ref->authority == NULL) && (ref->server == NULL))) {
if (bas->scheme != NULL)
res->scheme = g_strdup(bas->scheme);
if (bas->authority != NULL)
res->authority = g_strdup(bas->authority);
else if (bas->server != NULL) {
res->server = g_strdup(bas->server);
if (bas->user != NULL)
res->user = g_strdup(bas->user);
res->port = bas->port;
}
if (bas->path != NULL)
res->path = g_strdup(bas->path);
if (ref->query != NULL)
res->query = g_strdup (ref->query);
else if (bas->query != NULL)
res->query = g_strdup(bas->query);
if (ref->fragment != NULL)
res->fragment = g_strdup(ref->fragment);
goto step_7;
}
/*
* 3) If the scheme component is defined, indicating that the reference
* starts with a scheme name, then the reference is interpreted as an
* absolute URI and we are done. Otherwise, the reference URI's
* scheme is inherited from the base URI's scheme component.
*/
if (ref->scheme != NULL) {
val = uri_to_string(ref);
goto done;
}
if (bas->scheme != NULL)
res->scheme = g_strdup(bas->scheme);
if (ref->query != NULL)
res->query = g_strdup(ref->query);
if (ref->fragment != NULL)
res->fragment = g_strdup(ref->fragment);
/*
* 4) If the authority component is defined, then the reference is a
* network-path and we skip to step 7. Otherwise, the reference
* URI's authority is inherited from the base URI's authority
* component, which will also be undefined if the URI scheme does not
* use an authority component.
*/
if ((ref->authority != NULL) || (ref->server != NULL)) {
if (ref->authority != NULL)
res->authority = g_strdup(ref->authority);
else {
res->server = g_strdup(ref->server);
if (ref->user != NULL)
res->user = g_strdup(ref->user);
res->port = ref->port;
}
if (ref->path != NULL)
res->path = g_strdup(ref->path);
goto step_7;
}
if (bas->authority != NULL)
res->authority = g_strdup(bas->authority);
else if (bas->server != NULL) {
res->server = g_strdup(bas->server);
if (bas->user != NULL)
res->user = g_strdup(bas->user);
res->port = bas->port;
}
/*
* 5) If the path component begins with a slash character ("/"), then
* the reference is an absolute-path and we skip to step 7.
*/
if ((ref->path != NULL) && (ref->path[0] == '/')) {
res->path = g_strdup(ref->path);
goto step_7;
}
/*
* 6) If this step is reached, then we are resolving a relative-path
* reference. The relative path needs to be merged with the base
* URI's path. Although there are many ways to do this, we will
* describe a simple method using a separate string buffer.
*
* Allocate a buffer large enough for the result string.
*/
len = 2; /* extra / and 0 */
if (ref->path != NULL)
len += strlen(ref->path);
if (bas->path != NULL)
len += strlen(bas->path);
res->path = g_malloc(len);
res->path[0] = 0;
/*
* a) All but the last segment of the base URI's path component is
* copied to the buffer. In other words, any characters after the
* last (right-most) slash character, if any, are excluded.
*/
cur = 0;
out = 0;
if (bas->path != NULL) {
while (bas->path[cur] != 0) {
while ((bas->path[cur] != 0) && (bas->path[cur] != '/'))
cur++;
if (bas->path[cur] == 0)
break;
cur++;
while (out < cur) {
res->path[out] = bas->path[out];
out++;
}
}
}
res->path[out] = 0;
/*
* b) The reference's path component is appended to the buffer
* string.
*/
if (ref->path != NULL && ref->path[0] != 0) {
indx = 0;
/*
* Ensure the path includes a '/'
*/
if ((out == 0) && (bas->server != NULL))
res->path[out++] = '/';
while (ref->path[indx] != 0) {
res->path[out++] = ref->path[indx++];
}
}
res->path[out] = 0;
/*
* Steps c) to h) are really path normalization steps
*/
normalize_uri_path(res->path);
step_7:
/*
* 7) The resulting URI components, including any inherited from the
* base URI, are recombined to give the absolute form of the URI
* reference.
*/
val = uri_to_string(res);
done:
if (ref != NULL)
uri_free(ref);
if (bas != NULL)
uri_free(bas);
if (res != NULL)
uri_free(res);
return(val);
}
/**
* uri_resolve_relative:
* @URI: the URI reference under consideration
* @base: the base value
*
* Expresses the URI of the reference in terms relative to the
* base. Some examples of this operation include:
* base = "http://site1.com/docs/book1.html"
* URI input URI returned
* docs/pic1.gif pic1.gif
* docs/img/pic1.gif img/pic1.gif
* img/pic1.gif ../img/pic1.gif
* http://site1.com/docs/pic1.gif pic1.gif
* http://site2.com/docs/pic1.gif http://site2.com/docs/pic1.gif
*
* base = "docs/book1.html"
* URI input URI returned
* docs/pic1.gif pic1.gif
* docs/img/pic1.gif img/pic1.gif
* img/pic1.gif ../img/pic1.gif
* http://site1.com/docs/pic1.gif http://site1.com/docs/pic1.gif
*
*
* Note: if the URI reference is really wierd or complicated, it may be
* worthwhile to first convert it into a "nice" one by calling
* uri_resolve (using 'base') before calling this routine,
* since this routine (for reasonable efficiency) assumes URI has
* already been through some validation.
*
* Returns a new URI string (to be freed by the caller) or NULL in case
* error.
*/
char *
uri_resolve_relative (const char *uri, const char * base)
{
char *val = NULL;
int ret;
int ix;
int pos = 0;
int nbslash = 0;
int len;
URI *ref = NULL;
URI *bas = NULL;
char *bptr, *uptr, *vptr;
int remove_path = 0;
if ((uri == NULL) || (*uri == 0))
return NULL;
/*
* First parse URI into a standard form
*/
ref = uri_new ();
if (ref == NULL)
return NULL;
/* If URI not already in "relative" form */
if (uri[0] != '.') {
ret = uri_parse_into (ref, uri);
if (ret != 0)
goto done; /* Error in URI, return NULL */
} else
ref->path = g_strdup(uri);
/*
* Next parse base into the same standard form
*/
if ((base == NULL) || (*base == 0)) {
val = g_strdup (uri);
goto done;
}
bas = uri_new ();
if (bas == NULL)
goto done;
if (base[0] != '.') {
ret = uri_parse_into (bas, base);
if (ret != 0)
goto done; /* Error in base, return NULL */
} else
bas->path = g_strdup(base);
/*
* If the scheme / server on the URI differs from the base,
* just return the URI
*/
if ((ref->scheme != NULL) &&
((bas->scheme == NULL) ||
(strcmp (bas->scheme, ref->scheme)) ||
(strcmp (bas->server, ref->server)))) {
val = g_strdup (uri);
goto done;
}
if (!strcmp(bas->path, ref->path)) {
val = g_strdup("");
goto done;
}
if (bas->path == NULL) {
val = g_strdup(ref->path);
goto done;
}
if (ref->path == NULL) {
ref->path = (char *) "/";
remove_path = 1;
}
/*
* At this point (at last!) we can compare the two paths
*
* First we take care of the special case where either of the
* two path components may be missing (bug 316224)
*/
if (bas->path == NULL) {
if (ref->path != NULL) {
uptr = ref->path;
if (*uptr == '/')
uptr++;
/* exception characters from uri_to_string */
val = uri_string_escape(uptr, "/;&=+$,");
}
goto done;
}
bptr = bas->path;
if (ref->path == NULL) {
for (ix = 0; bptr[ix] != 0; ix++) {
if (bptr[ix] == '/')
nbslash++;
}
uptr = NULL;
len = 1; /* this is for a string terminator only */
} else {
/*
* Next we compare the two strings and find where they first differ
*/
if ((ref->path[pos] == '.') && (ref->path[pos+1] == '/'))
pos += 2;
if ((*bptr == '.') && (bptr[1] == '/'))
bptr += 2;
else if ((*bptr == '/') && (ref->path[pos] != '/'))
bptr++;
while ((bptr[pos] == ref->path[pos]) && (bptr[pos] != 0))
pos++;
if (bptr[pos] == ref->path[pos]) {
val = g_strdup("");
goto done; /* (I can't imagine why anyone would do this) */
}
/*
* In URI, "back up" to the last '/' encountered. This will be the
* beginning of the "unique" suffix of URI
*/
ix = pos;
if ((ref->path[ix] == '/') && (ix > 0))
ix--;
else if ((ref->path[ix] == 0) && (ix > 1) && (ref->path[ix - 1] == '/'))
ix -= 2;
for (; ix > 0; ix--) {
if (ref->path[ix] == '/')
break;
}
if (ix == 0) {
uptr = ref->path;
} else {
ix++;
uptr = &ref->path[ix];
}
/*
* In base, count the number of '/' from the differing point
*/
if (bptr[pos] != ref->path[pos]) {/* check for trivial URI == base */
for (; bptr[ix] != 0; ix++) {
if (bptr[ix] == '/')
nbslash++;
}
}
len = strlen (uptr) + 1;
}
if (nbslash == 0) {
if (uptr != NULL)
/* exception characters from uri_to_string */
val = uri_string_escape(uptr, "/;&=+$,");
goto done;
}
/*
* Allocate just enough space for the returned string -
* length of the remainder of the URI, plus enough space
* for the "../" groups, plus one for the terminator
*/
val = g_malloc (len + 3 * nbslash);
vptr = val;
/*
* Put in as many "../" as needed
*/
for (; nbslash>0; nbslash--) {
*vptr++ = '.';
*vptr++ = '.';
*vptr++ = '/';
}
/*
* Finish up with the end of the URI
*/
if (uptr != NULL) {
if ((vptr > val) && (len > 0) &&
(uptr[0] == '/') && (vptr[-1] == '/')) {
memcpy (vptr, uptr + 1, len - 1);
vptr[len - 2] = 0;
} else {
memcpy (vptr, uptr, len);
vptr[len - 1] = 0;
}
} else {
vptr[len - 1] = 0;
}
/* escape the freshly-built path */
vptr = val;
/* exception characters from uri_to_string */
val = uri_string_escape(vptr, "/;&=+$,");
g_free(vptr);
done:
/*
* Free the working variables
*/
if (remove_path != 0)
ref->path = NULL;
if (ref != NULL)
uri_free (ref);
if (bas != NULL)
uri_free (bas);
return val;
}
/*
* Utility functions to help parse and assemble query strings.
*/
struct QueryParams *
query_params_new (int init_alloc)
{
struct QueryParams *ps;
if (init_alloc <= 0) init_alloc = 1;
ps = g_new(QueryParams, 1);
ps->n = 0;
ps->alloc = init_alloc;
ps->p = g_new(QueryParam, ps->alloc);
return ps;
}
/* Ensure there is space to store at least one more parameter
* at the end of the set.
*/
int
query_params_append (struct QueryParams *ps,
const char *name, const char *value)
{
if (ps->n >= ps->alloc) {
ps->p = g_renew(QueryParam, ps->p, ps->alloc * 2);
ps->alloc *= 2;
}
ps->p[ps->n].name = g_strdup(name);
ps->p[ps->n].value = value ? g_strdup(value) : NULL;
ps->p[ps->n].ignore = 0;
ps->n++;
return 0;
}
char *
query_params_to_string (const struct QueryParams *ps)
{
int i, amp = 0;
char *escaped;
GString *str;
str = g_string_new("");
for (i = 0; i < ps->n; ++i) {
if (!ps->p[i].ignore) {
if (amp) g_string_append_c (str, '&');
amp = 1;
g_string_append (str, ps->p[i].name);
if (!ps->p[i].value) {
continue;
}
g_string_append_c (str, '=');
escaped = uri_string_escape(ps->p[i].value, "");
g_string_append (str, escaped);
g_free(escaped);
}
}
return g_string_free(str, FALSE);
}
void
query_params_free (struct QueryParams *ps)
{
int i;
for (i = 0; i < ps->n; ++i) {
g_free (ps->p[i].name);
g_free (ps->p[i].value);
}
g_free (ps->p);
g_free (ps);
}
struct QueryParams *
query_params_parse (const char *query)
{
struct QueryParams *ps;
const char *end, *eq;
ps = query_params_new (0);
if (!query || query[0] == '\0') return ps;
while (*query) {
char *name = NULL, *value = NULL;
/* Find the next separator, or end of the string. */
end = strchr (query, '&');
if (!end)
end = strchr (query, ';');
if (!end)
end = query + strlen (query);
/* Find the first '=' character between here and end. */
eq = strchr (query, '=');
if (eq && eq >= end) eq = NULL;
/* Empty section (eg. "&&"). */
if (end == query)
goto next;
/* If there is no '=' character, then we have just "name"
* and consistent with CGI.pm we assume value is "".
*/
else if (!eq) {
name = uri_string_unescape (query, end - query, NULL);
value = NULL;
}
/* Or if we have "name=" here (works around annoying
* problem when calling uri_string_unescape with len = 0).
*/
else if (eq+1 == end) {
name = uri_string_unescape (query, eq - query, NULL);
value = g_new0(char, 1);
}
/* If the '=' character is at the beginning then we have
* "=value" and consistent with CGI.pm we _ignore_ this.
*/
else if (query == eq)
goto next;
/* Otherwise it's "name=value". */
else {
name = uri_string_unescape (query, eq - query, NULL);
value = uri_string_unescape (eq+1, end - (eq+1), NULL);
}
/* Append to the parameter set. */
query_params_append (ps, name, value);
g_free(name);
g_free(value);
next:
query = end;
if (*query) query ++; /* skip '&' separator */
}
return ps;
}
void test(const char *x)
{
URI *uri = uri_parse(x);
QueryParams *qp;
char *out;
if (!uri) {
printf ("INVALID: %s\n", x);
return;
}
/* Escape the parameters. */
qp = query_params_parse(uri->query);
g_free(uri->query);
uri->query = query_params_to_string(qp);
query_params_free(qp);
out = uri_to_string(uri);
uri_free(uri);
printf("VALID: %s\n", out);
g_free(out);
}
int main()
{
test("gluster+unix:///b?c=d");
test("gluster+tcp://user:pwd@foo:80/b?c=d");
test("gluster+tcp://user:pwd@foo:8b0/b?c=d");
test("http://a/b?c=d");
test("http://a/b?c=/d/e");
test("http://a/b?c=/d/e&f");
test("http://a/b?c=/d/e&f=&&ggg");
return 0;
}
[-- Attachment #3: uri.h --]
[-- Type: text/x-c, Size: 4395 bytes --]
/**
* Summary: library of generic URI related routines
* Description: library of generic URI related routines
* Implements RFC 2396
*
* Copyright (C) 1998-2003 Daniel Veillard. All Rights Reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* DANIEL VEILLARD BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* Except as contained in this notice, the name of Daniel Veillard shall not
* be used in advertising or otherwise to promote the sale, use or other
* dealings in this Software without prior written authorization from him.
*
* Author: Daniel Veillard
**
* Copyright (C) 2007 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Authors:
* Richard W.M. Jones <rjones@redhat.com>
*
* Utility functions to help parse and assemble query strings.
*/
#ifndef QEMU_URI_H
#define QEMU_URI_H
#ifdef __cplusplus
extern "C" {
#endif
/**
* URI:
*
* A parsed URI reference. This is a struct containing the various fields
* as described in RFC 2396 but separated for further processing.
*/
typedef struct URI {
char *scheme; /* the URI scheme */
char *opaque; /* opaque part */
char *authority; /* the authority part */
char *server; /* the server part */
char *user; /* the user part */
int port; /* the port number */
char *path; /* the path string */
char *fragment; /* the fragment identifier */
int cleanup; /* parsing potentially unclean URI */
char *query; /* the query string (as it appears in the URI) */
} URI;
URI *uri_new(void);
char *uri_resolve(const char *URI, const char *base);
char *uri_resolve_relative(const char *URI, const char *base);
URI *uri_parse(const char *str);
URI *uri_parse_raw(const char *str, int raw);
int uri_parse_into(URI *uri, const char *str);
char *uri_to_string(URI *uri);
char *uri_string_escape(const char *str, const char *list);
char *uri_string_unescape(const char *str, int len, char *target);
void uri_free(URI *uri);
/* Single web service query parameter 'name=value'. */
typedef struct QueryParam {
char *name; /* Name (unescaped). */
char *value; /* Value (unescaped). */
int ignore; /* Ignore this field in qparam_get_query */
} QueryParam;
/* Set of parameters. */
typedef struct QueryParams {
int n; /* number of parameters used */
int alloc; /* allocated space */
QueryParam *p; /* array of parameters */
} QueryParams;
struct QueryParams *query_params_new (int init_alloc);
int query_param_append (QueryParams *ps, const char *name, const char *value);
extern char *query_param_to_string (const QueryParams *ps);
extern QueryParams *query_params_parse (const char *query);
extern void query_params_free (QueryParams *ps);
#ifdef __cplusplus
}
#endif
#endif /* QEMU_URI_H */
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [Qemu-devel] [PATCH v7 5/5] block: Support GlusterFS as a QEMU block backend.
2012-09-20 15:08 ` Paolo Bonzini
@ 2012-09-21 3:50 ` Bharata B Rao
2012-09-21 8:23 ` Paolo Bonzini
0 siblings, 1 reply; 24+ messages in thread
From: Bharata B Rao @ 2012-09-21 3:50 UTC (permalink / raw)
To: Paolo Bonzini
Cc: Kevin Wolf, Anthony Liguori, Anand Avati, Vijay Bellur,
Stefan Hajnoczi, Amar Tumballi, qemu-devel, Markus Armbruster,
Blue Swirl, Avi Kivity
On Thu, Sep 20, 2012 at 05:08:56PM +0200, Paolo Bonzini wrote:
>
> Just shooting around a possibility: why reinvent the wheel poorly if we
> can use a full-blown URI parsing library? The libxml2 one is very good
> and easy to use.
>
> It is also pretty much self-contained and has hardly seen a commit in 3
> years, so we can even make it completely self-contained and keep it in
> tree. See attachment, which also includes some functions taken from
> libvirt to parse query parameters.
>
> (I know this is controversial and would rather add a build dependency,
> but at the same time it is tempting not to).
It would be nice to have such URI parsing capability in QEMU. This would
make it very easy to parse gluster URI.
Regards,
Bharata.
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [Qemu-devel] [PATCH v7 5/5] block: Support GlusterFS as a QEMU block backend.
2012-09-21 3:50 ` Bharata B Rao
@ 2012-09-21 8:23 ` Paolo Bonzini
0 siblings, 0 replies; 24+ messages in thread
From: Paolo Bonzini @ 2012-09-21 8:23 UTC (permalink / raw)
To: bharata
Cc: Kevin Wolf, Anthony Liguori, Anand Avati, Vijay Bellur,
Stefan Hajnoczi, Amar Tumballi, qemu-devel, Markus Armbruster,
Blue Swirl, Avi Kivity
Il 21/09/2012 05:50, Bharata B Rao ha scritto:
>> > Just shooting around a possibility: why reinvent the wheel poorly if we
>> > can use a full-blown URI parsing library? The libxml2 one is very good
>> > and easy to use.
>> >
>> > It is also pretty much self-contained and has hardly seen a commit in 3
>> > years, so we can even make it completely self-contained and keep it in
>> > tree. See attachment, which also includes some functions taken from
>> > libvirt to parse query parameters.
>> >
>> > (I know this is controversial and would rather add a build dependency,
>> > but at the same time it is tempting not to).
> It would be nice to have such URI parsing capability in QEMU. This would
> make it very easy to parse gluster URI.
Feel free to take the two attached files in v8.
Paolo
^ permalink raw reply [flat|nested] 24+ messages in thread
end of thread, other threads:[~2012-09-21 8:23 UTC | newest]
Thread overview: 24+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2012-09-17 15:21 [Qemu-devel] [RFC v7 0/5] GlusterFS support in QEMU - v7 Bharata B Rao
2012-09-17 15:22 ` [Qemu-devel] [PATCH v7 1/5] sockets: Make inet_parse() non static Bharata B Rao
2012-09-18 12:47 ` Kevin Wolf
2012-09-18 12:57 ` Paolo Bonzini
2012-09-20 5:57 ` Bharata B Rao
2012-09-17 15:23 ` [Qemu-devel] [PATCH v7 2/5] sockets: Change inet_parse() to accept address specification without port Bharata B Rao
2012-09-18 13:22 ` Kevin Wolf
2012-09-18 13:31 ` Paolo Bonzini
2012-09-18 14:08 ` Kevin Wolf
2012-09-20 6:30 ` Bharata B Rao
2012-09-20 6:51 ` Kevin Wolf
2012-09-17 15:24 ` [Qemu-devel] [PATCH v7 3/5] aio: Fix qemu_aio_wait() to maintain correct walking_handlers count Bharata B Rao
2012-09-17 15:25 ` [Qemu-devel] [PATCH v7 4/5] configure: Add a config option for GlusterFS as block backend Bharata B Rao
2012-09-17 15:26 ` [Qemu-devel] [PATCH v7 5/5] block: Support GlusterFS as a QEMU " Bharata B Rao
2012-09-18 14:01 ` Kevin Wolf
2012-09-20 6:41 ` Bharata B Rao
2012-09-20 7:53 ` Paolo Bonzini
2012-09-20 8:20 ` Paolo Bonzini
2012-09-20 9:12 ` Bharata B Rao
2012-09-20 9:14 ` Paolo Bonzini
2012-09-20 9:34 ` Kevin Wolf
2012-09-20 15:08 ` Paolo Bonzini
2012-09-21 3:50 ` Bharata B Rao
2012-09-21 8:23 ` Paolo Bonzini
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).