Files
crun/python/crun_python.c
Giuseppe Scrivano 8cf80df516 container: do not fork on create
do not fork the current process on create, this prevents creating
additional processes to wait for.

It solves a race when the OCI runtime caller has SUBREAPER set, such
as Buildah.  When the initial process exited, the container process
cannot be directly waited for as there are other processes to reap
first.

Closes: https://github.com/containers/crun/issues/215

Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
2019-12-17 18:04:10 +01:00

497 lines
12 KiB
C

/*
*crun - OCI runtime written in C
*
*Copyright (C) 2018, 2019 Giuseppe Scrivano <giuseppe@scrivano.org>
*crun 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 3 of the License, or
*(at your option) any later version.
*
*crun 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 crun. If not, see <http://www.gnu.org/licenses/>.
*/
/*
An example of using this module:
import python_crun
import json
spec = json.loads(python_crun.spec())
spec['root']['path'] = '/path/to/the/rootfs'
spec['process']['args'] = ['/bin/echo', 'hello from a container']
ctr = python_crun.load_from_memory(json.dumps(spec))
ctx = python_crun.make_context("test-container")
python_crun.set_verbosity(python_crun.VERBOSITY_ERROR)
python_crun.run(ctx, ctr)
*/
#include <config.h>
#include <Python.h>
#include <libcrun/container.h>
#include <libcrun/status.h>
#include <libcrun/utils.h>
#include <libcrun/error.h>
#define CONTEXT_OBJ_TAG "crun-context"
#define CONTAINER_OBJ_TAG "crun-container"
static PyObject *
set_error (libcrun_error_t *err)
{
if ((*err)->status == 0)
PyErr_SetString (PyExc_RuntimeError, (*err)->msg);
else
{
cleanup_free char *msg = NULL;
asprintf (&msg, "%s: %s", (*err)->msg, strerror ((*err)->status));
if (msg == NULL)
return NULL;
PyErr_SetString (PyExc_RuntimeError, msg);
}
crun_error_release (err);
return NULL;
}
static void
free_container (PyObject *ptr)
{
libcrun_container_t *ctr = PyCapsule_GetPointer (ptr, CONTAINER_OBJ_TAG);
free_oci_container (ctr->container_def);
}
static PyObject *
container_load_from_file (PyObject *self, PyObject *args)
{
libcrun_error_t err;
const char *path;
libcrun_container_t *ctr;
if (!PyArg_ParseTuple (args, "s", &path))
return NULL;
ctr = libcrun_container_load_from_file (path, &err);
if (ctr == NULL)
return set_error (&err);
return PyCapsule_New (ctr, CONTAINER_OBJ_TAG, free_container);
}
static PyObject *
container_load_from_memory (PyObject *self, PyObject *args)
{
libcrun_error_t err;
const char *def;
libcrun_container_t *ctr;
if (!PyArg_ParseTuple (args, "s", &def))
return NULL;
ctr = libcrun_container_load_from_memory (def, &err);
if (ctr == NULL)
return set_error (&err);
return PyCapsule_New (ctr, CONTAINER_OBJ_TAG, free_container);
}
static void
free_context (void *ptr)
{
libcrun_context_t *ctx = ptr;
char *id = (char *) ctx->id;
free (ctx->state_root);
free (ctx->notify_socket);
free (id);
free (ctx);
}
static PyObject *
make_context (PyObject *self, PyObject *args, PyObject *kwargs)
{
char *id = NULL;
char *bundle = NULL;
char *state_root = NULL;
char *notify_socket = NULL;
static char *kwlist[] =
{ "id", "state-root", "systemd-cgroup", "notify-socket", "detach", NULL };
libcrun_context_t *ctx = malloc (sizeof (*ctx));
if (ctx == NULL)
return NULL;
memset (ctx, 0, sizeof (*ctx));
ctx->fifo_exec_wait_fd = -1;
if (!PyArg_ParseTupleAndKeywords
(args, kwargs, "s|sssbsb", kwlist, &id, &bundle, &state_root,
&ctx->systemd_cgroup, &notify_socket, &ctx->detach))
return NULL;
ctx->id = xstrdup (id);
ctx->bundle = xstrdup (bundle ? bundle : ".");
ctx->state_root = xstrdup (state_root);
ctx->notify_socket = xstrdup (notify_socket);
return PyCapsule_New (ctx, CONTEXT_OBJ_TAG, NULL);
}
static PyObject *
container_run (PyObject *self, PyObject *args)
{
libcrun_error_t err;
PyObject *ctx_obj = NULL;
PyObject *ctr_obj = NULL;
libcrun_container_t *ctr;
libcrun_context_t *ctx;
int ret;
if (!PyArg_ParseTuple (args, "OO", &ctx_obj, &ctr_obj))
return NULL;
ctx = PyCapsule_GetPointer (ctx_obj, CONTEXT_OBJ_TAG);
if (ctx == NULL)
return NULL;
ctr = PyCapsule_GetPointer (ctr_obj, CONTAINER_OBJ_TAG);
if (ctr == NULL)
return NULL;
Py_BEGIN_ALLOW_THREADS;
ret = libcrun_container_run (ctx, ctr, LIBCRUN_RUN_OPTIONS_PREFORK, &err);
Py_END_ALLOW_THREADS;
if (ret < 0)
return set_error (&err);
return PyLong_FromLong (ret);
}
static PyObject *
container_create (PyObject *self, PyObject *args)
{
libcrun_error_t err;
PyObject *ctx_obj = NULL;
PyObject *ctr_obj = NULL;
libcrun_container_t *ctr;
libcrun_context_t *ctx;
int ret;
if (!PyArg_ParseTuple (args, "OO", &ctx_obj, &ctr_obj))
return NULL;
ctx = PyCapsule_GetPointer (ctx_obj, CONTEXT_OBJ_TAG);
if (ctx == NULL)
return NULL;
ctr = PyCapsule_GetPointer (ctr_obj, CONTAINER_OBJ_TAG);
if (ctr == NULL)
return NULL;
Py_BEGIN_ALLOW_THREADS;
ret = libcrun_container_create (ctx, ctr, LIBCRUN_RUN_OPTIONS_PREFORK, &err);
Py_END_ALLOW_THREADS;
if (ret < 0)
return set_error (&err);
return PyLong_FromLong (ret);
}
static PyObject *
container_delete (PyObject *self, PyObject *args)
{
libcrun_error_t err;
PyObject *ctx_obj = NULL;
char *id = NULL;
bool force;
libcrun_context_t *ctx;
int ret;
if (!PyArg_ParseTuple (args, "Osn", &ctx_obj, &id, &force))
return NULL;
ctx = PyCapsule_GetPointer (ctx_obj, CONTEXT_OBJ_TAG);
if (ctx == NULL)
return NULL;
Py_BEGIN_ALLOW_THREADS;
ret = libcrun_container_delete (ctx, NULL, id, force, &err);
Py_END_ALLOW_THREADS;
if (ret < 0)
return set_error (&err);
Py_RETURN_NONE;
}
static PyObject *
container_kill (PyObject *self, PyObject *args)
{
libcrun_error_t err;
PyObject *ctx_obj = NULL;
char *id = NULL;
int signal;
libcrun_context_t *ctx;
int ret;
if (!PyArg_ParseTuple (args, "Osi", &ctx_obj, &id, &signal))
return NULL;
ctx = PyCapsule_GetPointer (ctx_obj, CONTEXT_OBJ_TAG);
if (ctx == NULL)
return NULL;
Py_BEGIN_ALLOW_THREADS;
ret = libcrun_container_kill (ctx, id, signal, &err);
Py_END_ALLOW_THREADS;
if (ret < 0)
return set_error (&err);
Py_RETURN_NONE;
}
static PyObject *
container_start (PyObject *self, PyObject *args)
{
libcrun_error_t err;
PyObject *ctx_obj = NULL;
char *id = NULL;
libcrun_context_t *ctx;
int ret;
if (!PyArg_ParseTuple (args, "Os", &ctx_obj, &id))
return NULL;
ctx = PyCapsule_GetPointer (ctx_obj, CONTEXT_OBJ_TAG);
if (ctx == NULL)
return NULL;
Py_BEGIN_ALLOW_THREADS;
ret = libcrun_container_start (ctx, id, &err);
Py_END_ALLOW_THREADS;
if (ret < 0)
return set_error (&err);
return PyLong_FromLong (ret);
}
static PyObject *
containers_list (PyObject *self, PyObject *args)
{
libcrun_error_t err;
PyObject *ctx_obj = NULL;
libcrun_context_t *ctx;
libcrun_container_list_t *containers, *it;
PyObject *retobj;
Py_ssize_t i = 0;
int ret;
if (!PyArg_ParseTuple (args, "O", &ctx_obj))
return NULL;
ctx = PyCapsule_GetPointer (ctx_obj, CONTEXT_OBJ_TAG);
if (ctx == NULL)
return NULL;
Py_BEGIN_ALLOW_THREADS;
ret = libcrun_get_containers_list (&containers, ctx->state_root, &err);
Py_END_ALLOW_THREADS;
if (ret < 0)
return set_error (&err);
i = 0;
for (it = containers; it; it = it->next)
i++;
retobj = PyList_New (i);
if (retobj == NULL)
return NULL;
i = 0;
for (it = containers; it; it = it->next)
PyList_SetItem (retobj, i++, PyUnicode_FromString (it->name));
libcrun_free_containers_list (containers);
return retobj;
}
static PyObject *
container_status (PyObject *self, PyObject *args)
{
libcrun_error_t err;
PyObject *ctx_obj = NULL;
libcrun_context_t *ctx;
char *id = NULL;
libcrun_container_status_t status;
cleanup_free char *buffer = NULL;
FILE *memfile;
int ret;
if (!PyArg_ParseTuple (args, "Os", &ctx_obj, &id))
return NULL;
ctx = PyCapsule_GetPointer (ctx_obj, CONTEXT_OBJ_TAG);
if (ctx == NULL)
return NULL;
buffer = malloc (4096);
if (buffer == NULL)
return NULL;
/* A bit silly (and expensive), libcrun_container_state needs a refactoring
to make this nicer. */
memset (buffer, 0, 4096);
memfile = fmemopen (buffer, 4095, "w");
Py_BEGIN_ALLOW_THREADS;
ret = libcrun_container_state (ctx, id, memfile, &err);
Py_END_ALLOW_THREADS;
if (ret < 0)
return set_error (&err);
fclose (memfile);
return PyUnicode_FromString (buffer);
}
static PyObject *
container_update (PyObject *self, PyObject *args)
{
libcrun_error_t err;
PyObject *ctx_obj = NULL;
libcrun_context_t *ctx;
char *id = NULL;
char *content = NULL;
yajl_val tree = NULL;
int ret;
parser_error parser_err = NULL;
struct parser_context parser_ctx = { 0, stderr };
oci_container_process *process = NULL;
if (!PyArg_ParseTuple (args, "Oss", &ctx_obj, &id, &content))
return NULL;
ctx = PyCapsule_GetPointer (ctx_obj, CONTEXT_OBJ_TAG);
if (ctx == NULL)
return NULL;
ret = parse_json_file (&tree, content, &parser_ctx, &err);
if (UNLIKELY (ret < 0))
return set_error (&err);
process = make_oci_container_process (tree, &parser_ctx, &parser_err);
yajl_tree_free (tree);
if (process == NULL)
{
cleanup_free char *msg = NULL;
asprintf (&msg, "cannot parse process: %s", parser_err);
if (msg == NULL)
return NULL;
free (parser_err);
PyErr_SetString (PyExc_RuntimeError, msg);
return NULL;
}
Py_BEGIN_ALLOW_THREADS;
ret = libcrun_container_exec (ctx, id, process, &err);
Py_END_ALLOW_THREADS;
free_oci_container_process (process);
if (ret < 0)
return set_error (&err);
Py_RETURN_NONE;
}
static PyObject *
container_spec (PyObject *self, PyObject *args)
{
libcrun_error_t err;
PyObject *ctx_obj = NULL;
libcrun_context_t *ctx;
char *id = NULL;
libcrun_container_status_t status;
cleanup_free char *buffer = NULL;
FILE *memfile;
int ret;
buffer = malloc (4096);
if (buffer == NULL)
return NULL;
memfile = fmemopen (buffer, 4095, "w");
Py_BEGIN_ALLOW_THREADS;
ret = libcrun_container_spec (geteuid () == 0, memfile, &err);
Py_END_ALLOW_THREADS;
if (ret < 0)
return set_error (&err);
buffer[ret] = '\0';
fclose (memfile);
return PyUnicode_FromString (buffer);
}
static PyObject *
get_verbosity (PyObject *self, PyObject *args)
{
libcrun_error_t err;
PyObject *ctx_obj = NULL;
libcrun_context_t *ctx;
int verbosity;
if (!PyArg_ParseTuple (args, "i", &verbosity))
return NULL;
return PyLong_FromLong (libcrun_get_verbosity (verbosity));
}
static PyObject *
set_verbosity (PyObject *self, PyObject *args)
{
libcrun_error_t err;
PyObject *ctx_obj = NULL;
libcrun_context_t *ctx;
int verbosity;
if (!PyArg_ParseTuple (args, "i", &verbosity))
return NULL;
libcrun_set_verbosity (verbosity);
Py_RETURN_NONE;
}
static PyMethodDef CrunMethods[] = {
{"load_from_file", container_load_from_file, METH_VARARGS,
"Load an OCI container from file."},
{"load_from_memory", container_load_from_memory, METH_VARARGS,
"Load an OCI container from memory."},
{"run", container_run, METH_VARARGS, "Run a container."},
{"create", container_run, METH_VARARGS, "Create a container."},
{"delete", container_delete, METH_VARARGS, "Delete a container."},
{"kill", container_kill, METH_VARARGS, "Kill a container."},
{"list", containers_list, METH_VARARGS, "List the containers."},
{"status", container_status, METH_VARARGS,
"Get the status of a container."},
{"update", container_update, METH_VARARGS,
"Update the constraints of a container."},
{"spec", container_spec, METH_VARARGS,
"Generate a new configuration file."},
{"make_context", (PyCFunction) make_context, METH_VARARGS | METH_KEYWORDS,
"Create a context object."},
{"set_verbosity", set_verbosity, METH_VARARGS, "Set the logging verbosity."},
{"get_verbosity", get_verbosity, METH_VARARGS, "Get the logging verbosity."},
{"spec", container_spec, METH_VARARGS,
"Generate a new configuration file."},
{NULL, NULL, 0, NULL}
};
PyMODINIT_FUNC
initpython_crun (void)
{
PyObject *module = Py_InitModule ("python_crun", CrunMethods);
(void) PyModule_AddIntConstant (module, "VERBOSITY_ERROR", LIBCRUN_VERBOSITY_ERROR);
(void) PyModule_AddIntConstant (module, "VERBOSITY_WARNING", LIBCRUN_VERBOSITY_WARNING);
}