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..bb2dcc3 --- /dev/null +++ b/src/backend-apple.c @@ -0,0 +1,137 @@ +/* + * 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; +} + +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 +odk_backend_apple_init(odk_backend_t *backend) +{ +#if !defined(ODK_RUNNER_MACOS) + errno = ENOSYS; + return -1; + +#else + int ret; + + backend->prepare = prepare; + backend->run = run; + backend->close = close_backend; + + ret = get_total_memory(&(backend->info)); + + return ret; +#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/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 *); diff --git a/src/odkrun.c b/src/odkrun.c index ba5468b..eec4fa7 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,9 +83,18 @@ 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("\ + -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\ "); @@ -423,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) { @@ -441,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' }, @@ -458,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); @@ -493,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); 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 *);