commit 9541b380389e451db58cba40ac005721600a2213 Author: Stéphane Graber Date: Mon May 5 22:38:22 2014 -0500 Initial release of basic python2 binding This is the first release of a basic out of tree python2 binding. It's not entirely in sync with the python3 (lacks arch_to_personality) but otherwise should behave identically. This binding is not officially supported by LXC upstream as part of 1.0.x nor do we have any plan to include it for any upcoming release. It is just released as a convenience for those who unfortunately can't yet use python3. Thanks to Dwight Engen for the original patchset! Signed-off-by: Stéphane Graber diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a0114d3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build/* +*.pyc diff --git a/lxc.c b/lxc.c new file mode 100644 index 0000000..e53e792 --- /dev/null +++ b/lxc.c @@ -0,0 +1,1801 @@ +/* + * python-lxc: Python bindings for LXC + * + * (C) Copyright Canonical Ltd. 2012-2013 + * + * Authors: + * Stéphane Graber + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#include +#include "structmember.h" +#include +#include +#include +#include + +#define INIT_MODULE(m) init ## m + +static int PyUnicode_FSConverter(PyObject *unicode, void *result) +{ + PyObject *bytes; + char **str = result; + + if (unicode == NULL) + return 1; + if (!PyUnicode_FSConverter(unicode, &bytes)) + return 0; + *str = strdup(PyBytes_AS_STRING(bytes)); + Py_XDECREF(bytes); + return 1; +} + +/* Helper functions */ + +int lxc_wait_for_pid_status(pid_t pid) +{ + int status, ret; + +again: + ret = waitpid(pid, &status, 0); + if (ret == -1) { + if (errno == EINTR) + goto again; + return -1; + } + if (ret != pid) + goto again; + return status; +} + +char** +convert_tuple_to_char_pointer_array(PyObject *argv) { + int argc; + int i, j; + + /* not a list or tuple */ + if (!PyList_Check(argv) && !PyTuple_Check(argv)) { + PyErr_SetString(PyExc_TypeError, "Expected list or tuple."); + return NULL; + } + + argc = PySequence_Fast_GET_SIZE(argv); + + char **result = (char**) calloc(argc + 1, sizeof(char*)); + + if (result == NULL) { + PyErr_SetNone(PyExc_MemoryError); + return NULL; + } + + for (i = 0; i < argc; i++) { + PyObject *pyobj = PySequence_Fast_GET_ITEM(argv, i); + assert(pyobj != NULL); + + char *str = NULL; +// PyObject *pystr = NULL; + +// pystr = PyUnicode_AsUTF8String(pyobj); +// if (!pystr) { + /* Maybe it wasn't UTF-8 encoded. An exception is already set. */ +// goto error; +// } + + str = PyBytes_AsString(pyobj); + if (!str) { + /* Maybe pystr wasn't a valid object. An exception is already set. + */ +// Py_DECREF(pystr); + goto error; + } + + /* We must make a copy of str, because it points into internal memory + * which we do not own. Assume it's NULL terminated, otherwise we'd + * have to use PyUnicode_AsUTF8AndSize() and be explicit about copying + * the memory. + */ + result[i] = strdup(str); + + /* Do not decref pyobj since we stole a reference by using + * PyTuple_GET_ITEM(). + */ +// Py_DECREF(pystr); + if (result[i] == NULL) { + PyErr_SetNone(PyExc_MemoryError); + goto error; + } + } + + result[argc] = NULL; + return result; + +error: + /* We can only iterate up to but not including i because malloc() does not + * initialize its memory. Thus if we got here, i points to the index + * after the last strdup'd entry in result. + */ + for (j = 0; j < i; j++) + free(result[j]); + free(result); + return NULL; +} + +struct lxc_attach_python_payload { + PyObject *fn; + PyObject *arg; +}; + +static int lxc_attach_python_exec(void* _payload) +{ + struct lxc_attach_python_payload *payload = + (struct lxc_attach_python_payload *)_payload; + PyObject *result = PyObject_CallFunctionObjArgs(payload->fn, + payload->arg, NULL); + + if (!result) { + PyErr_Print(); + return -1; + } + if (PyLong_Check(result)) + return (int)PyLong_AsLong(result); + else + return -1; +} + +static void lxc_attach_free_options(lxc_attach_options_t *options); + +static lxc_attach_options_t *lxc_attach_parse_options(PyObject *kwds) +{ + static char *kwlist[] = {"attach_flags", "namespaces", "personality", + "initial_cwd", "uid", "gid", "env_policy", + "extra_env_vars", "extra_keep_env", "stdin", + "stdout", "stderr", NULL}; + long temp_uid, temp_gid; + int temp_env_policy; + PyObject *extra_env_vars_obj = NULL; + PyObject *extra_keep_env_obj = NULL; + PyObject *stdin_obj = NULL; + PyObject *stdout_obj = NULL; + PyObject *stderr_obj = NULL; + PyObject *initial_cwd_obj = NULL; + PyObject *dummy; + bool parse_result; + + lxc_attach_options_t default_options = LXC_ATTACH_OPTIONS_DEFAULT; + lxc_attach_options_t *options = malloc(sizeof(*options)); + + if (!options) { + PyErr_SetNone(PyExc_MemoryError); + return NULL; + } + memcpy(options, &default_options, sizeof(*options)); + + /* we need some dummy variables because we can't be sure + * the data types match completely */ + temp_uid = -1; + temp_gid = -1; + temp_env_policy = options->env_policy; + + /* we need a dummy tuple */ + dummy = PyTuple_New(0); + + parse_result = PyArg_ParseTupleAndKeywords(dummy, kwds, "|iilO&lliOOOOO", + kwlist, &options->attach_flags, + &options->namespaces, + &options->personality, + PyUnicode_FSConverter, + &initial_cwd_obj, &temp_uid, + &temp_gid, &temp_env_policy, + &extra_env_vars_obj, + &extra_keep_env_obj, + &stdin_obj, &stdout_obj, + &stderr_obj); + + /* immediately get rid of the dummy tuple */ + Py_DECREF(dummy); + + if (!parse_result) { + lxc_attach_free_options(options); + return NULL; + } + + /* duplicate the string, so we don't depend on some random Python object */ + if (initial_cwd_obj != NULL) { + options->initial_cwd = strndup(PyBytes_AsString(initial_cwd_obj), + PyBytes_Size(initial_cwd_obj)); + Py_DECREF(initial_cwd_obj); + } + + /* do the type conversion from the types that match the parse string */ + if (temp_uid != -1) options->uid = (uid_t)temp_uid; + if (temp_gid != -1) options->gid = (gid_t)temp_gid; + options->env_policy = (lxc_attach_env_policy_t)temp_env_policy; + + if (extra_env_vars_obj) + options->extra_env_vars = + convert_tuple_to_char_pointer_array(extra_env_vars_obj); + if (extra_keep_env_obj) + options->extra_keep_env = + convert_tuple_to_char_pointer_array(extra_keep_env_obj); + if (stdin_obj) { + options->stdin_fd = PyObject_AsFileDescriptor(stdin_obj); + if (options->stdin_fd < 0) { + lxc_attach_free_options(options); + return NULL; + } + } + if (stdout_obj) { + options->stdout_fd = PyObject_AsFileDescriptor(stdout_obj); + if (options->stdout_fd < 0) { + lxc_attach_free_options(options); + return NULL; + } + } + if (stderr_obj) { + options->stderr_fd = PyObject_AsFileDescriptor(stderr_obj); + if (options->stderr_fd < 0) { + lxc_attach_free_options(options); + return NULL; + } + } + + return options; +} + +void lxc_attach_free_options(lxc_attach_options_t *options) +{ + int i; + if (!options) + return; + if (options->initial_cwd) + free(options->initial_cwd); + if (options->extra_env_vars) { + for (i = 0; options->extra_env_vars[i]; i++) + free(options->extra_env_vars[i]); + free(options->extra_env_vars); + } + if (options->extra_keep_env) { + for (i = 0; options->extra_keep_env[i]; i++) + free(options->extra_keep_env[i]); + free(options->extra_keep_env); + } + free(options); +} + +/* Module functions */ +static PyObject * +LXC_attach_run_command(PyObject *self, PyObject *arg) +{ + PyObject *args_obj = NULL; + int i, rv; + lxc_attach_command_t cmd = { + NULL, /* program */ + NULL /* argv[] */ + }; + + if (!PyArg_ParseTuple(arg, "sO", (const char**)&cmd.program, &args_obj)) + return NULL; + if (args_obj && PyList_Check(args_obj)) { + cmd.argv = convert_tuple_to_char_pointer_array(args_obj); + } else { + PyErr_Format(PyExc_TypeError, "Second part of tuple passed to " + "attach_run_command must be a list."); + return NULL; + } + + if (!cmd.argv) + return NULL; + + rv = lxc_attach_run_command(&cmd); + + for (i = 0; cmd.argv[i]; i++) + free(cmd.argv[i]); + free(cmd.argv); + + return PyLong_FromLong(rv); +} + +static PyObject * +LXC_attach_run_shell(PyObject *self, PyObject *arg) +{ + int rv; + + rv = lxc_attach_run_shell(NULL); + + return PyLong_FromLong(rv); +} + +static PyObject * +LXC_get_global_config_item(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"key", NULL}; + char* key = NULL; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "s|", kwlist, + &key)) + return NULL; + + return PyUnicode_FromString(lxc_get_global_config_item(key)); +} + +static PyObject * +LXC_get_version(PyObject *self, PyObject *args) +{ + return PyUnicode_FromString(lxc_get_version()); +} + +static PyObject * +LXC_list_containers(PyObject *self, PyObject *args, PyObject *kwds) +{ + char **names = NULL; + PyObject *list = NULL; + int list_count = 0; + + int list_active = 1; + int list_defined = 1; + + PyObject *py_list_active = NULL; + PyObject *py_list_defined = NULL; + + char* config_path = NULL; + + int i = 0; + PyObject *vargs = NULL; + static char *kwlist[] = {"active", "defined", "config_path", NULL}; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "|OOs", kwlist, + &py_list_active, + &py_list_defined, + &config_path, &vargs)) + return NULL; + + /* We default to listing everything */ + if (py_list_active && py_list_active != Py_True) { + list_active = 0; + } + + if (py_list_defined && py_list_defined != Py_True) { + list_defined = 0; + } + + /* Call the right API function based on filters */ + if (list_active == 1 && list_defined == 1) + list_count = list_all_containers(config_path, &names, NULL); + else if (list_active == 1) + list_count = list_active_containers(config_path, &names, NULL); + else if (list_defined == 1) + list_count = list_defined_containers(config_path, &names, NULL); + + /* Handle failure */ + if (list_count < 0) { + PyErr_SetString(PyExc_ValueError, "failure to list containers"); + return NULL; + } + + /* Generate the tuple */ + list = PyTuple_New(list_count); + for (i = 0; i < list_count; i++) { + PyTuple_SET_ITEM(list, i, PyUnicode_FromString(names[i])); + free(names[i]); + } + free(names); + + return list; +} + +/* Base type and functions for Container */ +typedef struct { + PyObject_HEAD + struct lxc_container *container; +} Container; + +static void +Container_dealloc(Container* self) +{ + lxc_container_put(self->container); + Py_TYPE(self)->tp_free((PyObject*)self); +} + +static int +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|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) { + Py_XDECREF(fs_config_path); + fprintf(stderr, "%d: error creating container %s\n", __LINE__, name); + return -1; + } + + Py_XDECREF(fs_config_path); + return 0; +} + +static PyObject * +Container_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + Container *self; + + self = (Container *)type->tp_alloc(type, 0); + + return (PyObject *)self; +} + +/* Container properties */ +static PyObject * +Container_config_file_name(Container *self, void *closure) +{ + return PyUnicode_FromString( + self->container->config_file_name(self->container)); +} + +static PyObject * +Container_controllable(Container *self, void *closure) +{ + if (self->container->may_control(self->container)) { + Py_RETURN_TRUE; + } + + Py_RETURN_FALSE; +} + +static PyObject * +Container_defined(Container *self, void *closure) +{ + if (self->container->is_defined(self->container)) { + Py_RETURN_TRUE; + } + + Py_RETURN_FALSE; +} + +static PyObject * +Container_init_pid(Container *self, void *closure) +{ + return PyLong_FromLong(self->container->init_pid(self->container)); +} + +static PyObject * +Container_name(Container *self, void *closure) +{ + return PyUnicode_FromString(self->container->name); +} + +static PyObject * +Container_running(Container *self, void *closure) +{ + if (self->container->is_running(self->container)) { + Py_RETURN_TRUE; + } + + Py_RETURN_FALSE; +} + +static PyObject * +Container_state(Container *self, void *closure) +{ + return PyUnicode_FromString(self->container->state(self->container)); +} + +/* Container Functions */ +static PyObject * +Container_add_device_node(Container *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"src_path", "dest_path", NULL}; + char *src_path = NULL; + char *dst_path = NULL; + PyObject *py_src_path = NULL; + PyObject *py_dst_path = NULL; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "O&|O&", kwlist, + PyUnicode_FSConverter, &py_src_path, + PyUnicode_FSConverter, &py_dst_path)) + return NULL; + + if (py_src_path != NULL) { + src_path = PyBytes_AS_STRING(py_src_path); + assert(src_path != NULL); + } + + if (py_dst_path != NULL) { + dst_path = PyBytes_AS_STRING(py_dst_path); + assert(dst_path != NULL); + } + + if (self->container->add_device_node(self->container, src_path, + dst_path)) { + Py_XDECREF(py_src_path); + Py_XDECREF(py_dst_path); + Py_RETURN_TRUE; + } + + Py_XDECREF(py_src_path); + Py_XDECREF(py_dst_path); + Py_RETURN_FALSE; +} + +static PyObject * +Container_attach_and_possibly_wait(Container *self, PyObject *args, + PyObject *kwds, int wait) +{ + struct lxc_attach_python_payload payload = { NULL, NULL }; + lxc_attach_options_t *options = NULL; + long ret; + pid_t pid; + + if (!PyArg_ParseTuple(args, "O|O", &payload.fn, &payload.arg)) + return NULL; + if (!PyCallable_Check(payload.fn)) { + PyErr_Format(PyExc_TypeError, "attach: object not callable"); + return NULL; + } + + options = lxc_attach_parse_options(kwds); + if (!options) + return NULL; + + ret = self->container->attach(self->container, lxc_attach_python_exec, + &payload, options, &pid); + if (ret < 0) + goto out; + + if (wait) { + ret = lxc_wait_for_pid_status(pid); + /* handle case where attach fails */ + if (WIFEXITED(ret) && WEXITSTATUS(ret) == 255) + ret = -1; + } else { + ret = (long)pid; + } + +out: + lxc_attach_free_options(options); + return PyLong_FromLong(ret); +} + +static PyObject * +Container_attach(Container *self, PyObject *args, PyObject *kwds) +{ + return Container_attach_and_possibly_wait(self, args, kwds, 0); +} + +static PyObject * +Container_attach_wait(Container *self, PyObject *args, PyObject *kwds) +{ + return Container_attach_and_possibly_wait(self, args, kwds, 1); +} + +static PyObject * +Container_clear_config(Container *self, PyObject *args, PyObject *kwds) +{ + self->container->clear_config(self->container); + + Py_RETURN_NONE; +} + +static PyObject * +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, + &key)) + return NULL; + + if (self->container->clear_config_item(self->container, key)) { + Py_RETURN_TRUE; + } + + Py_RETURN_FALSE; +} + +static PyObject * +Container_clone(Container *self, PyObject *args, PyObject *kwds) +{ + char *newname = NULL; + char *config_path = NULL; + int flags = 0; + char *bdevtype = NULL; + char *bdevdata = NULL; + unsigned long newsize = 0; + char **hookargs = NULL; + + PyObject *py_hookargs = NULL; + PyObject *py_config_path = NULL; + struct lxc_container *new_container = NULL; + int i = 0; + + static char *kwlist[] = {"newname", "config_path", "flags", "bdevtype", + "bdevdata", "newsize", "hookargs", NULL}; + if (! PyArg_ParseTupleAndKeywords(args, kwds, "s|O&isskO", kwlist, + &newname, + PyUnicode_FSConverter, &py_config_path, + &flags, &bdevtype, &bdevdata, &newsize, + &py_hookargs)) + return NULL; + + if (py_hookargs) { + if (PyTuple_Check(py_hookargs)) { + hookargs = convert_tuple_to_char_pointer_array(py_hookargs); + if (!hookargs) { + return NULL; + } + } + else { + PyErr_SetString(PyExc_ValueError, "hookargs needs to be a tuple"); + return NULL; + } + } + + if (py_config_path != NULL) { + config_path = PyBytes_AS_STRING(py_config_path); + assert(config_path != NULL); + } + + new_container = self->container->clone(self->container, newname, + config_path, flags, bdevtype, + bdevdata, newsize, hookargs); + + Py_XDECREF(py_config_path); + + if (hookargs) { + for (i = 0; i < PyTuple_GET_SIZE(py_hookargs); i++) + free(hookargs[i]); + free(hookargs); + } + + if (new_container == NULL) { + Py_RETURN_FALSE; + } + + lxc_container_put(new_container); + + Py_RETURN_TRUE; +} + +static PyObject * +Container_console(Container *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"ttynum", "stdinfd", "stdoutfd", "stderrfd", + "escape", NULL}; + int ttynum = -1, stdinfd = 0, stdoutfd = 1, stderrfd = 2, escape = 1; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "|iiiii", kwlist, + &ttynum, &stdinfd, &stdoutfd, &stderrfd, + &escape)) + return NULL; + + if (self->container->console(self->container, ttynum, + stdinfd, stdoutfd, stderrfd, escape) == 0) { + Py_RETURN_TRUE; + } + Py_RETURN_FALSE; +} + +static PyObject * +Container_console_getfd(Container *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"ttynum", NULL}; + int ttynum = -1, masterfd; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "|i", kwlist, &ttynum)) + return NULL; + + if (self->container->console_getfd(self->container, &ttynum, + &masterfd) < 0) { + PyErr_SetString(PyExc_ValueError, "Unable to allocate tty"); + return NULL; + } + return PyLong_FromLong(masterfd); +} + +static PyObject * +Container_create(Container *self, PyObject *args, PyObject *kwds) +{ + char* template_name = NULL; + int flags = 0; + char** create_args = {NULL}; + PyObject *retval = NULL, *vargs = NULL; + int i = 0; + static char *kwlist[] = {"template", "flags", "args", NULL}; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "s|iO", kwlist, + &template_name, &flags, &vargs)) + return NULL; + + 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, NULL, NULL, + flags, 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. + */ + for (i = 0; i < PyTuple_GET_SIZE(vargs); i++) + free(create_args[i]); + free(create_args); + } + + Py_INCREF(retval); + return retval; +} + +static PyObject * +Container_destroy(Container *self, PyObject *args, PyObject *kwds) +{ + if (self->container->destroy(self->container)) { + Py_RETURN_TRUE; + } + + Py_RETURN_FALSE; +} + +static PyObject * +Container_freeze(Container *self, PyObject *args, PyObject *kwds) +{ + if (self->container->freeze(self->container)) { + Py_RETURN_TRUE; + } + + Py_RETURN_FALSE; +} + +static PyObject * +Container_get_cgroup_item(Container *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"key", NULL}; + char* key = NULL; + int len = 0; + PyObject *ret = NULL; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, + &key)) + return NULL; + + len = self->container->get_cgroup_item(self->container, key, NULL, 0); + + if (len < 0) { + PyErr_SetString(PyExc_KeyError, "Invalid cgroup entry"); + return NULL; + } + + char* value = (char*) malloc(sizeof(char)*len + 1); + 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); + return NULL; + } + + ret = PyUnicode_FromString(value); + free(value); + return ret; +} + +static PyObject * +Container_get_config_item(Container *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"key", NULL}; + char* key = NULL; + int len = 0; + PyObject *ret = NULL; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "s|", kwlist, + &key)) + return NULL; + + len = self->container->get_config_item(self->container, key, NULL, 0); + + if (len < 0) { + PyErr_SetString(PyExc_KeyError, "Invalid configuration key"); + return NULL; + } + + if (len == 0) { + return PyUnicode_FromString(""); + } + + char* value = (char*) malloc(sizeof(char)*len + 1); + 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); + free(value); + return ret; +} + +static PyObject * +Container_get_config_path(Container *self, PyObject *args, PyObject *kwds) +{ + return PyUnicode_FromString( + self->container->get_config_path(self->container)); +} + +static PyObject * +Container_get_keys(Container *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"key", NULL}; + char* key = NULL; + int len = 0; + PyObject *ret = NULL; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "|s", kwlist, + &key)) + return NULL; + + len = self->container->get_keys(self->container, key, NULL, 0); + + if (len < 0) { + PyErr_SetString(PyExc_KeyError, "Invalid configuration key"); + return NULL; + } + + char* value = (char*) malloc(sizeof(char)*len + 1); + 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); + return NULL; + } + + ret = PyUnicode_FromString(value); + free(value); + return ret; +} + +static PyObject * +Container_get_interfaces(Container *self) +{ + int i = 0; + char** interfaces = NULL; + + PyObject* ret; + + /* Get the interfaces */ + interfaces = self->container->get_interfaces(self->container); + if (!interfaces) + return PyTuple_New(0); + + /* Count the entries */ + while (interfaces[i]) + i++; + + /* Create the new tuple */ + ret = PyTuple_New(i); + if (!ret) + return NULL; + + /* Add the entries to the tuple and free the memory */ + i = 0; + while (interfaces[i]) { + PyObject *unicode = PyUnicode_FromString(interfaces[i]); + if (!unicode) { + Py_DECREF(ret); + ret = NULL; + break; + } + PyTuple_SET_ITEM(ret, i, unicode); + i++; + } + + /* Free the list of IPs */ + i = 0; + while (interfaces[i]) { + free(interfaces[i]); + i++; + } + free(interfaces); + + return ret; +} + +static PyObject * +Container_get_ips(Container *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"interface", "family", "scope", NULL}; + char* interface = NULL; + char* family = NULL; + int scope = 0; + + int i = 0; + char** ips = NULL; + + PyObject* ret; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "|ssi", kwlist, + &interface, &family, &scope)) + return NULL; + + /* Get the IPs */ + ips = self->container->get_ips(self->container, interface, family, scope); + if (!ips) + return PyTuple_New(0); + + /* Count the entries */ + while (ips[i]) + i++; + + /* Create the new tuple */ + ret = PyTuple_New(i); + if (!ret) + return NULL; + + /* Add the entries to the tuple and free the memory */ + i = 0; + while (ips[i]) { + PyObject *unicode = PyUnicode_FromString(ips[i]); + if (!unicode) { + Py_DECREF(ret); + ret = NULL; + break; + } + PyTuple_SET_ITEM(ret, i, unicode); + i++; + } + + /* Free the list of IPs */ + i = 0; + while (ips[i]) { + free(ips[i]); + i++; + } + free(ips); + + return ret; +} + +static PyObject * +Container_get_running_config_item(Container *self, PyObject *args, + PyObject *kwds) +{ + static char *kwlist[] = {"key", NULL}; + char* key = NULL; + char* value = NULL; + PyObject *ret = NULL; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "s|", kwlist, + &key)) + return NULL; + + value = self->container->get_running_config_item(self->container, key); + + if (!value) + Py_RETURN_NONE; + + ret = PyUnicode_FromString(value); + free(value); + return ret; +} + + +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, "|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; +} + +static PyObject * +Container_reboot(Container *self, PyObject *args, PyObject *kwds) +{ + if (self->container->reboot(self->container)) { + Py_RETURN_TRUE; + } + + Py_RETURN_FALSE; +} + +static PyObject * +Container_rename(Container *self, PyObject *args, PyObject *kwds) +{ + char *new_name = NULL; + static char *kwlist[] = {"new_name", NULL}; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "s|", kwlist, + &new_name)) + return NULL; + + if (self->container->rename(self->container, new_name)) { + Py_RETURN_TRUE; + } + + Py_RETURN_FALSE; +} + +static PyObject * +Container_remove_device_node(Container *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"src_path", "dest_path", NULL}; + char *src_path = NULL; + char *dst_path = NULL; + PyObject *py_src_path = NULL; + PyObject *py_dst_path = NULL; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "O&|O&", kwlist, + PyUnicode_FSConverter, &py_src_path, + PyUnicode_FSConverter, &py_dst_path)) + return NULL; + + if (py_src_path != NULL) { + src_path = PyBytes_AS_STRING(py_src_path); + assert(src_path != NULL); + } + + if (py_dst_path != NULL) { + dst_path = PyBytes_AS_STRING(py_dst_path); + assert(dst_path != NULL); + } + + if (self->container->remove_device_node(self->container, src_path, + dst_path)) { + Py_XDECREF(py_src_path); + Py_XDECREF(py_dst_path); + Py_RETURN_TRUE; + } + + Py_XDECREF(py_src_path); + Py_XDECREF(py_dst_path); + Py_RETURN_FALSE; +} + +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, "|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; +} + +static PyObject * +Container_set_cgroup_item(Container *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"key", "value", NULL}; + char *key = NULL; + char *value = NULL; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "ss", kwlist, + &key, &value)) + return NULL; + + if (self->container->set_cgroup_item(self->container, key, value)) { + Py_RETURN_TRUE; + } + + Py_RETURN_FALSE; +} + +static PyObject * +Container_set_config_item(Container *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"key", "value", NULL}; + char *key = NULL; + char *value = NULL; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "ss", kwlist, + &key, &value)) + return NULL; + + if (self->container->set_config_item(self->container, key, value)) { + Py_RETURN_TRUE; + } + + Py_RETURN_FALSE; +} + +static PyObject * +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, + &path)) + return NULL; + + if (self->container->set_config_path(self->container, path)) { + Py_RETURN_TRUE; + } + + Py_RETURN_FALSE; +} + +static PyObject * +Container_shutdown(Container *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"timeout", NULL}; + int timeout = -1; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "|i", kwlist, + &timeout)) + return NULL; + + if (self->container->shutdown(self->container, timeout)) { + Py_RETURN_TRUE; + } + + Py_RETURN_FALSE; +} + +static PyObject * +Container_snapshot(Container *self, PyObject *args, PyObject *kwds) +{ + char *comment_path = NULL; + static char *kwlist[] = {"comment_path", NULL}; + int retval = 0; + int ret = 0; + char newname[20]; + PyObject *py_comment_path; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "|O&", kwlist, + PyUnicode_FSConverter, &py_comment_path)) + return NULL; + + if (py_comment_path != NULL) { + comment_path = PyBytes_AS_STRING(py_comment_path); + assert(comment_path != NULL); + } + + retval = self->container->snapshot(self->container, comment_path); + + Py_XDECREF(py_comment_path); + + if (retval < 0) { + Py_RETURN_FALSE; + } + + ret = snprintf(newname, 20, "snap%d", retval); + if (ret < 0 || ret >= 20) + return NULL; + + + return PyUnicode_FromString(newname); +} + +static PyObject * +Container_snapshot_destroy(Container *self, PyObject *args, PyObject *kwds) +{ + char *name = NULL; + static char *kwlist[] = {"name", NULL}; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "s|", kwlist, + &name)) + return NULL; + + if (self->container->snapshot_destroy(self->container, name)) { + Py_RETURN_TRUE; + } + + Py_RETURN_FALSE; +} + +static PyObject * +Container_snapshot_list(Container *self, PyObject *args, PyObject *kwds) +{ + struct lxc_snapshot *snap; + int snap_count = 0; + PyObject *list = NULL; + int i = 0; + + snap_count = self->container->snapshot_list(self->container, &snap); + + if (snap_count < 0) { + PyErr_SetString(PyExc_KeyError, "Unable to list snapshots"); + return NULL; + } + + list = PyTuple_New(snap_count); + for (i = 0; i < snap_count; i++) { + PyObject *list_entry = NULL; + + list_entry = PyTuple_New(4); + PyTuple_SET_ITEM(list_entry, 0, + PyUnicode_FromString(snap[i].name)); + PyTuple_SET_ITEM(list_entry, 1, + PyUnicode_FromString(snap[i].comment_pathname)); + PyTuple_SET_ITEM(list_entry, 2, + PyUnicode_FromString(snap[i].timestamp)); + PyTuple_SET_ITEM(list_entry, 3, + PyUnicode_FromString(snap[i].lxcpath)); + + snap[i].free(&snap[i]); + + PyTuple_SET_ITEM(list, i, list_entry); + } + + return list; +} + + +static PyObject * +Container_snapshot_restore(Container *self, PyObject *args, PyObject *kwds) +{ + char *name = NULL; + char *newname = NULL; + static char *kwlist[] = {"name", "newname", NULL}; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "s|s", kwlist, + &name, &newname)) + return NULL; + + if (self->container->snapshot_restore(self->container, name, newname)) { + Py_RETURN_TRUE; + } + + Py_RETURN_FALSE; +} + +static PyObject * +Container_start(Container *self, PyObject *args, PyObject *kwds) +{ + PyObject *useinit = NULL; + PyObject *daemonize = NULL; + PyObject *close_fds = NULL; + + PyObject *vargs = NULL; + char** init_args = {NULL}; + + PyObject *retval = NULL; + int init_useinit = 0, i = 0; + static char *kwlist[] = {"useinit", "daemonize", "close_fds", + "cmd", NULL}; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "|OOOO", kwlist, + &useinit, &daemonize, &close_fds, + &vargs)) + return NULL; + + if (useinit && useinit == Py_True) { + init_useinit = 1; + } + + if (vargs && PyTuple_Check(vargs)) { + init_args = convert_tuple_to_char_pointer_array(vargs); + if (!init_args) { + return NULL; + } + } + + if (close_fds && close_fds == Py_True) { + self->container->want_close_all_fds(self->container, true); + } + else { + self->container->want_close_all_fds(self->container, false); + } + + if (!daemonize || daemonize == Py_True) { + self->container->want_daemonize(self->container, true); + } + else { + self->container->want_daemonize(self->container, false); + } + + 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. + */ + for (i = 0; i < PyTuple_GET_SIZE(vargs); i++) + free(init_args[i]); + free(init_args); + } + + Py_INCREF(retval); + return retval; +} + +static PyObject * +Container_stop(Container *self, PyObject *args, PyObject *kwds) +{ + if (self->container->stop(self->container)) { + Py_RETURN_TRUE; + } + + Py_RETURN_FALSE; +} + +static PyObject * +Container_unfreeze(Container *self, PyObject *args, PyObject *kwds) +{ + if (self->container->unfreeze(self->container)) { + Py_RETURN_TRUE; + } + + Py_RETURN_FALSE; +} + +static PyObject * +Container_wait(Container *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"state", "timeout", NULL}; + char *state = NULL; + int timeout = -1; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "s|i", kwlist, + &state, &timeout)) + return NULL; + + if (self->container->wait(self->container, state, timeout)) { + Py_RETURN_TRUE; + } + + Py_RETURN_FALSE; +} + +/* Function/Properties list */ +static PyGetSetDef Container_getseters[] = { + {"config_file_name", + (getter)Container_config_file_name, NULL, + "Path to the container configuration", + NULL}, + {"controllable", + (getter)Container_controllable, NULL, + "Boolean indicating whether the container may be controlled", + NULL}, + {"defined", + (getter)Container_defined, NULL, + "Boolean indicating whether the container configuration exists", + NULL}, + {"init_pid", + (getter)Container_init_pid, NULL, + "PID of the container's init process in the host's PID namespace", + NULL}, + {"name", + (getter)Container_name, NULL, + "Container name", + NULL}, + {"running", + (getter)Container_running, NULL, + "Boolean indicating whether the container is running or not", + NULL}, + {"state", + (getter)Container_state, NULL, + "Container state", + NULL}, + {NULL, NULL, NULL, NULL, NULL} +}; + +static PyMethodDef Container_methods[] = { + {"add_device_node", (PyCFunction)Container_add_device_node, + METH_VARARGS|METH_KEYWORDS, + "add_device_node(src_path, dest_path) -> boolean\n" + "\n" + "Pass a new device to the container." + }, + {"attach", (PyCFunction)Container_attach, + METH_VARARGS|METH_KEYWORDS, + "attach(run, payload) -> int\n" + "\n" + "Attach to the container. Returns the pid of the attached process." + }, + {"attach_wait", (PyCFunction)Container_attach_wait, + METH_VARARGS|METH_KEYWORDS, + "attach(run, payload) -> int\n" + "\n" + "Attach to the container. Returns the exit code of the process." + }, + {"clear_config", (PyCFunction)Container_clear_config, + METH_NOARGS, + "clear_config()\n" + "\n" + "Clear any container configuration." + }, + {"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." + }, + {"console", (PyCFunction)Container_console, + METH_VARARGS|METH_KEYWORDS, + "console(ttynum = -1, stdinfd = 0, stdoutfd = 1, stderrfd = 2, " + "escape = 0) -> boolean\n" + "\n" + "Attach to container's console." + }, + {"console_getfd", (PyCFunction)Container_console_getfd, + METH_VARARGS|METH_KEYWORDS, + "console(ttynum = -1) -> boolean\n" + "\n" + "Attach to container's console." + }, + {"clone", (PyCFunction)Container_clone, + METH_VARARGS|METH_KEYWORDS, + "clone(newname, config_path, flags, bdevtype, bdevdata, newsize, " + "hookargs) -> boolean\n" + "\n" + "Create a new container based on the current one." + }, + {"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() -> boolean\n" + "\n" + "Destroys the container." + }, + {"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(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(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() -> 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(key) -> string\n" + "\n" + "Get a list of valid sub-keys for a key." + }, + {"get_interfaces", (PyCFunction)Container_get_interfaces, + METH_NOARGS, + "get_interface() -> tuple\n" + "\n" + "Get a tuple of interfaces for the container." + }, + {"get_ips", (PyCFunction)Container_get_ips, + METH_VARARGS|METH_KEYWORDS, + "get_ips(interface, family, scope) -> tuple\n" + "\n" + "Get a tuple of IPs for the container." + }, + {"get_running_config_item", (PyCFunction)Container_get_running_config_item, + METH_VARARGS|METH_KEYWORDS, + "get_running_config_item(key) -> string\n" + "\n" + "Get the runtime value of a config key." + }, + {"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." + }, + {"reboot", (PyCFunction)Container_reboot, + METH_NOARGS, + "reboot() -> boolean\n" + "\n" + "Ask the container to reboot." + }, + {"rename", (PyCFunction)Container_rename, + METH_VARARGS|METH_KEYWORDS, + "rename(new_name) -> boolean\n" + "\n" + "Rename the container." + }, + {"remove_device_node", (PyCFunction)Container_remove_device_node, + METH_VARARGS|METH_KEYWORDS, + "remove_device_node(src_path, dest_path) -> boolean\n" + "\n" + "Remove a device from the container." + }, + {"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(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(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(path) -> boolean\n" + "\n" + "Set the LXC config path (where the containers are stored)." + }, + {"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." + "-1 means wait forever, 0 means skip waiting." + }, + {"snapshot", (PyCFunction)Container_snapshot, + METH_VARARGS|METH_KEYWORDS, + "snapshot(comment_path = None) -> string\n" + "\n" + "Snapshot the container and return the snapshot name " + "(or False on error)." + }, + {"snapshot_destroy", (PyCFunction)Container_snapshot_destroy, + METH_VARARGS|METH_KEYWORDS, + "snapshot_destroy(name) -> boolean\n" + "\n" + "Destroy a snapshot." + }, + {"snapshot_list", (PyCFunction)Container_snapshot_list, + METH_NOARGS, + "snapshot_list() -> tuple of snapshot tuples\n" + "\n" + "List all snapshots for a container." + }, + {"snapshot_restore", (PyCFunction)Container_snapshot_restore, + METH_VARARGS|METH_KEYWORDS, + "snapshot_restore(name, newname = None) -> boolean\n" + "\n" + "Restore a container snapshot. If newname is provided a new " + "container will be created from the snapshot, otherwise an in-place " + "restore will be attempted." + }, + {"start", (PyCFunction)Container_start, + METH_VARARGS|METH_KEYWORDS, + "start(useinit = False, daemonize=True, close_fds=False, " + "cmd = (,)) -> boolean\n" + "\n" + "Start the container, return True on success.\n" + "When set useinit will make LXC use lxc-init to start the container.\n" + "The container can be started in the foreground with daemonize=False.\n" + "All fds may also be closed by passing close_fds=True." + }, + {"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() -> boolean\n" + "\n" + "Unfreezes the container and returns its return code." + }, + {"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." + }, + {NULL, NULL, 0, NULL} +}; + +static PyTypeObject _lxc_ContainerType = { +PyVarObject_HEAD_INIT(NULL, 0) + "lxc.Container", /* tp_name */ + sizeof(Container), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)Container_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_reserved */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | + Py_TPFLAGS_BASETYPE, /* tp_flags */ + "Container objects", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + Container_methods, /* tp_methods */ + 0, /* tp_members */ + Container_getseters, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + (initproc)Container_init, /* tp_init */ + 0, /* tp_alloc */ + Container_new, /* tp_new */ +}; + +static PyMethodDef LXC_methods[] = { + {"attach_run_command", (PyCFunction)LXC_attach_run_command, METH_O, + "Runs a command when attaching, to use as the run parameter for attach " + "or attach_wait"}, + {"attach_run_shell", (PyCFunction)LXC_attach_run_shell, METH_O, + "Starts up a shell when attaching, to use as the run parameter for " + "attach or attach_wait"}, + {"get_global_config_item", (PyCFunction)LXC_get_global_config_item, + METH_VARARGS|METH_KEYWORDS, + "Returns the current LXC config path"}, + {"get_version", (PyCFunction)LXC_get_version, METH_NOARGS, + "Returns the current LXC library version"}, + {"list_containers", (PyCFunction)LXC_list_containers, + METH_VARARGS|METH_KEYWORDS, + "Returns a list of container names or objects"}, + {NULL, NULL, 0, NULL} +}; + +PyMODINIT_FUNC +INIT_MODULE(_lxc)(void) +{ + PyObject* m; + PyObject* d; + + if (PyType_Ready(&_lxc_ContainerType) < 0) + return; + + m = Py_InitModule("_lxc", LXC_methods); + if (m == NULL) + return; + + Py_INCREF(&_lxc_ContainerType); + PyModule_AddObject(m, "Container", (PyObject *)&_lxc_ContainerType); + + /* add constants */ + d = PyModule_GetDict(m); + + #define PYLXC_EXPORT_CONST(c) \ + PyDict_SetItemString(d, #c, PyLong_FromLong(c)) + + /* namespace flags (no other python lib exports this) */ + PYLXC_EXPORT_CONST(CLONE_NEWUTS); + PYLXC_EXPORT_CONST(CLONE_NEWIPC); + PYLXC_EXPORT_CONST(CLONE_NEWUSER); + PYLXC_EXPORT_CONST(CLONE_NEWPID); + PYLXC_EXPORT_CONST(CLONE_NEWNET); + PYLXC_EXPORT_CONST(CLONE_NEWNS); + + /* attach: environment variable handling */ + PYLXC_EXPORT_CONST(LXC_ATTACH_CLEAR_ENV); + PYLXC_EXPORT_CONST(LXC_ATTACH_KEEP_ENV); + + /* attach: attach options */ + PYLXC_EXPORT_CONST(LXC_ATTACH_DEFAULT); + PYLXC_EXPORT_CONST(LXC_ATTACH_DROP_CAPABILITIES); + PYLXC_EXPORT_CONST(LXC_ATTACH_LSM_EXEC); + PYLXC_EXPORT_CONST(LXC_ATTACH_LSM_NOW); + PYLXC_EXPORT_CONST(LXC_ATTACH_MOVE_TO_CGROUP); + PYLXC_EXPORT_CONST(LXC_ATTACH_REMOUNT_PROC_SYS); + PYLXC_EXPORT_CONST(LXC_ATTACH_SET_PERSONALITY); + + /* clone: clone flags */ + PYLXC_EXPORT_CONST(LXC_CLONE_KEEPBDEVTYPE); + PYLXC_EXPORT_CONST(LXC_CLONE_KEEPMACADDR); + PYLXC_EXPORT_CONST(LXC_CLONE_KEEPNAME); + PYLXC_EXPORT_CONST(LXC_CLONE_MAYBE_SNAPSHOT); + PYLXC_EXPORT_CONST(LXC_CLONE_SNAPSHOT); + + /* create: create flags */ + PYLXC_EXPORT_CONST(LXC_CREATE_QUIET); + + #undef PYLXC_EXPORT_CONST + + return; +} + +/* + * kate: space-indent on; indent-width 4; mixedindent off; indent-mode cstyle; + */ diff --git a/lxc/__init__.py b/lxc/__init__.py new file mode 100644 index 0000000..45d139d --- /dev/null +++ b/lxc/__init__.py @@ -0,0 +1,503 @@ +# +# -*- coding: utf-8 -*- +# python-lxc: Python bindings for LXC +# +# (C) Copyright Canonical Ltd. 2012 +# +# Authors: +# Stéphane Graber +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# USA +# + +import _lxc +import os +import subprocess +import time + +default_config_path = _lxc.get_global_config_item("lxc.lxcpath") +get_global_config_item = _lxc.get_global_config_item +version = _lxc.get_version() + + +class ContainerNetwork(object): + props = {} + + def __init__(self, container, index): + self.container = container + self.index = index + + for key in self.container.get_keys("lxc.network.%s" % self.index): + if "." in key: + self.props[key.replace(".", "_")] = key + else: + self.props[key] = key + + if not self.props: + return False + + def __delattr__(self, key): + if key in ["container", "index", "props"]: + return object.__delattr__(self, key) + + if key not in self.props: + raise AttributeError("'%s' network has no attribute '%s'" % ( + self.__get_network_item("type"), key)) + + return self.__clear_network_item(self.props[key]) + + def __dir__(self): + return sorted(self.props.keys()) + + def __getattr__(self, key): + if key in ["container", "index", "props"]: + return object.__getattribute__(self, key) + + if key not in self.props: + raise AttributeError("'%s' network has no attribute '%s'" % ( + self.__get_network_item("type"), key)) + + return self.__get_network_item(self.props[key]) + + def __hasattr__(self, key): + if key in ["container", "index", "props"]: + return object.__hasattr__(self, key) + + if key not in self.props: + raise AttributeError("'%s' network has no attribute '%s'" % ( + self.__get_network_item("type"), key)) + + return True + + def __repr__(self): + return "'%s' network at index '%s'" % ( + self.__get_network_item("type"), self.index) + + def __setattr__(self, key, value): + if key in ["container", "index", "props"]: + return object.__setattr__(self, key, value) + + if key not in self.props: + raise AttributeError("'%s' network has no attribute '%s'" % ( + self.__get_network_item("type"), key)) + + return self.__set_network_item(self.props[key], value) + + def __clear_network_item(self, key): + return self.container.clear_config_item("lxc.network.%s.%s" % ( + self.index, key)) + + def __get_network_item(self, key): + return self.container.get_config_item("lxc.network.%s.%s" % ( + self.index, key)) + + def __set_network_item(self, key, value): + return self.container.set_config_item("lxc.network.%s.%s" % ( + self.index, key), value) + + +class ContainerNetworkList(): + def __init__(self, container): + self.container = container + + def __getitem__(self, index): + if index >= len(self): + raise IndexError("list index out of range") + + return ContainerNetwork(self.container, index) + + def __len__(self): + values = self.container.get_config_item("lxc.network") + + if values: + return len(values) + else: + return 0 + + def add(self, network_type): + index = len(self) + + return self.container.set_config_item("lxc.network.%s.type" % index, + network_type) + + def remove(self, index): + count = len(self) + if index >= count: + raise IndexError("list index out of range") + + return self.container.clear_config_item("lxc.network.%s" % index) + + +class Container(_lxc.Container): + def __init__(self, name, config_path=None): + """ + Creates a new Container instance. + """ + + if config_path: + _lxc.Container.__init__(self, name, config_path) + else: + _lxc.Container.__init__(self, name) + + self.network = ContainerNetworkList(self) + + def add_device_net(self, name, destname=None): + """ + Add network device to running container. + """ + + if not self.running: + return False + + if os.path.exists("/sys/class/net/%s/phy80211/name" % name): + with open("/sys/class/net/%s/phy80211/name" % name) as fd: + phy = fd.read().strip() + + if subprocess.call(['iw', 'phy', phy, 'set', 'netns', + str(self.init_pid)]) != 0: + return False + + if destname: + def rename_interface(args): + old, new = args + + return subprocess.call(['ip', 'link', 'set', + 'dev', old, 'name', new]) + + return self.attach_wait(rename_interface, (name, destname), + namespaces=(CLONE_NEWNET)) == 0 + + return True + + if not destname: + destname = name + + if not os.path.exists("/sys/class/net/%s/" % name): + return False + + return subprocess.call(['ip', 'link', 'set', + 'dev', name, + 'netns', str(self.init_pid), + 'name', destname]) == 0 + + def append_config_item(self, key, value): + """ + Append 'value' to 'key', assuming 'key' is a list. + If 'key' isn't a list, 'value' will be set as the value of 'key'. + """ + + return _lxc.Container.set_config_item(self, key, value) + + def create(self, template, flags=0, args=()): + """ + Create a new rootfs for the container. + + "template" must be a valid template name. + + "flags" (optional) is an integer representing the optional + create flags to be passed. + + "args" (optional) is a tuple of arguments to pass to the + template. It can also be provided as a dict. + """ + + if isinstance(args, dict): + template_args = [] + for item in args.items(): + template_args.append("--%s" % item[0]) + template_args.append("%s" % item[1]) + else: + template_args = args + + return _lxc.Container.create(self, template=template, + flags=flags, args=tuple(template_args)) + + def clone(self, newname, config_path=None, flags=0, bdevtype=None, + bdevdata=None, newsize=0, hookargs=()): + """ + Clone the current container. + """ + + args = {} + args['newname'] = newname + args['flags'] = flags + args['newsize'] = newsize + args['hookargs'] = hookargs + if config_path: + args['config_path'] = config_path + if bdevtype: + args['bdevtype'] = bdevtype + if bdevdata: + args['bdevdata'] = bdevdata + + if _lxc.Container.clone(self, **args): + return Container(newname, config_path=config_path) + else: + return False + + def console(self, ttynum=-1, stdinfd=0, stdoutfd=1, stderrfd=2, escape=1): + """ + Attach to console of running container. + """ + + if not self.running: + return False + + return _lxc.Container.console(self, ttynum, stdinfd, stdoutfd, + stderrfd, escape) + + def console_getfd(self, ttynum=-1): + """ + Attach to console of running container. + """ + + if not self.running: + return False + + return _lxc.Container.console_getfd(self, ttynum) + + def get_cgroup_item(self, key): + """ + Returns the value for a given cgroup entry. + A list is returned when multiple values are set. + """ + value = _lxc.Container.get_cgroup_item(self, key) + + if value is False: + return False + else: + return value.rstrip("\n") + + def get_config_item(self, key): + """ + Returns the value for a given config key. + A list is returned when multiple values are set. + """ + value = _lxc.Container.get_config_item(self, key) + + if value is False: + return False + elif value.endswith("\n"): + return value.rstrip("\n").split("\n") + else: + return value + + def get_keys(self, key=None): + """ + Returns a list of valid sub-keys. + """ + if key: + value = _lxc.Container.get_keys(self, key) + else: + value = _lxc.Container.get_keys(self) + + if value is False: + return False + elif value.endswith("\n"): + return value.rstrip("\n").split("\n") + else: + return value + + def get_interfaces(self): + """ + Get a tuple of interfaces for the container. + """ + + return _lxc.Container.get_interfaces(self) + + def get_ips(self, interface=None, family=None, scope=None, timeout=0): + """ + Get a tuple of IPs for the container. + """ + + kwargs = {} + if interface: + kwargs['interface'] = interface + if family: + kwargs['family'] = family + if scope: + kwargs['scope'] = scope + + ips = None + timeout = int(os.environ.get('LXC_GETIP_TIMEOUT', timeout)) + + while not ips: + ips = _lxc.Container.get_ips(self, **kwargs) + if timeout == 0: + break + + timeout -= 1 + time.sleep(1) + + return ips + + def rename(self, new_name): + """ + Rename the container. + On success, returns the new Container object. + On failure, returns False. + """ + + if _lxc.Container.rename(self, new_name): + return Container(new_name) + + return False + + def set_config_item(self, key, value): + """ + Set a config key to a provided value. + The value can be a list for the keys supporting multiple values. + """ + try: + old_value = self.get_config_item(key) + except KeyError: + old_value = None + + # Check if it's a list + def set_key(key, value): + self.clear_config_item(key) + if isinstance(value, list): + for entry in value: + if not _lxc.Container.set_config_item(self, key, entry): + return False + else: + _lxc.Container.set_config_item(self, key, value) + + set_key(key, value) + new_value = self.get_config_item(key) + + # loglevel is special and won't match the string we set + if key == "lxc.loglevel": + new_value = value + + if (isinstance(value, str) and isinstance(new_value, str) and + value == new_value): + return True + elif (isinstance(value, list) and isinstance(new_value, list) and + set(value) == set(new_value)): + return True + elif (isinstance(value, str) and isinstance(new_value, list) and + set([value]) == set(new_value)): + return True + elif old_value: + set_key(key, old_value) + return False + else: + self.clear_config_item(key) + return False + + def wait(self, state, timeout=-1): + """ + Wait for the container to reach a given state or timeout. + """ + + if isinstance(state, str): + state = state.upper() + + return _lxc.Container.wait(self, state, timeout) + + +def list_containers(active=True, defined=True, + as_object=False, config_path=None): + """ + List the containers on the system. + """ + + if config_path: + if not os.path.exists(config_path): + return tuple() + try: + entries = _lxc.list_containers(active=active, defined=defined, + config_path=config_path) + except ValueError: + return tuple() + else: + try: + entries = _lxc.list_containers(active=active, defined=defined) + except ValueError: + return tuple() + + if as_object: + return tuple([Container(name, config_path) for name in entries]) + else: + return entries + + +def attach_run_command(cmd): + """ + Run a command when attaching + + Please do not call directly, this will execvp the command. + This is to be used in conjunction with the attach method + of a container. + """ + if isinstance(cmd, tuple): + return _lxc.attach_run_command(cmd) + elif isinstance(cmd, list): + return _lxc.attach_run_command((cmd[0], cmd)) + else: + return _lxc.attach_run_command((cmd, [cmd])) + + +def attach_run_shell(): + """ + Run a shell when attaching + + Please do not call directly, this will execvp the shell. + This is to be used in conjunction with the attach method + of a container. + """ + return _lxc.attach_run_shell(None) + + +def arch_to_personality(arch): + """ + Determine the process personality corresponding to the architecture + """ + if isinstance(arch, bytes): + arch = str(arch, 'utf-8') + return _lxc.arch_to_personality(arch) + +# namespace flags (no other python lib exports this) +CLONE_NEWIPC = _lxc.CLONE_NEWIPC +CLONE_NEWNET = _lxc.CLONE_NEWNET +CLONE_NEWNS = _lxc.CLONE_NEWNS +CLONE_NEWPID = _lxc.CLONE_NEWPID +CLONE_NEWUSER = _lxc.CLONE_NEWUSER +CLONE_NEWUTS = _lxc.CLONE_NEWUTS + +# attach: environment variable handling +LXC_ATTACH_CLEAR_ENV = _lxc.LXC_ATTACH_CLEAR_ENV +LXC_ATTACH_KEEP_ENV = _lxc.LXC_ATTACH_KEEP_ENV + +# attach: attach options +LXC_ATTACH_DEFAULT = _lxc.LXC_ATTACH_DEFAULT +LXC_ATTACH_DROP_CAPABILITIES = _lxc.LXC_ATTACH_DROP_CAPABILITIES +LXC_ATTACH_LSM_EXEC = _lxc.LXC_ATTACH_LSM_EXEC +LXC_ATTACH_LSM_NOW = _lxc.LXC_ATTACH_LSM_NOW +LXC_ATTACH_MOVE_TO_CGROUP = _lxc.LXC_ATTACH_MOVE_TO_CGROUP +LXC_ATTACH_REMOUNT_PROC_SYS = _lxc.LXC_ATTACH_REMOUNT_PROC_SYS +LXC_ATTACH_SET_PERSONALITY = _lxc.LXC_ATTACH_SET_PERSONALITY + +# clone: clone flags +LXC_CLONE_KEEPBDEVTYPE = _lxc.LXC_CLONE_KEEPBDEVTYPE +LXC_CLONE_KEEPMACADDR = _lxc.LXC_CLONE_KEEPMACADDR +LXC_CLONE_KEEPNAME = _lxc.LXC_CLONE_KEEPNAME +LXC_CLONE_MAYBE_SNAPSHOT = _lxc.LXC_CLONE_MAYBE_SNAPSHOT +LXC_CLONE_SNAPSHOT = _lxc.LXC_CLONE_SNAPSHOT + +# create: create flags +LXC_CREATE_QUIET = _lxc.LXC_CREATE_QUIET diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..b2cfefe --- /dev/null +++ b/setup.py @@ -0,0 +1,33 @@ +#!/usr/bin/python +# +# python-lxc: Python bindings for LXC +# +# (C) Copyright Canonical Ltd. 2012 +# +# Authors: +# Stephane Graber +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +from distutils.core import setup, Extension + +module = Extension('_lxc', sources=['lxc.c'], libraries=['lxc']) + +setup(name='_lxc', + version='0.1', + description='LXC', + packages=['lxc'], + package_dir={'lxc': 'lxc'}, + ext_modules=[module])