Sunshine/sunshine/process.cpp

391 lines
9.7 KiB
C++
Raw Normal View History

//
// Created by loki on 12/14/19.
//
2021-05-26 17:34:25 +02:00
#define BOOST_BIND_GLOBAL_PLACEHOLDERS
2020-01-01 18:47:34 +01:00
#include "process.h"
2022-02-16 18:23:56 -05:00
#include <filesystem>
#include <string>
2021-05-17 21:21:57 +02:00
#include <vector>
2022-02-16 18:23:56 -05:00
#include <boost/algorithm/string.hpp>
#include <boost/filesystem.hpp>
2019-12-15 19:36:22 +01:00
#include <boost/property_tree/json_parser.hpp>
2021-05-17 21:21:57 +02:00
#include <boost/property_tree/ptree.hpp>
2019-12-15 19:36:22 +01:00
#include "main.h"
2021-05-17 21:21:57 +02:00
#include "utility.h"
namespace proc {
using namespace std::literals;
namespace bp = boost::process;
2019-12-15 19:36:22 +01:00
namespace pt = boost::property_tree;
proc_t proc;
2019-12-25 23:41:46 +01:00
void process_end(bp::child &proc, bp::group &proc_handle) {
if(!proc.running()) {
return;
}
BOOST_LOG(debug) << "Force termination Child-Process"sv;
2019-12-15 22:47:55 +01:00
proc_handle.terminate();
2019-12-15 22:47:55 +01:00
// avoid zombie process
proc.wait();
}
int exe(const std::string &cmd, bp::environment &env, file_t &file, std::error_code &ec) {
2019-12-15 15:44:23 +01:00
if(!file) {
return bp::system(cmd, env, bp::std_out > bp::null, bp::std_err > bp::null, ec);
}
return bp::system(cmd, env, bp::std_out > file.get(), bp::std_err > file.get(), ec);
}
int proc_t::execute(int app_id) {
if(!running() && _app_id != -1) {
// previous process exited on it's own, reset _process_handle
_process_handle = bp::group();
_app_id = -1;
}
2020-02-08 23:41:27 +01:00
if(app_id < 0 || app_id >= _apps.size()) {
BOOST_LOG(error) << "Couldn't find app with ID ["sv << app_id << ']';
return 404;
}
// Ensure starting from a clean slate
terminate();
2021-05-17 21:21:57 +02:00
_app_id = app_id;
auto &proc = _apps[app_id];
_undo_begin = std::begin(proc.prep_cmds);
2021-05-17 21:21:57 +02:00
_undo_it = _undo_begin;
if(!proc.output.empty() && proc.output != "null"sv) {
_pipe.reset(fopen(proc.output.c_str(), "a"));
}
std::error_code ec;
//Executed when returning from function
auto fg = util::fail_guard([&]() {
2019-12-25 23:41:46 +01:00
terminate();
});
for(; _undo_it != std::end(proc.prep_cmds); ++_undo_it) {
auto &cmd = _undo_it->do_cmd;
BOOST_LOG(info) << "Executing: ["sv << cmd << ']';
auto ret = exe(cmd, _env, _pipe, ec);
if(ec) {
BOOST_LOG(error) << "Couldn't run ["sv << cmd << "]: System: "sv << ec.message();
return -1;
}
if(ret != 0) {
BOOST_LOG(error) << '[' << cmd << "] failed with code ["sv << ret << ']';
return -1;
}
}
for(auto &cmd : proc.detached) {
BOOST_LOG(info) << "Spawning ["sv << cmd << ']';
if(proc.output.empty() || proc.output == "null"sv) {
bp::spawn(cmd, _env, bp::std_out > bp::null, bp::std_err > bp::null, ec);
}
else {
bp::spawn(cmd, _env, bp::std_out > _pipe.get(), bp::std_err > _pipe.get(), ec);
}
if(ec) {
BOOST_LOG(warning) << "Couldn't spawn ["sv << cmd << "]: System: "sv << ec.message();
}
}
if(proc.cmd.empty()) {
BOOST_LOG(debug) << "Executing [Desktop]"sv;
placebo = true;
2022-02-16 18:23:56 -05:00
}
else {
boost::filesystem::path working_dir = proc.working_dir.empty() ?
boost::filesystem::path(proc.cmd).parent_path() :
boost::filesystem::path(proc.working_dir);
if(proc.output.empty() || proc.output == "null"sv) {
BOOST_LOG(info) << "Executing: ["sv << proc.cmd << ']';
_process = bp::child(_process_handle, proc.cmd, _env, bp::start_dir(working_dir), bp::std_out > bp::null, bp::std_err > bp::null, ec);
}
else {
BOOST_LOG(info) << "Executing: ["sv << proc.cmd << ']';
_process = bp::child(_process_handle, proc.cmd, _env, bp::start_dir(working_dir), bp::std_out > _pipe.get(), bp::std_err > _pipe.get(), ec);
}
}
if(ec) {
BOOST_LOG(warning) << "Couldn't run ["sv << proc.cmd << "]: System: "sv << ec.message();
return -1;
}
fg.disable();
return 0;
}
int proc_t::running() {
if(placebo || _process.running()) {
return _app_id;
}
return -1;
}
2019-12-25 23:41:46 +01:00
void proc_t::terminate() {
std::error_code ec;
// Ensure child process is terminated
placebo = false;
2019-12-25 23:41:46 +01:00
process_end(_process, _process_handle);
_app_id = -1;
if(ec) {
BOOST_LOG(fatal) << "System: "sv << ec.message();
log_flush();
std::abort();
}
2021-05-17 21:21:57 +02:00
for(; _undo_it != _undo_begin; --_undo_it) {
auto &cmd = (_undo_it - 1)->undo_cmd;
if(cmd.empty()) {
continue;
}
BOOST_LOG(debug) << "Executing: ["sv << cmd << ']';
auto ret = exe(cmd, _env, _pipe, ec);
if(ec) {
BOOST_LOG(fatal) << "System: "sv << ec.message();
log_flush();
std::abort();
}
if(ret != 0) {
BOOST_LOG(fatal) << "Return code ["sv << ret << ']';
log_flush();
std::abort();
}
}
_pipe.reset();
}
2019-12-15 19:36:22 +01:00
const std::vector<ctx_t> &proc_t::get_apps() const {
return _apps;
2019-12-15 19:36:22 +01:00
}
2020-02-08 16:26:38 +01:00
std::vector<ctx_t> &proc_t::get_apps() {
return _apps;
}
2019-12-15 19:36:22 +01:00
2022-01-20 14:31:16 +01:00
/// Gets application image from application list.
/// Returns default image if image configuration is not set.
/// returns http content-type header compatible image type
2022-01-24 21:33:26 +01:00
std::string proc_t::get_app_image(int app_id) {
2022-02-16 18:23:56 -05:00
auto app_index = app_id - 1;
2022-01-20 15:55:06 +01:00
if(app_index < 0 || app_index >= _apps.size()) {
2022-01-18 22:06:20 +01:00
BOOST_LOG(error) << "Couldn't find app with ID ["sv << app_id << ']';
2022-01-24 21:33:26 +01:00
return SUNSHINE_ASSETS_DIR "/box.png";
2022-01-20 15:55:06 +01:00
}
auto app_image_path = _apps[app_index].image_path;
2022-02-16 18:23:56 -05:00
if(app_image_path.empty()) {
2022-01-24 21:33:26 +01:00
return SUNSHINE_ASSETS_DIR "/box.png";
2022-01-18 22:06:20 +01:00
}
2022-01-20 14:31:16 +01:00
2022-01-24 21:33:26 +01:00
auto image_extension = std::filesystem::path(app_image_path).extension().string();
2022-01-25 21:13:54 +01:00
boost::to_lower(image_extension);
2022-01-20 15:55:06 +01:00
std::error_code code;
2022-02-16 18:23:56 -05:00
if(!std::filesystem::exists(app_image_path, code) || image_extension != ".png") {
2022-01-24 21:33:26 +01:00
return SUNSHINE_ASSETS_DIR "/box.png";
2022-01-20 14:31:16 +01:00
}
2022-01-20 15:55:06 +01:00
// return only "content-type" http header compatible image type.
2022-01-24 21:33:26 +01:00
return app_image_path;
2022-01-18 22:06:20 +01:00
}
proc_t::~proc_t() {
2019-12-25 23:41:46 +01:00
terminate();
}
2019-12-15 19:36:22 +01:00
std::string_view::iterator find_match(std::string_view::iterator begin, std::string_view::iterator end) {
int stack = 0;
--begin;
do {
++begin;
switch(*begin) {
2021-05-17 21:21:57 +02:00
case '(':
++stack;
break;
2021-05-17 21:21:57 +02:00
case ')':
--stack;
}
} while(begin != end && stack != 0);
if(begin == end) {
throw std::out_of_range("Missing closing bracket \')\'");
}
return begin;
}
std::string parse_env_val(bp::native_environment &env, const std::string_view &val_raw) {
2021-05-17 21:21:57 +02:00
auto pos = std::begin(val_raw);
auto dollar = std::find(pos, std::end(val_raw), '$');
std::stringstream ss;
while(dollar != std::end(val_raw)) {
auto next = dollar + 1;
if(next != std::end(val_raw)) {
switch(*next) {
2021-05-17 21:21:57 +02:00
case '(': {
ss.write(pos, (dollar - pos));
auto var_begin = next + 1;
auto var_end = find_match(next, std::end(val_raw));
2021-05-17 21:21:57 +02:00
ss << env[std::string { var_begin, var_end }].to_string();
2021-05-17 21:21:57 +02:00
pos = var_end + 1;
next = var_end;
2021-05-17 21:21:57 +02:00
break;
}
case '$':
ss.write(pos, (next - pos));
pos = next + 1;
++next;
break;
}
dollar = std::find(next, std::end(val_raw), '$');
}
else {
dollar = next;
}
}
ss.write(pos, (dollar - pos));
return ss.str();
}
2021-05-17 21:21:57 +02:00
std::optional<proc::proc_t> parse(const std::string &file_name) {
2019-12-15 19:36:22 +01:00
pt::ptree tree;
try {
pt::read_json(file_name, tree);
auto &apps_node = tree.get_child("apps"s);
auto &env_vars = tree.get_child("env"s);
auto this_env = boost::this_process::environment();
2019-12-15 19:36:22 +01:00
2021-05-17 21:21:57 +02:00
for(auto &[name, val] : env_vars) {
this_env[name] = parse_env_val(this_env, val.get_value<std::string>());
}
std::vector<proc::ctx_t> apps;
2021-05-17 21:21:57 +02:00
for(auto &[_, app_node] : apps_node) {
2019-12-15 19:36:22 +01:00
proc::ctx_t ctx;
2021-05-17 21:21:57 +02:00
auto prep_nodes_opt = app_node.get_child_optional("prep-cmd"s);
auto detached_nodes_opt = app_node.get_child_optional("detached"s);
2021-05-17 21:21:57 +02:00
auto output = app_node.get_optional<std::string>("output"s);
auto name = parse_env_val(this_env, app_node.get<std::string>("name"s));
auto cmd = app_node.get_optional<std::string>("cmd"s);
2022-01-23 11:33:02 +01:00
auto image_path = app_node.get_optional<std::string>("image-path"s);
auto working_dir = app_node.get_optional<std::string>("working-dir"s);
2019-12-15 19:36:22 +01:00
std::vector<proc::cmd_t> prep_cmds;
if(prep_nodes_opt) {
auto &prep_nodes = *prep_nodes_opt;
prep_cmds.reserve(prep_nodes.size());
for(auto &[_, prep_node] : prep_nodes) {
2021-05-17 21:21:57 +02:00
auto do_cmd = parse_env_val(this_env, prep_node.get<std::string>("do"s));
auto undo_cmd = prep_node.get_optional<std::string>("undo"s);
if(undo_cmd) {
prep_cmds.emplace_back(std::move(do_cmd), parse_env_val(this_env, *undo_cmd));
}
else {
prep_cmds.emplace_back(std::move(do_cmd));
}
2019-12-15 19:36:22 +01:00
}
}
std::vector<std::string> detached;
if(detached_nodes_opt) {
auto &detached_nodes = *detached_nodes_opt;
detached.reserve(detached_nodes.size());
for(auto &[_, detached_val] : detached_nodes) {
detached.emplace_back(parse_env_val(this_env, detached_val.get_value<std::string>()));
}
}
2019-12-15 19:36:22 +01:00
if(output) {
ctx.output = parse_env_val(this_env, *output);
}
if(cmd) {
ctx.cmd = parse_env_val(this_env, *cmd);
}
if(working_dir) {
ctx.working_dir = parse_env_val(this_env, *working_dir);
}
2022-02-16 18:23:56 -05:00
if(image_path) {
2022-01-20 15:55:06 +01:00
ctx.image_path = parse_env_val(this_env, *image_path);
2022-01-18 22:06:20 +01:00
}
2021-05-17 21:21:57 +02:00
ctx.name = std::move(name);
2019-12-15 19:36:22 +01:00
ctx.prep_cmds = std::move(prep_cmds);
2021-05-17 21:21:57 +02:00
ctx.detached = std::move(detached);
2019-12-15 19:36:22 +01:00
apps.emplace_back(std::move(ctx));
2019-12-15 19:36:22 +01:00
}
return proc::proc_t {
std::move(this_env), std::move(apps)
2019-12-15 19:36:22 +01:00
};
2021-05-17 21:21:57 +02:00
}
catch(std::exception &e) {
BOOST_LOG(error) << e.what();
2019-12-15 19:36:22 +01:00
}
return std::nullopt;
}
void refresh(const std::string &file_name) {
auto proc_opt = proc::parse(file_name);
if(proc_opt) {
2021-05-09 18:55:34 +02:00
{
proc::ctx_t ctx;
ctx.name = "Desktop"s;
proc_opt->get_apps().emplace(std::begin(proc_opt->get_apps()), std::move(ctx));
}
2019-12-15 19:36:22 +01:00
proc = std::move(*proc_opt);
}
}
2021-05-17 21:21:57 +02:00
} // namespace proc