diff --git a/client/Makefile.linux b/client/Makefile.linux
index bfac0c26e31..845df211e1a 100644
--- a/client/Makefile.linux
+++ b/client/Makefile.linux
@@ -140,7 +140,7 @@ clean:
LIBS = ../lib/boinc.a \
-L /usr/local/lib/ \
-lpthread \
- -lX11 -lXss \
+ -lX11 \
-lcurl -lssl -lcrypto \
-lz -ldl
diff --git a/client/app_test.cpp b/client/app_test.cpp
index ea1a8d85662..00e99a91b7c 100644
--- a/client/app_test.cpp
+++ b/client/app_test.cpp
@@ -15,37 +15,68 @@
// You should have received a copy of the GNU Lesser General Public License
// along with BOINC. If not, see .
-// A framework that lets you run jobs under a BOINC client
-// without a project, and without fake XML files
-// This lets you debug client/app interactions.
+// This framework lets you run jobs under a BOINC client without a project.
+// This lets you debug client/app interactions like suspend/resume.
+// The properties of the app and its files
+// are described procedurally (in this file)
+// rather than with XML files.
//
// To use this framework:
// - edit this file to describe your application:
// input/output files, attributes, etc.
-// NOTE: currently it's set up for an app that uses the WSL wrapper
-//
+// It currently has several test cases, selected with #ifdef
// - build the BOINC client with these changes
// - make a BOINC data directory, say 'test'
// (or you can use an existing BOINC data directory,
-// in which case the client will also run jobs that are there)
+// in which case the client will also run existing jobs)
// - make a directory test/slots/app_test
-// The client will run the test job there.
+// The client will run your test job there.
// Clean it out between runs.
// - make a dir test/projects/app_test
// - In the project directory, put:
// - the executable file
// - the input file(s) with physical names
-// - run boinc (in the data directory if you created one)
-// when the job is done, the client won't clean out the slot dir.
-// You can examine the contents of the slot dir,
-// and examine the output files in the project dir.
+// NOTE: slots/app_test and projects/app_test can be symbolic links
+// in case you have multiple test cases
+// - run boinc in the data directory, e.g. test/
+// The client will copy files and create link files
+// as needed in the slot dir,
+// and create init_data.xml there.
+// When the job is done, the client won't clean out the slot dir.
+// You can examine the contents of the slot and project dir,
// Clean out the slot dir between tests.
#include "client_state.h"
-// set to 0 to enable app test
+// define exactly one
+
+//#define APP_NONE
+//#define APP_WSL_WRAPPER
+// type physical logical copy?
+// app wsl_wrapper.exe wsl_wrapper.exe
+// app worker worker
+// app main main yes
+// input infile in
+// output outfile out
+#define APP_DOCKER_WRAPPER_COPY
+// type physical logical copy?
+// app worker worker yes
+// app job_copy.toml job_copy.toml yes
+// app Dockerfile_copy Dockerfile_copy yes
+// app docker_wrapper docker_wrapper
+// input infile in yes
+// output outfile out yes
+//#define APP_DOCKER_WRAPPER_MOUNT
+// type physical logical copy?
+// app worker worker
+// app job_mount.toml job_mount.toml yes
+// app Dockerfile_mount Dockerfile_mount yes
+// app main.sh main.sh yes
+// app docker_wrapper docker_wrapper
+// input infile in
+// output outfile out
-#if 1
+#ifdef APP_NONE
void CLIENT_STATE::app_test_init() {}
#else
@@ -83,6 +114,8 @@ static APP* make_app(PROJECT* proj) {
#define OUTPUT_FILE 1
#define MAIN_PROG 2
+// if log_name is NULL, logical name is physical name
+//
static FILE_REF* make_file(
PROJECT *proj, const char* phys_name, const char* log_name,
int ftype, bool copy_file
@@ -163,6 +196,10 @@ void CLIENT_STATE::app_test_init() {
#endif
APP_VERSION *av = make_app_version(app);
+
+////////////// APP VERSION FILES /////////////////
+
+#ifdef APP_WSL_WRAPPER
av->app_files.push_back(
*make_file(app->project, "wsl_wrapper.exe", NULL, MAIN_PROG, false)
);
@@ -172,6 +209,21 @@ void CLIENT_STATE::app_test_init() {
av->app_files.push_back(
*make_file(app->project, "worker", NULL, INPUT_FILE, false)
);
+#endif
+#ifdef APP_DOCKER_WRAPPER_COPY
+ av->app_files.push_back(
+ *make_file(app->project, "docker_wrapper.exe", NULL, MAIN_PROG, false)
+ );
+ av->app_files.push_back(
+ *make_file(app->project, "worker", NULL, INPUT_FILE, true)
+ );
+ av->app_files.push_back(
+ *make_file(app->project, "job_copy.toml", "job.toml", INPUT_FILE, true)
+ );
+ av->app_files.push_back(
+ *make_file(app->project, "Dockerfile_copy", "Dockerfile", INPUT_FILE, true)
+ );
+#endif
// can put other stuff here like
#if 0
@@ -181,19 +233,35 @@ void CLIENT_STATE::app_test_init() {
#endif
WORKUNIT *wu = make_workunit(av);
-#if 1
+
+////////////// INPUT FILES /////////////////
+
+#ifdef APP_WSL_WRAPPER
//wu->command_line = "--nsecs 60";
wu->input_files.push_back(
*make_file(proj, "infile", "in", INPUT_FILE, false)
);
#endif
+#ifdef APP_DOCKER_WRAPPER_COPY
+ wu->input_files.push_back(
+ *make_file(proj, "infile", "in", INPUT_FILE, true)
+ );
+#endif
RESULT *result = make_result(av, wu);
-#if 1
+
+////////////// OUTPUT FILES /////////////////
+
+#ifdef APP_WSL_WRAPPER
result->output_files.push_back(
*make_file(proj, "outfile", "out", OUTPUT_FILE, false)
);
#endif
+#ifdef APP_DOCKER_WRAPPER_COPY
+ result->output_files.push_back(
+ *make_file(proj, "outfile", "out", OUTPUT_FILE, true)
+ );
+#endif
// tell the client not to get work or run benchmarks
//
diff --git a/client/client_state.cpp b/client/client_state.cpp
index 39c1e1e3afb..e1c356a57ce 100644
--- a/client/client_state.cpp
+++ b/client/client_state.cpp
@@ -262,14 +262,16 @@ void CLIENT_STATE::show_host_info() {
"- OS: %s (%s)",
wsl.os_name.c_str(), wsl.os_version.c_str()
);
- if (wsl.is_docker_available) {
- msg_printf(NULL, MSG_INFO, "- Docker version %s",
- wsl.docker_version.c_str()
+ if (!wsl.docker_version.empty()) {
+ msg_printf(NULL, MSG_INFO, "- Docker version %s (%s)",
+ wsl.docker_version.c_str(),
+ docker_type_str(wsl.docker_type)
);
}
- if (wsl.is_docker_compose_available) {
- msg_printf(NULL, MSG_INFO, "- Docker compose version %s",
- wsl.docker_compose_version.c_str()
+ if (!wsl.docker_compose_version.empty()) {
+ msg_printf(NULL, MSG_INFO, "- Docker compose version %s (%s)",
+ wsl.docker_compose_version.c_str(),
+ docker_type_str(wsl.docker_compose_type)
);
}
}
@@ -292,14 +294,16 @@ void CLIENT_STATE::show_host_info() {
}
#ifndef _WIN64
- if (host_info.docker_available && strlen(host_info.docker_version)) {
- msg_printf(NULL, MSG_INFO, "Docker version %s found",
- host_info.docker_version
+ if (strlen(host_info.docker_version)) {
+ msg_printf(NULL, MSG_INFO, "Docker: version %s (%s)",
+ host_info.docker_version,
+ docker_type_str(host_info.docker_type)
);
}
- if (host_info.docker_compose_available && strlen(host_info.docker_compose_version)) {
- msg_printf(NULL, MSG_INFO, "Docker compose version %s found",
- host_info.docker_compose_version
+ if (strlen(host_info.docker_compose_version)) {
+ msg_printf(NULL, MSG_INFO, "Docker compose: version %s (%s)",
+ host_info.docker_compose_version,
+ docker_type_str(host_info.docker_compose_type)
);
}
#endif
diff --git a/client/cs_scheduler.cpp b/client/cs_scheduler.cpp
index e9bdc16f468..b36dfe1d453 100644
--- a/client/cs_scheduler.cpp
+++ b/client/cs_scheduler.cpp
@@ -141,6 +141,9 @@ int CLIENT_STATE::make_scheduler_request(PROJECT* p) {
g_use_sandbox?1:0,
p->dont_request_more_work?1:0
);
+ if (cc_config.dont_use_docker) {
+ fprintf(f, " \n");
+ }
work_fetch.write_request(f, p);
// write client capabilities
diff --git a/client/hostinfo_unix.cpp b/client/hostinfo_unix.cpp
index 972cb6fb402..7c3898378f1 100644
--- a/client/hostinfo_unix.cpp
+++ b/client/hostinfo_unix.cpp
@@ -1233,44 +1233,68 @@ int HOST_INFO::get_virtualbox_version() {
return 0;
}
-// check if docker compose is installed on volunteer's host
-// populates docker compose version and docker_compose_present on success
-bool HOST_INFO::get_docker_compose_info(){
- FILE* f = popen(command_get_docker_compose_version, "r");
+// check if docker is installed on this host
+// populate docker_version on success
+//
+bool HOST_INFO::get_docker_version_aux(DOCKER_TYPE type){
+ bool ret = false;
+ string cmd = string(docker_cli_prog(type)) + " --version";
+ FILE* f = popen(cmd.c_str(), "r");
if (f) {
char buf[256];
fgets(buf, 256, f);
std::string version;
- if (get_docker_compose_version_string(buf, version)) {
- docker_compose_available = true;
- safe_strcpy(docker_compose_version, version.c_str());
+ if (get_docker_version_string(type, buf, version)) {
+ safe_strcpy(docker_version, version.c_str());
+ docker_type = type;
+ ret = true;
}
pclose(f);
+ }
+ return ret;
+}
+
+bool HOST_INFO::get_docker_version(){
+ if (get_docker_version_aux(PODMAN)) {
+ return true;
+ }
+ if (get_docker_version_aux(DOCKER)) {
return true;
}
return false;
}
-
-// check if docker is installed on volunteer's host
-// populates docker version and docker_present on success
-bool HOST_INFO::get_docker_info(){
- FILE* f = popen(command_get_docker_version, "r");
+// check if docker compose is installed on this host
+// populate docker_compose_version on success
+//
+bool HOST_INFO::get_docker_compose_version_aux(DOCKER_TYPE type){
+ bool ret = false;
+ string cmd = string(docker_cli_prog(type)) + " compose version";
+ FILE* f = popen(cmd.c_str(), "r");
if (f) {
char buf[256];
fgets(buf, 256, f);
std::string version;
- if (get_docker_version_string(buf, version)) {
- docker_available = true;
- safe_strcpy(docker_version, version.c_str());
+ if (get_docker_compose_version_string(type, buf, version)) {
+ safe_strcpy(docker_compose_version, version.c_str());
+ docker_compose_type = type;
+ ret = true;
}
pclose(f);
+ }
+ return ret;
+}
+
+bool HOST_INFO::get_docker_compose_version(){
+ if (get_docker_compose_version_aux(PODMAN)) {
+ return true;
+ }
+ if (get_docker_compose_version_aux(DOCKER)) {
return true;
}
return false;
}
-
// get p_vendor, p_model, p_features
//
int HOST_INFO::get_cpu_info() {
@@ -1718,10 +1742,8 @@ int HOST_INFO::get_host_info(bool init) {
get_virtualbox_version();
}
- if(!cc_config.dont_use_docker){
- get_docker_info();
- get_docker_compose_info();
- }
+ get_docker_version();
+ get_docker_compose_version();
get_cpu_info();
get_cpu_count();
diff --git a/client/hostinfo_wsl.cpp b/client/hostinfo_wsl.cpp
index 7b5dba8da62..133d0ebaeea 100644
--- a/client/hostinfo_wsl.cpp
+++ b/client/hostinfo_wsl.cpp
@@ -30,6 +30,9 @@ using std::string;
//
#define CMD_TIMEOUT 10.0
+static void get_docker_version(WSL_CMD&, WSL_DISTRO&);
+static void get_docker_compose_version(WSL_CMD&, WSL_DISTRO&);
+
// scan the registry to get the list of all WSL distros on this host.
// See https://patrickwu.space/2020/07/19/wsl-related-registry/
//
@@ -215,7 +218,7 @@ int get_wsl_information(
// try running 'lsb_release -a'
//
- if (!rs.run_command(wd.distro_name, command_lsbrelease)) {
+ if (!rs.run_program_in_wsl(wd.distro_name, command_lsbrelease)) {
read_from_pipe(rs.out_read, rs.proc_handle, reply, CMD_TIMEOUT);
HOST_INFO::parse_linux_os_info(
reply, lsbrelease,
@@ -230,7 +233,7 @@ int get_wsl_information(
//
if (!got_both(wd)) {
const std::string command_osrelease = "cat " + std::string(file_osrelease);
- if (!rs.run_command( wd.distro_name, command_osrelease)) {
+ if (!rs.run_program_in_wsl( wd.distro_name, command_osrelease)) {
read_from_pipe(rs.out_read, rs.proc_handle, reply, CMD_TIMEOUT);
HOST_INFO::parse_linux_os_info(
reply, osrelease,
@@ -246,7 +249,7 @@ int get_wsl_information(
//
if (!got_both(wd)) {
const std::string command_redhatrelease = "cat " + std::string(file_redhatrelease);
- if (!rs.run_command(
+ if (!rs.run_program_in_wsl(
wd.distro_name, command_redhatrelease
)) {
read_from_pipe(rs.out_read, rs.proc_handle, reply, CMD_TIMEOUT);
@@ -267,7 +270,7 @@ int get_wsl_information(
//
if (!got_both(wd)) {
const std::string command_sysctl = "sysctl -a";
- if (!rs.run_command(
+ if (!rs.run_program_in_wsl(
wd.distro_name, command_sysctl
)) {
read_from_pipe(rs.out_read, rs.proc_handle, reply, CMD_TIMEOUT);
@@ -284,7 +287,7 @@ int get_wsl_information(
//
if (!got_both(wd)) {
const std::string command_uname_s = "uname -s";
- if (!rs.run_command(
+ if (!rs.run_program_in_wsl(
wd.distro_name, command_uname_s
)) {
read_from_pipe(rs.out_read, rs.proc_handle, os_name_str, CMD_TIMEOUT);
@@ -298,7 +301,7 @@ int get_wsl_information(
//
if (!got_both(wd)) {
const std::string command_uname_r = "uname -r";
- if (!rs.run_command(
+ if (!rs.run_program_in_wsl(
wd.distro_name, command_uname_r
)) {
read_from_pipe(rs.out_read, rs.proc_handle, os_version_str, CMD_TIMEOUT);
@@ -314,32 +317,8 @@ int get_wsl_information(
// see if Docker is installed in the distro
//
if (detect_docker) {
- if (!rs.run_command(
- wd.distro_name, command_get_docker_version
- )) {
- read_from_pipe(rs.out_read, rs.proc_handle, reply, CMD_TIMEOUT);
- string version;
- wd.is_docker_available = HOST_INFO::get_docker_version_string(
- reply, version
- );
- if (wd.is_docker_available) {
- wd.docker_version = version;
- }
- CloseHandle(rs.proc_handle);
- }
- if (!rs.run_command(
- wd.distro_name, command_get_docker_compose_version
- )) {
- read_from_pipe(rs.out_read, rs.proc_handle, reply, CMD_TIMEOUT);
- string version;
- wd.is_docker_compose_available = HOST_INFO::get_docker_compose_version_string(
- reply, version
- );
- if (wd.is_docker_compose_available) {
- wd.docker_compose_version = version;
- }
- CloseHandle(rs.proc_handle);
- }
+ get_docker_version(rs, wd);
+ get_docker_compose_version(rs, wd);
}
usable_distros.distros.push_back(wd);
@@ -347,3 +326,49 @@ int get_wsl_information(
return 0;
}
+
+static bool get_docker_version_aux(WSL_CMD &rs, WSL_DISTRO &wd, DOCKER_TYPE type) {
+ bool ret = false;
+ string reply;
+ string cmd = string(docker_cli_prog(type)) + " --version";
+ if (!rs.run_program_in_wsl(wd.distro_name, cmd.c_str())) {
+ read_from_pipe(rs.out_read, rs.proc_handle, reply, CMD_TIMEOUT);
+ string version;
+ if (HOST_INFO::get_docker_version_string(type, reply.c_str(), version)) {
+ wd.docker_version = version;
+ wd.docker_type = type;
+ ret = true;
+ }
+ CloseHandle(rs.proc_handle);
+ }
+ return ret;
+}
+
+static void get_docker_version(WSL_CMD &rs, WSL_DISTRO &wd) {
+ if (get_docker_version_aux(rs, wd, PODMAN)) return;
+ get_docker_version_aux(rs, wd, DOCKER);
+}
+
+static bool get_docker_compose_version_aux(WSL_CMD &rs, WSL_DISTRO &wd, DOCKER_TYPE type) {
+ bool ret = false;
+ string reply;
+ string cmd = string(docker_cli_prog(type)) + " compose version";
+ if (!rs.run_program_in_wsl(wd.distro_name, cmd.c_str())) {
+ read_from_pipe(rs.out_read, rs.proc_handle, reply, CMD_TIMEOUT);
+ string version;
+ if (HOST_INFO::get_docker_compose_version_string(
+ type, reply.c_str(), version
+ )) {
+ wd.docker_compose_version = version;
+ wd.docker_compose_type = type;
+ ret = true;
+ }
+ CloseHandle(rs.proc_handle);
+ }
+ return false;
+}
+
+static void get_docker_compose_version(WSL_CMD& rs, WSL_DISTRO &wd) {
+ if (get_docker_compose_version_aux(rs, wd, PODMAN)) return;
+ get_docker_compose_version_aux(rs, wd, DOCKER);
+}
diff --git a/lib/common_defs.h b/lib/common_defs.h
index ea98c5040b8..52c535bb9d3 100644
--- a/lib/common_defs.h
+++ b/lib/common_defs.h
@@ -422,4 +422,7 @@ struct DEVICE_STATUS {
#define LINUX_DEFAULT_DATA_DIR "/var/lib/boinc"
#endif
+// impementations of Docker
+enum DOCKER_TYPE {NONE, DOCKER, PODMAN};
+
#endif
diff --git a/lib/hostinfo.cpp b/lib/hostinfo.cpp
index 6adf88bd449..842da0a293f 100644
--- a/lib/hostinfo.cpp
+++ b/lib/hostinfo.cpp
@@ -24,6 +24,7 @@
#include "config.h"
#include
#include
+#include
#if HAVE_UNISTD_H
#include
#endif
@@ -37,6 +38,8 @@
#include "hostinfo.h"
+using std::string;
+
HOST_INFO::HOST_INFO() {
clear_host_info();
}
@@ -74,8 +77,6 @@ void HOST_INFO::clear_host_info() {
#ifdef _WIN64
wsl_distros.clear();
#else
- docker_available = false;
- docker_compose_available = false;
safe_strcpy(docker_version, "");
safe_strcpy(docker_compose_version, "");
#endif
@@ -142,10 +143,9 @@ int HOST_INFO::parse(XML_PARSER& xp, bool static_items_only) {
continue;
}
#else
- if (xp.parse_bool("docker_available", docker_available)) continue;
- if (xp.parse_bool("docker_compose_available", docker_compose_available)) continue;
if (xp.parse_str("docker_version", docker_version, sizeof(docker_version))) continue;
if (xp.parse_str("docker_compose_version", docker_compose_version, sizeof(docker_compose_version))) continue;
+ if (xp.parse_str("docker_version", docker_version, sizeof(docker_version))) continue;
#endif
if (xp.parse_str("product_name", product_name, sizeof(product_name))) continue;
if (xp.parse_str("virtualbox_version", virtualbox_version, sizeof(virtualbox_version))) continue;
@@ -236,26 +236,21 @@ int HOST_INFO::write(
);
#ifdef _WIN64
wsl_distros.write_xml(out);
-
#else
- out.printf(
- " %d\n",
- docker_available ? 1 : 0
- );
- out.printf(
- " %d\n",
- docker_compose_available ? 1 : 0
- );
if (strlen(docker_version)) {
out.printf(
- " %s\n",
- docker_version
+ " %s\n"
+ " %d\n",
+ docker_version,
+ docker_type
);
}
if (strlen(docker_compose_version)) {
out.printf(
- " %s\n",
- docker_compose_version
+ " %s\n"
+ " %d\n",
+ docker_compose_version,
+ docker_compose_type
);
}
#endif
@@ -340,32 +335,78 @@ int HOST_INFO::write_cpu_benchmarks(FILE* out) {
return 0;
}
-bool HOST_INFO::get_docker_version_string(std::string raw, std::string& parsed) {
- std::string prefix = "Docker version";
- size_t pos1 = raw.find(prefix);
- if (pos1 == std::string::npos) {
- return false;
- }
- size_t pos2 = raw.find(",");
- if (pos2 == std::string::npos) {
- return false;
- }
- parsed = raw.substr(pos1 + prefix.size() + 1, pos2 - pos1 - prefix.size() - 1);
- if (!parsed.empty() && parsed[parsed.length() - 1] == '\n') {
- parsed.erase(parsed.length() - 1);
+// name of CLI program
+//
+const char* docker_cli_prog(DOCKER_TYPE type) {
+ switch (type) {
+ case DOCKER: return "docker";
+ case PODMAN: return "podman";
+ default: break;
}
- return true;
+ return "unknown";
}
-bool HOST_INFO::get_docker_compose_version_string(std::string raw, std::string& parsed) {
- std::string prefix = "Docker Compose version v";
- size_t pos1 = raw.find(prefix);
- if (pos1 == std::string::npos) {
- return false;
+
+// display name
+//
+const char* docker_type_str(DOCKER_TYPE type) {
+ switch (type) {
+ case DOCKER: return "Docker";
+ case PODMAN: return "podman";
+ default: break;
}
- parsed = raw.substr(pos1 + prefix.size(), raw.size() - pos1 - prefix.size());
- if (!parsed.empty() && parsed[parsed.length() - 1] == '\n') {
- parsed.erase(parsed.length() - 1);
+ return "unknown";
+}
+
+bool HOST_INFO::get_docker_version_string(
+ DOCKER_TYPE type, const char* raw, string &version
+) {
+ char *p, *q;
+ const char *prefix;
+ switch (type) {
+ case DOCKER:
+ // Docker version 24.0.7, build 24.0.7-0ubuntu2~22.04.1
+ prefix = "Docker version ";
+ p = (char*)strstr(raw, prefix);
+ if (!p) return false;
+ p += strlen(prefix);
+ q = (char*)strstr(p, ",");
+ if (!q) return false;
+ *q = 0;
+ version = p;
+ return true;
+ case PODMAN:
+ // podman version 4.9.3
+ prefix = "podman version ";
+ p = (char*)strstr(raw, prefix);
+ if (!p) return false;
+ p += strlen(prefix);
+ q = (char*)strstr(p, "\n");
+ if (q) *q = 0;
+ version = p;
+ return true;
+ default: break;
}
- return true;
+ return false;
}
+bool HOST_INFO::get_docker_compose_version_string(
+ DOCKER_TYPE type, const char *raw, string& version
+) {
+ char *p, *q;
+ const char* prefix;
+ switch (type) {
+ case DOCKER:
+ // Docker Compose version v2.17.3
+ prefix = "Docker Compose version v";
+ p = (char*)strstr(raw, prefix);
+ if (!p) return false;
+ p += strlen(prefix);
+ q = (char*)strstr(p, "\n");
+ if (q) *q = 0;
+ version = p;
+ return true;
+ // not sure about podman case
+ default: break;
+ }
+ return false;
+}
diff --git a/lib/hostinfo.h b/lib/hostinfo.h
index 760e911fa01..bf58cb9fbba 100644
--- a/lib/hostinfo.h
+++ b/lib/hostinfo.h
@@ -50,8 +50,9 @@ enum LINUX_OS_INFO_PARSER {
const char command_lsbrelease[] = "/usr/bin/lsb_release -a 2>&1";
const char file_osrelease[] = "/etc/os-release";
const char file_redhatrelease[] = "/etc/redhat-release";
-const char command_get_docker_version[] = "docker --version";
-const char command_get_docker_compose_version[] = "docker compose version";
+
+extern const char* docker_cli_prog(DOCKER_TYPE type);
+extern const char* docker_type_str(DOCKER_TYPE type);
// if you add fields, update clear_host_info()
@@ -89,11 +90,10 @@ class HOST_INFO {
// on Windows, Docker info is per WSL_DISTRO, not global
WSL_DISTROS wsl_distros;
#else
- bool docker_available;
- // Docker is present and allowed by config
- bool docker_compose_available;
- char docker_version[256];
+ char docker_version[256]; // null if not present
+ DOCKER_TYPE docker_type;
char docker_compose_version[256];
+ DOCKER_TYPE docker_compose_type;
#endif
char product_name[256]; // manufacturer and/or model of system
@@ -136,8 +136,10 @@ class HOST_INFO {
int get_virtualbox_version();
#ifndef _WIN64
// on Windows, Docker info is per WSL_DISTRO, not global
- bool get_docker_info();
- bool get_docker_compose_info();
+ bool get_docker_version();
+ bool get_docker_version_aux(DOCKER_TYPE);
+ bool get_docker_compose_version();
+ bool get_docker_compose_version_aux(DOCKER_TYPE);
#endif
void make_random_string(const char* salt, char* out);
void generate_host_cpid();
@@ -157,8 +159,12 @@ class HOST_INFO {
char* os_name, const int os_name_size, char* os_version,
const int os_version_size
);
- static bool get_docker_version_string(std::string raw, std::string& parsed);
- static bool get_docker_compose_version_string(std::string raw, std::string& parsed);
+ static bool get_docker_version_string(
+ DOCKER_TYPE type, const char* raw, std::string& version
+ );
+ static bool get_docker_compose_version_string(
+ DOCKER_TYPE type, const char* raw, std::string& version
+ );
#ifdef _WIN32
void win_get_processor_info();
#endif
diff --git a/lib/util.cpp b/lib/util.cpp
index 2af4b0fb945..c094f242fc2 100644
--- a/lib/util.cpp
+++ b/lib/util.cpp
@@ -207,9 +207,9 @@ void boinc_crash() {
}
// chdir into the given directory, and run a program there.
-// argv is set up Unix-style, i.e. argv[0] is the program name
+// Don't wait for it to exit.
+// argv is Unix-style, i.e. argv[0] is the program name
//
-
#ifdef _WIN32
int run_program(
const char* dir, const char* file, int argc, char *const argv[], HANDLE& id
@@ -238,7 +238,7 @@ int run_program(
cmdline,
NULL,
NULL,
- FALSE,
+ FALSE, // don't inherit handles
0,
NULL,
dir,
@@ -279,7 +279,151 @@ int run_program(
}
#endif
+// Run command, wait for exit.
+// Return its output as vector of lines.
+// Win: output includes stdout and stderr
+// Unix: if you want stderr too, add 2>&1 to command
+// Return error if command failed
+//
+int run_command(char *cmd, vector &out) {
+ out.clear();
#ifdef _WIN32
+ HANDLE pipe_read, pipe_write;
+ SECURITY_ATTRIBUTES sa;
+ STARTUPINFO si;
+ PROCESS_INFORMATION pi;
+
+ memset(&si, 0, sizeof(si));
+ memset(&pi, 0, sizeof(pi));
+ memset(&sa, 0, sizeof(sa));
+
+ sa.nLength = sizeof(SECURITY_ATTRIBUTES);
+ sa.bInheritHandle = TRUE;
+ sa.lpSecurityDescriptor = NULL;
+
+ if (!CreatePipe(&pipe_read, &pipe_write, &sa, 0)) return -1;
+ SetHandleInformation(pipe_read, HANDLE_FLAG_INHERIT, 0);
+
+ si.cb = sizeof(STARTUPINFO);
+ si.dwFlags |= STARTF_FORCEOFFFEEDBACK | STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
+ si.wShowWindow = SW_HIDE;
+ si.hStdOutput = pipe_write;
+ si.hStdError = pipe_write;
+ si.hStdInput = NULL;
+
+ if (!CreateProcess(
+ NULL,
+ (LPTSTR)cmd,
+ NULL,
+ NULL,
+ TRUE, // inherit handles
+ CREATE_NO_WINDOW,
+ NULL,
+ NULL,
+ &si,
+ &pi
+ )) {
+ return -1;
+ }
+
+ // wait for command to finish
+ //
+ WaitForSingleObject(pi.hProcess, INFINITE);
+
+ unsigned long exit_code;
+ GetExitCodeProcess(pi.hProcess, &exit_code);
+ if (exit_code) return -1;
+
+ DWORD count, nread;
+ PeekNamedPipe(pipe_read, NULL, NULL, NULL, &count, NULL);
+ if (count == 0) {
+ return 0;
+ }
+ char* buf = (char*)malloc(count+1);
+ if (!ReadFile(pipe_read, buf, count, &nread, NULL)) {
+ free(buf);
+ return -1;
+ }
+ buf[nread] = 0;
+ char* p = buf;
+ while (*p) {
+ char* q = strchr(p, '\n');
+ if (!q) break;
+ *q = 0;
+ out.push_back(string(p));
+ p = q + 1;
+ }
+ free(buf);
+#else
+#ifndef _USING_FCGI_
+ char buf[256];
+ FILE* fp = popen(cmd, "r");
+ if (!fp) {
+ fprintf(stderr, "popen() failed: %s\n", cmd);
+ return ERR_FOPEN;
+ }
+ while (fgets(buf, 256, fp)) {
+ out.push_back(buf);
+ }
+#endif
+#endif
+ return 0;
+}
+
+#ifdef _WIN32
+
+// run the program, and return handles to write to and read from it
+//
+int run_program_pipe(
+ char *cmd, HANDLE &write_handle, HANDLE &read_handle, HANDLE &proc_handle
+) {
+ HANDLE in_read, in_write, out_read, out_write;
+
+ SECURITY_ATTRIBUTES sa;
+ STARTUPINFO si;
+ PROCESS_INFORMATION pi;
+
+ memset(&si, 0, sizeof(si));
+ memset(&pi, 0, sizeof(pi));
+ memset(&sa, 0, sizeof(sa));
+
+ sa.nLength = sizeof(SECURITY_ATTRIBUTES);
+ sa.bInheritHandle = TRUE;
+ sa.lpSecurityDescriptor = NULL;
+
+ if (!CreatePipe(&out_read, &out_write, &sa, 0)) return -1;
+ if (!SetHandleInformation(out_read, HANDLE_FLAG_INHERIT, 0)) return -1;
+ if (!CreatePipe(&in_read, &in_write, &sa, 0)) return -1;
+ if (!SetHandleInformation(in_write, HANDLE_FLAG_INHERIT, 0)) return -1;
+
+ si.cb = sizeof(STARTUPINFO);
+ si.dwFlags |= STARTF_FORCEOFFFEEDBACK | STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
+ si.wShowWindow = SW_HIDE;
+ si.hStdOutput = out_write;
+ si.hStdError = out_write;
+ si.hStdInput = in_read;
+
+ if (!CreateProcess(
+ NULL,
+ (LPTSTR)cmd,
+ NULL,
+ NULL,
+ TRUE, // inherit handles
+ CREATE_NO_WINDOW,
+ NULL,
+ NULL,
+ &si,
+ &pi
+ )) {
+ return -1;
+ }
+
+ write_handle = in_write;
+ read_handle = out_read;
+ proc_handle = pi.hProcess;
+ return 0;
+}
+
int kill_process_with_status(int pid, int exit_code) {
int retval;
@@ -376,8 +520,7 @@ double rand_normal() {
return z*cos(PI2*u2);
}
-// determines the real path and filename of the current process
-// not the current working directory
+// get the path of the calling process's executable
//
int get_real_executable_path(char* path, size_t max_len) {
#if defined(__APPLE__)
diff --git a/lib/util.h b/lib/util.h
index fd7f6bedf60..50edb8a680e 100644
--- a/lib/util.h
+++ b/lib/util.h
@@ -89,6 +89,11 @@ extern int kill_process_with_status(int, int exit_code=0);
#endif
extern bool process_exists(PROCESS_REF);
+
+// chdir into the given directory, and run a program there.
+// Don't wait for it to exit.
+// argv is Unix-style, i.e. argv[0] is the program name
+//
extern int run_program(
const char* dir, // directory to run program in; NULL if current dir
const char* file, // path of executable
@@ -96,6 +101,15 @@ extern int run_program(
char *const argv[], // cmdline args, UNIX-style
PROCESS_REF& // ID of child process
);
+
+#ifdef _WIN32
+// run program, return handles to read and write to it
+//
+extern int run_program_pipe(
+ char *cmd, HANDLE &write_handle, HANDLE &read_handle, HANDLE &proc_handle
+);
+#endif
+
extern int kill_process(PROCESS_REF);
extern int get_exit_status(PROCESS_REF, int& status, double dt);
// get exit code of process
@@ -106,6 +120,14 @@ extern int get_exit_status(PROCESS_REF, int& status, double dt);
// Note: to see if a process has exited:
// get_exit_status(pid, status, 0) == 0
+// Run command.
+// Wait for exit, and return output as vector of lines.
+// Return error if command failed
+//
+extern int run_command(char *cmd, std::vector &out);
+
+// get the path of the calling process's executable
+//
extern int get_real_executable_path(char* path, size_t max_len);
#ifdef GCL_SIMULATOR
diff --git a/lib/win_util.cpp b/lib/win_util.cpp
index bc79d432099..7f1812f6417 100644
--- a/lib/win_util.cpp
+++ b/lib/win_util.cpp
@@ -224,7 +224,15 @@ int WSL_CMD::setup() {
return 0;
}
-int WSL_CMD::run_command(
+int WSL_CMD::setup_root(const char* distro_name) {
+ char cmd[1024];
+ sprintf(cmd, "wsl -d %s -u root", distro_name);
+ int retval = run_program_pipe(cmd, in_write, out_read, proc_handle);
+ if (retval) return retval;
+ return 0;
+}
+
+int WSL_CMD::run_program_in_wsl(
const string distro_name, const string command, bool use_cwd
) {
HRESULT ret = pWslLaunch(
diff --git a/lib/win_util.h b/lib/win_util.h
index 4965abb85fc..7f7ec02c869 100644
--- a/lib/win_util.h
+++ b/lib/win_util.h
@@ -28,7 +28,13 @@ extern char* windows_format_error_string(
unsigned long dwError, char* pszBuf, int iSize ...
);
-// struct for running a WSL command, connected via pipes
+// struct for running a program in a WSL, connected via pipes.
+// This can be a one-time command,
+// or a shell to which you send a sequence of commands via the pipe.
+// In the latter case:
+// - write to the input pipe to run commands from the shell
+// - the output of each command should end with 'EOM'
+// so that you know when you've read the complete output.
//
struct WSL_CMD {
HANDLE in_read = NULL;
@@ -44,11 +50,19 @@ struct WSL_CMD {
if (out_write) CloseHandle(out_write);
}
+ // Use WslLaunch() to run a shell in the WSL container
+ // The shell will run as the default user
+ //
int setup();
+ // Use wsl.exe to run a shell as root in the WSL container
+ //
+ int setup_root(const char* distro_name);
+
// run command, direct both stdout and stderr to the out pipe
+ // Use read_from_pipe() to get the output.
//
- int run_command(
+ int run_program_in_wsl(
const std::string distro_name, const std::string command,
bool use_cwd = false
);
diff --git a/lib/wslinfo.cpp b/lib/wslinfo.cpp
index 19692c5a742..bcb1474b705 100644
--- a/lib/wslinfo.cpp
+++ b/lib/wslinfo.cpp
@@ -15,8 +15,12 @@
// You should have received a copy of the GNU Lesser General Public License
// along with BOINC. If not, see .
+// write and parse WSL_DISTRO structs,
+// which describe WSL distros and their possible Docker contents
+
#include
+#include "common_defs.h"
#include "wslinfo.h"
void WSL_DISTRO::clear() {
@@ -25,8 +29,6 @@ void WSL_DISTRO::clear() {
os_version = "";
is_default = false;
wsl_version = 1;
- is_docker_available = false;
- is_docker_compose_available = false;
docker_version = "";
docker_compose_version = "";
}
@@ -42,25 +44,36 @@ void WSL_DISTRO::write_xml(MIOFILE& f) {
" %s\n"
" %s\n"
" %d\n"
- " %d\n"
- " %d\n"
- " %d\n"
- " %s\n"
- " %s\n"
- " \n",
+ " %d\n",
dn,
n,
v,
is_default ? 1 : 0,
- wsl_version,
- is_docker_available ? 1 : 0,
- is_docker_compose_available ? 1 : 0,
- docker_version.c_str(),
- docker_compose_version.c_str()
+ wsl_version
+ );
+ if (!docker_version.empty()) {
+ f.printf(
+ " %s\n"
+ " %d\n",
+ docker_version.c_str(),
+ docker_type
+ );
+ }
+ if (!docker_compose_version.empty()) {
+ f.printf(
+ " %s\n"
+ " %d\n",
+ docker_compose_version.c_str(),
+ docker_compose_type
+ );
+ }
+ f.printf(
+ " \n"
);
}
int WSL_DISTRO::parse(XML_PARSER& xp) {
+ int i;
clear();
while (!xp.get_tag()) {
if (xp.match_tag("/distro")) {
@@ -71,10 +84,16 @@ int WSL_DISTRO::parse(XML_PARSER& xp) {
if (xp.parse_string("os_version", os_version)) continue;
if (xp.parse_bool("is_default", is_default)) continue;
if (xp.parse_int("wsl_version", wsl_version)) continue;
- if (xp.parse_bool("is_docker_available", is_docker_available)) continue;
- if (xp.parse_bool("is_docker_compose_available", is_docker_compose_available)) continue;
if (xp.parse_string("docker_version", docker_version)) continue;
+ if (xp.parse_int("docker_type", i)) {
+ docker_type = (DOCKER_TYPE) i;
+ continue;
+ }
if (xp.parse_string("docker_compose_version", docker_compose_version)) continue;
+ if (xp.parse_int("docker_compose_type", i)) {
+ docker_compose_type = (DOCKER_TYPE) i;
+ continue;
+ }
}
return ERR_XML_PARSE;
}
@@ -126,3 +145,12 @@ WSL_DISTRO* WSL_DISTROS::find_match(
}
return NULL;
}
+
+WSL_DISTRO* WSL_DISTROS::find_docker() {
+ for (WSL_DISTRO &wd: distros) {
+ if (!wd.docker_version.empty()) {
+ return &wd;
+ }
+ }
+ return NULL;
+}
diff --git a/lib/wslinfo.h b/lib/wslinfo.h
index 19790bdbd67..4677aa5be1c 100644
--- a/lib/wslinfo.h
+++ b/lib/wslinfo.h
@@ -25,8 +25,10 @@
#include "miofile.h"
#include "parse.h"
+#include "common_defs.h"
-// describes a WSL (Windows Subsystem for Linux) distro
+// describes a WSL (Windows Subsystem for Linux) distro,
+// and its Docker features
//
struct WSL_DISTRO {
std::string distro_name;
@@ -39,14 +41,13 @@ struct WSL_DISTRO {
// version of WSL (currently 1 or 2)
bool is_default;
// this is the default distro
- bool is_docker_available;
- // Docker is present and allowed by config
- bool is_docker_compose_available;
- // Docker Compose is present and allowed by config
std::string docker_version;
- // version of Docker
+ // version of Docker (or podman)
+ // empty if not present
+ DOCKER_TYPE docker_type;
std::string docker_compose_version;
- // version of Docker Compose
+ // version of Docker Compose; empty if none
+ DOCKER_TYPE docker_compose_type;
WSL_DISTRO(){
clear();
@@ -68,6 +69,8 @@ struct WSL_DISTROS {
WSL_DISTRO *find_match(
const char *os_name_regexp, const char *os_version_regexp
);
+ WSL_DISTRO *find_docker();
+ // find a distro containing Docker
};
#endif
diff --git a/samples/docker_wrapper/Makefile b/samples/docker_wrapper/Makefile
new file mode 100644
index 00000000000..78e1a88d4d6
--- /dev/null
+++ b/samples/docker_wrapper/Makefile
@@ -0,0 +1,10 @@
+all: docker_wrapper
+
+CXXFLAGS = -g -I../../lib -I../../api\
+ -L../../lib -L../../api \
+ -Wformat-overflow=0
+
+docker_wrapper: docker_wrapper.cpp
+ g++ $(CXXFLAGS) docker_wrapper.cpp \
+ -lboinc_api -lboinc -lpthread \
+ -o docker_wrapper
diff --git a/samples/docker_wrapper/docker_wrapper.cpp b/samples/docker_wrapper/docker_wrapper.cpp
new file mode 100644
index 00000000000..317175b9dd4
--- /dev/null
+++ b/samples/docker_wrapper/docker_wrapper.cpp
@@ -0,0 +1,596 @@
+// docker_wrapper: runs a BOINC job in a Docker container
+//
+// runs in a directory (normally slot dir) containing
+//
+// Dockerfile
+// job.toml
+// optional job config file
+// files added to container via Dockerfile
+// other input files
+//
+// For now all files must be
+//
+// Win:
+// There must be a WSL image containing the Docker engine
+// e.g. an Ubuntu image downloaded from the Windows app store.
+// This image can access the host (Win) filesystem.
+// The wrapper runs a pipe-connected shell in WSL
+// (running in the current dir)
+// and sends commands (e.g. docker commands) via the pipe.
+//
+// Unix:
+// The host must have the Docker engine.
+// The wrapper runs Docker commands using popen()
+//
+// Logic:
+// If the container already exists
+// this is a restart of the job
+// start the container if it's stopped
+// else
+// this is the first run of the job
+// if the image doesn't already exist
+// build image with 'docker build'
+// (need a log around the above?)
+// create the container with -v to mount slot, project dirs
+// copy input files as needed
+// start container
+// loop: handle msgs from client, check for container exit
+// on successful exit
+// copy output files as needed
+
+// Names:
+// image name
+// name: lower case letters, digits, separators (. _ -); max 4096 chars
+// tag: max 128 chars
+// in the universal model, each WU has a different image
+// so we'll use: boinc____
+//
+// container name:
+// letters, numbers, _
+// max 255 chars
+// we'll use: boinc____
+
+// standalone mode:
+// image name: boinc
+// container name: boinc
+// slot dir: .
+// project dir: project/
+
+// enable standalone tests on Win
+//
+#define WIN_STANDALONE_COPY 0
+#define WIN_STANDALONE_MOUNT 0
+
+#include
+#include
+#include
+
+#include "toml.h"
+ // from https://github.com/mayah/tinytoml
+
+#include "util.h"
+#include "boinc_api.h"
+
+#ifdef _WIN32
+#include "win_util.h"
+#endif
+
+using std::string;
+using std::vector;
+
+#define POLL_PERIOD 1.0
+
+enum JOB_STATUS {JOB_IN_PROGRESS, JOB_SUCCESS, JOB_FAIL};
+
+struct RSC_USAGE {
+ double cpu_time;
+ double wss;
+ void clear() {
+ cpu_time = 0;
+ wss = 0;
+ }
+};
+
+struct FILE_COPY {
+ string src;
+ string dst;
+};
+
+// parsed version of job.toml
+//
+struct CONFIG {
+ string slot_dir_mount;
+ // mount slot dir here
+ string project_dir_mount;
+ // mount project dir here
+ vector copy_to_container;
+ vector copy_from_container;
+ void print() {
+ fprintf(stderr, "Wrapper config file:\n");
+ if (!slot_dir_mount.empty()) {
+ fprintf(stderr, " slot dir mounted at: %s\n", slot_dir_mount.c_str());
+ }
+ if (!project_dir_mount.empty()) {
+ fprintf(stderr, " project dir mounted at: %s\n", project_dir_mount.c_str());
+ }
+ for (FILE_COPY c:copy_to_container) {
+ fprintf(stderr, " copy to:src %s dst %s\n", c.src.c_str(), c.dst.c_str());
+ }
+ for (FILE_COPY c:copy_from_container) {
+ fprintf(stderr, " copy from: src %s dst %s\n", c.src.c_str(), c.dst.c_str());
+ }
+ }
+};
+
+char image_name[512];
+char container_name[512];
+APP_INIT_DATA aid;
+CONFIG config;
+bool running;
+bool verbose = true;
+const char* config_file = "job.toml";
+const char* dockerfile = "Dockerfile";
+const char* cli_prog;
+
+#ifdef _WIN32
+WSL_CMD ctl_wc;
+#endif
+
+// parse a list of file copy specs
+//
+int parse_config_copies(const toml::Value *x, vector &copies) {
+ const toml::Array& ar = x->as();
+ for (const toml::Value& a : ar) {
+ FILE_COPY copy;
+ const toml::Value *b = a.find("src");
+ if (!b) return -1;
+ copy.src = b->as();
+ const toml::Value *c = a.find("dst");
+ if (!c) return -1;
+ copy.dst = c->as();
+ copies.push_back(copy);
+ }
+ return 0;
+}
+
+// parse job config file
+//
+int parse_config_file() {
+ int retval;
+ std::ifstream ifs(config_file);
+ if (ifs.fail()) {
+ return -1;
+ }
+ toml::ParseResult r = toml::parse(ifs);
+ if (!r.valid()) {
+ fprintf(stderr, "TOML error: %s\n", r.errorReason.c_str());
+ return 1;
+ }
+ const toml::Value &v = r.value;
+ const toml::Value *x;
+ x = v.find("slot_dir_mount");
+ if (x) {
+ config.slot_dir_mount = x->as();
+ }
+ x = v.find("project_dir_mount");
+ if (x) {
+ config.project_dir_mount = x->as();
+ }
+ x = v.find("copy_to_container");
+ if (x) {
+ retval = parse_config_copies(x, config.copy_to_container);
+ if (retval) return retval;
+ }
+ x = v.find("copy_from_container");
+ if (x) {
+ retval = parse_config_copies(x, config.copy_from_container);
+ if (retval) return retval;
+ }
+
+ return 0;
+}
+
+// See if command output includes "Error"
+//
+int error_output(vector &out) {
+ for (string line: out) {
+ if (strstr(line.c_str(), "Error")) {
+ return -1;
+ }
+ }
+ return 0;
+}
+
+inline int run_docker_command(char* cmd, vector &out) {
+ int retval;
+ if (verbose) {
+ fprintf(stderr, "running docker command: %s\n", cmd);
+ }
+#ifdef _WIN32
+ // Win: run the command in the WSL container
+
+ char buf[1024];
+ string output;
+
+ sprintf(buf, "%s; echo EOM\n", cmd);
+ write_to_pipe(ctl_wc.in_write, buf);
+ retval = read_from_pipe(
+ ctl_wc.out_read, ctl_wc.proc_handle, output, TIMEOUT, "EOM"
+ );
+ if (retval) return retval;
+ out = split(output, '\n');
+#else
+ retval = run_command(cmd, out);
+ if (retval) return retval;
+#endif
+ if (verbose) {
+ fprintf(stderr, "output:\n");
+ for (string line: out) {
+ fprintf(stderr, "%s\n", line.c_str());
+ }
+ }
+ return 0;
+}
+
+////////// IMAGE ////////////
+
+void get_image_name() {
+ char *p = strrchr(aid.project_dir, '/');
+ sprintf(image_name, "boinc__%s__%s",
+ p+1, aid.wu_name
+ );
+}
+
+int image_exists(bool &exists) {
+ char cmd[256];
+ vector out;
+
+ sprintf(cmd, "%s images", cli_prog);
+ int retval = run_docker_command(cmd, out);
+ if (retval) return retval;
+ for (string line: out) {
+ if (line.find(image_name) != string::npos) {
+ exists = true;
+ return 0;
+ }
+ }
+ exists = false;
+ return 0;
+}
+
+int build_image() {
+ char cmd[256];
+ vector out;
+ sprintf(cmd, "%s build . -t %s -f %s", cli_prog, image_name, dockerfile);
+ int retval = run_docker_command(cmd, out);
+ if (retval) return retval;
+ return 0;
+}
+
+// build image if needed
+//
+int get_image() {
+ bool exists;
+ int retval;
+
+ retval = image_exists(exists);
+ if (retval) {
+ fprintf(stderr, "image_exists() failed: %d\n", retval);
+ exit(1);
+ }
+ if (!exists) {
+ if (verbose) fprintf(stderr, "building image\n");
+ retval = build_image();
+ if (retval) {
+ fprintf(stderr, "build_image() failed: %d\n", retval);
+ exit(1);
+ }
+ }
+ return 0;
+}
+
+////////// CONTAINER ////////////
+
+void get_container_name() {
+ char *p = strrchr(aid.project_dir, '/');
+ sprintf(container_name, "boinc__%s__%s", p+1, aid.result_name);
+}
+
+int container_exists(bool &exists) {
+ char cmd[1024];
+ int retval;
+ vector out;
+
+ sprintf(cmd, "%s ps --filter \"name=%s\"",
+ cli_prog, container_name
+ );
+ retval = run_docker_command(cmd, out);
+ if (retval) return retval;
+ for (string line: out) {
+ if (strstr(line.c_str(), container_name)) {
+ exists = true;
+ return 0;
+ }
+ }
+ exists = false;
+ return 0;
+}
+
+int create_container() {
+ char cmd[1024];
+ char slot_cmd[256], project_cmd[256];
+ vector out;
+ int retval;
+
+ retval = get_image();
+ if (retval) return retval;
+
+ if (config.slot_dir_mount.empty()) {
+ slot_cmd[0] = 0;
+ } else {
+ sprintf(slot_cmd, " -v .:%s",
+ config.slot_dir_mount.c_str()
+ );
+ }
+ if (config.project_dir_mount.empty()) {
+ project_cmd[0] = 0;
+ } else {
+ sprintf(project_cmd, " -v %s:%s",
+ aid.project_dir, config.project_dir_mount.c_str()
+ );
+ }
+ sprintf(cmd, "%s create --name %s %s %s %s",
+ cli_prog,
+ container_name,
+ slot_cmd, project_cmd,
+ image_name
+ );
+ retval = run_docker_command(cmd, out);
+ if (retval) return retval;
+ if (error_output(out)) return -1;
+
+ // copy files into container
+ //
+ for (FILE_COPY &c: config.copy_to_container) {
+ sprintf(cmd, "%s cp %s %s:%s",
+ cli_prog,
+ c.src.c_str(), container_name, c.dst.c_str()
+ );
+ retval = run_docker_command(cmd, out);
+ if (retval) return retval;
+ if (error_output(out)) return -1;
+ }
+ return 0;
+}
+
+int copy_files_from_container() {
+ char cmd[1024];
+ int retval;
+ vector out;
+
+ for (FILE_COPY &c: config.copy_from_container) {
+ sprintf(cmd, "%s cp %s:%s %s",
+ cli_prog,
+ container_name, c.src.c_str(), c.dst.c_str()
+ );
+ retval = run_docker_command(cmd, out);
+ if (retval) return retval;
+ }
+ return 0;
+}
+
+////////// JOB CONTROL ////////////
+
+int container_op(const char *op) {
+ char cmd[1024];
+ vector out;
+ sprintf(cmd, "%s %s %s", cli_prog, op, container_name);
+ int retval = run_docker_command(cmd, out);
+ return retval;
+}
+
+// Clean up at end of job.
+// Show log output if verbose;
+// remove container and image
+//
+void cleanup() {
+ char cmd[1024];
+ vector out;
+
+ if (verbose) {
+ sprintf(cmd, "%s logs %s", cli_prog, container_name);
+ run_docker_command(cmd, out);
+ fprintf(stderr, "container log:\n");
+ for (string line : out) {
+ fprintf(stderr, " %s\n", line.c_str());
+ }
+ }
+
+ container_op("stop");
+
+ sprintf(cmd, "%s container rm %s", cli_prog, container_name);
+ run_docker_command(cmd, out);
+
+ sprintf(cmd, "%s image rm %s", cli_prog, image_name);
+ run_docker_command(cmd, out);
+}
+
+void poll_client_msgs() {
+ BOINC_STATUS status;
+ boinc_get_status(&status);
+ if (status.no_heartbeat || status.quit_request || status.abort_request) {
+ fprintf(stderr, "got quit/abort from client\n");
+ container_op("stop");
+ exit(0);
+ }
+ if (status.suspended) {
+ if (verbose) {
+ fprintf(stderr, "client: suspended\n");
+ }
+ if (running) {
+ container_op("pause");
+ running = false;
+ }
+ } else {
+ if (verbose) {
+ fprintf(stderr, "client: not suspended\n");
+ }
+ if (!running) {
+ container_op("unpause");
+ running = true;
+ }
+ }
+}
+
+JOB_STATUS poll_app(RSC_USAGE &ru) {
+ char cmd[1024];
+ vector out;
+ int retval;
+
+ sprintf(cmd, "%s ps --all -f \"name=%s\"", cli_prog, container_name);
+ retval = run_docker_command(cmd, out);
+ if (retval) return JOB_FAIL;
+ for (string line: out) {
+ if (strstr(line.c_str(), container_name)) {
+ if (strstr(line.c_str(), "Exited")) {
+ return JOB_SUCCESS;
+ }
+ return JOB_IN_PROGRESS;
+ }
+ }
+ return JOB_FAIL;
+}
+
+#ifdef _WIN32
+// find a WSL distro with Docker and set up a command link to it
+//
+int wsl_init() {
+ string distro_name;
+ DOCKER_TYPE docker_type;
+ if (boinc_is_standalone()) {
+ distro_name = "Ubuntu";
+ docker_type = PODMAN;
+ } else {
+ WSL_DISTRO* distro = aid.host_info.wsl_distros.find_docker();
+ if (!distro) return -1;
+ distro_name = distro->distro_name;
+ docker_type = distro->docker_type;
+ }
+ cli_prog = docker_cli_prog(docker_type);
+ if (docker_type == DOCKER) {
+ int retval = ctl_wc.setup();
+ if (retval) return retval;
+ retval = ctl_wc.run_program_in_wsl(distro_name, "", true);
+ if (retval) return retval;
+ } else if (docker_type == PODMAN) {
+ int retval = ctl_wc.setup_root(distro_name.c_str());
+ if (retval) return retval;
+ } else {
+ return -1;
+ }
+ return 0;
+}
+#endif
+
+int main(int argc, char** argv) {
+ BOINC_OPTIONS options;
+ int retval;
+ bool sporadic = false, exists;
+ RSC_USAGE ru;
+
+ for (int j=1; j../../projects/foobar/in
diff --git a/samples/docker_wrapper/test_mount/job_mount.toml b/samples/docker_wrapper/test_mount/job_mount.toml
new file mode 100644
index 00000000000..c7ab43ed4c1
--- /dev/null
+++ b/samples/docker_wrapper/test_mount/job_mount.toml
@@ -0,0 +1,2 @@
+slot_dir_mount = "/app/slot"
+project_dir_mount = "/app/project"
diff --git a/samples/docker_wrapper/test_mount/main.sh b/samples/docker_wrapper/test_mount/main.sh
new file mode 100755
index 00000000000..360d35e47e7
--- /dev/null
+++ b/samples/docker_wrapper/test_mount/main.sh
@@ -0,0 +1,7 @@
+#! /bin/bash
+
+resolve () {
+ sed 's/..\/..\/projects\/[^\/]*\//project\//; s/<\/soft_link>//' $1 | tr -d '\r\n'
+}
+
+$(resolve slot/worker) --nsecs 1 $(resolve slot/in) slot/out
diff --git a/samples/docker_wrapper/test_mount/worker b/samples/docker_wrapper/test_mount/worker
new file mode 100644
index 00000000000..0299794411b
--- /dev/null
+++ b/samples/docker_wrapper/test_mount/worker
@@ -0,0 +1 @@
+../../projects/test/worker
diff --git a/samples/docker_wrapper/toml.h b/samples/docker_wrapper/toml.h
new file mode 100644
index 00000000000..6b0c6e0488a
--- /dev/null
+++ b/samples/docker_wrapper/toml.h
@@ -0,0 +1,2049 @@
+#ifndef TINYTOML_H_
+#define TINYTOML_H_
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include