#include #include #include #include #include #include #include #include #include #include #include "appinfo.h" #include "executionmanager.h" #include "log.h" extern "C" { #include #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; bool check_for_errors = false; public: Options() { const char *home = std::getenv("HOME"); if(!home || strlen(home) <= 0) { log_directory = "StartUpApps/logs"; return; } using fp = std::filesystem::path; fp fs = fp(home) / ".local" / "share" / "StartUpApps" / "logs"; log_directory = fs.string(); } }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 << R"(Usage: StartUpRunner [OPTIONS] OPTIONS: --help|-h : Prints this screen. --version|-v : Prints version info. --threads|-t [T] : Sets number of threads to [T]. --log_dir|-ld [Dir] : Sets log directory to [Dir]. --defaults|-d : Prints the default values used if no arguments are provided. --json|-j [FileName]: Set Json Startup List to FileName. --test|-T [FileName]: Check the json file for errors. For a detailed info about the json startup file use --help json.)" << std::endl; } void printVersion() { std::cout << "StartUpRunner: Version-0.0.3 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 << R"(Json StartUp List file format: Top level is an Array of Apps. Each App Entry contains: [REQUIRED] name[string] : Name of the module. [REQUIRED] app_name[string] : Path to the program with its name. [REQUIRED] run_mode[string] : Mode can be daemon or script. [OPTIONAL] app_args[list[string]] : List of arguments to the program. Mode daemon : The app forks the program and exits without waiting. Mode script : The app forks the program and waits for it to finish execution. If no run_mode is given it is presumed to be script. [OPTIONAL] after[{name,state}] : List containing names and state of modules. name[string] : Name of the other module. state[string]: State of the other module. State exited : Specified module must have finished execution. State running : Specified module must be running. This module will be launched after the module foundin this list using name is at the state specified as state. [OPTIONAL] env{key: value} : Additional enviroment variables. Every key in this object will be used as environment name, and every corresponding value as its value. [OPTIONAL] working_dir[string] : Working directory of the program. [OPTIONAL] create_log[boolean] : Create a log file at logs path. [OPTIONAL] run_as[string] : Run as a particular user. [OPTIONAL] run_every[string] : Run task at every specified interval. format should be like: [digits][unit] valid units are: s or S: seconds m: minutes h or H: hours d or D: days w or W: weeks M: months Example: 15s Example: 2h [OPTIONAL] run_at[string] : Run task at every specified time. Format of string should be like: "[time] Daily": for running daily at that time "[day] [time] Weekly": for running every week on those days at that time. "[day_month] [time] Monthly": for running every month on those days at that time. [time] format is 24Hrs with [HH:MM:SS] [day] format is any of [Mon, Tue, Wed, Thu, Fri, Sat, Sun] [day_month] format is 1-31. For Weekly, [day] can be a list of days seperated by commas. For Monthly, [day_month] can be a list of days seperated by commas. For Any, [time] can be a list of timestamps seperated by commas. [time] Daily: for running daily at that time. [day] [time] Weekly: for running every week on those days at that time. [OPTIONAL] run_on_start[boolean] : Run on initial startup as well [Only valid for scheduled tasks]. Example startup_apps.json: [ { "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"}], "env": {"PATH": "/home/:/usr/bin/", "NAME": "ONE"}, "working_dir": "/home/etc", "run_as": "username", "run_every": "10s", "run_on_start": true }, { "name": "LS 2", "app_name": "/bin/ls", "app_arpgs": ["-lh"], "run_mode": "script", "create_log": true, "after": [{"module": "LS 0", "state": "exited"}], "working_dir": "/home/etc/Documents", "run_on_start": false, "run_at": "1,15,30 01:30:00 Monthly" } ] )"; } 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 >= 2) { 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, "--test", "-T"); if(i >= 0) { int j = i + 1; if(j > arg_list.size()) { printHelp(); exit(-1); } options.check_for_errors = true; std::cout << "Testing json file." << 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) { bool run_validity = i->checkValidity(); i->printInfo(out); 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; } } if(!run_validity) { out << "[ERR]: " << i->getModuleName() << " incorrect format for scheduled run. Please see help page." << std::endl; } else if((i->getScheduled()) && (!i->getRunOnStart())) { const auto* zone = std::chrono::current_zone(); auto local_now = zone->to_local(i->getNextTimeToRun()); out << "Module: " << i->getModuleName() << " is scheduled for execution at: " << std::format("{:%F %T}", local_now) << std::endl; } else if((i->getScheduled()) && (i->getRunOnStart())) { const auto* zone = std::chrono::current_zone(); auto local_now = zone->to_local(i->getNextTimeToRun()); out << "Module: " << i->getModuleName() << " will run on init and is also scheduled for execution at: " << std::format("{:%F %T}", local_now) << std::endl; } else { out << "Module: " << i->getModuleName() << " is to be launched on init." << i->getScheduled() << i->getRunOnStart() << run_validity << std::endl; } } return 0; } int main(int argc, char *argv[]) { parseConfig(); parseArgs(argc, argv); if(!std::filesystem::exists(options.log_directory)) { std::filesystem::create_directories(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; std::cerr << "startup_apps.json file is not readable from location: " << json_file_path << "." << 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; std::cerr << "Invalid JSON format in: " << json_file_path << "." << 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; std::cerr << "Field error in: " << json_file_path << ", check log for more details." << 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; std::cerr << "Field error in: " << json_file_path << ", check log for more details." << 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; std::cerr << "Field error in: " << json_file_path << ", check log for more details." << 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; std::unordered_map env_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; } } if(object.find("env") != object.end()) { json k = object["env"].get(); for (auto &it: k.items()) { env_list[it.key()] = it.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(); if(object.find("run_as") != object.end()) app_info.run_as = object["run_as"].get(); if(object.find("run_at") != object.end()) app_info.run_at = object["run_at"].get(); if(object.find("run_every") != object.end()) app_info.run_every = object["run_every"].get(); if(object.find("run_on_start") != object.end()) app_info.run_on_start = object["run_on_start"].get(); app_info.env = env_list; 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; log_file << "Checking App List for errors." << std::endl; std::optional list_check; if(options.check_for_errors) list_check = checkAppList(initial_app_list, list, std::cerr); else 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 { std::cerr << "Dependency error in: " << json_file_path << "check log for more details." << std::endl; log_file << "Errors present in app list; Please fix them" << std::endl; return -1; } if(options.check_for_errors) return 0; 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; }