From 7dd0ea97b58ecfb6c08ddaea9d631fd62eb78335 Mon Sep 17 00:00:00 2001 From: Savinda Senevirathne Date: Fri, 25 Jun 2021 14:16:22 +0530 Subject: [PATCH] Instance users and dockerd. (#18) --- CMakeLists.txt | 11 + README.md | 6 +- bootstrap-contract/hotpocket_contract.h | 5 +- src/conf.cpp | 23 +- src/conf.hpp | 4 +- src/hp_manager.cpp | 341 +++++++++++++++++++----- src/hp_manager.hpp | 24 +- src/hpfs_manager.cpp | 158 +++++++---- src/hpfs_manager.hpp | 5 +- src/msg/json/msg_json.cpp | 7 +- src/pchheader.hpp | 4 + src/sqlite.cpp | 58 +++- src/sqlite.hpp | 2 + src/util/util.cpp | 100 ++++++- src/util/util.hpp | 18 ++ 15 files changed, 607 insertions(+), 159 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 599e1da..ad6393f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,10 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY build) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-result -Wreturn-type") +add_executable(bootstrap_contract + bootstrap-contract/bootstrap_contract.cpp +) + add_executable(sagent src/conf.cpp src/comm/comm_handler.cpp @@ -35,11 +39,18 @@ target_link_libraries(sagent ${CMAKE_DL_LIBS} # Needed for stacktrace support ) +add_dependencies(sagent + bootstrap_contract +) + add_custom_command(TARGET sagent POST_BUILD COMMAND cp ./dependencies/bin/hpws ./build/ COMMAND cp ./dependencies/bin/hpfs ./build/ COMMAND cp -r ./dependencies/default_contract ./build/ COMMAND cp ./bootstrap-contract/script.sh ./build/default_contract/contract_fs/seed/state/script.sh + COMMAND cp ./build/bootstrap_contract ./build/default_contract/contract_fs/seed/state/bootstrap_contract + COMMAND cp ./installer/user-install.sh ./build/user-install.sh + COMMAND cp ./installer/user-uninstall.sh ./build/user-uninstall.sh ) target_precompile_headers(sagent PUBLIC src/pchheader.hpp) diff --git a/README.md b/README.md index dd3a3d6..fab61cc 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ A C++ version of sashimono agent * Boost Stacktrace - https://www.boost.org ## Setting up Sashimono Agent environment -- Place a hotpocket contract named **default_contract** (Copies of this contract will be made when new instances are created) inside the **dependencies** directory. This should be a new contract (Created by `hpcore new`) which has configured binaries in hp.cfg. In the future this will be placed by the installation process. +- Place a hotpocket contract named **default_contract** (Copies of this contract will be made when new instances are created) inside the **dependencies** directory. This should be a new contract (Created by `hpcore new`). In the future this will be placed by the installation process. - Run the setup script located at the repo root (tested on Ubuntu 18.04). ``` @@ -25,6 +25,10 @@ A C++ version of sashimono agent 1. Run `cmake .` (You only have to do this once) 1. Run `make` (Sashimono agent binary will be created as `./build/sagent`) +## Sashimono installation script. +1. Run `sashimono-install.sh` script as root user to setup sashimono environment. +1. Run `sashimono-uninstall.sh` script as root user to do sashimono cleanups. + ## Sashimono Client - Replace the sashimono-client.key file created inside dataDir in the first run by the key file found on this [link](https://geveoau.sharepoint.com/:u:/g/EX5U8SxYyM5Anyq2rAcMXtkBEOO_XWT7hCo30SGIsDAyLg?e=LycwQx). This is because we have hardcoded the pubkey in message board. This will generate the same pubkey we have hardcoded. - A sample **bundle.zip** bundle can be found [here](https://geveoau.sharepoint.com/:u:/g/EdurCbuttzdCnuQCyIb0SKEBWq4j9LKdgAIjJvt3zwueew?e=lPYfMG). diff --git a/bootstrap-contract/hotpocket_contract.h b/bootstrap-contract/hotpocket_contract.h index 3aa4d5a..74dcbd7 100644 --- a/bootstrap-contract/hotpocket_contract.h +++ b/bootstrap-contract/hotpocket_contract.h @@ -278,6 +278,7 @@ int hp_deinit_contract() // Send termination control message. __hp_write_control_msg("{\"type\":\"contract_end\"}", 23); close(__hpc.control_fd); + return 0; } const struct hp_contract_context *hp_get_context() @@ -547,7 +548,7 @@ int hp_update_config(const struct hp_config *config) */ void hp_set_config_string(char **config_str, const char *value, const size_t value_size) { - *config_str = (char*)realloc(*config_str, value_size); + *config_str = (char *)realloc(*config_str, value_size); strncpy(*config_str, value, value_size); } @@ -560,7 +561,7 @@ void hp_set_config_string(char **config_str, const char *value, const size_t val void hp_set_config_unl(struct hp_config *config, const struct hp_unl_node *new_unl, const size_t new_unl_count) { const size_t mem_size = sizeof(struct hp_unl_node) * new_unl_count; - config->unl.list = (hp_unl_node*)realloc(config->unl.list, mem_size); + config->unl.list = (struct hp_unl_node *)realloc(config->unl.list, mem_size); memcpy(config->unl.list, new_unl, mem_size); config->unl.count = new_unl_count; } diff --git a/src/conf.cpp b/src/conf.cpp index 7013ec4..4d2ed93 100644 --- a/src/conf.cpp +++ b/src/conf.cpp @@ -106,6 +106,8 @@ namespace conf ctx.exe_dir = dirname(exepath.data()); ctx.hpws_exe_path = ctx.exe_dir + "/hpws"; ctx.hpfs_exe_path = ctx.exe_dir + "/hpfs"; + ctx.user_install_sh = ctx.exe_dir + "/user-install.sh"; + ctx.user_uninstall_sh = ctx.exe_dir + "/user-uninstall.sh"; ctx.default_contract_path = ctx.exe_dir + "/default_contract"; ctx.config_dir = ctx.exe_dir + "/cfg"; ctx.config_file = ctx.config_dir + "/sa.cfg"; @@ -119,12 +121,14 @@ namespace conf */ int validate_dir_paths() { - const std::string paths[5] = { + const std::string paths[7] = { ctx.config_file, ctx.log_dir, ctx.data_dir, ctx.hpws_exe_path, - ctx.default_contract_path}; + ctx.default_contract_path, + ctx.user_install_sh, + ctx.user_uninstall_sh}; for (const std::string &path : paths) { @@ -198,19 +202,7 @@ namespace conf try { const jsoncons::ojson &hp = d["hp"]; - // Check whether the instance_folder is specified. - cfg.hp.instance_folder = hp["instance_folder"].as(); - if (cfg.hp.instance_folder.empty()) - { - std::cerr << "Hp instance folder path is missing.\n"; - return -1; - } - else if (!util::is_dir_exists(cfg.hp.instance_folder)) - { - std::cerr << "Hp instance folder does not exist.\n"; - return -1; - } - + cfg.hp.init_peer_port = hp["init_peer_port"].as(); if (cfg.hp.init_peer_port <= 1024) { @@ -322,7 +314,6 @@ namespace conf // Hp configs. { jsoncons::ojson hp_config; - hp_config.insert_or_assign("instance_folder", cfg.hp.instance_folder); hp_config.insert_or_assign("init_peer_port", cfg.hp.init_peer_port); hp_config.insert_or_assign("init_user_port", cfg.hp.init_user_port); diff --git a/src/conf.hpp b/src/conf.hpp index 0e6f4e0..f12a2ed 100644 --- a/src/conf.hpp +++ b/src/conf.hpp @@ -56,7 +56,6 @@ namespace conf struct hp_config { - std::string instance_folder; uint16_t init_peer_port; uint16_t init_user_port; }; @@ -86,6 +85,9 @@ namespace conf std::string hpfs_exe_path; // hpfs executable file path. std::string default_contract_path; // Path to default contract. + std::string user_install_sh; + std::string user_uninstall_sh; + std::string config_dir; // Config dir full path. std::string config_file; // Full path to the config file. std::string log_dir; // Log directory full path. diff --git a/src/hp_manager.cpp b/src/hp_manager.cpp index 4d5ddad..35bb614 100644 --- a/src/hp_manager.cpp +++ b/src/hp_manager.cpp @@ -25,6 +25,18 @@ namespace hp std::thread hp_monitor_thread; bool is_shutting_down = false; + // We instruct the demon to restart the container automatically once the container exits except manually stopping. + constexpr const char *DOCKER_RUN = "DOCKER_HOST=unix:///run/user/$(id -u %s)/docker.sock /usr/bin/sashimono-agent/dockerbin/docker run -t -i -d --stop-signal=SIGINT --name=%s -p %s:%s -p %s:%s \ + --restart unless-stopped --mount type=bind,source=%s,target=/contract hotpocketdev/hotpocket:ubt.20.04 run /contract"; + constexpr const char *DOCKER_START = "DOCKER_HOST=unix:///run/user/$(id -u %s)/docker.sock /usr/bin/sashimono-agent/dockerbin/docker start %s"; + constexpr const char *DOCKER_STOP = "DOCKER_HOST=unix:///run/user/$(id -u %s)/docker.sock /usr/bin/sashimono-agent/dockerbin/docker stop %s"; + constexpr const char *DOCKER_REMOVE = "DOCKER_HOST=unix:///run/user/$(id -u %s)/docker.sock /usr/bin/sashimono-agent/dockerbin/docker rm -f %s"; + constexpr const char *DOCKER_STATUS = "DOCKER_HOST=unix:///run/user/$(id -u %s)/docker.sock /usr/bin/sashimono-agent/dockerbin/docker inspect --format='{{json .State.Status}}' %s"; + constexpr const char *COPY_DIR = "cp -r %s %s"; + constexpr const char *MOVE_DIR = "mv %s %s"; + constexpr const char *CHOWN_DIR = "chown -R %s:%s %s"; + constexpr const char *RUN_SH = "chmod +x %s && sudo bash %s %s"; // Enable execute permission before running in case bash script does not have the permission. + /** * Initialize hp related environment. */ @@ -72,7 +84,7 @@ namespace hp void hp_monitor_loop() { LOG_INFO << "HP instance monitor started."; - std::vector running_instance_names; + std::vector> running_instances; util::mask_signal(); @@ -84,14 +96,14 @@ namespace hp // time until the app closes in a SIGINT. if (counter == 0 || counter == 600) { - sqlite::get_running_instance_names(db, running_instance_names); - for (const auto &name : running_instance_names) + sqlite::get_running_instance_user_and_name_list(db, running_instances); + for (const auto &[username, name] : running_instances) { std::string status; - const int res = check_instance_status(name, status); + const int res = check_instance_status(username, name, status); if (res == 0 && status != CONTAINER_STATES[STATES::RUNNING]) { - if (docker_start(name) == -1) + if (docker_start(username, name) == -1) { // We only change the current status variable from the monitor loop. // We try to start this container in next iteration as well untill the desired state is achieved. @@ -142,16 +154,33 @@ namespace hp instance_ports = {(uint16_t)(last_assigned_ports.peer_port + 1), (uint16_t)(last_assigned_ports.user_port + 1)}; } - const std::string name = crypto::generate_uuid(); // This will be the docker container name as well as the contract folder name. + int user_id; + std::string username; + if (install_user(user_id, username) == -1) + return -1; + + const std::string container_name = crypto::generate_uuid(); // This will be the docker container name as well as the contract folder name. + const std::string contract_dir = util::get_user_contract_dir(username, container_name); + std::string hpfs_log_level; bool is_full_history; - if (create_contract(info, name, owner_pubkey, instance_ports) != 0 || - read_contract_cfg_values(name, hpfs_log_level, is_full_history) == -1 || - hpfs::start_fs_processes(name, hpfs_log_level, is_full_history) == -1 || - run_container(name, instance_ports) != 0 || // Gives 3200 if docker failed. + if (create_contract(username, contract_dir, owner_pubkey, instance_ports, info) == -1 || + read_contract_cfg_values(contract_dir, hpfs_log_level, is_full_history) == -1 || + hpfs::start_fs_processes(username, contract_dir, hpfs_log_level, is_full_history) == -1) + { + LOG_ERROR << errno << ": Error creating hp instance for " << owner_pubkey; + // Remove user if instance creation failed. + uninstall_user(username); + return -1; + } + + if (run_container(username, container_name, contract_dir, instance_ports, info) == -1 || sqlite::insert_hp_instance_row(db, info) == -1) { - LOG_ERROR << errno << ": Error creating and running new hp instance for " << owner_pubkey; + LOG_ERROR << errno << ": Error running new hp instance for " << owner_pubkey; + // Stop started hpfs processes and remove user if running instance failed. + hpfs::stop_fs_processes(username); + uninstall_user(username); return -1; } @@ -165,24 +194,28 @@ namespace hp /** * Runs a hotpocket docker image on the given contract and the ports. - * @param folder_name Contract directory folder name. + * @param username Username of the instance user. + * @param container_name Name of the container. + * @param contract_dir Directory for the contract. * @param assigned_ports Assigned ports to the container. * @return 0 on success execution or relavent error code on error. */ - int run_container(const std::string &folder_name, const ports &assigned_ports) + int run_container(std::string_view username, std::string_view container_name, std::string_view contract_dir, const ports &assigned_ports, instance_info &info) { - // We instruct the demon to restart the container automatically once the container exits except manually stopping. - const std::string command = "docker run -t -i -d --stop-signal=SIGINT --name=" + folder_name + " \ - -p " + - std::to_string(assigned_ports.user_port) + ":" + std::to_string(assigned_ports.user_port) + " \ - -p " + - std::to_string(assigned_ports.peer_port) + ":" + std::to_string(assigned_ports.peer_port) + " \ - --restart unless-stopped --mount type=bind,source=" + - conf::cfg.hp.instance_folder + "/" + - folder_name + ",target=/contract \ - hpcore:latest run /contract"; + const std::string user_port = std::to_string(assigned_ports.user_port); + const std::string peer_port = std::to_string(assigned_ports.peer_port); + const int len = 303 + username.length() + container_name.length() + (user_port.length() * 2) + (peer_port.length() * 2) + contract_dir.length(); + char command[len]; + sprintf(command, DOCKER_RUN, username.data(), container_name.data(), user_port.data(), user_port.data(), peer_port.data(), peer_port.data(), contract_dir.data()); + if (system(command) != 0) + { + LOG_ERROR << "Error when running container. name: " << container_name; + return -1; + } - return system(command.c_str()); + info.container_name = container_name; + info.contract_dir = contract_dir; + return 0; } /** @@ -190,7 +223,7 @@ namespace hp * @param container_name Name of the container. * @return 0 on success execution or relavent error code on error. */ - int stop_container(const std::string &container_name) + int stop_container(std::string_view container_name) { instance_info info; const int res = sqlite::is_container_exists(db, container_name, info); @@ -204,8 +237,10 @@ namespace hp LOG_ERROR << "Given container is not running. name: " << container_name; return -1; } - const std::string command = "docker stop " + container_name; - if (system(command.c_str()) != 0 || sqlite::update_status_in_container(db, container_name, CONTAINER_STATES[STATES::STOPPED]) == -1) + + if (docker_stop(info.username, container_name) == -1 || + sqlite::update_status_in_container(db, container_name, CONTAINER_STATES[STATES::STOPPED]) == -1 || + hpfs::stop_fs_processes(info.username) == -1) { LOG_ERROR << "Error when stopping container. name: " << container_name; return -1; @@ -219,7 +254,7 @@ namespace hp * @param container_name Name of the container. * @return 0 on success execution or relavent error code on error. */ - int start_container(const std::string &container_name) + int start_container(std::string_view container_name) { instance_info info; const int res = sqlite::is_container_exists(db, container_name, info); @@ -236,12 +271,28 @@ namespace hp std::string hpfs_log_level; bool is_full_history; - if (read_contract_cfg_values(container_name, hpfs_log_level, is_full_history) == -1 || - hpfs::start_fs_processes(container_name, hpfs_log_level, is_full_history) == -1 || - docker_start(container_name) != 0 || - sqlite::update_status_in_container(db, container_name, CONTAINER_STATES[STATES::RUNNING]) == -1) + const std::string contract_dir = util::get_user_contract_dir(info.username, container_name); + if (read_contract_cfg_values(contract_dir, hpfs_log_level, is_full_history) == -1 || + hpfs::start_fs_processes(info.username, contract_dir, hpfs_log_level, is_full_history) == -1) + { + LOG_ERROR << "Error when setting up container. name: " << container_name; + return -1; + } + + if (docker_start(info.username, container_name) == -1) { LOG_ERROR << "Error when starting container. name: " << container_name; + // Stop started hpfs processes if starting instance failed. + hpfs::stop_fs_processes(info.username); + return -1; + } + + if (sqlite::update_status_in_container(db, container_name, CONTAINER_STATES[STATES::RUNNING]) == -1) + { + LOG_ERROR << "Error when starting container. name: " << container_name; + // Stop started docker and hpfs processes if database update fails. + docker_stop(info.username, container_name); + hpfs::stop_fs_processes(info.username); return -1; } @@ -250,14 +301,31 @@ namespace hp /** * Execute docker start command. + * @param username Username of the instance user. * @param container_name Name of the container. * @return 0 on successful execution and -1 on error. */ - int docker_start(const std::string &container_name) + int docker_start(std::string_view username, std::string_view container_name) { - const std::string command = "docker start " + container_name; - const int res = system(command.c_str()); - return res == 0 ? 0 : -1; + const int len = 100 + username.length() + container_name.length(); + char command[len]; + sprintf(command, DOCKER_START, username.data(), container_name.data()); + return system(command) == 0 ? 0 : -1; + } + + /** + * Execute docker stop command. + * @param username Username of the instance user. + * @param container_name Name of the container. + * @return 0 on successful execution and -1 on error. + */ + int docker_stop(std::string_view username, std::string_view container_name) + { + + const int len = 99 + username.length() + container_name.length(); + char command[len]; + sprintf(command, DOCKER_STOP, username.data(), container_name.data()); + return system(command) == 0 ? 0 : -1; } /** @@ -265,7 +333,7 @@ namespace hp * @param container_name Name of the container. * @return 0 on success execution or relavent error code on error. */ - int destroy_container(const std::string &container_name) + int destroy_container(std::string_view container_name) { instance_info info; const int res = sqlite::is_container_exists(db, container_name, info); @@ -274,12 +342,14 @@ namespace hp LOG_ERROR << "Given container not found. name: " << container_name; return -1; } - const std::string command = "docker container rm -f " + container_name; - const std::string folder_path = conf::cfg.hp.instance_folder + "/" + container_name; - if (system(command.c_str()) != 0 || + const int len = 100 + info.username.length() + container_name.length(); + char command[len]; + sprintf(command, DOCKER_REMOVE, info.username.data(), container_name.data()); + + if (system(command) != 0 || sqlite::update_status_in_container(db, container_name, CONTAINER_STATES[STATES::DESTROYED]) == -1 || - util::remove_directory_recursively(folder_path) == -1) + hpfs::stop_fs_processes(info.username) == -1) { LOG_ERROR << errno << ": Error destroying container " << container_name; return -1; @@ -287,30 +357,49 @@ namespace hp // Add the port pair of the destroyed container to the vacant port vector. if (std::find(vacant_ports.begin(), vacant_ports.end(), info.assigned_ports) == vacant_ports.end()) vacant_ports.push_back(info.assigned_ports); + + // Remove user after destroying. + if (uninstall_user(info.username) == -1) + return -1; + return 0; } /** * Creates a copy of default contract with the given name and the ports in the instance folder given in the config file. - * @param info Information of the created contract instance. - * @param folder_name Folder name for the contract directory. + * @param username Name of the instance user. + * @param contract_dir Directory of the contract. * @param owner_pubkey Public key of the owner of the instance. * @param assigned_ports Assigned ports to the instance. + * @param info Information of the created contract instance. * @return -1 on error and 0 on success. * */ - int create_contract(instance_info &info, const std::string &folder_name, std::string_view owner_pubkey, const ports &assigned_ports) + int create_contract(std::string_view username, std::string_view contract_dir, std::string_view owner_pubkey, const ports &assigned_ports, instance_info &info) { - const std::string folder_path = conf::cfg.hp.instance_folder + "/" + folder_name; - const std::string command = "cp -r " + conf::ctx.default_contract_path + " " + folder_path; - if (system(command.c_str()) != 0) + // Creating a temporary directory to do the config manipulations before moved to the contract dir. + // Folders inside /tmp directory will be cleaned after a reboot. So this will self cleanup folders + // that might be remaining due to another error in the workflow. + char templ[17] = "/tmp/sashiXXXXXX"; + char *temp_foldername = mkdtemp(templ); + if (temp_foldername == NULL) { - LOG_ERROR << "Default contract copying failed to " << folder_path; + LOG_ERROR << errno << ": Error creating temporary directory to create contract folder."; + return -1; + } + const std::string source_path = conf::ctx.default_contract_path + "/*"; + int len = 25 + source_path.length(); + char cp_command[len]; + sprintf(cp_command, COPY_DIR, source_path.data(), temp_foldername); + if (system(cp_command) != 0) + { + LOG_ERROR << "Default contract copying failed to " << temp_foldername; return -1; } // Read the config file into json document object. - const std::string config_file_path = folder_path + "/cfg/hp.cfg"; + std::string config_file_path(temp_foldername); + config_file_path.append("/cfg/hp.cfg"); const int config_fd = open(config_file_path.data(), O_RDWR, FILE_PERMS); if (config_fd == -1) { @@ -359,6 +448,7 @@ namespace hp jsoncons::ojson unl(jsoncons::json_array_arg); unl.push_back(util::to_hex(pubkey)); d["contract"]["unl"] = unl; + d["contract"]["bin_path"] = "bootstrap_contract"; d["contract"]["bin_args"] = owner_pubkey; d["mesh"]["port"] = assigned_ports.peer_port; d["user"]["port"] = assigned_ports.user_port; @@ -372,10 +462,31 @@ namespace hp } close(config_fd); + // Move the contract to contract dir + len = 22 + contract_dir.length(); + char mv_command[len]; + sprintf(mv_command, MOVE_DIR, temp_foldername, contract_dir.data()); + if (system(mv_command) != 0) + { + LOG_ERROR << "Default contract moving failed to " << contract_dir; + return -1; + } + + // Transfer ownership to the instance user. + len = 12 + (username.length() * 2) + contract_dir.length(); + char own_command[len]; + sprintf(own_command, CHOWN_DIR, username.data(), username.data(), contract_dir.data()); + if (system(own_command) != 0) + { + LOG_ERROR << "Changing contract ownership failed " << contract_dir; + return -1; + } + info.owner_pubkey = owner_pubkey; + info.username = username; + info.contract_dir = contract_dir; info.ip = "localhost"; info.contract_id = contract_id; - info.name = folder_name; info.pubkey = pubkey_hex; info.assigned_ports = assigned_ports; info.status = CONTAINER_STATES[STATES::RUNNING]; @@ -418,19 +529,22 @@ namespace hp /** * Check the status of the given container using docker inspect command. - * @param name Name of the container. + * @param username Username of the instance user. + * @param container_name Name of the container. * @param status The variable that holds the status of the container. * @return 0 on success and -1 on error. */ - int check_instance_status(std::string_view name, std::string &status) + int check_instance_status(std::string_view username, std::string_view container_name, std::string &status) { - std::string command("docker inspect --format='{{json .State.Status}}' "); - command.append(name); - FILE *fpipe = popen(command.c_str(), "r"); + const int len = 136 + username.length() + container_name.length(); + char command[len]; + sprintf(command, DOCKER_STATUS, username.data(), container_name.data()); + + FILE *fpipe = popen(command, "r"); if (fpipe == NULL) { - LOG_ERROR << "Error on popen for command " << command; + LOG_ERROR << "Error on popen for command " << std::string(command); return -1; } char buffer[20]; @@ -448,17 +562,16 @@ namespace hp /** * Read only required contract config values - * @param contract_name Name of the contract. + * @param contract_dir Directory of the contract. * @param log_level Log level to be read. * @param is_full_history Contract history mode. * @return 0 on success. -1 on failure. */ - int read_contract_cfg_values(std::string_view contract_name, std::string &log_level, bool &is_full_history) + int read_contract_cfg_values(std::string_view contract_dir, std::string &log_level, bool &is_full_history) { - const std::string folder_path = conf::cfg.hp.instance_folder + "/" + contract_name.data(); - // Read the config file into json document object. - const std::string config_file_path = folder_path + "/cfg/hp.cfg"; + std::string config_file_path(contract_dir); + config_file_path.append("/cfg/hp.cfg"); const int config_fd = open(config_file_path.data(), O_RDONLY); if (config_fd == -1) { @@ -524,4 +637,110 @@ namespace hp return 0; } + /** + * Executes the given bash file and populates final comma seperated output into a vector. + * @param file_name Name of the bash script. + * @param output_params Final output of the bash script. + * @param input_param Input parameter to the bash script (Optional). + */ + int execute_bash_file(std::string_view file_name, std::vector &output_params, std::string_view input_param) + { + const int len = 23 + (file_name.length() * 2) + input_param.length(); + char command[len]; + sprintf(command, RUN_SH, file_name.data(), file_name.data(), input_param.empty() ? "\0" : input_param.data()); + + FILE *fpipe = popen(command, "r"); + if (fpipe == NULL) + { + LOG_ERROR << "Error on popen for command " << std::string(command); + return -1; + } + + char buffer[200]; + std::string output; + + // Only take the last cout string It contains the output of the execution. + while (fgets(buffer, sizeof(buffer), fpipe) != NULL) + { + output = buffer; + // Replace ending new line character at the end of the log line. + if (!output.empty()) + { + if (output.back() == '\n') + output.pop_back(); + LOG_DEBUG << output; + } + } + + pclose(fpipe); + util::split_string(output_params, output, ","); + return 0; + } + + /** + * Create new user and install dependencies and populate id and username. + * @param user_id Uid of the created user to be populated. + * @param username Username of the created user to be populated. + */ + int install_user(int &user_id, std::string &username) + { + std::vector params; + if (execute_bash_file(conf::ctx.user_install_sh, params) == -1) + return -1; + + if (strncmp(params.at(params.size() - 1).data(), "INST_SUC", 8) == 0) // If success. + { + if (util::stoi(params.at(0), user_id) == -1) + { + LOG_ERROR << "Create user error: Invalid user id."; + return -1; + } + username = params.at(1); + LOG_DEBUG << "Created new user : " << username << ", uid : " << user_id; + return 0; + } + else if (strncmp(params.at(params.size() - 1).data(), "INST_ERR", 8) == 0) // If error. + { + const std::string error = params.at(0); + LOG_ERROR << "User creation error : " << error; + return -1; + } + else + { + const std::string error = params.at(0); + LOG_ERROR << "Unknown user creation error : " << error; + return -1; + } + } + + /** + * Delete the given user and remove dependencies. + * @param username Username of the user to be deleted. + */ + int uninstall_user(std::string_view username) + { + std::vector params; + if (execute_bash_file(conf::ctx.user_uninstall_sh, params, username) == -1) + return -1; + + // const std::string contract_dir = util::get_user_contract_dir(info.username, container_name); + if (strncmp(params.at(params.size() - 1).data(), "UNINST_SUC", 8) == 0) // If success. + { + LOG_DEBUG << "Deleted the user : " << username; + return 0; + } + if (strncmp(params.at(params.size() - 1).data(), "UNINST_ERR", 8) == 0) // If error. + { + const std::string error = params.at(0); + LOG_ERROR << "User removing error : " << error; + return -1; + } + else + { + const std::string error = params.at(0); + LOG_ERROR << "Unknown user removing error : " << error; + return -1; + } + } + } // namespace hp diff --git a/src/hp_manager.hpp b/src/hp_manager.hpp index b5ed195..c7f7655 100644 --- a/src/hp_manager.hpp +++ b/src/hp_manager.hpp @@ -31,12 +31,14 @@ namespace hp struct instance_info { std::string owner_pubkey; - std::string name; + std::string container_name; + std::string contract_dir; std::string ip; std::string pubkey; std::string contract_id; ports assigned_ports; std::string status; + std::string username; }; struct resources @@ -50,15 +52,19 @@ namespace hp void deinit(); void hp_monitor_loop(); int create_new_instance(instance_info &info, std::string_view owner_pubkey); - int run_container(const std::string &folder_name, const ports &assigned_ports); - int start_container(const std::string &container_name); - int docker_start(const std::string &container_name); - int stop_container(const std::string &container_name); - int destroy_container(const std::string &container_name); + int run_container(std::string_view username, std::string_view container_name, std::string_view contract_dir, const ports &assigned_ports, instance_info &info); + int start_container(std::string_view container_name); + int docker_start(std::string_view username, std::string_view container_name); + int docker_stop(std::string_view username, std::string_view container_name); + int stop_container(std::string_view container_name); + int destroy_container(std::string_view container_name); void kill_all_containers(); - int create_contract(instance_info &info, const std::string &folder_name, std::string_view owner_pubkey, const ports &assigned_ports); + int create_contract(std::string_view username, std::string_view contract_dir, std::string_view owner_pubkey, const ports &assigned_ports, instance_info &info); int write_json_file(const int fd, const jsoncons::ojson &d); - int check_instance_status(std::string_view name, std::string &status); - int read_contract_cfg_values(std::string_view contract_name, std::string &log_level, bool &is_full_history); + int check_instance_status(std::string_view username, std::string_view container_name, std::string &status); + int read_contract_cfg_values(std::string_view contract_dir, std::string &log_level, bool &is_full_history); + int execute_bash_file(std::string_view file_name, std::vector &output_params, std::string_view input_param = {}); + int install_user(int &user_id, std::string &username); + int uninstall_user(std::string_view username); } // namespace hp #endif \ No newline at end of file diff --git a/src/hpfs_manager.cpp b/src/hpfs_manager.cpp index f5d5f97..e1e5ab1 100644 --- a/src/hpfs_manager.cpp +++ b/src/hpfs_manager.cpp @@ -4,67 +4,78 @@ namespace hpfs { - constexpr ino_t ROOT_INO = 1; - constexpr uint16_t HPFS_PROCESS_INIT_TIMEOUT = 2000; - constexpr uint16_t HPFS_INIT_CHECK_INTERVAL = 20; + constexpr const char *PGREP_HPFS = "pgrep -f hpfs -u %s"; /** * Starts the hpfs process for the instance. + * @param username Username of the instance user. * @param fs_dir File system directory * @param mount_dir Mount directory. * @param log_level Log level for the hpfs. * @param merge Whether changes are needed to be merged. * @return -1 on error, pid of the spawned hpfs process if success. */ - int start_hpfs_process(std::string_view fs_dir, std::string_view mount_dir, std::string_view log_level, const bool merge) + int start_hpfs_process(std::string_view username, std::string_view fs_dir, std::string_view mount_dir, std::string_view log_level, const bool merge) { + util::user_info user; + if (util::get_system_user_info(username, user) == -1) + return -1; + const pid_t pid = fork(); - if (pid > 0) - { - // Sashimono process. - LOG_DEBUG << "Starting hpfs process at " << mount_dir << "."; + // This check for hpfs FUSE interface is commented out since hpfs fuse fs is running under the instance user. + // So the mount won't be accessible by the root, so stat sys call cannot be used. + // if (pid > 0) + // { + // const ino_t ROOT_INO = 1; + // const uint16_t HPFS_PROCESS_INIT_TIMEOUT = 2000; + // const uint16_t HPFS_INIT_CHECK_INTERVAL = 20; - // Wait until hpfs is initialized properly. - const uint16_t max_retries = HPFS_PROCESS_INIT_TIMEOUT / HPFS_INIT_CHECK_INTERVAL; - bool hpfs_initialized = false; - uint16_t retry_count = 0; - do - { - util::sleep(HPFS_INIT_CHECK_INTERVAL); + // // Sashimono process. - // Check if hpfs process is still running. - // Sending signal 0 to test whether process exist. - if (util::kill_process(pid, false, 0) == -1) - { - LOG_ERROR << "hpfs process " << pid << " has stopped."; - break; - } + // LOG_DEBUG << "Starting hpfs process at " << mount_dir << "."; - // We check for the specific inode no. of the mounted root dir. That means hpfs FUSE interface is up. - struct stat st; - if (stat(mount_dir.data(), &st) == -1) - { - LOG_ERROR << errno << ": Error in checking hpfs status at mount " << mount_dir << "."; - break; - } + // // Wait until hpfs is initialized properly. + // const uint16_t max_retries = HPFS_PROCESS_INIT_TIMEOUT / HPFS_INIT_CHECK_INTERVAL; + // bool hpfs_initialized = false; + // uint16_t retry_count = 0; + // do + // { + // util::sleep(HPFS_INIT_CHECK_INTERVAL); - hpfs_initialized = (st.st_ino == ROOT_INO); - // Keep retrying until root inode no. matches or timeout occurs. + // // Check if hpfs process is still running. + // // Sending signal 0 to test whether process exist. + // if (util::kill_process(pid, false, 0) == -1) + // { + // LOG_ERROR << "hpfs process " << pid << " has stopped."; + // break; + // } - } while (!hpfs_initialized && ++retry_count <= max_retries); + // // We check for the specific inode no. of the mounted root dir. That means hpfs FUSE interface is up. + // struct stat st; + // if (stat(mount_dir.data(), &st) == -1) + // { + // LOG_ERROR << errno << ": Error in checking hpfs status at mount " << mount_dir << "."; + // break; + // } - // Kill the process if hpfs couldn't be initialized properly. - if (!hpfs_initialized) - { - LOG_ERROR << "Couldn't initialize hpfs process at mount " << mount_dir << "."; - util::kill_process(pid, true); - return -1; - } + // hpfs_initialized = (st.st_ino == ROOT_INO); + // // Keep retrying until root inode no. matches or timeout occurs. - LOG_DEBUG << "hpfs process started. pid:" << pid; - } - else if (pid == 0) + // } while (!hpfs_initialized && ++retry_count <= max_retries); + + // // Kill the process if hpfs couldn't be initialized properly. + // if (!hpfs_initialized) + // { + // LOG_ERROR << "Couldn't initialize hpfs process at mount " << mount_dir << "."; + // util::kill_process(pid, true); + // return -1; + // } + + // LOG_DEBUG << "hpfs process started. pid:" << pid; + // } + + if (pid == 0) { // hpfs process. util::fork_detach(); @@ -86,39 +97,44 @@ namespace hpfs (char *)trace_arg.data(), NULL}; + setgid(user.group_id); + setuid(user.user_id); const int ret = execv(execv_args[0], execv_args); std::cerr << errno << ": hpfs process start failed at mount " << mount_dir << ".\n"; exit(1); } - else + else if (pid < 0) { LOG_ERROR << errno << ": fork() failed when starting hpfs process at mount " << mount_dir << "."; return -1; } + LOG_DEBUG << "hpfs process started. mount:" << mount_dir << ",pid : " << pid; return pid; } /** * Creates hpfs processes for the instance. + * @param username Username of the instance user. * @param contract_dir Contract directory. * @param log_level Log level for hpfs. * @param is_full_history Whether hpfs instances are for full history node. - * @param pids pids of the hpfs instances. - * @return -1 on error and 0 on success and pids will be populated. + * @return -1 on error and 0 on success. * */ - int start_fs_processes(std::string_view contract_dir, std::string_view log_level, const bool is_full_history) + int start_fs_processes(std::string_view username, const std::string &contract_dir, std::string_view log_level, const bool is_full_history) { - const std::string contract_fs_path = conf::cfg.hp.instance_folder + "/" + (const char *)contract_dir.data() + "/contract_fs"; - if (start_hpfs_process(contract_fs_path, contract_fs_path + "/mnt", log_level, !is_full_history) <= 0) + std::string fs_path = contract_dir + "/contract_fs"; + std::string mnt_path = fs_path + "/mnt"; + if (start_hpfs_process(username, fs_path, mnt_path, log_level, !is_full_history) <= 0) { LOG_ERROR << errno << " : Error occured while starting contract_fs processes - " << contract_dir; return -1; } - const std::string ledger_fs_path = conf::cfg.hp.instance_folder + "/" + (const char *)contract_dir.data() + "/ledger_fs"; - if (start_hpfs_process(ledger_fs_path, ledger_fs_path + "/mnt", log_level, true) <= 0) + fs_path = contract_dir + "/ledger_fs"; + mnt_path = fs_path + "/mnt"; + if (start_hpfs_process(username, fs_path, mnt_path, log_level, true) <= 0) { LOG_ERROR << errno << " : Error occured while starting ledger_fs processes - " << contract_dir; return -1; @@ -127,4 +143,46 @@ namespace hpfs return 0; } + /** + * Stop hpfs processes of the instance. + * @param username Username of the instance user. + * @return -1 on error and 0 on success. + * + */ + int stop_fs_processes(std::string_view username) + { + const int len = 19 + username.length(); + char command[len]; + sprintf(command, PGREP_HPFS, username.data()); + + FILE *fpipe = popen(command, "r"); + if (fpipe == NULL) + { + LOG_ERROR << "Error on popen for command: " << std::string(command); + return -1; + } + + char buffer[50]; + int pid; + + while (fgets(buffer, sizeof(buffer), fpipe) != NULL) + { + if(util::stoi(buffer, pid) == -1) + { + LOG_ERROR << "Error converting " << buffer << " to a valid pid."; + pclose(fpipe); + return -1; + } + util::kill_process(pid, true); + } + + if (pclose(fpipe) != 0) + { + LOG_ERROR << errno << ": Error getting pids of hpfs for user: " << username; + return -1; + } + + return 0; + } + } // namespace hp diff --git a/src/hpfs_manager.hpp b/src/hpfs_manager.hpp index d549c92..fc457da 100644 --- a/src/hpfs_manager.hpp +++ b/src/hpfs_manager.hpp @@ -5,7 +5,8 @@ namespace hpfs { - int start_hpfs_process(std::string_view fs_dir, std::string_view mount_dir, std::string_view log_level, const bool merge = true); - int start_fs_processes(std::string_view contract_dir, std::string_view log_level, const bool is_full_history); + int start_hpfs_process(std::string_view username, std::string_view fs_dir, std::string_view mount_dir, std::string_view log_level, const bool merge); + int start_fs_processes(std::string_view username, const std::string &contract_dir, std::string_view log_level, const bool is_full_history); + int stop_fs_processes(std::string_view username); } // namespace hpfs #endif \ No newline at end of file diff --git a/src/msg/json/msg_json.cpp b/src/msg/json/msg_json.cpp index a725cd1..614bd70 100644 --- a/src/msg/json/msg_json.cpp +++ b/src/msg/json/msg_json.cpp @@ -256,6 +256,7 @@ namespace msg::json * 'reply_for': '' * 'type': '', * "name": "" + * "username": """ * "ip": "" * "pubkey": "" * "contract_id": "" @@ -279,8 +280,12 @@ namespace msg::json msg += SEP_COMMA; msg += "name"; msg += SEP_COLON; - msg += info.name; + msg += info.container_name; msg += SEP_COMMA; + // msg += "username"; // Uncomment if username is required for debugging. + // msg += SEP_COLON; + // msg += info.username; + // msg += SEP_COMMA; msg += "ip"; msg += SEP_COLON; msg += info.ip; diff --git a/src/pchheader.hpp b/src/pchheader.hpp index e8a2259..d391648 100644 --- a/src/pchheader.hpp +++ b/src/pchheader.hpp @@ -14,16 +14,20 @@ #include #include #include +#include #include #include +#include #include #include +#include #include #include #include #include #include #include +#include #include #endif \ No newline at end of file diff --git a/src/sqlite.cpp b/src/sqlite.cpp index ac0892a..d248594 100644 --- a/src/sqlite.cpp +++ b/src/sqlite.cpp @@ -21,9 +21,9 @@ namespace sqlite constexpr const char *INSTANCE_TABLE = "instances"; constexpr const char *INSERT_INTO_HP_INSTANCE = "INSERT INTO instances(" - "owner_pubkey, time, status, name, ip," + "owner_pubkey, time, username, status, name, ip," "peer_port, user_port, pubkey, contract_id" - ") VALUES(?,?,?,?,?,?,?,?,?)"; + ") VALUES(?,?,?,?,?,?,?,?,?,?)"; constexpr const char *GET_VACANT_PORTS_FROM_HP = "SELECT DISTINCT peer_port, user_port FROM " "instances WHERE status == ? AND user_port NOT IN" @@ -35,10 +35,12 @@ namespace sqlite constexpr const char *UPDATE_CURRENT_STATUS_IN_HP = "UPDATE instances SET current_status = ? WHERE name = ?"; - constexpr const char *IS_CONTAINER_EXISTS = "SELECT * FROM instances WHERE name = ?"; + constexpr const char *IS_CONTAINER_EXISTS = "SELECT username, status, peer_port, user_port FROM instances WHERE name = ?"; constexpr const char *GET_RUNNING_INSTANCE_NAMES = "SELECT name FROM instances WHERE status = ?"; + constexpr const char *GET_RUNNING_INSTANCE_USER_AND_NAME_LIST = "SELECT username,name FROM instances WHERE status = ?"; + constexpr const char *IS_TABLE_EXISTS = "SELECT * FROM sqlite_master WHERE type='table' AND name = ?"; /** @@ -284,6 +286,7 @@ namespace sqlite const std::vector columns{ table_column_info("owner_pubkey", COLUMN_DATA_TYPE::TEXT), table_column_info("time", COLUMN_DATA_TYPE::INT), + table_column_info("username", COLUMN_DATA_TYPE::TEXT), table_column_info("status", COLUMN_DATA_TYPE::TEXT), table_column_info("current_status", COLUMN_DATA_TYPE::TEXT), table_column_info("name", COLUMN_DATA_TYPE::TEXT, true), @@ -313,13 +316,14 @@ namespace sqlite if (sqlite3_prepare_v2(db, INSERT_INTO_HP_INSTANCE, -1, &stmt, 0) == SQLITE_OK && stmt != NULL && sqlite3_bind_text(stmt, 1, info.owner_pubkey.data(), info.owner_pubkey.length(), SQLITE_STATIC) == SQLITE_OK && sqlite3_bind_int64(stmt, 2, util::get_epoch_milliseconds()) == SQLITE_OK && - sqlite3_bind_text(stmt, 3, info.status.data(), info.status.length(), SQLITE_STATIC) == SQLITE_OK && - sqlite3_bind_text(stmt, 4, info.name.data(), info.name.length(), SQLITE_STATIC) == SQLITE_OK && - sqlite3_bind_text(stmt, 5, info.ip.data(), info.ip.length(), SQLITE_STATIC) == SQLITE_OK && - sqlite3_bind_int64(stmt, 6, info.assigned_ports.peer_port) == SQLITE_OK && - sqlite3_bind_int64(stmt, 7, info.assigned_ports.user_port) == SQLITE_OK && - sqlite3_bind_text(stmt, 8, info.pubkey.data(), info.pubkey.length(), SQLITE_STATIC) == SQLITE_OK && - sqlite3_bind_text(stmt, 9, info.contract_id.data(), info.contract_id.length(), SQLITE_STATIC) == SQLITE_OK && + sqlite3_bind_text(stmt, 3, info.username.data(), info.username.length(), SQLITE_STATIC) == SQLITE_OK && + sqlite3_bind_text(stmt, 4, info.status.data(), info.status.length(), SQLITE_STATIC) == SQLITE_OK && + sqlite3_bind_text(stmt, 5, info.container_name.data(), info.container_name.length(), SQLITE_STATIC) == SQLITE_OK && + sqlite3_bind_text(stmt, 6, info.ip.data(), info.ip.length(), SQLITE_STATIC) == SQLITE_OK && + sqlite3_bind_int64(stmt, 7, info.assigned_ports.peer_port) == SQLITE_OK && + sqlite3_bind_int64(stmt, 8, info.assigned_ports.user_port) == SQLITE_OK && + sqlite3_bind_text(stmt, 9, info.pubkey.data(), info.pubkey.length(), SQLITE_STATIC) == SQLITE_OK && + sqlite3_bind_text(stmt, 10, info.contract_id.data(), info.contract_id.length(), SQLITE_STATIC) == SQLITE_OK && sqlite3_step(stmt) == SQLITE_DONE) { sqlite3_finalize(stmt); @@ -346,9 +350,10 @@ namespace sqlite sqlite3_step(stmt) == SQLITE_ROW) { // Populate only the necessary fields. - info.status = std::string(reinterpret_cast(sqlite3_column_text(stmt, 2))); - info.assigned_ports.peer_port = sqlite3_column_int64(stmt, 6); - info.assigned_ports.user_port = sqlite3_column_int64(stmt, 7); + info.username = std::string(reinterpret_cast(sqlite3_column_text(stmt, 0))); + info.status = std::string(reinterpret_cast(sqlite3_column_text(stmt, 1))); + info.assigned_ports.peer_port = sqlite3_column_int64(stmt, 2); + info.assigned_ports.user_port = sqlite3_column_int64(stmt, 3); // Finalize and distroys the statement. sqlite3_finalize(stmt); @@ -484,4 +489,31 @@ namespace sqlite // Finalize and distroys the statement. sqlite3_finalize(stmt); } + + /** + * Populate the given vector with user name and name of running hp instances. + * @param db Database connection. + * @param running_instances Vector to hold user name and name of instances. + */ + void get_running_instance_user_and_name_list(sqlite3 *db, std::vector> &running_instances) + { + running_instances.clear(); + + sqlite3_stmt *stmt; + std::string_view running_status(hp::CONTAINER_STATES[hp::STATES::RUNNING]); + + if (sqlite3_prepare_v2(db, GET_RUNNING_INSTANCE_USER_AND_NAME_LIST, -1, &stmt, 0) == SQLITE_OK && stmt != NULL && + sqlite3_bind_text(stmt, 1, running_status.data(), running_status.length(), SQLITE_STATIC) == SQLITE_OK) + { + while (stmt != NULL && sqlite3_step(stmt) == SQLITE_ROW) + { + const std::string username(reinterpret_cast(sqlite3_column_text(stmt, 0))); + const std::string name(reinterpret_cast(sqlite3_column_text(stmt, 1))); + running_instances.push_back({username, name}); + } + } + + // Finalize and distroys the statement. + sqlite3_finalize(stmt); + } } diff --git a/src/sqlite.hpp b/src/sqlite.hpp index b6c229b..6eee204 100644 --- a/src/sqlite.hpp +++ b/src/sqlite.hpp @@ -76,5 +76,7 @@ namespace sqlite void get_vacant_ports(sqlite3 *db, std::vector &vacant_ports); void get_running_instance_names(sqlite3 *db, std::vector &running_instance_names); + + void get_running_instance_user_and_name_list(sqlite3 *db, std::vector> &running_instances); } #endif diff --git a/src/util/util.cpp b/src/util/util.cpp index 54bece4..8fe5169 100644 --- a/src/util/util.cpp +++ b/src/util/util.cpp @@ -187,9 +187,7 @@ namespace util { return nftw( dir_path.data(), [](const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf) - { - return remove(fpath); - }, + { return remove(fpath); }, 1, FTW_DEPTH | FTW_PHYS); } @@ -212,4 +210,100 @@ namespace util return 0; } + /** + * Split string by given delimeter. + * @param collection Splitted strings params. + * @param delimeter Delimeter to split string. + */ + void split_string(std::vector &collection, std::string_view str, std::string_view delimeter) + { + if (str.empty()) + return; + + size_t start = 0; + size_t end = str.find(delimeter); + + while (end != std::string::npos) + { + // Do not add empty strings. + if (start != end) + collection.push_back(std::string(str.substr(start, end - start))); + start = end + delimeter.length(); + end = str.find(delimeter, start); + } + + // If there are any leftover from the source string add the remaining. + if (start < str.size()) + collection.push_back(std::string(str.substr(start))); + } + + /** + * Converts given string to a int. A wrapper function for std::stoi. + * @param str String variable. + * @param result Variable to store the answer from the conversion. + * @return Returns 0 in a successful conversion and -1 on error. + */ + int stoi(const std::string &str, int &result) + { + try + { + result = std::stoi(str); + } + catch (const std::exception &e) + { + // Return -1 if any exceptions are captured. + return -1; + } + return 0; + } + + /** + * Construct the user contract directory path when username is given. + * @param username Username of the user. + * @return Contract directory path. + */ + const std::string get_user_contract_dir(const std::string &username, std::string_view container_name) + { + return "/home/" + username + "/" + container_name.data(); + } + + /** + * Get system user info by given user name. + * @param username Username of the user. + * @param user_info User info struct to be populated. + * @return -1 of error, 0 on success. + */ + int get_system_user_info(std::string_view username, user_info &user_info) + { + const struct passwd *pwd = getpwnam(username.data()); + + if (pwd == NULL) + { + LOG_ERROR << errno << ": Error in getpwnam " << username; + return -1; + } + + user_info.username = username; + user_info.user_id = pwd->pw_uid; + user_info.group_id = pwd->pw_gid; + user_info.home_dir = pwd->pw_dir; + return 0; + } + + /** + * Find and replace given substring inside a string. + * @param str String to be modified. + * @param find Substring to be searched. + * @param replace Substring to be replaced. + */ + void find_and_replace(std::string &str, std::string_view find, std::string_view replace) + { + size_t pos = str.find(find); + while (pos != std::string::npos) + { + str.replace(pos, find.length(), replace); + pos = str.find(find); + } + } + } // namespace util diff --git a/src/util/util.hpp b/src/util/util.hpp index 340ed06..6789c5c 100644 --- a/src/util/util.hpp +++ b/src/util/util.hpp @@ -8,6 +8,14 @@ */ namespace util { + struct user_info + { + std::string username; + int user_id; + int group_id; + std::string home_dir; + }; + const std::string to_hex(const std::string_view bin); const std::string to_bin(const std::string_view hex); @@ -34,6 +42,16 @@ namespace util int kill_process(const pid_t pid, const bool wait, const int signal = SIGINT); + void split_string(std::vector &collection, std::string_view str, std::string_view delimeter); + + int stoi(const std::string &str, int &result); + + const std::string get_user_contract_dir(const std::string &username, std::string_view container_name); + + int get_system_user_info(std::string_view username, user_info &user_info); + + void find_and_replace(std::string &str, std::string_view find, std::string_view replace); + } // namespace util #endif