Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ jobs:
#- test: centos10-build
- test: clang-format
- test: clang-check
- test: bindings
- test: checkpoint-restore
- test: fuzzing
- test: codespell
Expand All @@ -97,7 +98,7 @@ jobs:

sudo add-apt-repository -y ppa:criu/ppa
# add-apt-repository runs apt-get update so we don't have to.
sudo apt-get install -q -y criu automake libtool autotools-dev libseccomp-dev git make libcap-dev cmake pkg-config gcc wget libsystemd-dev gperf clang-format libjson-c-dev containerd runc libasan6 libprotobuf-c-dev mawk
sudo apt-get install -q -y criu automake libtool autotools-dev libseccomp-dev git make libcap-dev cmake pkg-config gcc wget libsystemd-dev gperf clang-format libjson-c-dev containerd runc libasan6 libprotobuf-c-dev mawk python3-dev lua5.4 liblua5.4-dev
# Fedora (used by maintainers as well as for packaging) has newer
# md2man than in Ubuntu, so we have to install it from source.
GOBIN=~/.local/bin go install github.com/cpuguy83/go-md2man/v2@latest
Expand Down Expand Up @@ -187,6 +188,18 @@ jobs:
./configure --enable-embedded-blake3 --enable-shared
make -j $(nproc)
;;
bindings)
# Build the Python and Lua bindings and load each freshly
# built module. The bindings link against internal libcrun
# symbols, so a build here catches API drift and missing
# exports (e.g. libcrun_container_spec, parse_json_file)
# before it reaches a release.
./configure --enable-embedded-blake3 --enable-shared --with-python-bindings --with-lua-bindings CFLAGS='-Wall -Werror'
make -j $(nproc)
export LD_LIBRARY_PATH="$PWD/.libs:$LD_LIBRARY_PATH"
PYTHONPATH="$PWD/.libs" python3 python/test_python_bindings.py
LUA_CPATH="$PWD/.libs/?.so;;" lua5.4 lua/test_lua_bindings.lua
;;
embedded-blake3)
./configure --enable-embedded-blake3
make -j $(nproc)
Expand Down
3 changes: 2 additions & 1 deletion Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,8 @@ EXTRA_DIST = COPYING COPYING.libcrun README.md NEWS SECURITY.md rpm/crun.spec au
src/libcrun/syscalls.h \
crun.1.md crun.1 libcrun.lds \
krun.1.md krun.1 \
lua/luacrun.rockspec
lua/luacrun.rockspec lua/test_lua_bindings.lua \
python/test_python_bindings.py

if !HAVE_EMBEDDED_BLAKE3
EXTRA_DIST += src/libcrun/blake3/blake3.c \
Expand Down
2 changes: 2 additions & 0 deletions libcrun.lds
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
global:
/* Not all the libcrun_ functions are exported, only those marked LIBCRUN_PUBLIC. */
libcrun_*;
/* Utility used by the language bindings (python/, lua/). */
parse_json_file;
/* libocispec functions */
runtime_spec_*;
free_runtime_spec_*;
Expand Down
3 changes: 2 additions & 1 deletion lua/lua_crun.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include <lauxlib.h>
#include <libcrun/container.h>
#include <libcrun/status.h>
#include <libcrun/spec.h>
#include <libcrun/utils.h>
#include <libcrun/error.h>

Expand Down Expand Up @@ -368,7 +369,7 @@ luacrun_ctx_run (lua_State *S)
lua_pushnil (S);
return luacrun_error (S, &crun_err) + 1;
} else {
luaL_error(S, "failed to run container");
return luaL_error (S, "failed to run container");
}
}
else
Expand Down
56 changes: 56 additions & 0 deletions lua/test_lua_bindings.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
-- crun - OCI runtime written in C
--
-- Copyright (C) 2026 crun Authors
-- 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 2.1 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/>.
--
-- Smoke test for the Lua bindings. It does not run a container (that
-- requires a privileged environment); it only exercises the parts of the
-- module that must keep working so a build/link regression is caught early.
-- Set package.cpath (or LUA_CPATH) so that require("luacrun") resolves to
-- the freshly built luacrun.so before running.

