#include "../pchheader.hpp" #include "util.hpp" namespace util { 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. const std::string to_hex(const std::string_view bin) { // Allocate the target string. std::string encoded_string; encoded_string.resize(bin.size() * 2); // Get encoded string. sodium_bin2hex( encoded_string.data(), encoded_string.length() + 1, // + 1 because sodium writes ending '\0' character as well. reinterpret_cast(bin.data()), bin.size()); return encoded_string; } const std::string to_bin(const std::string_view hex) { std::string bin; bin.resize(hex.size() / 2); const char *hex_end; size_t bin_len; if (sodium_hex2bin( reinterpret_cast(bin.data()), bin.size(), hex.data(), hex.size(), "", &bin_len, &hex_end)) { return ""; // Empty indicates error. } return bin; } /** * Check whether given directory exists. * @param path Directory path. * @return Returns true if given directory exists otherwise false. */ bool is_dir_exists(std::string_view path) { struct stat st; return (stat(path.data(), &st) == 0 && S_ISDIR(st.st_mode)); } /** * Check whether given file exists. * @param path File path. * @return Returns true if give file exists otherwise false. */ bool is_file_exists(std::string_view path) { struct stat st; return (stat(path.data(), &st) == 0 && S_ISREG(st.st_mode)); } /** * Recursively creates directories and sub-directories if not exist. * @param path Directory path. * @return Returns 0 operations succeeded otherwise -1. */ int create_dir_tree_recursive(std::string_view path) { if (strcmp(path.data(), "/") == 0) // No need of checking if we are at root. return 0; // Check whether this dir exists or not. struct stat st; if (stat(path.data(), &st) != 0 || !S_ISDIR(st.st_mode)) { // Check and create parent dir tree first. char *path2 = strdup(path.data()); char *parent_dir_path = dirname(path2); bool error_thrown = false; if (create_dir_tree_recursive(parent_dir_path) == -1) error_thrown = true; free(path2); // Create this dir. if (!error_thrown && mkdir(path.data(), DIR_PERMS) == -1) { std::cerr << errno << ": Error in recursive dir creation. " << path << std::endl; error_thrown = true; } if (error_thrown) return -1; } return 0; } /** * Reads the entire file from given file discriptor. * @param fd File descriptor to be read. * @param buf String buffer to be populated. * @param offset Begin offset of the file to read. * @return Returns number of bytes read in a successful read and -1 on error. */ int read_from_fd(const int fd, std::string &buf, const off_t offset) { struct stat st; if (fstat(fd, &st) == -1) { std::cerr << errno << ": Error in stat for reading entire file." << std::endl; return -1; } buf.resize(st.st_size - offset); return pread(fd, buf.data(), buf.size(), offset); } /** * Provide a safe std::string overload for realpath. * @param path Path. * @returns Returns the realpath as string. */ const std::string realpath(std::string_view path) { std::array buffer; if (!::realpath(path.data(), buffer.data())) return {}; buffer[PATH_MAX] = '\0'; return buffer.data(); } /** * Clears signal mask and signal handlers from the caller. * Called by other processes forked from sagent threads so they get detatched from * the sagent signal setup. */ void fork_detach() { // Restore signal handlers to defaults. signal(SIGINT, SIG_DFL); signal(SIGSEGV, SIG_DFL); signal(SIGABRT, SIG_DFL); // Remove any signal masks applied by sagent. sigset_t mask; sigemptyset(&mask); pthread_sigmask(SIG_SETMASK, &mask, NULL); // Set process group id (so the terminal doesn't send kill signals to forked children). setpgrp(); } // Applies signal mask to the calling thread. void mask_signal() { sigset_t mask; sigemptyset(&mask); sigaddset(&mask, SIGINT); sigaddset(&mask, SIGPIPE); pthread_sigmask(SIG_BLOCK, &mask, NULL); } /** * Sleeps the current thread for specified no. of milliseconds. */ void sleep(const uint64_t milliseconds) { std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds)); } /** * Returns current time in UNIX epoch milliseconds. */ uint64_t get_epoch_milliseconds() { return std::chrono::duration_cast>( std::chrono::system_clock::now().time_since_epoch()) .count(); } /** * Remove a directory recursively with it's content. FTW_DEPTH is provided so all of the files and subdirectories within * The path will be processed. FTW_PHYS is provided so symbolic links won't be followed. */ int remove_directory_recursively(std::string_view dir_path) { return nftw( dir_path.data(), [](const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf) { return remove(fpath); }, 1, FTW_DEPTH | FTW_PHYS); } // Kill a process with a signal and if specified, wait until it stops running. int kill_process(const pid_t pid, const bool wait, const int signal) { if (kill(pid, signal) == -1) { LOG_ERROR << errno << ": Error issuing signal to pid " << pid; return -1; } const int wait_options = wait ? 0 : WNOHANG; if (waitpid(pid, NULL, wait_options) == -1) { LOG_ERROR << errno << ": waitpid after kill (pid:" << pid << ") failed."; return -1; } 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; } /** * Converts given string to a uint16_t. A wrapper function for std::stoul. * @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 stoul(const std::string &str, uint16_t &result) { try { result = std::stoul(str); } catch (const std::exception &e) { // Return -1 if any exceptions are captured. return -1; } return 0; } /** * Converts given string to a uint_64. A wrapper function for std::stoull. * @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 stoull(const std::string &str, uint64_t &result) { try { result = std::stoull(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); } } /** * Writes the given json doc to a file. * @param fd File descriptor to the open file. * @param d A valid JSON document. * @return 0 on success. -1 on failure. */ int write_json_file(const int fd, const jsoncons::ojson &d) { std::string json; // Convert json object to a string. try { jsoncons::json_options options; options.object_array_line_splits(jsoncons::line_split_kind::multi_line); options.spaces_around_comma(jsoncons::spaces_option::no_spaces); std::ostringstream os; os << jsoncons::pretty_print(d, options); json = os.str(); os.clear(); } catch (const std::exception &e) { LOG_ERROR << "Converting modified hp config json to string failed. "; return -1; } if (ftruncate(fd, 0) == -1 || write(fd, json.data(), json.size()) == -1) { LOG_ERROR << "Writing modified hp config file failed. "; return -1; } return 0; } /** * Reads the given file to a json doc. * @param fd File descriptor to the open file. * @param d JSON document to be populated. * @return 0 on success. -1 on failure. */ int read_json_file(const int fd, jsoncons::ojson &d) { std::string buf; if (util::read_from_fd(fd, buf) == -1) { std::cerr << "Error reading from the config file. " << errno << '\n'; return -1; } try { d = jsoncons::ojson::parse(buf, jsoncons::strict_json_parsing()); } catch (const std::exception &e) { std::cerr << "Invalid config file format. " << e.what() << '\n'; return -1; } buf.clear(); 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_params Input parameters to the bash script (Optional). */ int execute_bash_file(std::string_view file_name, std::vector &output_params, const std::vector &input_params) { std::string params = ""; for (auto itr = input_params.begin(); itr != input_params.end(); itr++) { // Empty params are appended as '-' to preserve param order. params.append(itr->empty() ? "-" : *itr); if (std::next(itr) != input_params.end()) params.append(" "); } const int len = 23 + (file_name.length() * 2) + params.length(); char command[len]; sprintf(command, RUN_SH, file_name.data(), file_name.data(), params.empty() ? "\0" : params.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_INFO << output; } } pclose(fpipe); util::split_string(output_params, output, ","); return 0; } /** * Execute bash command and take the output. * @param command Command to execute. * @param output Pointer to populate output. * @param output_len Length of the output. * @return 0 on success and -1 on error. */ int execute_bash_cmd(const char *command, char *output, const int output_len) { FILE *fpipe = popen(command, "r"); if (!fpipe) { LOG_ERROR << "Error on popen for command " << std::string(command); return -1; } if (!fgets(output, output_len, fpipe)) { LOG_ERROR << "Error on fgets for command " << std::string(command); return -1; } if (pclose(fpipe) < 0) { LOG_ERROR << "Error on pclose for command " << std::string(command); return -1; } return 0; } } // namespace util