From 6873ed23757e48e1e778d82e39cf85ca827be5fc Mon Sep 17 00:00:00 2001 From: Damien Goutte-Gattat Date: Mon, 28 Sep 2026 19:20:28 +0100 Subject: [PATCH 1/4] Add the odk_backend_docker_build command function. Move all the code that assembles the command line to invoke the `docker` command into a separate public function. This will allow to reuse that function from other backends that are reusing the same command line interface as the `docker` command. --- src/backend-docker.c | 82 ++++++++++++++++++++++++-------------------- src/backend-docker.h | 5 +++ 2 files changed, 49 insertions(+), 38 deletions(-) diff --git a/src/backend-docker.c b/src/backend-docker.c index 03ebe94..bae5f59 100644 --- a/src/backend-docker.c +++ b/src/backend-docker.c @@ -42,8 +42,6 @@ #include /* for getuid/getgid */ #endif -#include - #include "procutil.h" #include "util.h" @@ -87,12 +85,49 @@ static int run(odk_backend_t *backend, odk_run_config_t *cfg, char **command) { int rc; - size_t n, i = 0; - char **argv, **cursor, *image_qualifier; + char **argv; mem_registry_t mr = { 0 }; (void) backend; + argv = odk_backend_docker_build_command(&mr, cfg, command); + rc = spawn_process(argv); + mr_free(&mr); + + return rc; +} + +static int +close_backend(odk_backend_t *backend) +{ + (void) backend; + + return 0; +} + +static int +get_total_memory(odk_backend_info_t *info) +{ + FILE *p; + int ret = -1; + + if ( (p = popen("docker info --format={{.MemTotal}}", "r")) != NULL ) { + if ( fscanf(p, "%lu", &(info->total_memory)) == 1 ) + ret = 0; + else + errno = ESRCH; + pclose(p); + } + + return ret; +} + +char ** +odk_backend_docker_build_command(mem_registry_t *mr, odk_run_config_t *cfg, char **command) +{ + size_t n, i = 0; + char **argv, **cursor, *image_qualifier; + image_qualifier = strchr(cfg->image_name, '/') ? "" : "obolibrary/"; /* Number of tokens in the command line */ @@ -105,7 +140,7 @@ run(odk_backend_t *backend, odk_run_config_t *cfg, char **command) n += 1; /* Assembling the command line */ - argv = mr_alloc(&mr, sizeof(char *) * n); + argv = mr_alloc(mr, sizeof(char *) * n); argv[i++] = "docker"; argv[i++] = "run"; argv[i++] = "--rm"; @@ -114,17 +149,17 @@ run(odk_backend_t *backend, odk_run_config_t *cfg, char **command) argv[i++] = (char *)cfg->work_directory; for ( int j = 0; j < cfg->n_bindings; j++ ) { argv[i++] = "-v"; - argv[i++] = mr_sprintf(&mr, "%s:%s", cfg->bindings[j].host_directory, cfg->bindings[j].container_directory); + argv[i++] = mr_sprintf(mr, "%s:%s", cfg->bindings[j].host_directory, cfg->bindings[j].container_directory); } for ( int j = 0; j < cfg->n_env_vars; j++ ) { if ( cfg->env_vars[j].value != NULL ) { argv[i++] = "-e"; - argv[i++] = mr_sprintf(&mr, "%s=%s", cfg->env_vars[j].name, cfg->env_vars[j].value); + argv[i++] = mr_sprintf(mr, "%s=%s", cfg->env_vars[j].name, cfg->env_vars[j].value); } } for ( int j = 0; j < cfg->n_backend_opts; j++ ) argv[i++] = (char *)cfg->backend_opts[j].name; - argv[i++] = mr_sprintf(&mr, "%s%s:%s", image_qualifier, cfg->image_name, cfg->image_tag); + argv[i++] = mr_sprintf(mr, "%s%s:%s", image_qualifier, cfg->image_name, cfg->image_tag); if ( cfg->flags & ODK_FLAG_TIMEDEBUG ) { argv[i++] = "/usr/bin/time"; argv[i++] = "-f"; @@ -138,36 +173,7 @@ run(odk_backend_t *backend, odk_run_config_t *cfg, char **command) argv[i++] = *cursor; argv[i] = NULL; - /* Execute */ - rc = spawn_process(argv); - mr_free(&mr); - - return rc; -} - -static int -close_backend(odk_backend_t *backend) -{ - (void) backend; - - return 0; -} - -static int -get_total_memory(odk_backend_info_t *info) -{ - FILE *p; - int ret = -1; - - if ( (p = popen("docker info --format={{.MemTotal}}", "r")) != NULL ) { - if ( fscanf(p, "%lu", &(info->total_memory)) == 1 ) - ret = 0; - else - errno = ESRCH; - pclose(p); - } - - return ret; + return argv; } int diff --git a/src/backend-docker.h b/src/backend-docker.h index 30e5ce2..b35dea4 100644 --- a/src/backend-docker.h +++ b/src/backend-docker.h @@ -31,12 +31,17 @@ #ifndef ICP20240622_BACKEND_DOCKER_H #define ICP20240622_BACKEND_DOCKER_H +#include + #include "backend.h" #ifdef __cpluscplus extern "C" { #endif +char ** +odk_backend_docker_build_command(mem_registry_t *, odk_run_config_t *, char **); + int odk_backend_docker_init(odk_backend_t *); From 9f54d3f5804f54b84988244efc64e8eb70814a9f Mon Sep 17 00:00:00 2001 From: Damien Goutte-Gattat Date: Mon, 28 Sep 2026 19:25:45 +0100 Subject: [PATCH 2/4] Add experimental "apple" backend. Add a new backend that relies on Apple's `container` command to create and run a container from a Docker image. closes #1 --- Makefile.am | 1 + doc/odkrun.1.in | 5 +++ src/backend-apple.c | 103 ++++++++++++++++++++++++++++++++++++++++++++ src/backend-apple.h | 47 ++++++++++++++++++++ src/odkrun.c | 18 +++++++- 5 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 src/backend-apple.c create mode 100644 src/backend-apple.h diff --git a/Makefile.am b/Makefile.am index ac5c541..23781ed 100644 --- a/Makefile.am +++ b/Makefile.am @@ -17,6 +17,7 @@ odkrun_SOURCES = src/odkrun.c \ src/backend-docker.c src/backend-docker.h \ src/backend-singularity.c src/backend-singularity.h \ src/backend-native.c src/backend-native.h \ + src/backend-apple.c src/backend-apple.h \ src/owlapi.c src/owlapi.h src/owlapi-options.h \ src/runconf.c src/runconf.h \ src/oaklib.h src/oaklib.c \ diff --git a/doc/odkrun.1.in b/doc/odkrun.1.in index d10e003..9c4c5a2 100644 --- a/doc/odkrun.1.in +++ b/doc/odkrun.1.in @@ -14,6 +14,7 @@ odkrun - ODK Runner tool .IR tag ] .RB [ -l | --lite ] .RB [ -s | --singulary ] +.RB [ --apple ] .RB [ -n | --native ] .RB [ --root ] .RB [ -e | --env @@ -64,6 +65,10 @@ Use the \fIobolibrary/odklite\fR image. .BR -s ", " --singularity Run the container with Singularity rather than Docker. This is experimental but should mostly work. +.BR --apple +Run the container with Apple Container rather than Docker. +This is only available on macOS. This is experimental but +should mostly work. .TP .BR -n ", " --native Run in the native system rather than in a container. This is diff --git a/src/backend-apple.c b/src/backend-apple.c new file mode 100644 index 0000000..dd78e55 --- /dev/null +++ b/src/backend-apple.c @@ -0,0 +1,103 @@ +/* + * ODK Runner + * Copyright (C) 2026 Damien Goutte-Gattat + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "backend-apple.h" + +#include +#include +#include + +#include "procutil.h" +#include "util.h" +#include "backend-docker.h" + +#if defined(ODK_RUNNER_MACOS) + +static int +prepare(odk_backend_t *backend, odk_run_config_t *cfg) +{ + (void) backend; + + if ( (cfg->flags & ODK_FLAG_RUNASROOT) == 0 ) { + odk_add_env_var(cfg, "ODK_USER_ID", "1000", 0); + odk_add_env_var(cfg, "ODK_GROUP_ID", "1000", 0); + } + + return 0; +} + +static int +run(odk_backend_t *backend, odk_run_config_t *cfg, char **command) +{ + int rc; + char **argv; + mem_registry_t mr = { 0 }; + + (void) backend; + + argv = odk_backend_docker_build_command(&mr, cfg, command); + argv[0] = "container"; + rc = spawn_process(argv); + mr_free(&mr); + + return rc; +} + +static int +close_backend(odk_backend_t *backend) +{ + (void) backend; + + return 0; +} + +#endif /* ODK_RUNNER_MACOS */ + +int +odk_backend_apple_init(odk_backend_t *backend) +{ +#if !defined(ODK_RUNNER_MACOS) + errno = ENOSYS; + return -1; + +#else + backend->prepare = prepare; + backend->run = run; + backend->close = close_backend; + + backend->info.total_memory = get_physical_memory(); + + return 0; +#endif +} diff --git a/src/backend-apple.h b/src/backend-apple.h new file mode 100644 index 0000000..0ae6c85 --- /dev/null +++ b/src/backend-apple.h @@ -0,0 +1,47 @@ +/* + * ODK Runner + * Copyright (C) 2026 Damien Goutte-Gattat + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef ICP20260928_BACKEND_APPLE_H +#define ICP20260928_BACKEND_APPLE_H + +#include "backend.h" + +#ifdef __cpluscplus +extern "C" { +#endif + +int +odk_backend_apple_init(odk_backend_t *); + +#ifdef __cpluscplus +} +#endif + +#endif /* !ICP20260928_BACKEND_APPLE_H */ diff --git a/src/odkrun.c b/src/odkrun.c index ba5468b..164e98b 100644 --- a/src/odkrun.c +++ b/src/odkrun.c @@ -48,6 +48,7 @@ #include "backend-docker.h" #include "backend-singularity.h" #include "backend-native.h" +#include "backend-apple.h" #include "oaklib.h" #include "owlapi.h" #include "runconf.h" @@ -82,7 +83,13 @@ Start a ODK container.\n"); puts("Backend options:\n\ -s, --singulary Run the container with Singularity rather\n\ - than Docker (experimental).\n\ + than Docker (experimental)."); +#if defined(ODK_RUNNER_MACOS) + puts("\ + --apple Run the container with Apple Container rather\n\ + than Docker (experimental)."); +#endif + puts("\ -n, --native Run in the native system, not in a container\n\ (VERY experimental).\n\ --root Run as a superuser within the container.\n\ @@ -450,6 +457,9 @@ main(int argc, char **argv) { "owlapi-option", 1, NULL, 257 }, { "java-property", 1, NULL, 258 }, { "assume-odk-repository", 0, NULL, 259 }, +#if defined (ODK_RUNNER_MACOS) + { "apple", 0, NULL, 260 }, +#endif { NULL, 0, NULL, 0 } }; @@ -530,6 +540,12 @@ main(int argc, char **argv) case 259: in_odk_repo = 1; break; + +#if defined(ODK_RUNNER_MACOS) + case 260: + backend_init = odk_backend_apple_init; + break; +#endif } } From 7b49daf9b7fd309803dd4dd05bf8c15f7c3be9d5 Mon Sep 17 00:00:00 2001 From: Damien Goutte-Gattat Date: Mon, 28 Sep 2026 20:27:53 +0100 Subject: [PATCH 3/4] Do not show unavailable backends. When printing the help message, do not show options corresponding to backends that are not available on the current system. --- src/odkrun.c | 40 ++++++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/src/odkrun.c b/src/odkrun.c index 164e98b..eec4fa7 100644 --- a/src/odkrun.c +++ b/src/odkrun.c @@ -86,12 +86,15 @@ Start a ODK container.\n"); than Docker (experimental)."); #if defined(ODK_RUNNER_MACOS) puts("\ - --apple Run the container with Apple Container rather\n\ + -a, --apple Run the container with Apple Container rather\n\ than Docker (experimental)."); #endif +#if !defined(ODK_RUNNER_WINDOWS) puts("\ -n, --native Run in the native system, not in a container\n\ - (VERY experimental).\n\ + (VERY experimental)."); +#endif + puts("\ --root Run as a superuser within the container.\n\ "); @@ -430,6 +433,14 @@ write_setup_script(odk_run_config_t *cfg, FILE *out) /* Main function. */ +#if defined(ODK_RUNNER_LINUX) +#define BACKEND_OPTS "n" +#elif defined(ODK_RUNNER_MACOS) +#define BACKEND_OPTS "an" +#else +#define BACKEND_OPTS "" +#endif + int main(int argc, char **argv) { @@ -448,7 +459,12 @@ main(int argc, char **argv) { "tag", 1, NULL, 't' }, { "lite", 0, NULL, 'l' }, { "singularity", 0, NULL, 's' }, +#if defined (ODK_RUNNER_MACOS) + { "apple", 0, NULL, 'a' }, +#endif +#if !defined (ODK_RUNNER_WINDOWS) { "native", 0, NULL, 'n' }, +#endif { "env", 1, NULL, 'e' }, { "oak-cache", 1, NULL, 'k' }, { "oak-user-cache", 0, NULL, 'K' }, @@ -457,9 +473,6 @@ main(int argc, char **argv) { "owlapi-option", 1, NULL, 257 }, { "java-property", 1, NULL, 258 }, { "assume-odk-repository", 0, NULL, 259 }, -#if defined (ODK_RUNNER_MACOS) - { "apple", 0, NULL, 260 }, -#endif { NULL, 0, NULL, 0 } }; @@ -468,7 +481,8 @@ main(int argc, char **argv) odk_init_config(&cfg); - while ( (c = getopt_long(argc, argv, "+hvdi:t:lsne:k:Km:", options, NULL)) != -1 ) { + while ( (c = getopt_long(argc, argv, "+hvdi:t:lse:k:Km:" BACKEND_OPTS, + options, NULL)) != -1 ) { switch ( c ) { case 'h': usage(EXIT_SUCCESS); @@ -503,9 +517,17 @@ main(int argc, char **argv) backend_init = odk_backend_singularity_init; break; +#if !defined(ODK_RUNNER_WINDOWS) case 'n': backend_init = odk_backend_native_init; break; +#endif + +#if defined(ODK_RUNNER_MACOS) + case 'a': + backend_init = odk_backend_apple_init; + break; +#endif case 'k': odk_set_oak_cache_directory(&cfg, optarg, 0); @@ -540,12 +562,6 @@ main(int argc, char **argv) case 259: in_odk_repo = 1; break; - -#if defined(ODK_RUNNER_MACOS) - case 260: - backend_init = odk_backend_apple_init; - break; -#endif } } From ec5ac0aab7e15e630eccfd48858393436f33c4cc Mon Sep 17 00:00:00 2001 From: Damien Goutte-Gattat Date: Mon, 28 Sep 2026 21:22:20 +0100 Subject: [PATCH 4/4] Get the amount of memory allocated to Apple containers. The Apple Container backend should not assume that the entire physical memory is available to the containers. It should instead query the container service to obtain whatever value the service is configured to use. --- src/backend-apple.c | 38 +++++++++++++++++++++++++++++++++-- src/util.c | 48 +++++++++++++++++++++++++++++++++++++++++++++ src/util.h | 3 +++ 3 files changed, 87 insertions(+), 2 deletions(-) diff --git a/src/backend-apple.c b/src/backend-apple.c index dd78e55..bb2dcc3 100644 --- a/src/backend-apple.c +++ b/src/backend-apple.c @@ -82,6 +82,38 @@ close_backend(odk_backend_t *backend) return 0; } +static int +get_total_memory(odk_backend_info_t *info) +{ + FILE *p; + int ret = -1; + + if ( (p = popen("container system property ls", "r")) != NULL ) { + char line[128]; + ssize_t n; + int memory, container_section = 0; + + /* + * Contrary to `docker info`, `container system property ls` + * does not allow to query an individual setting, so we need to + * parse the entire output. + */ + while ( ! feof(p) ) { + if ( (n = get_line(p, line, sizeof(line))) > 0 ) { + if ( line[0] == '[' ) { + container_section = strcmp(line, "[container]") == 0; + } + else if ( container_section && sscanf(line, "memory = \"%dgb\"", &memory) == 1 ) + info->total_memory = (long) memory * 1024 * 1024 * 1024; + } + } + pclose(p); + ret = 0; + } + + return ret; +} + #endif /* ODK_RUNNER_MACOS */ int @@ -92,12 +124,14 @@ odk_backend_apple_init(odk_backend_t *backend) return -1; #else + int ret; + backend->prepare = prepare; backend->run = run; backend->close = close_backend; - backend->info.total_memory = get_physical_memory(); + ret = get_total_memory(&(backend->info)); - return 0; + return ret; #endif } diff --git a/src/util.c b/src/util.c index 36beec2..7fa4506 100644 --- a/src/util.c +++ b/src/util.c @@ -249,6 +249,54 @@ read_line_from_pipe(const char *command) return line; } +/* + * Discards characters from the specified stream up to the next + * end of line. + */ +static int +discard_line(FILE *f) +{ + int c; + + while ( (c = fgetc(f)) != '\n' && c != EOF ) ; + + return c == EOF ? -1 : 0; +} + +/** + * Reads a single line from the specified stream. + * This function differs from standard fgets(3) in two aspects: the + * newline character is not stored, and if the line to read is too long + * to fit into the provided buffer, the whole line is discarded. + * + * @param f The stream to read from. + * @param buffer A character buffer to be filled with the read line. + * @param len The size of the @a buffer array. + * + * @return The number of characters read, or -1 if an error occured. + */ +ssize_t +get_line(FILE *f, char *buffer, size_t len) +{ + int c; + size_t n = 0; + + while ( (c = fgetc(f)) != '\n' ) { + if ( c == EOF ) + return -1; + + if ( n >= len -1 ) { /* line too long */ + discard_line(f); + return -1; + } + + buffer[n++] = (char)c; + } + buffer[n] = '\0'; + + return n; +} + /** * Gets the path to a file or directory under the per-user directory. * diff --git a/src/util.h b/src/util.h index af3bc02..dca4137 100644 --- a/src/util.h +++ b/src/util.h @@ -63,6 +63,9 @@ read_file(const char *, size_t *, size_t); char * read_line_from_pipe(const char *); +ssize_t +get_line(FILE *, char *, size_t); + char * get_user_path(enum odk_userdir_type, const char *);