local crun = require("luacrun")

-- Spec generation must keep working (feature exercised by callers).
local spec = crun.container_spec(true)
assert(type(spec) == "string" and #spec > 0, "generated spec is empty")
assert(spec:find("ociVersion"), "generated spec has no ociVersion")

-- Every documented entry point must be present. This also catches a missing
-- registration in luacrun_library_reg.
local expected = {
"new_ctx", "container_spec", "new_container_from_string",
"new_container_from_file", "get_verbosity", "set_verbosity", "run",
"create_container", "delete_container", "kill_container",
"start_container", "status_container", "iter_container_names",
"update_container",
}
for _, name in ipairs(expected) do
assert(crun[name] ~= nil, "missing function " .. name)
end

-- Verbosity constants.
for _, name in ipairs({ "VERBOSITY_ERROR", "VERBOSITY_WARNING", "VERBOSITY_DEBUG" }) do
assert(crun[name] ~= nil, "missing constant " .. name)
end

crun.set_verbosity(crun.VERBOSITY_ERROR)
assert(crun.get_verbosity() == crun.VERBOSITY_ERROR, "verbosity round-trip failed")

-- Loading a container from the generated spec must resolve every symbol the
-- module needs (this is what caught the missing parse_json_file /
-- libcrun_container_spec exports).
assert(crun.new_container_from_string(spec) ~= nil, "new_container_from_string failed")

print("lua bindings smoke test: OK")
39 changes: 22 additions & 17 deletions python/crun_python.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ python_crun.run(ctx, ctr)
#include <Python.h>
#include <libcrun/container.h>
#include <libcrun/status.h>
#include <libcrun/spec.h>
#include <libcrun/utils.h>
#include <libcrun/error.h>

Expand Down Expand Up @@ -105,13 +106,23 @@ container_load_from_memory (PyObject *self arg_unused, PyObject *args)
}

static void
free_context (void *ptr)
free_context (PyObject *ptr)
{
libcrun_context_t *ctx = ptr;
char *id = (char *) ctx->id;
free (ctx->state_root);
free (ctx->notify_socket);
libcrun_context_t *ctx = PyCapsule_GetPointer (ptr, CONTEXT_OBJ_TAG);
void *id, *bundle, *state_root, *notify_socket;

if (ctx == NULL)
return;

id = (void *) ctx->id;
bundle = (void *) ctx->bundle;
state_root = (void *) ctx->state_root;
notify_socket = (void *) ctx->notify_socket;

free (id);
free (bundle);
free (state_root);
free (notify_socket);
free (ctx);
}

Expand All @@ -134,13 +145,16 @@ make_context (PyObject *self arg_unused, PyObject *args, PyObject *kwargs)
if (!PyArg_ParseTupleAndKeywords
(args, kwargs, "s|ssbsbbbb", kwlist, &id, &bundle, &state_root,
&ctx->systemd_cgroup, &notify_socket, &ctx->detach, &ctx->no_new_keyring, &ctx->force_no_cgroup, &ctx->no_pivot))
return NULL;
{
free (ctx);
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);
return PyCapsule_New (ctx, CONTEXT_OBJ_TAG, free_context);
}

