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 +#include +#include +#include + +namespace toml { + +// ---------------------------------------------------------------------- +// Declarations + +class Value; +typedef std::chrono::system_clock::time_point Time; +typedef std::vector Array; +typedef std::map Table; + +namespace internal { +template struct call_traits_value { + typedef T return_type; +}; +template struct call_traits_ref { + typedef const T& return_type; +}; +} // namespace internal + +template struct call_traits; +template<> struct call_traits : public internal::call_traits_value {}; +template<> struct call_traits : public internal::call_traits_value {}; +template<> struct call_traits : public internal::call_traits_value {}; +template<> struct call_traits : public internal::call_traits_value {}; +template<> struct call_traits : public internal::call_traits_ref {}; +template<> struct call_traits