#include #include #include #include #include #include #include #include #include #include #include "appinfo.h" #include "executionmanager.h" #include "log.h" extern "C" { #include #include #include } char *get_current_time() { const auto now = std::chrono::system_clock::now(); const std::time_t t_c = std::chrono::system_clock::to_time_t(now); return std::ctime(&t_c); } struct Options { std::string json_file_path = ""; std::string log_directory = ""; long long int log_file_max_size = 10 * 1024 * 1024; int threads = 16; }options; void createConfig() { using json = nlohmann::json; json config; config["log_file_max_size"] = 10 * 1024 * 1024; std::string str = config.dump(); std::fstream file_out("config.json", std::fstream::out); file_out << str; file_out.flush(); file_out.close(); } void parseConfig() { using json = nlohmann::json; if(!std::filesystem::exists("config.json")) { std::cout << "No config file creating one." << std::endl; createConfig(); } std::fstream file_in("config.json", std::fstream::in); std::stringstream ss; char data; while(!file_in.eof()) { file_in.get(data); if(file_in.eof()) break; ss << data; } json config = json::parse(ss.str()); if(config.find("log_file_max_size") != config.end() && config["log_file_max_size"].is_number_integer()) { options.log_file_max_size = config["log_file_max_size"].get(); } } std::string strip(std::string& t) { std::string g; for(auto i: t) { if(i == ' ' || i == '\t' || i == '\n' || i == '\v' || i == '\r') continue; else g += i; } return g; } void printHelp() { std::cout << "Usage: StartUpRunner [OPTIONS]" << std::endl; std::cout << "OPTIONS:" << std::endl; std::cout << "\t--help|-h : Prints this screen." << std::endl; std::cout << "\t--version|-v : Prints version info." << std::endl; std::cout << "\t--threads|-t [T] : Sets number of threads to [T]." << std::endl; std::cout << "\t--log_dir|-ld [Dir] : Sets log directory to [Dir]." << std::endl; std::cout << "\t--defaults|-d : Prints the default values used if no arguments are provided." << std::endl; std::cout << "\t--json|-j [FileName]: Set Json Startup List to FileName." << std::endl; std::cout << "For a detailed info about the json startup file use --help json." << std::endl; } void printVersion() { std::cout << "StartUpRunner: Version-0.0.1 Pre-Alpha Pre-BigBang Era Edition." << std::endl; } void printDefaults() { std::cout << "StartUpRunner Default Values: " << std::endl; std::cout << "Json file : " << options.json_file_path << std::endl; std::cout << "Logs directory : " << options.log_directory << std::endl; std::cout << "Log file max size: " << options.threads << std::endl; std::cout << "Threads : " << options.threads << std::endl; } void printJsonHelp() { std::cout << "Json StartUp List file format: " << std::endl; std::cout << "Top level is an Array of Apps." << std::endl; std::cout << "Each App Entry contains: " << std::endl; std::cout << "\t[REQUIRED] name[string] : Name of the module." << std::endl; std::cout << "\t[REQUIRED] app_name[string] : Path to the program with its name." << std::endl; std::cout << "\t[OPTIONAL] app_args[list[string]] : List of arguments to the program." << std::endl; std::cout << "\t[REQUIRED] run_mode[string] : Mode can be daemon or script." << std::endl; std::cout << "\t\tMode daemon: The app forks the program and exits without waiting." << std::endl; std::cout << "\t\tMode script: The app forks the program and waits for it to finish execution." << std::endl; std::cout << "\t\tMode script: If no run_mode is given it is presumed to be script." << std::endl; std::cout << "\t[OPTIONAL] working_dir[string] : Working directory of the program." << std::endl; std::cout << "\t[OPTIONAL] create_log[boolean] : Create a log file at logs path." << std::endl; std::cout << std::endl << "Example startup_apps.json: "; std::cout << R"( [ { "name": "LS 0", "app_name": "/bin/ls", "app_args": ["-l"], "run_mode": "script", "create_log": true, "working_dir": "/home/" }, { "name": "LS 1", "app_name": "/bin/ls", "app_args": ["-l"], "run_mode": "script", "create_log": true, "after": [{"module": "LS 0", "state": "exited"}] "working_dir": "/home/etc" } ] )" << std::endl; } int contains(std::vector &list, std::string l, std::string v) { for(int i = 0; i < list.size(); i++) { if(list[i] == l || list[i] == v) return i; } return -1; } int toint(std::string s) { std::stringstream ss; int ret; ss << s; ss >> ret; return ret; } void parseArgs(int c, char *v[]) { if(c == 1) return; if(c == 2) { std::string arg1(v[1]); if(arg1 == "--help" || arg1 == "-h") { printHelp(); exit(0); } if(arg1 == "--version" || arg1 == "-v") { printVersion(); exit(0); } if(arg1 == "--defaults" || arg1 == "-d") { printDefaults(); exit(0); } } if(c >= 3) { std::vector arg_list; for(int i=1; i= 0) { int j = i + 1; if(j > arg_list.size()) { printHelp(); exit(-1); } options.json_file_path = arg_list[j]; std::cout << "Json file path: " << options.json_file_path << std::endl; contains_any_known_arg = true; } i = contains(arg_list, "--log_dir", "-ld"); if(i >= 0) { int j = i + 1; if(j > arg_list.size()) { printHelp(); exit(-1); } options.log_directory = arg_list[j]; std::cout << "Log directory: " << options.log_directory << std::endl; contains_any_known_arg = true; } i = contains(arg_list, "--threads", "-t"); if(i >= 0) { int j = i + 1; if(j > arg_list.size()) { printHelp(); exit(-1); } options.threads = toint(arg_list[j]); std::cout << "Threads: " << options.threads << std::endl; contains_any_known_arg = true; } i = contains(arg_list, "--help", "-h"); if(i >= 0) { int j = i + 1; if(j > arg_list.size()) { printHelp(); exit(-1); } if(arg_list[j] == "json") { printJsonHelp(); exit(0); } else { printHelp(); exit(-1); } contains_any_known_arg = true; } if(!contains_any_known_arg) { printHelp(); exit(-1); } } } std::string string_strip(std::string str) { std::string ch; bool front_found = false; for(int i=0; i string_split(std::string s, const std::string& delimiter) { std::vector tokens; size_t pos = 0; std::string token; while ((pos = s.find(delimiter)) != std::string::npos) { token = s.substr(0, pos); tokens.push_back(token); s.erase(0, pos + delimiter.length()); } tokens.push_back(string_strip(s)); return tokens; } std::string tolower(std::string str) { std::string s; std::transform(str.begin(), str.end(), std::back_inserter(s), [](char c){ return std::tolower(c); }); return s; } void printListModules(const std::vector>& item_list, std::ostream &out) { std::stringstream ss; int s = item_list.size(); ss << "List Contains --> "; for(auto &i: item_list) { s--; if(s > 0) ss << i->getModuleName() << ", "; else ss << i->getModuleName(); } out << ss.str() << std::endl; } bool listContainsAfter(const std::vector> &list, const AppInfo::AppData &item) { std::map contains_list; std::string it = item.getAfterKeys(); auto string_list = string_split(it, ","); for(auto &i: string_list) { bool found = false; for(auto &j: list) { if(tolower(string_strip(j->getModuleName())) == tolower(string_strip(i))) { contains_list[i] = true; found = true; break; } } if(!found) contains_list[i] = false; } bool value = std::all_of(contains_list.begin(), contains_list.end(), [](const auto &it) { return it.second; }); return value; } std::optional checkAppList(std::deque ©_list, std::vector> &after_list, std::ostream &out) { out << "Checking app list with: " << copy_list.size() << " apps for errors..." << std::endl; int reshuffle_count = 0; while(copy_list.size() != 0) { size_t copy_list_size = copy_list.size(); for(auto item = copy_list.begin(); item != copy_list.end();) { if(item->after.size() == 0) { std::shared_ptr app_info = std::make_shared(); app_info->copyFromData(*item); after_list.push_back(std::move(app_info)); out << "Module has no dependency: " << item->module_name << std::endl; reshuffle_count = 0; item = copy_list.erase(item); } else if(listContainsAfter(after_list, *item)) { out << "Module: " << item->module_name << " has dependency: " << item->getAfterString() << " and it was found." << std::endl; std::shared_ptr app_info = std::make_shared(); app_info->copyFromData(*item); for(auto &[key, value]: item->after) { bool found = false; for(int i=0;igetModuleName())) == tolower(string_strip(key))) { if(value == "running") { app_info->insertParent(after_list[i], AppInfo::State::running); } else if(value == "exited") { app_info->insertParent(after_list[i], AppInfo::State::exited); } else { out << "Unknown state for: " << item->module_name << "'s dependency of: " << after_list[i]->getModuleName() << std::endl; return std::nullopt; } found = true; break; } } if(!found) { out << "Depenedency not found" << std::endl; } } after_list.push_back(std::move(app_info)); reshuffle_count = 0; item = copy_list.erase(item); } else { item++; } if(copy_list.size() == 0 || item == copy_list.end()) break; } if(copy_list.size() == copy_list_size) { reshuffle_count++; if(reshuffle_count >= 3) { out << "List contains cyclic dependency or unresolvable dependency..." << std::endl; out << "Affected apps in list: " << std::endl; for(auto &i: copy_list) { i.printInfo(out); } std::cout << "Resolved list order: " << std::endl; printListModules(after_list, out); return std::nullopt; } } } for(auto &i: after_list) { for(auto &[j, state]: i->getParents()) { if(state == AppInfo::State::exited && j->getRunMode() == AppInfo::RunMode::daemon) { out << "[WARN]: " << i->getModuleName() << " is waiting for a daemon: " << j->getModuleName() << " to start exeuction." << std::endl; } } } return 0; } int main(int argc, char *argv[]) { parseConfig(); parseArgs(argc, argv); if(!std::filesystem::exists(options.log_directory)) { std::filesystem::create_directory(options.log_directory); } std::filesystem::path json_path = options.json_file_path; std::string log_file_path = std::filesystem::path(options.log_directory) / "StartUpRunner.log"; Log log_file(log_file_path, options.log_file_max_size); std::cout << "Starter Init: Log file path: " << log_file_path << std::endl; std::cout << "Log file open: " << log_file.isOpen() << std::endl; log_file << "-------- Starting app --------" << std::endl; log_file << "Current Time: " << get_current_time(); log_file << "Log file max size: " << options.log_file_max_size << std::endl; log_file << "Current path: " << std::filesystem::current_path() << std::endl; std::string json_file_path = json_path; if(std::filesystem::is_directory(json_path)) { json_file_path = json_path / "startup_apps.json"; } else if(json_path == "") { json_file_path = "startup_apps.json"; } if(!std::filesystem::exists(json_file_path)) { log_file << std::vformat("startup_apps.json not found exiting at: {}.\n", std::make_format_args(json_file_path)); exit(-1); } else { log_file << "startup_apps.json loaded from: " << json_file_path << std::endl; } std::string data = ""; std::string t_data = ""; std::fstream json_file; json_file.open(json_file_path, std::fstream::in); if(!json_file.is_open()) { log_file << "Why is the startup_apps.json file not open?" << std::endl; return -1; } std::stringstream test; test << json_file.rdbuf(); json_file.close(); using json = nlohmann::json; bool valid = json::accept(test.str()); if(!valid) { log_file << "Json Document Parsing error...." << std::endl; return -1; } json file = json::parse(test.str()); std::deque initial_app_list; for(auto i = file.begin(); i != file.end(); i++) { AppInfo::AppData app_info; auto object = i->get(); if(object.find("app_name") == object.end()) { log_file << "Error in json." << std::endl; log_file << "Field app name is a required field." << std::endl; exit(-2); } if(object.find("name") == object.end()) { log_file << "Error in json." << std::endl; log_file << "Field name is a required field." << std::endl; exit(-2); } if(object.find("run_mode") == object.end()) { log_file << "Error in json." << std::endl; log_file << "Field run_mode is a required field." << std::endl; exit(-2); } app_info.program_name = object["app_name"].get(); std::string args = ""; std::deque c_args; std::deque arg_list; if(object.find("app_args") != object.end()) { for(const auto &k: object["app_args"].get()) { std::string str = k.get(); args += str + " "; arg_list.push_back(str); c_args.push_back(str.c_str()); } } std::unordered_map after_list; if(object.find("after") != object.end()) { for(const auto &k: object["after"].get()) { auto l = k.get(); auto key = l["module"].get(); auto value = l["state"].get(); after_list[key] = value; } } app_info.args = arg_list; if(object.find("working_dir") != object.end()) app_info.working_dir = object["working_dir"].get(); if(object.find("create_log") != object.end()) app_info.create_log = object["create_log"].get(); app_info.after = after_list; app_info.module_name = object["name"].get(); app_info.run_mode = object["run_mode"].get(); app_info.printInfo(log_file.rdbuf()); initial_app_list.push_back(app_info); } std::vector> list; auto list_check = checkAppList(initial_app_list, list, log_file.rdbuf()); if(list_check.has_value()) { log_file << "No errors found in app list." << std::endl; } else { log_file << "Errors present in app list; Please fix them" << std::endl; return -1; } for(int i=0;iprintInfo(log_file.rdbuf()); } log_file << "Order of Execution -> " << std::endl; printListModules(list, log_file.rdbuf()); ExecutionManager manager{options.threads}; manager.setLogFile(log_file); manager.setLogFileMaxSize(options.log_file_max_size); manager.setLogDirectoy(options.log_directory); manager.setJobs(list); manager.run(); return 0; }