static PyObject *
Expand Down Expand Up @@ -328,7 +342,6 @@ container_status (PyObject *self arg_unused, PyObject *args)
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;
Expand Down Expand Up @@ -413,10 +426,6 @@ static PyObject *
container_spec (PyObject *self arg_unused, PyObject *args arg_unused)
{
libcrun_error_t err = NULL;
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;
Expand Down Expand Up @@ -446,9 +455,6 @@ get_verbosity (PyObject *self arg_unused, PyObject *args)
static PyObject *
set_verbosity (PyObject *self arg_unused, PyObject *args)
{
libcrun_error_t err;
PyObject *ctx_obj = NULL;
libcrun_context_t *ctx;
int verbosity;

if (!PyArg_ParseTuple (args, "i", &verbosity))
Expand All @@ -467,6 +473,7 @@ static PyMethodDef CrunMethods[] = {
{"create", container_create, METH_VARARGS, "Create a container."},
{"delete", container_delete, METH_VARARGS, "Delete a container."},
{"kill", container_kill, METH_VARARGS, "Kill a container."},
{"start", container_start, METH_VARARGS, "Start a container."},
{"list", containers_list, METH_VARARGS, "List the containers."},
{"status", container_status, METH_VARARGS,
"Get the status of a container."},
Expand All @@ -478,8 +485,6 @@ static PyMethodDef CrunMethods[] = {
"Create a context object."},
{"set_verbosity", set_verbosity, METH_VARARGS, "Set the logging verbosity."},
{"get_verbosity", get_verbosity, METH_NOARGS, "Get the logging verbosity."},
{"spec", container_spec, METH_VARARGS,
"Generate a new configuration file."},
{NULL, NULL, 0, NULL}
};

Expand Down
64 changes: 64 additions & 0 deletions python/test_python_bindings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#!/usr/bin/env python3
# crun - OCI runtime written in C
#
# Copyright (C) 2026 crun Authors
# 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 2.1 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/>.
#
# Smoke test for the Python bindings. It does not run a container (that
# requires a privileged environment); it only exercises the parts of the
# module that must keep working so a build/link regression is caught early.
# Point PYTHONPATH at the directory holding python_crun.so before running.

import gc
import json
import sys

import python_crun


def main():
# Spec generation must keep working (feature exercised by callers).
spec = json.loads(python_crun.spec())
assert spec.get("ociVersion"), "generated spec has no ociVersion"
assert spec["process"]["args"], "generated spec has no process args"

# Every documented entry point must be present.
expected = [
"load_from_file", "load_from_memory", "run", "create", "delete",
"kill", "start", "list", "status", "update", "spec", "make_context",
"set_verbosity", "get_verbosity",
]
missing = [name for name in expected if not hasattr(python_crun, name)]
assert not missing, "missing methods: %s" % missing

# Verbosity constants.
for const in ("VERBOSITY_ERROR", "VERBOSITY_WARNING", "VERBOSITY_DEBUG"):
assert hasattr(python_crun, const), "missing constant %s" % const

# Loading a container from the generated spec must resolve every symbol
# the module needs (this is what caught the missing parse_json_file /
# libcrun_container_spec exports).
python_crun.load_from_memory(json.dumps(spec))

# Creating and dropping a context must run the capsule destructor without
# crashing.
ctx = python_crun.make_context("crun-python-smoke-test")
del ctx
gc.collect()

print("python bindings smoke test: OK")


if __name__ == "__main__":
sys.exit(main())
4 changes: 3 additions & 1 deletion src/libcrun/string_map.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
#ifndef STRING_MAP_H
#define STRING_MAP_H

#define _GNU_SOURCE
#ifndef _GNU_SOURCE
# define _GNU_SOURCE
#endif

#include <config.h>

Expand Down
4 changes: 3 additions & 1 deletion src/libcrun/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,9 @@ void get_current_timestamp (char *out, size_t len);

int set_blocking_fd (int fd, bool blocking, libcrun_error_t *err);

int parse_json_file (json_object **out, const char *jsondata, struct parser_context *ctx, libcrun_error_t *err);
/* Exported for the language bindings (python/, lua/), which parse a
process JSON blob before calling libcrun_container_exec. */
LIBCRUN_PUBLIC int parse_json_file (json_object **out, const char *jsondata, struct parser_context *ctx, libcrun_error_t *err);

/* Check that the string STR has the prefix PREFIX. */
static inline int
Expand Down
Loading