[libvirt] Supporting vhost-net and macvtap in libvirt for QEMU
by Anthony Liguori
Disclaimer: I am neither an SR-IOV nor a vhost-net expert, but I've CC'd
people that are who can throw tomatoes at me for getting bits wrong :-)
I wanted to start a discussion about supporting vhost-net in libvirt.
vhost-net has not yet been merged into qemu but I expect it will be soon
so it's a good time to start this discussion.
There are two modes worth supporting for vhost-net in libvirt. The
first mode is where vhost-net backs to a tun/tap device. This is
behaves in very much the same way that -net tap behaves in qemu today.
Basically, the difference is that the virtio backend is in the kernel
instead of in qemu so there should be some performance improvement.
Current, libvirt invokes qemu with -net tap,fd=X where X is an already
open fd to a tun/tap device. I suspect that after we merge vhost-net,
libvirt could support vhost-net in this mode by just doing -net
vhost,fd=X. I think the only real question for libvirt is whether to
provide a user visible switch to use vhost or to just always use vhost
when it's available and it makes sense. Personally, I think the later
makes sense.
The more interesting invocation of vhost-net though is one where the
vhost-net device backs directly to a physical network card. In this
mode, vhost should get considerably better performance than the current
implementation. I don't know the syntax yet, but I think it's
reasonable to assume that it will look something like -net
tap,dev=eth0. The effect will be that eth0 is dedicated to the guest.
On most modern systems, there is a small number of network devices so
this model is not all that useful except when dealing with SR-IOV
adapters. In that case, each physical device can be exposed as many
virtual devices (VFs). There are a few restrictions here though. The
biggest is that currently, you can only change the number of VFs by
reloading a kernel module so it's really a parameter that must be set at
startup time.
I think there are a few ways libvirt could support vhost-net in this
second mode. The simplest would be to introduce a new tag similar to
<source network='br0'>. In fact, if you probed the device type for the
network parameter, you could probably do something like <source
network='eth0'> and have it Just Work.
Another model would be to have libvirt see an SR-IOV adapter as a
network pool whereas it handled all of the VF management. Considering
how inflexible SR-IOV is today, I'm not sure whether this is the best model.
Has anyone put any more thought into this problem or how this should be
modeled in libvirt? Michael, could you share your current thinking for
-net syntax?
--
Regards,
Anthony Liguori
1 year
[libvirt] Libvirt multi queue support
by Naor Shlomo
Hello experts,
Could anyone please tell me if Multi Queue it fully supported in Libvirt and if so what version contains it?
Thanks,
Naor
8 years, 5 months
[libvirt] [libvirt-python PATCH] setup: Make libvirt API XML path configurable
by Martin Kletzander
Adding a support for LIBVIRT_API_PATH evironment variable, which can
control where the script should look for the 'libvirt-api.xml' file.
This allows building libvirt-python against different libvirt than the
one installed in the system. This may be used for example in autotest
or by packagers without the need to install libvirt into the system.
Signed-off-by: Martin Kletzander <mkletzan(a)redhat.com>
---
setup.py | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/setup.py b/setup.py
index 17b4722..566c210 100755
--- a/setup.py
+++ b/setup.py
@@ -109,7 +109,17 @@ class my_build(build):
"""Check with pkg-config that libvirt is present and extract
the API XML file paths we need from it"""
- libvirt_api = get_pkgconfig_data(["--variable", "libvirt_api"], "libvirt")
+ libvirt_api = os.getenv("LIBVIRT_API_PATH")
+
+ if libvirt_api:
+ if not libvirt_api.endswith("-api.xml"):
+ raise ValueError("Invalid path '%s' for API XML" % libvirt_api)
+ if not os.path.exists(libvirt_api):
+ raise ValueError("API XML '%s' does not exist, "
+ "have you built libvirt?" % libvirt_api)
+ else:
+ libvirt_api = get_pkgconfig_data(["--variable", "libvirt_api"],
+ "libvirt")
offset = libvirt_api.index("-api.xml")
libvirt_qemu_api = libvirt_api[0:offset] + "-qemu-api.xml"
--
1.8.4.3
10 years, 8 months
[libvirt] [PATCH] qemu: Fix seamless SPICE migration
by Martin Kletzander
Since the wait is done during migration (still inside
QEMU_ASYNC_JOB_MIGRATION_OUT), the code should enter the monitor as such
in order to prohibit all other jobs from interfering in the meantime.
This patch fixes bug #1009886 in which qemuDomainGetBlockInfo was
waiting on the monitor condition and after GetSpiceMigrationStatus
mangled its internal data, the daemon crashed.
Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1009886
Signed-off-by: Martin Kletzander <mkletzan(a)redhat.com>
---
src/qemu/qemu_migration.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/qemu/qemu_migration.c b/src/qemu/qemu_migration.c
index d7b89fc..3a1aab7 100644
--- a/src/qemu/qemu_migration.c
+++ b/src/qemu/qemu_migration.c
@@ -1595,7 +1595,10 @@ qemuMigrationWaitForSpice(virQEMUDriverPtr driver,
/* Poll every 50ms for progress & to allow cancellation */
struct timespec ts = { .tv_sec = 0, .tv_nsec = 50 * 1000 * 1000ull };
- qemuDomainObjEnterMonitor(driver, vm);
+ if (qemuDomainObjEnterMonitorAsync(driver, vm,
+ QEMU_ASYNC_JOB_MIGRATION_OUT) < 0)
+ return -1;
+
if (qemuMonitorGetSpiceMigrationStatus(priv->mon,
&spice_migrated) < 0) {
qemuDomainObjExitMonitor(driver, vm);
--
1.8.3.2
10 years, 8 months
[libvirt] [PATCH] qemu: add PCI-multibus support for ppc
by Olivia Yin
Signed-off-by: Olivia Yin <hong-hua.yin(a)freescale.com>
---
src/qemu/qemu_capabilities.c | 10 ++++++++++
1 files changed, 10 insertions(+), 0 deletions(-)
diff --git a/src/qemu/qemu_capabilities.c b/src/qemu/qemu_capabilities.c
index 7bc1ebc..7d7791d 100644
--- a/src/qemu/qemu_capabilities.c
+++ b/src/qemu/qemu_capabilities.c
@@ -2209,6 +2209,11 @@ virQEMUCapsInitHelp(virQEMUCapsPtr qemuCaps, uid_t runUid, gid_t runGid)
virQEMUCapsClear(qemuCaps, QEMU_CAPS_NO_ACPI);
}
+ /* ppc support PCI-multibus */
+ if (qemuCaps->arch == VIR_ARCH_PPC) {
+ virQEMUCapsSet(qemuCaps, QEMU_CAPS_PCI_MULTIBUS);
+ }
+
/* virQEMUCapsExtractDeviceStr will only set additional caps if qemu
* understands the 0.13.0+ notion of "-device driver,". */
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE) &&
@@ -2450,6 +2455,11 @@ virQEMUCapsInitQMP(virQEMUCapsPtr qemuCaps,
virQEMUCapsSet(qemuCaps, QEMU_CAPS_NO_ACPI);
}
+ /* ppc support PCI-multibus */
+ if (qemuCaps->arch == VIR_ARCH_PPC) {
+ virQEMUCapsSet(qemuCaps, QEMU_CAPS_PCI_MULTIBUS);
+ }
+
if (virQEMUCapsProbeQMPCommands(qemuCaps, mon) < 0)
goto cleanup;
if (virQEMUCapsProbeQMPEvents(qemuCaps, mon) < 0)
--
1.6.4
10 years, 8 months
[libvirt] [PATCH 0/4] Expose FSFreeze/FSThaw within the guest as commands
by Tomoki Sekiyama
Currently FSFreeze and FSThaw are supported by qemu guest agent and they are
used internally in snapshot-create command with --quiesce option.
However, when users want to utilize the native snapshot feature of storage
devices (such as LVM over iSCSI, various enterprise storage systems, etc.),
they need to issue fsfreeze command separately from libvirt-driven snapshots.
(OpenStack cinder provides these storages' snapshot feature, but it cannot
quiesce the guest filesystems automatically for now.)
Although virDomainQemuGuestAgent() API could be used for this purpose, it
depends too much on specific hypervisor implementation.
This patchset adds virDomainFSFreeze()/virDomainFSThaw() APIs and virsh
domfsfreeze/domfsthaw commands to enable the users to freeze and thaw
domain's filesystems cleanly.
The APIs has mountPoint and flags option currently unsupported for future
extension, as virDomainFSTrim() API.
Duplicated FSFreeze results in error caused by qemu guest agent.
---
Tomoki Sekiyama (4):
Introduce virDomainFSFreeze() public API
remote: Implement virDomainFSFreeze and virDomainFSThaw
qemu: Implement virDomainFSFreeze
virsh: Expose new virDomainFSFreeze and virDomainFSThaw API
include/libvirt/libvirt.h.in | 8 ++
src/access/viraccessperm.c | 2 -
src/access/viraccessperm.h | 6 ++
src/driver.h | 12 ++++
src/libvirt.c | 92 +++++++++++++++++++++++++++
src/libvirt_public.syms | 6 ++
src/qemu/qemu_driver.c | 142 ++++++++++++++++++++++++++++++++++++++++++
src/remote/remote_driver.c | 2 +
src/remote/remote_protocol.x | 26 +++++++-
src/remote_protocol-structs | 12 ++++
src/rpc/gendispatch.pl | 2 +
tools/virsh-domain.c | 108 ++++++++++++++++++++++++++++++++
tools/virsh.pod | 17 +++++
13 files changed, 433 insertions(+), 2 deletions(-)
10 years, 8 months
[libvirt] [PATCH] Rename virDomainGetRootFilesystem to virDomainGetFilesystemForTarget
by Daniel P. Berrange
The virDomainGetRootFilesystem method can be generalized to allow
any filesystem path to be obtained.
While doing this, start a new test case for purpose of testing various
helper methods in the domain_conf.{c,h} files, such as this one.
Signed-off-by: Daniel P. Berrange <berrange(a)redhat.com>
---
.gitignore | 1 +
src/conf/domain_conf.c | 5 +-
src/conf/domain_conf.h | 3 +-
src/libvirt_private.syms | 2 +-
src/lxc/lxc_container.c | 2 +-
src/lxc/lxc_process.c | 2 +-
tests/Makefile.am | 6 ++
tests/domainconfdata/getfilesystem.xml | 28 ++++++++
tests/domainconftest.c | 120 +++++++++++++++++++++++++++++++++
tests/testutils.c | 57 ++++++++++++++++
tests/testutils.h | 5 ++
11 files changed, 225 insertions(+), 6 deletions(-)
create mode 100644 tests/domainconfdata/getfilesystem.xml
create mode 100644 tests/domainconftest.c
diff --git a/.gitignore b/.gitignore
index d5a6cf5..cbb3e8a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -147,6 +147,7 @@
/tests/commandtest
/tests/conftest
/tests/cputest
+/tests/domainconftest
/tests/domainsnapshotxml2xmltest
/tests/esxutilstest
/tests/eventtest
diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c
index 0079234..dd36026 100644
--- a/src/conf/domain_conf.c
+++ b/src/conf/domain_conf.c
@@ -17827,12 +17827,13 @@ virDiskNameToBusDeviceIndex(virDomainDiskDefPtr disk,
}
virDomainFSDefPtr
-virDomainGetRootFilesystem(virDomainDefPtr def)
+virDomainGetFilesystemForTarget(virDomainDefPtr def,
+ const char *path)
{
size_t i;
for (i = 0; i < def->nfss; i++) {
- if (STREQ(def->fss[i]->dst, "/"))
+ if (STREQ(def->fss[i]->dst, path))
return def->fss[i];
}
diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h
index 647d115..ea7f1a1 100644
--- a/src/conf/domain_conf.h
+++ b/src/conf/domain_conf.h
@@ -2541,7 +2541,8 @@ int virDiskNameToBusDeviceIndex(virDomainDiskDefPtr disk,
int *busIdx,
int *devIdx);
-virDomainFSDefPtr virDomainGetRootFilesystem(virDomainDefPtr def);
+virDomainFSDefPtr virDomainGetFilesystemForTarget(virDomainDefPtr def,
+ const char *path);
int virDomainFSIndexByName(virDomainDefPtr def, const char *name);
int virDomainVideoDefaultType(const virDomainDef *def);
int virDomainVideoDefaultRAM(const virDomainDef *def, int type);
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index 2dbb8f8..a870e9c 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -225,7 +225,7 @@ virDomainFSTypeFromString;
virDomainFSTypeToString;
virDomainFSWrpolicyTypeFromString;
virDomainFSWrpolicyTypeToString;
-virDomainGetRootFilesystem;
+virDomainGetFilesystemForTarget;
virDomainGraphicsAuthConnectedTypeFromString;
virDomainGraphicsAuthConnectedTypeToString;
virDomainGraphicsDefFree;
diff --git a/src/lxc/lxc_container.c b/src/lxc/lxc_container.c
index c6bdc8c..c32b085 100644
--- a/src/lxc/lxc_container.c
+++ b/src/lxc/lxc_container.c
@@ -1823,7 +1823,7 @@ static int lxcContainerChild(void *data)
if (lxcContainerSetID(vmDef) < 0)
goto cleanup;
- root = virDomainGetRootFilesystem(vmDef);
+ root = virDomainGetFilesystemForTarget(vmDef, "/");
if (argv->nttyPaths) {
const char *tty = argv->ttyPaths[0];
diff --git a/src/lxc/lxc_process.c b/src/lxc/lxc_process.c
index cc9c1a2..f0190a0 100644
--- a/src/lxc/lxc_process.c
+++ b/src/lxc/lxc_process.c
@@ -934,7 +934,7 @@ virLXCProcessReadLogOutput(virDomainObjPtr vm,
static int
virLXCProcessEnsureRootFS(virDomainObjPtr vm)
{
- virDomainFSDefPtr root = virDomainGetRootFilesystem(vm->def);
+ virDomainFSDefPtr root = virDomainGetFilesystemForTarget(vm->def, "/");
if (root)
return 0;
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 568b7a0..90fb3a0 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -65,6 +65,7 @@ EXTRA_DIST = \
commanddata \
confdata \
cputestdata \
+ domainconfdata \
domainschemadata \
domainschematest \
domainsnapshotschematest \
@@ -141,6 +142,7 @@ test_programs = virshtest sockettest \
virportallocatortest \
sysinfotest \
virstoragetest \
+ domainconftest \
$(NULL)
if WITH_REMOTE
@@ -893,6 +895,10 @@ sysinfotest_SOURCES = \
sysinfotest.c testutils.h testutils.c
sysinfotest_LDADD = $(LDADDS)
+domainconftest_SOURCES = \
+ domainconftest.c testutils.h testutils.c
+domainconftest_LDADD = $(LDADDS)
+
fdstreamtest_SOURCES = \
fdstreamtest.c testutils.h testutils.c
fdstreamtest_LDADD = $(LDADDS)
diff --git a/tests/domainconfdata/getfilesystem.xml b/tests/domainconfdata/getfilesystem.xml
new file mode 100644
index 0000000..2ee78b4
--- /dev/null
+++ b/tests/domainconfdata/getfilesystem.xml
@@ -0,0 +1,28 @@
+<domain type='test'>
+ <name>demo</name>
+ <uuid>8369f1ac-7e46-e869-4ca5-759d51478066</uuid>
+ <memory unit='KiB'>500000</memory>
+ <currentMemory unit='KiB'>500000</currentMemory>
+ <vcpu placement='static'>1</vcpu>
+ <os>
+ <type arch='x86_64'>hvm</type>
+ <init>/bin/sh</init>
+ </os>
+ <clock offset='utc'/>
+ <on_poweroff>destroy</on_poweroff>
+ <on_reboot>restart</on_reboot>
+ <on_crash>destroy</on_crash>
+ <devices>
+ <filesystem type='mount' accessmode='passthrough'>
+ <source dir='/'/>
+ <target dir='/'/>
+ </filesystem>
+ <filesystem type='mount' accessmode='passthrough'>
+ <source dir='/'/>
+ <target dir='/dev'/>
+ </filesystem>
+ <console type='pty'>
+ <target type='lxc' port='0'/>
+ </console>
+ </devices>
+</domain>
diff --git a/tests/domainconftest.c b/tests/domainconftest.c
new file mode 100644
index 0000000..d38ef5c
--- /dev/null
+++ b/tests/domainconftest.c
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2013 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, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Author: Daniel P. Berrange <berrange(a)redhat.com>
+ */
+
+#include <config.h>
+
+#include "testutils.h"
+#include "virerror.h"
+#include "viralloc.h"
+#include "virlog.h"
+
+#include "domain_conf.h"
+
+#define VIR_FROM_THIS VIR_FROM_NONE
+
+static virCapsPtr caps;
+static virDomainXMLOptionPtr xmlopt;
+
+struct testGetFilesystemData {
+ const char *filename;
+ const char *path;
+ bool expectEntry;
+};
+
+static int testGetFilesystem(const void *opaque)
+{
+ int ret = -1;
+ char *xmlData = NULL;
+ virDomainDefPtr def = NULL;
+ char *filename = NULL;
+ const struct testGetFilesystemData *data = opaque;
+ virDomainFSDefPtr fsdef;
+
+ if (virAsprintf(&filename, "%s/domainconfdata/%s.xml",
+ abs_srcdir, data->filename) < 0)
+ goto cleanup;
+
+ if (virtTestLoadFile(filename, &xmlData) < 0)
+ goto cleanup;
+
+ if (!(def = virDomainDefParseString(xmlData, caps, xmlopt,
+ 1 << VIR_DOMAIN_VIRT_TEST, 0)))
+ goto cleanup;
+
+ fsdef = virDomainGetFilesystemForTarget(def,
+ data->path);
+ if (!fsdef) {
+ if (data->expectEntry) {
+ fprintf(stderr, "Expected FS for path '%s' in '%s'\n",
+ data->path, filename);
+ goto cleanup;
+ }
+ } else {
+ if (!data->expectEntry) {
+ fprintf(stderr, "Unexpected FS for path '%s' in '%s'\n",
+ data->path, filename);
+ goto cleanup;
+ }
+ }
+
+ ret = 0;
+
+cleanup:
+ virDomainDefFree(def);
+ VIR_FREE(xmlData);
+ VIR_FREE(filename);
+ return ret;
+}
+
+static int
+mymain(void)
+{
+ int ret = 0;
+
+ if ((caps = virTestGenericCapsInit()) == NULL)
+ goto cleanup;
+
+ if (!(xmlopt = virTestGenericDomainXMLConfInit()))
+ goto cleanup;
+
+#define DO_TEST_GET_FS(fspath, expect) \
+ do { \
+ struct testGetFilesystemData data = { \
+ .filename = "getfilesystem", \
+ .path = fspath, \
+ .expectEntry = expect, \
+ }; \
+ if (virtTestRun("Get FS " fspath, testGetFilesystem, &data) < 0) \
+ ret = -1; \
+ } while (0)
+
+ DO_TEST_GET_FS("/", true);
+ DO_TEST_GET_FS("/dev", true);
+ DO_TEST_GET_FS("/dev/pts", false);
+ DO_TEST_GET_FS("/doesnotexist", false);
+
+ virObjectUnref(caps);
+ virObjectUnref(xmlopt);
+
+ cleanup:
+ return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+}
+
+VIRT_TEST_MAIN(mymain)
diff --git a/tests/testutils.c b/tests/testutils.c
index 32fe374..ac2a654 100644
--- a/tests/testutils.c
+++ b/tests/testutils.c
@@ -645,3 +645,60 @@ int virtTestClearLineRegex(const char *pattern,
return 0;
}
+
+
+virCapsPtr virTestGenericCapsInit(void)
+{
+ virCapsPtr caps;
+ virCapsGuestPtr guest;
+
+ if ((caps = virCapabilitiesNew(VIR_ARCH_X86_64,
+ 0, 0)) == NULL)
+ return NULL;
+
+ if ((guest = virCapabilitiesAddGuest(caps, "hvm", VIR_ARCH_I686,
+ "/usr/bin/acme-virt", NULL,
+ 0, NULL)) == NULL)
+ goto error;
+
+ if (!virCapabilitiesAddGuestDomain(guest, "test", NULL, NULL, 0, NULL))
+ goto error;
+
+
+ if ((guest = virCapabilitiesAddGuest(caps, "hvm", VIR_ARCH_X86_64,
+ "/usr/bin/acme-virt", NULL,
+ 0, NULL)) == NULL)
+ goto error;
+
+ if (!virCapabilitiesAddGuestDomain(guest, "test", NULL, NULL, 0, NULL))
+ goto error;
+
+
+ if (virTestGetDebug()) {
+ char *caps_str;
+
+ caps_str = virCapabilitiesFormatXML(caps);
+ if (!caps_str)
+ goto error;
+
+ fprintf(stderr, "Generic driver capabilities:\n%s", caps_str);
+
+ VIR_FREE(caps_str);
+ }
+
+ return caps;
+
+error:
+ virObjectUnref(caps);
+ return NULL;
+}
+
+static virDomainDefParserConfig virTestGenericDomainDefParserConfig;
+static virDomainXMLPrivateDataCallbacks virTestGenericPrivateDataCallbacks;
+
+virDomainXMLOptionPtr virTestGenericDomainXMLConfInit(void)
+{
+ return virDomainXMLOptionNew(&virTestGenericDomainDefParserConfig,
+ &virTestGenericPrivateDataCallbacks,
+ NULL);
+}
diff --git a/tests/testutils.h b/tests/testutils.h
index 674d3df..8d2048b 100644
--- a/tests/testutils.h
+++ b/tests/testutils.h
@@ -27,6 +27,8 @@
# include "viralloc.h"
# include "virfile.h"
# include "virstring.h"
+# include "capabilities.h"
+# include "domain_conf.h"
# define EXIT_AM_SKIP 77 /* tell Automake we're skipping a test */
# define EXIT_AM_HARDFAIL 99 /* tell Automake that the framework is broken */
@@ -102,4 +104,7 @@ int virtTestMain(int argc,
return virtTestMain(argc, argv, func); \
}
+virCapsPtr virTestGenericCapsInit(void);
+virDomainXMLOptionPtr virTestGenericDomainXMLConfInit(void);
+
#endif /* __VIT_TEST_UTILS_H__ */
--
1.8.4.2
10 years, 9 months
[libvirt] [PATCH 0/2] Introduce max_anonymous_clients
by Michal Privoznik
https://bugzilla.redhat.com/show_bug.cgi?id=981729
So far we can limit how many clients are connected,
how many are waiting in accept() line but we could
not control the count of accepted but not
authenticated yet.
Michal Privoznik (2):
virNetServer: Introduce unauth clients counter
daemon: Introduce max_anonymous_clients
daemon/libvirtd-config.c | 1 +
daemon/libvirtd-config.h | 1 +
daemon/libvirtd.aug | 1 +
daemon/libvirtd.c | 1 +
daemon/libvirtd.conf | 3 ++
daemon/remote.c | 21 ++++++++-----
daemon/test_libvirtd.aug.in | 1 +
src/locking/lock_daemon.c | 4 +--
src/lxc/lxc_controller.c | 2 +-
src/rpc/virnetserver.c | 73 +++++++++++++++++++++++++++++++++++++++++----
src/rpc/virnetserver.h | 3 ++
11 files changed, 95 insertions(+), 16 deletions(-)
--
1.8.5.1
10 years, 9 months