[lxc-devel] [PATCH] python: Lots of fixes in C extension
Serge Hallyn
serge.hallyn at ubuntu.com
Thu Apr 18 15:34:15 UTC 2013
Quoting Stéphane Graber (stgraber at ubuntu.com):
> Fixes a lot of issues found by a code review done by Barry Warsaw.
>
> Those include:
> - Wrong signature for getters
> - Various memory leaks
> - Various optimizations
> - More consistent return values
> - Proper exception handling
>
> Reported-by: Barry Warsaw <barry at ubuntu.com>
> Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
Much of this (like PyTuple_GET_SIZE) I have no idea on, but overall looks
good, and of course I'll trust Barry's advice :)
Acked-by: Serge E. Hallyn <serge.hallyn at ubuntu.com>
> ---
> src/python-lxc/lxc.c | 270 +++++++++++++++++++++++++++++++++++----------------
> 1 file changed, 184 insertions(+), 86 deletions(-)
>
> diff --git a/src/python-lxc/lxc.c b/src/python-lxc/lxc.c
> index 8da6f36..85bab11 100644
> --- a/src/python-lxc/lxc.c
> +++ b/src/python-lxc/lxc.c
> @@ -34,13 +34,19 @@ typedef struct {
>
> char**
> convert_tuple_to_char_pointer_array(PyObject *argv) {
> - int argc = PyTuple_Size(argv);
> + int argc = PyTuple_GET_SIZE(argv);
> int i;
>
> char **result = (char**) malloc(sizeof(char*)*argc + 1);
>
> + if (result == NULL) {
> + PyErr_SetNone(PyExc_MemoryError);
> + return NULL;
> + }
> +
> for (i = 0; i < argc; i++) {
> - PyObject *pyobj = PyTuple_GetItem(argv, i);
> + PyObject *pyobj = PyTuple_GET_ITEM(argv, i);
> + assert(pyobj != NULL);
>
> char *str = NULL;
> PyObject *pystr = NULL;
> @@ -51,8 +57,17 @@ convert_tuple_to_char_pointer_array(PyObject *argv) {
> }
>
> pystr = PyUnicode_AsUTF8String(pyobj);
> + if (pystr == NULL) {
> + PyErr_SetString(PyExc_ValueError, "Unable to convert to UTF-8");
> + free(result);
> + return NULL;
> + }
> +
> str = PyBytes_AsString(pystr);
> + assert(str != NULL);
> +
> memcpy((char *) &result[i], (char *) &str, sizeof(str));
> + Py_DECREF(pystr);
> }
>
> result[argc] = NULL;
> @@ -82,18 +97,27 @@ Container_init(Container *self, PyObject *args, PyObject *kwds)
> {
> static char *kwlist[] = {"name", "config_path", NULL};
> char *name = NULL;
> + PyObject *fs_config_path = NULL;
> char *config_path = NULL;
>
> - if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|s", kwlist,
> - &name, &config_path))
> + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|O&", kwlist,
> + &name,
> + PyUnicode_FSConverter, &fs_config_path))
> return -1;
>
> + if (fs_config_path != NULL) {
> + config_path = PyBytes_AS_STRING(fs_config_path);
> + assert(config_path != NULL);
> + }
> +
> self->container = lxc_container_new(name, config_path);
> if (!self->container) {
> - fprintf(stderr, "%d: error creating lxc_container %s\n", __LINE__, name);
> + Py_XDECREF(fs_config_path);
> + fprintf(stderr, "%d: error creating container %s\n", __LINE__, name);
> return -1;
> }
>
> + Py_XDECREF(fs_config_path);
> return 0;
> }
>
> @@ -111,13 +135,14 @@ LXC_get_version(PyObject *self, PyObject *args)
>
> // Container properties
> static PyObject *
> -Container_config_file_name(Container *self, PyObject *args, PyObject *kwds)
> +Container_config_file_name(Container *self, void *closure)
> {
> - return PyUnicode_FromString(self->container->config_file_name(self->container));
> + return PyUnicode_FromString(
> + self->container->config_file_name(self->container));
> }
>
> static PyObject *
> -Container_defined(Container *self, PyObject *args, PyObject *kwds)
> +Container_defined(Container *self, void *closure)
> {
> if (self->container->is_defined(self->container)) {
> Py_RETURN_TRUE;
> @@ -127,19 +152,19 @@ Container_defined(Container *self, PyObject *args, PyObject *kwds)
> }
>
> static PyObject *
> -Container_init_pid(Container *self, PyObject *args, PyObject *kwds)
> +Container_init_pid(Container *self, void *closure)
> {
> - return Py_BuildValue("i", self->container->init_pid(self->container));
> + return PyLong_FromLong(self->container->init_pid(self->container));
> }
>
> static PyObject *
> -Container_name(Container *self, PyObject *args, PyObject *kwds)
> +Container_name(Container *self, void *closure)
> {
> return PyUnicode_FromString(self->container->name);
> }
>
> static PyObject *
> -Container_running(Container *self, PyObject *args, PyObject *kwds)
> +Container_running(Container *self, void *closure)
> {
> if (self->container->is_running(self->container)) {
> Py_RETURN_TRUE;
> @@ -149,7 +174,7 @@ Container_running(Container *self, PyObject *args, PyObject *kwds)
> }
>
> static PyObject *
> -Container_state(Container *self, PyObject *args, PyObject *kwds)
> +Container_state(Container *self, void *closure)
> {
> return PyUnicode_FromString(self->container->state(self->container));
> }
> @@ -161,9 +186,9 @@ Container_clear_config_item(Container *self, PyObject *args, PyObject *kwds)
> static char *kwlist[] = {"key", NULL};
> char *key = NULL;
>
> - if (! PyArg_ParseTupleAndKeywords(args, kwds, "s|", kwlist,
> + if (! PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist,
> &key))
> - Py_RETURN_FALSE;
> + return NULL;
>
> if (self->container->clear_config_item(self->container, key)) {
> Py_RETURN_TRUE;
> @@ -177,27 +202,40 @@ Container_create(Container *self, PyObject *args, PyObject *kwds)
> {
> char* template_name = NULL;
> char** create_args = {NULL};
> - PyObject *vargs = NULL;
> + PyObject *retval = NULL, *vargs = NULL;
> static char *kwlist[] = {"template", "args", NULL};
>
> if (! PyArg_ParseTupleAndKeywords(args, kwds, "s|O", kwlist,
> &template_name, &vargs))
> - Py_RETURN_FALSE;
> + return NULL;
>
> - if (vargs && PyTuple_Check(vargs)) {
> - create_args = convert_tuple_to_char_pointer_array(vargs);
> - if (!create_args) {
> + if (vargs) {
> + if (PyTuple_Check(vargs)) {
> + create_args = convert_tuple_to_char_pointer_array(vargs);
> + if (!create_args) {
> + return NULL;
> + }
> + }
> + else {
> + PyErr_SetString(PyExc_ValueError, "args needs to be a tuple");
> return NULL;
> }
> }
>
> - if (self->container->create(self->container, template_name, create_args)) {
> + if (self->container->create(self->container, template_name, create_args))
> + retval = Py_True;
> + else
> + retval = Py_False;
> +
> + if (vargs) {
> + /* We cannot have gotten here unless vargs was given and create_args
> + * was successfully allocated.
> + */
> free(create_args);
> - Py_RETURN_TRUE;
> }
>
> - free(create_args);
> - Py_RETURN_FALSE;
> + Py_INCREF(retval);
> + return retval;
> }
>
> static PyObject *
> @@ -228,20 +266,26 @@ Container_get_cgroup_item(Container *self, PyObject *args, PyObject *kwds)
> int len = 0;
> PyObject *ret = NULL;
>
> - if (! PyArg_ParseTupleAndKeywords(args, kwds, "s|", kwlist,
> + if (! PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist,
> &key))
> - Py_RETURN_FALSE;
> + return NULL;
>
> len = self->container->get_cgroup_item(self->container, key, NULL, 0);
>
> - if (len <= 0) {
> - Py_RETURN_FALSE;
> + if (len < 0) {
> + PyErr_SetString(PyExc_KeyError, "Invalid cgroup entry");
> + return NULL;
> }
>
> char* value = (char*) malloc(sizeof(char)*len + 1);
> - if (self->container->get_cgroup_item(self->container, key, value, len + 1) != len) {
> + if (value == NULL)
> + return PyErr_NoMemory();
> +
> + if (self->container->get_cgroup_item(self->container,
> + key, value, len + 1) != len) {
> + PyErr_SetString(PyExc_ValueError, "Unable to read config value");
> free(value);
> - Py_RETURN_FALSE;
> + return NULL;
> }
>
> ret = PyUnicode_FromString(value);
> @@ -259,18 +303,24 @@ Container_get_config_item(Container *self, PyObject *args, PyObject *kwds)
>
> if (! PyArg_ParseTupleAndKeywords(args, kwds, "s|", kwlist,
> &key))
> - Py_RETURN_FALSE;
> + return NULL;
>
> len = self->container->get_config_item(self->container, key, NULL, 0);
>
> - if (len <= 0) {
> - Py_RETURN_FALSE;
> + if (len < 0) {
> + PyErr_SetString(PyExc_KeyError, "Invalid configuration key");
> + return NULL;
> }
>
> char* value = (char*) malloc(sizeof(char)*len + 1);
> - if (self->container->get_config_item(self->container, key, value, len + 1) != len) {
> - free(value);
> - Py_RETURN_FALSE;
> + if (value == NULL)
> + return PyErr_NoMemory();
> +
> + if (self->container->get_config_item(self->container,
> + key, value, len + 1) != len) {
> + PyErr_SetString(PyExc_ValueError, "Unable to read config value");
> + free(value);
> + return NULL;
> }
>
> ret = PyUnicode_FromString(value);
> @@ -281,7 +331,8 @@ Container_get_config_item(Container *self, PyObject *args, PyObject *kwds)
> static PyObject *
> Container_get_config_path(Container *self, PyObject *args, PyObject *kwds)
> {
> - return PyUnicode_FromString(self->container->get_config_path(self->container));
> + return PyUnicode_FromString(
> + self->container->get_config_path(self->container));
> }
>
> static PyObject *
> @@ -294,18 +345,24 @@ Container_get_keys(Container *self, PyObject *args, PyObject *kwds)
>
> if (! PyArg_ParseTupleAndKeywords(args, kwds, "|s", kwlist,
> &key))
> - Py_RETURN_FALSE;
> + return NULL;
>
> len = self->container->get_keys(self->container, key, NULL, 0);
>
> - if (len <= 0) {
> - Py_RETURN_FALSE;
> + if (len < 0) {
> + PyErr_SetString(PyExc_KeyError, "Invalid configuration key");
> + return NULL;
> }
>
> char* value = (char*) malloc(sizeof(char)*len + 1);
> - if (self->container->get_keys(self->container, key, value, len + 1) != len) {
> + if (value == NULL)
> + return PyErr_NoMemory();
> +
> + if (self->container->get_keys(self->container,
> + key, value, len + 1) != len) {
> + PyErr_SetString(PyExc_ValueError, "Unable to read config keys");
> free(value);
> - Py_RETURN_FALSE;
> + return NULL;
> }
>
> ret = PyUnicode_FromString(value);
> @@ -317,16 +374,24 @@ static PyObject *
> Container_load_config(Container *self, PyObject *args, PyObject *kwds)
> {
> static char *kwlist[] = {"path", NULL};
> + PyObject *fs_path = NULL;
> char* path = NULL;
>
> - if (! PyArg_ParseTupleAndKeywords(args, kwds, "|s", kwlist,
> - &path))
> - Py_RETURN_FALSE;
> + if (! PyArg_ParseTupleAndKeywords(args, kwds, "|O&", kwlist,
> + PyUnicode_FSConverter, &fs_path))
> + return NULL;
> +
> + if (fs_path != NULL) {
> + path = PyBytes_AS_STRING(fs_path);
> + assert(path != NULL);
> + }
>
> if (self->container->load_config(self->container, path)) {
> + Py_XDECREF(fs_path);
> Py_RETURN_TRUE;
> }
>
> + Py_XDECREF(fs_path);
> Py_RETURN_FALSE;
> }
>
> @@ -334,16 +399,24 @@ static PyObject *
> Container_save_config(Container *self, PyObject *args, PyObject *kwds)
> {
> static char *kwlist[] = {"path", NULL};
> + PyObject *fs_path = NULL;
> char* path = NULL;
>
> - if (! PyArg_ParseTupleAndKeywords(args, kwds, "|s", kwlist,
> - &path))
> - Py_RETURN_FALSE;
> + if (! PyArg_ParseTupleAndKeywords(args, kwds, "|O&", kwlist,
> + PyUnicode_FSConverter, &fs_path))
> + return NULL;
> +
> + if (fs_path != NULL) {
> + path = PyBytes_AS_STRING(fs_path);
> + assert(path != NULL);
> + }
>
> if (self->container->save_config(self->container, path)) {
> + Py_XDECREF(fs_path);
> Py_RETURN_TRUE;
> }
>
> + Py_XDECREF(fs_path);
> Py_RETURN_FALSE;
> }
>
> @@ -354,9 +427,9 @@ Container_set_cgroup_item(Container *self, PyObject *args, PyObject *kwds)
> char *key = NULL;
> char *value = NULL;
>
> - if (! PyArg_ParseTupleAndKeywords(args, kwds, "ss|", kwlist,
> + if (! PyArg_ParseTupleAndKeywords(args, kwds, "ss", kwlist,
> &key, &value))
> - Py_RETURN_FALSE;
> + return NULL;
>
> if (self->container->set_cgroup_item(self->container, key, value)) {
> Py_RETURN_TRUE;
> @@ -372,9 +445,9 @@ Container_set_config_item(Container *self, PyObject *args, PyObject *kwds)
> char *key = NULL;
> char *value = NULL;
>
> - if (! PyArg_ParseTupleAndKeywords(args, kwds, "ss|", kwlist,
> + if (! PyArg_ParseTupleAndKeywords(args, kwds, "ss", kwlist,
> &key, &value))
> - Py_RETURN_FALSE;
> + return NULL;
>
> if (self->container->set_config_item(self->container, key, value)) {
> Py_RETURN_TRUE;
> @@ -389,9 +462,9 @@ Container_set_config_path(Container *self, PyObject *args, PyObject *kwds)
> static char *kwlist[] = {"path", NULL};
> char *path = NULL;
>
> - if (! PyArg_ParseTupleAndKeywords(args, kwds, "s|", kwlist,
> + if (! PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist,
> &path))
> - Py_RETURN_FALSE;
> + return NULL;
>
> if (self->container->set_config_path(self->container, path)) {
> Py_RETURN_TRUE;
> @@ -408,7 +481,7 @@ Container_shutdown(Container *self, PyObject *args, PyObject *kwds)
>
> if (! PyArg_ParseTupleAndKeywords(args, kwds, "|i", kwlist,
> &timeout))
> - Py_RETURN_FALSE;
> + return NULL;
>
> if (self->container->shutdown(self->container, timeout)) {
> Py_RETURN_TRUE;
> @@ -421,13 +494,13 @@ static PyObject *
> Container_start(Container *self, PyObject *args, PyObject *kwds)
> {
> char** init_args = {NULL};
> - PyObject *useinit = NULL, *vargs = NULL;
> + PyObject *useinit = NULL, *retval = NULL, *vargs = NULL;
> int init_useinit = 0;
> static char *kwlist[] = {"useinit", "cmd", NULL};
>
> if (! PyArg_ParseTupleAndKeywords(args, kwds, "|OO", kwlist,
> &useinit, &vargs))
> - Py_RETURN_FALSE;
> + return NULL;
>
> if (useinit && useinit == Py_True) {
> init_useinit = 1;
> @@ -442,13 +515,20 @@ Container_start(Container *self, PyObject *args, PyObject *kwds)
>
> self->container->want_daemonize(self->container);
>
> - if (self->container->start(self->container, init_useinit, init_args)) {
> + if (self->container->start(self->container, init_useinit, init_args))
> + retval = Py_True;
> + else
> + retval = Py_False;
> +
> + if (vargs) {
> + /* We cannot have gotten here unless vargs was given and create_args
> + * was successfully allocated.
> + */
> free(init_args);
> - Py_RETURN_TRUE;
> }
>
> - free(init_args);
> - Py_RETURN_FALSE;
> + Py_INCREF(retval);
> + return retval;
> }
>
> static PyObject *
> @@ -480,7 +560,7 @@ Container_wait(Container *self, PyObject *args, PyObject *kwds)
>
> if (! PyArg_ParseTupleAndKeywords(args, kwds, "s|i", kwlist,
> &state, &timeout))
> - Py_RETURN_FALSE;
> + return NULL;
>
> if (self->container->wait(self->container, state, timeout)) {
> Py_RETURN_TRUE;
> @@ -491,125 +571,143 @@ Container_wait(Container *self, PyObject *args, PyObject *kwds)
>
> static PyGetSetDef Container_getseters[] = {
> {"config_file_name",
> - (getter)Container_config_file_name, 0,
> + (getter)Container_config_file_name, NULL,
> "Path to the container configuration",
> NULL},
> {"defined",
> - (getter)Container_defined, 0,
> + (getter)Container_defined, NULL,
> "Boolean indicating whether the container configuration exists",
> NULL},
> {"init_pid",
> - (getter)Container_init_pid, 0,
> + (getter)Container_init_pid, NULL,
> "PID of the container's init process in the host's PID namespace",
> NULL},
> {"name",
> - (getter)Container_name, 0,
> + (getter)Container_name, NULL,
> "Container name",
> NULL},
> {"running",
> - (getter)Container_running, 0,
> + (getter)Container_running, NULL,
> "Boolean indicating whether the container is running or not",
> NULL},
> {"state",
> - (getter)Container_state, 0,
> + (getter)Container_state, NULL,
> "Container state",
> NULL},
> {NULL, NULL, NULL, NULL, NULL}
> };
>
> static PyMethodDef Container_methods[] = {
> - {"clear_config_item", (PyCFunction)Container_clear_config_item, METH_VARARGS | METH_KEYWORDS,
> + {"clear_config_item", (PyCFunction)Container_clear_config_item,
> + METH_VARARGS|METH_KEYWORDS,
> "clear_config_item(key) -> boolean\n"
> "\n"
> "Clear the current value of a config key."
> },
> - {"create", (PyCFunction)Container_create, METH_VARARGS | METH_KEYWORDS,
> + {"create", (PyCFunction)Container_create,
> + METH_VARARGS|METH_KEYWORDS,
> "create(template, args = (,)) -> boolean\n"
> "\n"
> "Create a new rootfs for the container, using the given template "
> "and passing some optional arguments to it."
> },
> - {"destroy", (PyCFunction)Container_destroy, METH_NOARGS,
> + {"destroy", (PyCFunction)Container_destroy,
> + METH_NOARGS,
> "destroy() -> boolean\n"
> "\n"
> "Destroys the container."
> },
> - {"freeze", (PyCFunction)Container_freeze, METH_NOARGS,
> + {"freeze", (PyCFunction)Container_freeze,
> + METH_NOARGS,
> "freeze() -> boolean\n"
> "\n"
> "Freezes the container and returns its return code."
> },
> - {"get_cgroup_item", (PyCFunction)Container_get_cgroup_item, METH_VARARGS | METH_KEYWORDS,
> + {"get_cgroup_item", (PyCFunction)Container_get_cgroup_item,
> + METH_VARARGS|METH_KEYWORDS,
> "get_cgroup_item(key) -> string\n"
> "\n"
> "Get the current value of a cgroup entry."
> },
> - {"get_config_item", (PyCFunction)Container_get_config_item, METH_VARARGS | METH_KEYWORDS,
> + {"get_config_item", (PyCFunction)Container_get_config_item,
> + METH_VARARGS|METH_KEYWORDS,
> "get_config_item(key) -> string\n"
> "\n"
> "Get the current value of a config key."
> },
> - {"get_config_path", (PyCFunction)Container_get_config_path, METH_NOARGS,
> + {"get_config_path", (PyCFunction)Container_get_config_path,
> + METH_NOARGS,
> "get_config_path() -> string\n"
> "\n"
> "Return the LXC config path (where the containers are stored)."
> },
> - {"get_keys", (PyCFunction)Container_get_keys, METH_VARARGS | METH_KEYWORDS,
> + {"get_keys", (PyCFunction)Container_get_keys,
> + METH_VARARGS|METH_KEYWORDS,
> "get_keys(key) -> string\n"
> "\n"
> "Get a list of valid sub-keys for a key."
> },
> - {"load_config", (PyCFunction)Container_load_config, METH_VARARGS | METH_KEYWORDS,
> + {"load_config", (PyCFunction)Container_load_config,
> + METH_VARARGS|METH_KEYWORDS,
> "load_config(path = DEFAULT) -> boolean\n"
> "\n"
> "Read the container configuration from its default "
> "location or from an alternative location if provided."
> },
> - {"save_config", (PyCFunction)Container_save_config, METH_VARARGS | METH_KEYWORDS,
> + {"save_config", (PyCFunction)Container_save_config,
> + METH_VARARGS|METH_KEYWORDS,
> "save_config(path = DEFAULT) -> boolean\n"
> "\n"
> "Save the container configuration to its default "
> "location or to an alternative location if provided."
> },
> - {"set_cgroup_item", (PyCFunction)Container_set_cgroup_item, METH_VARARGS | METH_KEYWORDS,
> + {"set_cgroup_item", (PyCFunction)Container_set_cgroup_item,
> + METH_VARARGS|METH_KEYWORDS,
> "set_cgroup_item(key, value) -> boolean\n"
> "\n"
> "Set a cgroup entry to the provided value."
> },
> - {"set_config_item", (PyCFunction)Container_set_config_item, METH_VARARGS | METH_KEYWORDS,
> + {"set_config_item", (PyCFunction)Container_set_config_item,
> + METH_VARARGS|METH_KEYWORDS,
> "set_config_item(key, value) -> boolean\n"
> "\n"
> "Set a config key to the provided value."
> },
> - {"set_config_path", (PyCFunction)Container_set_config_path, METH_VARARGS | METH_KEYWORDS,
> + {"set_config_path", (PyCFunction)Container_set_config_path,
> + METH_VARARGS|METH_KEYWORDS,
> "set_config_path(path) -> boolean\n"
> "\n"
> "Set the LXC config path (where the containers are stored)."
> },
> - {"shutdown", (PyCFunction)Container_shutdown, METH_VARARGS | METH_KEYWORDS,
> + {"shutdown", (PyCFunction)Container_shutdown,
> + METH_VARARGS|METH_KEYWORDS,
> "shutdown(timeout = -1) -> boolean\n"
> "\n"
> "Sends SIGPWR to the container and wait for it to shutdown "
> "unless timeout is set to a positive value, in which case "
> "the container will be killed when the timeout is reached."
> },
> - {"start", (PyCFunction)Container_start, METH_VARARGS | METH_KEYWORDS,
> + {"start", (PyCFunction)Container_start,
> + METH_VARARGS|METH_KEYWORDS,
> "start(useinit = False, cmd = (,)) -> boolean\n"
> "\n"
> "Start the container, optionally using lxc-init and "
> "an alternate init command, then returns its return code."
> },
> - {"stop", (PyCFunction)Container_stop, METH_NOARGS,
> + {"stop", (PyCFunction)Container_stop,
> + METH_NOARGS,
> "stop() -> boolean\n"
> "\n"
> "Stop the container and returns its return code."
> },
> - {"unfreeze", (PyCFunction)Container_unfreeze, METH_NOARGS,
> + {"unfreeze", (PyCFunction)Container_unfreeze,
> + METH_NOARGS,
> "unfreeze() -> boolean\n"
> "\n"
> "Unfreezes the container and returns its return code."
> },
> - {"wait", (PyCFunction)Container_wait, METH_VARARGS | METH_KEYWORDS,
> + {"wait", (PyCFunction)Container_wait,
> + METH_VARARGS|METH_KEYWORDS,
> "wait(state, timeout = -1) -> boolean\n"
> "\n"
> "Wait for the container to reach a given state or timeout."
> --
> 1.8.1.2
>
>
> ------------------------------------------------------------------------------
> Precog is a next-generation analytics platform capable of advanced
> analytics on semi-structured data. The platform includes APIs for building
> apps and a phenomenal toolset for data science. Developers can use
> our toolset for easy data analysis & visualization. Get a free account!
> http://www2.precog.com/precogplatform/slashdotnewsletter
> _______________________________________________
> Lxc-devel mailing list
> Lxc-devel at lists.sourceforge.net
> https://lists.sourceforge.net/lists/listinfo/lxc-devel
More information about the lxc-devel
mailing list