diff --git a/.gitignore b/.gitignore index 5982d6b..faef43b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,8 @@ build/* *.sh *.json - +*.qt/* +Build/* +.qtcreator/* +*swp +*.cmake diff --git a/.qt/QtDeploySupport.cmake b/.qt/QtDeploySupport.cmake deleted file mode 100644 index 5dabcd3..0000000 --- a/.qt/QtDeploySupport.cmake +++ /dev/null @@ -1,45 +0,0 @@ -cmake_minimum_required(VERSION 3.16...3.21) - -# These are part of the public API. Projects should use them to provide a -# consistent set of prefix-relative destinations. -if(NOT QT_DEPLOY_BIN_DIR) - set(QT_DEPLOY_BIN_DIR "bin") -endif() -if(NOT QT_DEPLOY_LIB_DIR) - set(QT_DEPLOY_LIB_DIR "lib") -endif() -if(NOT QT_DEPLOY_PLUGINS_DIR) - set(QT_DEPLOY_PLUGINS_DIR "plugins") -endif() -if(NOT QT_DEPLOY_QML_DIR) - set(QT_DEPLOY_QML_DIR "qml") -endif() -if(NOT QT_DEPLOY_PREFIX) - set(QT_DEPLOY_PREFIX "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}") -endif() -if(QT_DEPLOY_PREFIX STREQUAL "") - set(QT_DEPLOY_PREFIX .) -endif() - -# These are internal implementation details. They may be removed at any time. -set(__QT_DEPLOY_SYSTEM_NAME "Linux") -set(__QT_DEPLOY_IS_SHARED_LIBS_BUILD "ON") -set(__QT_DEPLOY_TOOL "") -set(__QT_DEPLOY_IMPL_DIR "/home/alfred/custom_start_up_build/.qt") -set(__QT_DEPLOY_VERBOSE "") -set(__QT_CMAKE_EXPORT_NAMESPACE "Qt6") -set(__QT_DEPLOY_GENERATOR_IS_MULTI_CONFIG "0") -set(__QT_DEPLOY_ACTIVE_CONFIG "") -set(__QT_NO_CREATE_VERSIONLESS_FUNCTIONS "") -set(__QT_DEFAULT_MAJOR_VERSION "6") - -# Define the CMake commands to be made available during deployment. -set(__qt_deploy_support_files - "/usr/lib/aarch64-linux-gnu/cmake/Qt6Core/Qt6CoreDeploySupport.cmake" -) -foreach(__qt_deploy_support_file IN LISTS __qt_deploy_support_files) - include("${__qt_deploy_support_file}") -endforeach() - -unset(__qt_deploy_support_file) -unset(__qt_deploy_support_files) diff --git a/CMakeLists.txt b/CMakeLists.txt index 09bc533..1baee4e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,13 +1,16 @@ cmake_minimum_required(VERSION 3.16) -project(helloworld VERSION 1.0.0 LANGUAGES CXX) +project(StartUpRunner VERSION 1.0.0 LANGUAGES CXX) -set(CMAKE_CXX_STANDARD 23) +set(CMAKE_CXX_STANDARD 26) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(Boost CONFIG) -find_package(Qt6 REQUIRED COMPONENTS Core) +find_package(nlohmann_json CONFIG) -qt_add_executable(StartUpRunner main.cpp) +add_executable(StartUpRunner main.cpp + appinfo.h appinfo.cpp + executionmanager.h executionmanager.cpp + log.h log.cpp) -target_link_libraries(StartUpRunner PRIVATE Qt6::Core) +target_link_libraries(StartUpRunner PRIVATE nlohmann_json::nlohmann_json) diff --git a/appinfo.cpp b/appinfo.cpp new file mode 100644 index 0000000..5f52a10 --- /dev/null +++ b/appinfo.cpp @@ -0,0 +1,292 @@ +#include "appinfo.h" + +#include +#include + +AppInfo::~AppInfo() { + std::cout << "Module: " << getModuleName() << " finished ops." << std::endl; +} + +void AppInfo::setProgramName(std::string name) +{ + this->program_name = name; +} + +void AppInfo::setModuleName(std::string name) +{ + this->module_name = name; +} + +void AppInfo::setArgs(std::deque args) +{ + this->args = args; +} + +void AppInfo::setWorkingDir(std::string working_dir) +{ + this->working_dir = working_dir; +} + +void AppInfo::setAfter(std::unordered_map after) +{ + this->after = after; +} + +void AppInfo::setCreateLog(bool create_log) +{ + this->create_log = create_log; +} + +void AppInfo::setRunMode(std::string mode) +{ + if(mode == "script") { + this->mode = RunMode::script; + } + else if(mode == "daemon") { + this->mode = RunMode::daemon; + } +} + +void AppInfo::setFinished() +{ + this->state = State::exited; +} + +void AppInfo::setRunning() +{ + this->state = State::running; +} + +void AppInfo::insertParent(std::shared_ptr parent, State state) +{ + this->parents[parent] = state; +} + +bool AppInfo::isParentDependencyCleared(std::osyncstream &out) const +{ + for(auto &[key, value]: parents) { + if(key->getState() != value) { + out << key->getModuleName() << " not at state: " << stateToString(state) << + " but is at:" << stateToString(key->getState()) << std::endl; + return false; + } + } + return true; +} + +std::string AppInfo::getModuleName() const +{ + return module_name; +} + +std::string AppInfo::getProgramName() const +{ + return program_name; +} + +std::string AppInfo::getWorkingDir() const +{ + return working_dir; +} + +std::string AppInfo::getAfterString() const +{ + std::stringstream ss; + for(auto &[key,value]: after) { + ss << key << "[" << value << "], "; + } + std::string after = ss.str(); + after = after.substr(0,after.size() - 2); + return after; +} + +std::string AppInfo::getAfterKeys() const +{ + std::stringstream ss; + for(auto &[key,value]: after) { + ss << key << ", "; + } + std::string after = ss.str(); + after = after.substr(0,after.size() - 2); + return after; +} + +AppInfo::RunMode AppInfo::getRunMode() const +{ + return this->mode; +} + +AppInfo::State AppInfo::getState() const +{ + return this->state; +} + +void AppInfo::copyFromData(AppInfo::AppData &other) +{ + this->module_name = other.module_name; + this->program_name = other.program_name; + this->create_log = other.create_log; + this->working_dir = other.working_dir; + for(auto &i: other.args) { + this->args.push_back(i); + } + setRunMode(other.run_mode); +} + +std::unordered_map, AppInfo::State> AppInfo::getParents() const +{ + return this->parents; +} + +std::unordered_map AppInfo::getAfter() const +{ + return after; +} + +bool AppInfo::getCreateLog() +{ + return create_log; +} + +std::deque AppInfo::getArgs() const +{ + return args; +} + +AppInfo::AppInfo() { + +} + +bool AppInfo::operator==(const AppInfo &other) +{ + if(this->module_name == other.module_name && this->program_name == other.program_name + && this->args == other.args + && this->working_dir == other.working_dir + && this->after == other.after + && this->create_log == other.create_log + ) + return true; + else + return false; +} + +void AppInfo::printInfo(std::ostream &out) +{ + out << "************************************App************************************" << std::endl; + out << "Module: " << module_name << std::endl; + out << "App Name: " << program_name << std::endl; + out << "Arg List: " << std::endl; + int index = 0; + for(auto &i: args) { + out << "Arg[" << index++ << "]: " << i << std::endl; + } + out << "Run Mode: " << runmodeToString() << std::endl; + out << "Working Directory: " << working_dir << std::endl; + std::stringstream ss; + for(auto &[key,value]: after) { + ss << key << "[" << value << "], "; + } + std::string after = ss.str(); + after = after.substr(0,after.size() - 2); + out << "After: " << after << std::endl; + std::stringstream ss2; + for(auto &[key, value]: parents) { + if(!key) { + ss2 << "Invalid Parent" << std::endl; + } + else if(value == State::running) { + ss2 << key->getModuleName() << " At: " << key << " with state: running." << std::endl; + } + else if(value == State::exited) { + ss2 << key->getModuleName() << " At: " << key << " with state: exited." << std::endl; + } + else { + ss2 << key->getModuleName() << " At: " << key << " with state: unknown." << std::endl; + } + } + if(parents.size() != 0) { + out << "Parents: " << parents.size() << " modules." << std::endl; + out << ss2.str(); + } + else { + out << "Parents: None." << std::endl; + } + out << "Create Log: " << create_log << std::endl; + out << "************************************End************************************" << std::endl; +} + +std::string AppInfo::runmodeToString() const +{ + switch(this->mode) { + case RunMode::script: + return "script"; + case RunMode::daemon: + return "daemon"; + default: + return "unknown"; + } +} + +std::string AppInfo::stateToString() const +{ + return stateToString(this->state); +} + +std::string AppInfo::stateToString(State s) const +{ + switch(s) { + case State::initial: + return "initial"; + case State::running: + return "running"; + case State::exited: + return "exited"; + default: + return "unknown"; + } +} + +void AppInfo::AppData::printInfo(std::ostream &out) +{ + out << "************************************App************************************" << std::endl; + out << "Module: " << AppData::module_name << std::endl; + out << "App Name: " << AppData::program_name << std::endl; + out << "Arg List: " << std::endl; + int index = 0; + for(auto &i: AppData::args) { + out << "Arg[" << index++ << "]: " << i << std::endl; + } + out << "Run Mode: " << run_mode << std::endl; + out << "Working Directory: " << AppData::working_dir << std::endl; + std::stringstream ss; + for(auto &[key,value]: AppData::after) { + ss << key << "[" << value << "], "; + } + std::string after = ss.str(); + after = after.substr(0,after.size() - 2); + out << "After: " << after << std::endl; + out << "Create Log: " << AppData::create_log << std::endl; + out << "************************************End************************************" << std::endl; +} + +std::string AppInfo::AppData::getAfterString() const +{ + std::stringstream ss; + for(auto &[key,value]: after) { + ss << key << "[" << value << "], "; + } + std::string after = ss.str(); + after = after.substr(0,after.size() - 2); + return after; +} + +std::string AppInfo::AppData::getAfterKeys() const +{ + std::stringstream ss; + for(auto &[key,value]: after) { + ss << key << ", "; + } + std::string after = ss.str(); + after = after.substr(0,after.size() - 2); + return after; +} diff --git a/appinfo.h b/appinfo.h new file mode 100644 index 0000000..a830988 --- /dev/null +++ b/appinfo.h @@ -0,0 +1,84 @@ +#ifndef APPINFO_H +#define APPINFO_H + +#include +#include +#include +#include +#include +#include + +class AppInfo +{ + typedef std::unordered_map string_map; +public: + enum class State { + initial, + running, + exited + }; + enum class RunMode { + script, + daemon + }; + struct AppData { + std::string program_name; + std::string module_name; + std::deque args; + std::string working_dir; + string_map after; + std::string run_mode; + bool create_log = false; + void printInfo(std::ostream &out); + std::string getAfterString() const; + std::string getAfterKeys() const; + }; + +private: + std::string program_name; + std::string module_name; + std::deque args; + std::string working_dir; + string_map after; + bool create_log = false; + std::unordered_map, State> parents; + State state = State::initial; + RunMode mode = RunMode::script; +public: + void setProgramName(std::string name); + void setModuleName(std::string name); + void setArgs(std::deque args); + void setWorkingDir(std::string working_dir); + void setAfter(string_map after); + void setCreateLog(bool create_log); + void setRunMode(std::string mode); + void setFinished(); + void setRunning(); + void insertParent(std::shared_ptr parent, State state); + bool isParentDependencyCleared(std::osyncstream &out) const; + std::string getModuleName() const; + std::string getProgramName() const; + std::deque getArgs() const; + std::string getWorkingDir() const; + string_map getAfter() const; + std::string getAfterString() const; + std::string getAfterKeys() const; + RunMode getRunMode() const; + State getState() const; + std::unordered_map, State> getParents() const; + void copyFromData(AppInfo::AppData &other); + bool getCreateLog(); + AppInfo(); + ~AppInfo(); + AppInfo(const AppInfo &other) = delete; + AppInfo(const AppInfo &&other) = delete; + AppInfo &operator=(const AppInfo &other) = delete; + bool operator==(const AppInfo &other); + void printInfo(std::ostream& out); +private: + std::string runmodeToString() const; + std::string stateToString() const; + std::string stateToString(State s) const; +}; + +#endif // APPINFO_H diff --git a/executionmanager.cpp b/executionmanager.cpp new file mode 100644 index 0000000..dda1fac --- /dev/null +++ b/executionmanager.cpp @@ -0,0 +1,205 @@ +#include "executionmanager.h" +#include +#include +#include +#include + +extern "C" { +#include +#include +#include +} + +ExecutionManager::ExecutionManager(int threads) : + log_file{nullptr} +{ + this->threads = threads; + if(this->threads <= 1) { + this->threads = 2; // Does not work with threads = 1; + } +} + +void ExecutionManager::setLogFile(const Log &log_file) +{ + this->log_file.copyFromOther(log_file); +} + +void ExecutionManager::setLogFileMaxSize(long long size) +{ + log_file_max_size = size; +} + +void ExecutionManager::setLogDirectoy(std::string log_dir) +{ + this->log_dir = log_dir; + if(!std::filesystem::exists(log_dir)) { + std::filesystem::create_directories(log_dir); + log_file << "Created log dir: " << log_dir << std::endl; + } +} + +void ExecutionManager::setJobs(std::vector > &job_list) +{ + this->job_list = std::move(job_list); +} + +void ExecutionManager::run() +{ + finished = false; + initial_list_size = job_list.size(); + while(!job_list.empty()) { + for(int i=active_thread_count.load(); i < threads ;i++) { + int val = 0; + active_thread_count.fetch_add(1); + { + std::lock_guard lock_get(this->job_list_mutex); + val = index_count++; + if(job_list.empty()) { + log_file << "Main Thread: Job list is empty, waiting for finish." << std::endl; + break; + } + if(val > (initial_list_size - 1)) { + log_file << "Main Thread: All jobs queued." << std::endl; + break; + } + } + std::shared_ptr thread = std::make_shared([this, val](){ + std::shared_ptr ptr; + std::osyncstream os(log_file.rdbuf()); + size_t list_size = 0; + { + std::lock_guard lock_get(this->job_list_mutex); + if(job_list.size() == 0) { + os << std::vformat("Thread[{}] called while the job list was empty: ", std::make_format_args(val)) + << std::endl; + return; + } + ptr = this->job_list[0]; + job_list.erase(job_list.begin()); + list_size = job_list.size(); + } + std::string module_name = ptr->getModuleName(); + os << std::vformat("Thread[{}] running: ", std::make_format_args(val)) + << module_name << " current list size: " << list_size << std::endl; + int old = state_change.load(); + while(!ptr->isParentDependencyCleared(os)) + { + state_change.wait(old); + old = state_change.load(); + os << std::vformat("Thread[{}] waiting for dependency: ", std::make_format_args(val)) + << module_name << " current state count: " << old << std::endl; + } + + // Create variables required for forking + std::string dir = ptr->getWorkingDir(); + std::string program_name = ptr->getProgramName(); + std::string program_log_name_temp = module_name + ".log"; + std::string program_log_name = ""; + for(auto &ch: program_log_name_temp) { + if(ch != ' ') { + program_log_name += ch; + } + else { + program_log_name += '_'; + } + } + std::string program_log_file = std::filesystem::path(this->log_dir) / program_log_name; + bool create_log = ptr->getCreateLog(); + std::deque pargs = ptr->getArgs(); + int total_args = pargs.size() + 2; + char *args[total_args]; + args[0] = program_name.data(); + for(int i=1,index=0; index < pargs.size(); i++,index++) { + args[i] = pargs[index].data(); + } + args[total_args - 1] = (char *)NULL; + // Do fork and run + auto pid = fork(); + FILE *popen_file = nullptr; + switch(pid) { + case 0: { + if(!dir.empty()) { + int err = chdir(dir.c_str()); + if(err != 0) { + os << "Child changing dir failed: " << err << std::endl; + } + } + os << "Child running: " << program_name << " with args: " << args << std::endl; + bool log_file_exists = std::filesystem::exists(program_log_file); + if(create_log) { + int output_fd = -99; + if(!log_file_exists) + output_fd = open(program_log_file.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); + else { + auto size = std::filesystem::file_size(program_log_file); + if(size < log_file_max_size) + { + output_fd = open(program_log_file.c_str(), O_WRONLY | O_APPEND, S_IRUSR | S_IWUSR); + } + else { + output_fd = open(program_log_file.c_str(), O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR); + os << "Truncating log file: " << program_log_file << std::endl; + } + } + if (output_fd < 0) { + std::cout << "Failed to open log file " << program_log_file << ":" + << std::endl; + std::perror("open"); + std::exit(1); + } + if (dup2(output_fd, STDOUT_FILENO) < 0) { + std::perror("dup2 (stdout)"); + std::exit(1); + } + if (dup2(output_fd, STDERR_FILENO) < 0) { + std::perror("dup2 (stderr)"); + std::exit(1); + } + } + execv(program_name.c_str(), args); + exit(0); + } + default: { + // After fork in parent do + ptr->setRunning(); + state_change.fetch_add(1); + state_change.notify_all(); + break; + } + } + + // If script daemon wait here + AppInfo::RunMode mode = ptr->getRunMode(); + int status; + if(mode == AppInfo::RunMode::script) { + os << "Run mode is script so will wait for child to finish: " << module_name << std::endl; + int wait = waitpid(pid, &status, WUNTRACED); + os << "Parent wait finished: " << wait << " child status: " << status << std::endl; + } + + // Set finished + ptr->setFinished(); + state_change.fetch_add(1); + state_change.notify_all(); + os << std::vformat("Thread[{}] finished: ", std::make_format_args(val)) + << module_name<< std::endl; + active_thread_count.fetch_min(1); + }); + thread_list.push_back(thread); + } + std::lock_guard lock_get(this->job_list_mutex); + if(job_list.empty()) { + log_file << "Main Thread: Job list is empty, waiting for finish." << std::endl; + break; + } + if((index_count) > (initial_list_size - 1)) { + log_file << "Main Thread: All jobs queued." << std::endl; + break; + } + } + for(auto i: thread_list) { + if(i->joinable()) + i->join(); + } + finished = true; +} diff --git a/executionmanager.h b/executionmanager.h new file mode 100644 index 0000000..d95e3dd --- /dev/null +++ b/executionmanager.h @@ -0,0 +1,34 @@ +#ifndef EXECUTIONMANAGER_H +#define EXECUTIONMANAGER_H + +#include +#include +#include +#include +#include "appinfo.h" +#include "log.h" + +class ExecutionManager +{ + bool finished = false; + int threads; + int index_count = 0; + int initial_list_size = 0; + long long int log_file_max_size = 0; + std::string log_dir; + std::mutex job_list_mutex; + std::atomic state_change = 0; + std::atomic active_thread_count = 0; + std::vector> thread_list; + std::vector> job_list; + Log log_file; +public: + ExecutionManager(int threads=16); + void setLogFile(const Log &other); + void setLogFileMaxSize(long long int size); + void setLogDirectoy(std::string log_dir); + void setJobs(std::vector>& job_list); + void run(); +}; + +#endif // EXECUTIONMANAGER_H diff --git a/log.cpp b/log.cpp new file mode 100644 index 0000000..07b84d6 --- /dev/null +++ b/log.cpp @@ -0,0 +1,79 @@ +#include "log.h" + +Log::Log(std::string file_name, int max_size) : + log_file{nullptr} +{ + bool out_mode = false; + if(std::filesystem::exists(file_name)) + if(std::filesystem::file_size(file_name) < max_size) + log_file_s.open(file_name, std::fstream::app); + else { + out_mode = true; + std::cout << "Log file size greater than: " << max_size << std::endl; + std::cout << "Truncating..." << std::endl; + log_file_s.open(file_name, std::fstream::out); + } + else { + log_file_s.open(file_name, std::fstream::out); + out_mode = true; + } + if(log_file_s.is_open()) + { + std::cout << "Logs openned in " << (( out_mode ) ? "truncate" : "append") << " mode" << std::endl; + log_file.rdbuf(log_file_s.rdbuf()); + } + else { + std::cout << "Unable to open log file" << std::endl; + } +} + +Log::Log(int *other) : + log_file{nullptr} +{ + if(other == nullptr) + return; + exit(-1); +} + +Log::~Log() +{ + if(log_file_s.is_open()) { + log_file_s.flush(); + log_file_s.close(); + } +} + +Log::Log(const Log &other): + log_file{nullptr} +{ + log_file.rdbuf(other.log_file.rdbuf()); +} + +void Log::copyFromOther(const Log &other) +{ + log_file.rdbuf(other.log_file.rdbuf()); +} + +bool Log::isOpen() +{ + return log_file_s.is_open(); +} + +std::ostream &Log::rdbuf() +{ + return log_file; +} + +Log &Log::operator<<(std::ostream &(*f)(std::ostream &)) { + if(f == (std::basic_ostream& (*)(std::basic_ostream&)) &std::endl) { + log_file << f; + std::cout << f; + } + return *this; +} + +Log &Log::operator<<(std::filesystem::path p) { + log_file << p.string(); + std::cout << p.string(); + return *this; +} \ No newline at end of file diff --git a/log.h b/log.h new file mode 100644 index 0000000..1e620b1 --- /dev/null +++ b/log.h @@ -0,0 +1,41 @@ +#ifndef LOG_H +#define LOG_H + +#include +#include +#include + +class Log { + std::ostream log_file; + std::fstream log_file_s; +public: + explicit Log(std::string file_name, int max_size); + explicit Log(int *other); + ~Log(); + Log(const Log &other); + void copyFromOther(const Log &other); + bool isOpen(); + std::ostream& rdbuf(); + Log& operator<<(std::filesystem::path p); + Log& operator<<(std::ostream&(*f)(std::ostream&)); + template + Log &operator<<(T t) { + log_file << t; + std::cout << t; + return *this; + } + template + Log &operator<<(const T *t) { + log_file << t; + std::cout << t; + return *this; + } + template + Log &operator<<(T *t) { + log_file << t; + std::cout << t; + return *this; + } +}; + +#endif // LOG_H diff --git a/main.cpp b/main.cpp index ddce7ff..56768f8 100644 --- a/main.cpp +++ b/main.cpp @@ -2,108 +2,560 @@ #include #include #include -#include -#include -#include -#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); + 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; + std::cout << data; + } + std::cout << std::endl; + 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::cout << "Log file max size: " << options.log_file_max_size << std::endl; +} + +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 << "\tname[string] : Name of the module." << std::endl; + std::cout << "\tapp_name[string] : Path to the program with its name." << std::endl; + std::cout << "\tapp_args[list[string]] : List of arguments to the program." << std::endl; + std::cout << "\trun_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 << "\tworking_dir[string] : Working directory of the program." << std::endl; + std::cout << "\tcreate_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 > c) { + 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 > c) { + 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 > c) { + 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 > c) { + 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); + } + } +} + +// void forkAndWait(std::string program_name, std::string name, std::string args, std::string dir, bool bash = false) { +// auto pid = fork(); +// switch(pid) { +// case 0: { +// if(!dir.empty()) { +// int err = chdir(dir.c_str()); +// if(err != 0) { +// std::cout << "Child changing dir failed: " << err << std::endl; +// } +// } +// std::cout << "Child running: " << program_name << " with args: " << args; +// if(bash) +// execl(program_name.c_str(), program_name.c_str(), "-c", args.c_str(), (char *)NULL); +// else +// execl(program_name.c_str(), program_name.c_str(), args.c_str(), (char *)NULL); +// exit(0); +// } +// default: { +// int status; +// int wait = waitpid(pid, &status, WUNTRACED); +// std::cout << "Parent wait finished: " << wait << " child status: " << status << std::endl; +// } +// } +// } + +// void forkAndContinue(std::string program_name, std::string name, std::string args, std::string dir, bool bash = false) { +// auto pid = fork(); +// if(pid == 0) { +// if(!dir.empty()) { +// int err = chdir(dir.c_str()); +// if(err != 0) { +// std::cout << "Child changing dir failed: " << err << std::endl; +// } +// } +// std::cout << "Child running: " << program_name << " with args: " << args; +// if(bash) +// execl(program_name.c_str(), program_name.c_str(), "-c", args.c_str(), (char *)NULL); +// else +// execl(program_name.c_str(), program_name.c_str(), args.c_str(), (char *)NULL); +// exit(0); +// } +// } + +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(); item++) { + if(item->getAfterString().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); + } + 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[]) { - QCoreApplication app(argc, 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: " << get_current_time(); + 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); + } - if(!std::filesystem::exists("/home/ud/logs/")) { - std::filesystem::create_directory("/home/ud/logs"); - } - std::fstream log_file("/home/ud/logs/StartUpRunner.log", std::fstream::app); - if(!log_file.is_open()) { - log_file.open("/home/ud/logs/StartUpRunner.log", std::fstream::out); - } - log_file << "-------- Starting app: " << get_current_time(); - log_file << "Current path: " << std::filesystem::current_path() << "\n"; - if(!std::filesystem::exists("startup_apps.json")) { - log_file << "startup_apps.json not found exiting.\n"; - log_file.flush(); - log_file.close(); - exit(-1); - } - std::string data = ""; - std::string t_data = ""; - std::fstream json_file; - json_file.open("startup_apps.json", std::fstream::in); - if(!json_file.is_open()) { - std::cout << "Why is the startup_apps.json file not open?" << std::endl; - return -1; - } - std::stringstream test; - test << json_file.rdbuf(); - json_file.close(); - QByteArray arr_data = QByteArray::fromStdString(test.str()); - QJsonArray top = QJsonDocument::fromJson(arr_data).array(); - std::vector active_process_list; - for(auto i: top) { - auto object = i.toObject(); - std::string name = object["app_name"].toString().toStdString(); - std::string args = ""; - QStringList sargs; - for(auto k: object["app_args"].toArray()) { - args += k.toString().toStdString(); - sargs << k.toString(); + 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); } - QString mode = object["mode"].toString("wait"); - QString working_dir = object["working_dir"].toString("/home/ud/StartUpApps/"); - std::string file_name = std::filesystem::path(name).stem().string(); - std::string log_file_name = std::format("/home/ud/logs/{}.log", file_name); - bool create_log_file = object["create_log"].toBool(); - std::string command; - if(create_log_file) - command = std::format("{} {} > {}", name, args, log_file_name); - else - command = std::format("{} {}", name, args); - log_file << "Running: " << command << " with working dir: " << working_dir.toStdString() << "\n"; - QProcess *proc = new QProcess(); - QObject::connect(proc, &QProcess::started, [&, command, proc, working_dir] () { - log_file << "Started process: " << command << " with pid: " << QString::number(proc->processId()).toStdString() << " with working directory as: " << working_dir.toStdString() << std::endl; - }); - if (mode == "wait") { - - QObject::connect(proc, &QProcess::finished, [&, command, proc] (int exitcode, QProcess::ExitStatus status) { - auto process = proc; - if(status == QProcess::NormalExit) - log_file << "Process: " << command << " with pid: " << QString::number(proc->processId()).toStdString() << std::endl << " exited with exit code: " << exitcode << std::endl; - else - log_file << "Process: " << command << " with pid: " << QString::number(proc->processId()).toStdString() << std::endl << " CRASHED with exit code: " << exitcode << std::endl; - auto it = std::find(active_process_list.begin(), active_process_list.end(), process); - active_process_list.erase(it); - if(active_process_list.size() == 0) { - log_file << "-------- All processes finished." << std::endl; - log_file.flush(); - log_file.close(); - app.exit(0); - } - }); - active_process_list.push_back(proc); + 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; } - if(!working_dir.isEmpty()) - proc->setWorkingDirectory(working_dir); - proc->setStandardOutputFile(QString::fromStdString(log_file_name)); - proc->setProgram(QString::fromStdString(name)); - proc->setArguments(sargs); - if(mode == "wait") - proc->start(QString::fromStdString(name), sargs); - else - proc->startDetached(QString::fromStdString(name), sargs); - } - return app.exec(); + 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; }