Compare commits

...
16 changed files with 1331 additions and 25687 deletions

6
.gitignore vendored
View file

@ -1,4 +1,8 @@
build/* build/*
*.sh *.sh
*.json *.json
*.qt/*
Build/*
.qtcreator/*
*swp
*.cmake

Binary file not shown.

View file

@ -1,6 +0,0 @@
# ninja log v6
1 62 1754932580772976334 helloworld_autogen/timestamp 7853c8a1c93b2124
1 62 1754932580772976334 helloworld_autogen/mocs_compilation.cpp 7853c8a1c93b2124
1 62 1754932580772976334 /home/alfred/custom_start_up_build/helloworld_autogen/timestamp 7853c8a1c93b2124
1 62 1754932580772976334 /home/alfred/custom_start_up_build/helloworld_autogen/mocs_compilation.cpp 7853c8a1c93b2124
62 80 1754932580784976334 CMakeFiles/helloworld.dir/helloworld_autogen/mocs_compilation.cpp.o 6c2b962c6a90f545

View file

@ -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)

View file

@ -1,13 +1,16 @@
cmake_minimum_required(VERSION 3.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) set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Boost CONFIG) 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)

322
appinfo.cpp Normal file
View file

@ -0,0 +1,322 @@
#include "appinfo.h"
#include <iostream>
#include <sstream>
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<std::string> args)
{
this->args = args;
}
void AppInfo::setWorkingDir(std::string working_dir)
{
this->working_dir = working_dir;
}
void AppInfo::setEnv(string_map env)
{
this->env = env;
}
void AppInfo::setAfter(std::unordered_map<std::string, std::string> 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<AppInfo> 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(value) <<
" 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;
}
AppInfo::string_map AppInfo::getEnv() const
{
return env;
}
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);
}
this->after = other.after;
this->env = other.env;
setRunMode(other.run_mode);
}
std::unordered_map<std::shared_ptr<AppInfo>, AppInfo::State> AppInfo::getParents() const
{
return this->parents;
}
std::unordered_map<std::string, std::string> AppInfo::getAfter() const
{
return after;
}
bool AppInfo::getCreateLog()
{
return create_log;
}
std::deque<std::string> 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();
if(!after.empty() && after.size() >= 2)
after = after.substr(0,after.size() - 2);
out << "After: " << after << std::endl;
std::stringstream ss2, ss3;
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;
}
for(auto &[key,value]: env) {
ss3 << key << "=" << value << ", ";
}
std::string env = ss3.str();
if(!env.empty() && env.size() >= 2)
env = env.substr(0,env.size() - 2);
out << "Environment: " << env << 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, ss2;
for(auto &[key,value]: AppData::after) {
ss << key << "[" << value << "], ";
}
std::string after = ss.str();
if(!after.empty() && after.size() >= 2)
after = after.substr(0,after.size() - 2);
out << "After: " << after << std::endl;
ss.clear();
ss.flush();
for(auto &[key,value]: AppData::env) {
ss2 << key << "=" << value << ", ";
}
std::string env = ss2.str();
if(!env.empty() && env.size() >= 2)
env = env.substr(0,env.size() - 2);
out << "Environment: " << env << 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;
}

89
appinfo.h Normal file
View file

@ -0,0 +1,89 @@
#ifndef APPINFO_H
#define APPINFO_H
#include <string>
#include <deque>
#include <vector>
#include <memory>
#include <unordered_map>
#include <syncstream>
#include <atomic>
class AppInfo
{
typedef std::unordered_map<std::string, std::string> 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<std::string> args;
std::string working_dir;
string_map after;
string_map env;
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<std::string> args;
std::string working_dir;
string_map after;
string_map env;
bool create_log = false;
std::unordered_map<std::shared_ptr<AppInfo>, State> parents;
std::atomic<State> state{State::initial};
RunMode mode = RunMode::script;
public:
void setProgramName(std::string name);
void setModuleName(std::string name);
void setArgs(std::deque<std::string> args);
void setWorkingDir(std::string working_dir);
void setAfter(string_map after);
void setEnv(string_map env);
void setCreateLog(bool create_log);
void setRunMode(std::string mode);
void setFinished();
void setRunning();
void insertParent(std::shared_ptr<AppInfo> parent, State state);
bool isParentDependencyCleared(std::osyncstream &out) const;
std::string getModuleName() const;
std::string getProgramName() const;
std::deque<std::string> getArgs() const;
std::string getWorkingDir() const;
string_map getEnv() const;
string_map getAfter() const;
std::string getAfterString() const;
std::string getAfterKeys() const;
RunMode getRunMode() const;
State getState() const;
std::unordered_map<std::shared_ptr<AppInfo>, 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

215
executionmanager.cpp Normal file
View file

@ -0,0 +1,215 @@
#include "executionmanager.h"
#include <iostream>
#include <format>
#include <syncstream>
#include <filesystem>
extern "C" {
#include <fcntl.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
}
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<std::shared_ptr<AppInfo> > &job_list)
{
this->job_list = std::move(job_list);
}
void ExecutionManager::run()
{
finished = false;
initial_list_size = job_list.size();
while(true) {
for(int i=active_thread_count.load(); i < threads ;i++) {
int val = 0;
{
std::lock_guard<std::mutex> 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;
}
}
active_thread_count.fetch_add(1);
std::shared_ptr<std::thread> thread = std::make_shared<std::thread>([this, val](){
std::shared_ptr<AppInfo> ptr;
std::osyncstream os(log_file.rdbuf());
size_t list_size = 0;
{
std::lock_guard<std::mutex> 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<std::string> pargs = ptr->getArgs();
int total_args = pargs.size() + 2;
std::vector<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;
bool log_file_exists = std::filesystem::exists(program_log_file);
long long size = 0;
if(log_file_exists)
size = std::filesystem::file_size(program_log_file);
// Do fork and run
auto pid = fork();
FILE *popen_file = nullptr;
std::unordered_map<std::string,std::string> env = ptr->getEnv();
switch(pid) {
case 0: {
if(!dir.empty()) {
int err = chdir(dir.c_str());
}
for(auto &[key,value]: env) {
setenv(key.data(), value.data(), 1);
}
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 {
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);
}
}
if (output_fd < 0) {
_exit(3);
}
if (dup2(output_fd, STDOUT_FILENO) < 0) {
_exit(3);
}
if (dup2(output_fd, STDERR_FILENO) < 0) {
_exit(3);
}
close(output_fd);
}
execv(program_name.c_str(), args.data());
_exit(1);
}
case -1: {
os << "Fork failed for module: " << module_name << std::endl;
ptr->setFinished(); // Or a dedicated failed state
state_change.fetch_add(1);
state_change.notify_all();
break;
}
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);
ptr->setFinished();
os << "Parent wait finished: " << wait << " child status: " << status << std::endl;
}
// Set finished
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_sub(1);
});
thread_list.push_back(thread);
}
{
std::lock_guard<std::mutex> 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;
}
}
if (active_thread_count.load() >= threads) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
for(auto i: thread_list) {
if(i->joinable())
i->join();
}
finished = true;
}

34
executionmanager.h Normal file
View file

@ -0,0 +1,34 @@
#ifndef EXECUTIONMANAGER_H
#define EXECUTIONMANAGER_H
#include <mutex>
#include <memory>
#include <atomic>
#include <thread>
#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<int> state_change = 0;
std::atomic<int> active_thread_count = 0;
std::vector<std::shared_ptr<std::thread>> thread_list;
std::vector<std::shared_ptr<AppInfo>> 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<std::shared_ptr<AppInfo>>& job_list);
void run();
};
#endif // EXECUTIONMANAGER_H

25526
json.hpp

File diff suppressed because it is too large Load diff

79
log.cpp Normal file
View file

@ -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<char>& (*)(std::basic_ostream<char>&)) &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;
}

41
log.h Normal file
View file

@ -0,0 +1,41 @@
#ifndef LOG_H
#define LOG_H
#include <fstream>
#include <iostream>
#include <filesystem>
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<typename T>
Log &operator<<(T t) {
log_file << t;
std::cout << t;
return *this;
}
template<typename T>
Log &operator<<(const T *t) {
log_file << t;
std::cout << t;
return *this;
}
template<typename T>
Log &operator<<(T *t) {
log_file << t;
std::cout << t;
return *this;
}
};
#endif // LOG_H

613
main.cpp
View file

@ -2,108 +2,537 @@
#include <fstream> #include <fstream>
#include <chrono> #include <chrono>
#include <format> #include <format>
#include <QObject> #include <nlohmann/json.hpp>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
#include <QString> #include <deque>
#include <QCoreApplication> #include <vector>
#include <QProcess> #include <cctype>
#include "appinfo.h"
#include "executionmanager.h"
#include "log.h"
extern "C" {
#include <sys/types.h>
#include <pwd.h>
#include <unistd.h> #include <unistd.h>
}
char *get_current_time() { char *get_current_time() {
const auto now = std::chrono::system_clock::now(); const auto now = std::chrono::system_clock::now();
const std::time_t t_c = std::chrono::system_clock::to_time_t(now); const std::time_t t_c = std::chrono::system_clock::to_time_t(now);
return std::ctime(&t_c); 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<long long int>();
}
}
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.2 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] after[{name,state}] : List containing names and state of modules." << std::endl;
std::cout << "\t\tname[string] : Name of the other module." << std::endl;
std::cout << "\t\tstate[string]: State of the other module." << std::endl;
std::cout << "\t\t\t\tState exited : Specified module must have finished execution." << std::endl;
std::cout << "\t\t\t\tState running : Specified module must be running." << std::endl;
std::cout << "\t\tThis module will be launched after the module found\n\t\t"
"in this list using name is at the state specified as state." << std::endl;
std::cout << "\t[OPTIONAL] env{} : Additional enviroment variables." << std::endl;
std::cout << "\t\tEvery key in this object will be used as environment name,\n\t\tand every corresponding value as its value." << 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"}],
"env": {"PATH": "/home/:/usr/bin/", "NAME": "ONE"}
"working_dir": "/home/etc"
}
]
)" << std::endl;
}
int contains(std::vector<std::string> &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<std::string> arg_list;
for(int i=1; i<c; i++) {
arg_list.push_back(v[i]);
}
bool contains_any_known_arg = false;
int i = contains(arg_list, "--json", "-j");
if(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<str.size(); i++) {
if(!front_found) {
if(str[i] == ' ' || str[i] == '\t' || str[i] == '\n' || str[i] == '\v' || str[i] == '\r')
continue;
else {
front_found = true;
ch += str[i];
continue;
}
}
else
ch += str[i];
}
auto pos = ch.find_last_not_of(" \t\n\v\r");
if(pos == std::string::npos)
return ch;
ch.erase(pos+1);
return ch;
}
std::vector<std::string> string_split(std::string s, const std::string& delimiter) {
std::vector<std::string> 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<std::shared_ptr<AppInfo>>& 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<std::shared_ptr<AppInfo>> &list, const AppInfo::AppData &item) {
std::map<std::string, bool> 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<int> checkAppList(std::deque<AppInfo::AppData> &copy_list, std::vector<std::shared_ptr<AppInfo>> &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<AppInfo> app_info = std::make_shared<AppInfo>();
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<AppInfo> app_info = std::make_shared<AppInfo>();
app_info->copyFromData(*item);
for(auto &[key, value]: item->after) {
bool found = false;
for(int i=0;i<after_list.size();i++) {
if(tolower(string_strip(after_list[i]->getModuleName())) == 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[]) { 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 --------" << 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<AppInfo::AppData> initial_app_list;
for(auto i = file.begin(); i != file.end(); i++) {
AppInfo::AppData app_info;
auto object = i->get<json>();
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/")) { app_info.program_name = object["app_name"].get<std::string>();
std::filesystem::create_directory("/home/ud/logs"); std::string args = "";
} std::deque<const char*> c_args;
std::fstream log_file("/home/ud/logs/StartUpRunner.log", std::fstream::app); std::deque<std::string> arg_list;
if(!log_file.is_open()) { if(object.find("app_args") != object.end()) {
log_file.open("/home/ud/logs/StartUpRunner.log", std::fstream::out); for(const auto &k: object["app_args"].get<json>()) {
} std::string str = k.get<std::string>();
log_file << "-------- Starting app: " << get_current_time(); args += str + " ";
log_file << "Current path: " << std::filesystem::current_path() << "\n"; arg_list.push_back(str);
if(!std::filesystem::exists("startup_apps.json")) { c_args.push_back(str.c_str());
log_file << "startup_apps.json not found exiting.\n"; }
log_file.flush(); }
log_file.close(); std::unordered_map<std::string, std::string> after_list;
exit(-1); std::unordered_map<std::string, std::string> env_list;
} if(object.find("after") != object.end()) {
std::string data = ""; for(const auto &k: object["after"].get<json>()) {
std::string t_data = ""; auto l = k.get<json>();
std::fstream json_file; auto key = l["module"].get<std::string>();
json_file.open("startup_apps.json", std::fstream::in); auto value = l["state"].get<std::string>();
if(!json_file.is_open()) { after_list[key] = value;
std::cout << "Why is the startup_apps.json file not open?" << std::endl; }
return -1; }
} if(object.find("env") != object.end()) {
std::stringstream test; json k = object["env"].get<json>();
test << json_file.rdbuf(); for (auto &it: k.items()) {
json_file.close(); env_list[it.key()] = it.value();
QByteArray arr_data = QByteArray::fromStdString(test.str()); }
QJsonArray top = QJsonDocument::fromJson(arr_data).array(); }
std::vector<QProcess *> active_process_list; app_info.args = arg_list;
for(auto i: top) { if(object.find("working_dir") != object.end())
auto object = i.toObject(); app_info.working_dir = object["working_dir"].get<std::string>();
std::string name = object["app_name"].toString().toStdString(); if(object.find("create_log") != object.end())
std::string args = ""; app_info.create_log = object["create_log"].get<bool>();
QStringList sargs; app_info.env = env_list;
for(auto k: object["app_args"].toArray()) { app_info.after = after_list;
args += k.toString().toStdString(); app_info.module_name = object["name"].get<std::string>();
sargs << k.toString(); app_info.run_mode = object["run_mode"].get<std::string>();
app_info.printInfo(log_file.rdbuf());
initial_app_list.push_back(app_info);
} }
QString mode = object["mode"].toString("wait"); std::vector<std::shared_ptr<AppInfo>> list;
QString working_dir = object["working_dir"].toString("/home/ud/StartUpApps/"); auto list_check = checkAppList(initial_app_list, list, log_file.rdbuf());
std::string file_name = std::filesystem::path(name).stem().string(); if(list_check.has_value()) {
std::string log_file_name = std::format("/home/ud/logs/{}.log", file_name); log_file << "No errors found in app list." << std::endl;
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);
} }
if(!working_dir.isEmpty()) else {
proc->setWorkingDirectory(working_dir); log_file << "Errors present in app list; Please fix them" << std::endl;
proc->setStandardOutputFile(QString::fromStdString(log_file_name)); return -1;
proc->setProgram(QString::fromStdString(name)); }
proc->setArguments(sargs); for(int i=0;i<list.size();i++) {
if(mode == "wait") list[i]->printInfo(log_file.rdbuf());
proc->start(QString::fromStdString(name), sargs); }
else log_file << "Order of Execution -> " << std::endl;
proc->startDetached(QString::fromStdString(name), sargs); printListModules(list, log_file.rdbuf());
} ExecutionManager manager{options.threads};
return app.exec(); manager.setLogFile(log_file);
manager.setLogFileMaxSize(options.log_file_max_size);
manager.setLogDirectoy(options.log_directory);
manager.setJobs(list);
manager.run();
return 0;
} }

View file

@ -1,11 +0,0 @@
[
{
"app_name": "/home/ud/StartUpApps/piFinder",
"app_args": [],
"mode": "no-wait"
},
{
"app_name": "/home/ud/git_external/acer-predator-turbo-and-rgb-keyboard-linux-module/install.sh",
"working_dir": "/home/ud/git_external/acer-predator-turbo-and-rgb-keyboard-linux-module/"
}
]

4
startup_service.dinit Normal file
View file

@ -0,0 +1,4 @@
type = scripted
command = /path_to_executable/StartUpRunner --log_dir /var/logs/StartUpRunner/ --json /path_to_startup_json/startup_apps.json
logfile = /var/log/dinit/StartUpRunner.log
after = network.target

12
startup_service.systemd Normal file
View file

@ -0,0 +1,12 @@
[Unit]
Description=A Simple Startup Runner Service
After=network.target
[Service]
Type=forking
ExecStart=/path_to_executable/StartUpRunner
KillMode=process
WorkingDirectory=/path_to_executable/StartUpApps/
[Install]
WantedBy=multi-user.target