// The Construct // // Copyright (C) The Construct Developers, Authors & Contributors // Copyright (C) 2016-2020 Jason Volk // // Permission to use, copy, modify, and/or distribute this software for any // purpose with or without fee is hereby granted, provided that the above // copyright notice and this permission notice is present in all copies. The // full license for this software is available in the LICENSE file. decltype(ircd::m::app::log) ircd::m::app::log { "m.app" }; decltype(ircd::m::app::path) ircd::m::app::path { { "name", "ircd.m.app.path" }, { "default", string_view{} }, { "persist", false }, }; decltype(ircd::m::app::autorun) ircd::m::app::autorun { { "name", "ircd.m.app.autorun" }, { "default", true }, }; decltype(ircd::m::app::enable) ircd::m::app::enable { { "name", "ircd.m.app.enable" }, { "default", true }, }; decltype(ircd::m::app::bin) ircd::m::app::bin; template<> decltype(ircd::util::instance_list::allocator) ircd::util::instance_list::allocator{}; template<> decltype(ircd::util::instance_list::list) ircd::util::instance_list::list { allocator }; // // init // void ircd::m::app::init() { if(!enable || !path || ircd::read_only) return; std::vector files { fs::ls(string_view(path)) }; bin = std::set { begin(files), std::remove_if(begin(files), end(files), [] (const std::string &file) { return !fs::is_exec(file); }) }; log::debug { log, "Found %zu executable in `%s'", bin.size(), string_view{path}, }; if(!autorun) { log::warning { log, "Autorun is disabled by the configuration. Apps may still be executed manually.", }; return; } events::type::for_each_in("ircd.app.run.auto", [] (const string_view &, const event::idx &run_event_idx) { const m::event::fetch run_event { std::nothrow, run_event_idx }; if(!run_event.valid || !my(run_event)) return true; const m::room room { at<"room_id"_>(run_event) }; const m::event::idx app_event_idx { room.get(std::nothrow, "ircd.app", at<"state_key"_>(run_event)) }; if(!app_event_idx) return true; const m::event::fetch app_event { std::nothrow, app_event_idx }; if(!app_event.valid || !my(app_event)) return true; log::debug { log, "Attempting app:%lu run.auto:%lu", app_event_idx, run_event_idx, }; auto app { std::make_unique(app_event_idx) }; const auto pid { app->child.run() }; app.release(); return true; }); } void ircd::m::app::fini() { std::vector apps { std::begin(list), std::end(list) }; for(auto *const &app : apps) { app->child.join(15); delete app; } } // // app::app // ircd::m::app::app(const m::event::idx &event_idx) :event_idx { event_idx } ,feature { m::get(event_idx, "content") } ,config { feature } ,arg { config.at("arg") } ,binpath{[&] { if(!path) throw m::FORBIDDEN { "Configure the 'ircd.m.app.path' to permit." }; if(!enable) throw m::FORBIDDEN { "Configure 'ircd.m.app.enable' to permit." }; const json::string file { arg.at(0) }; string_view part[2]; part[0] = path; part[1] = file; const auto ret { fs::path_string(part) }; if(!bin.count(ret)) throw m::NOT_FOUND { "Executable '%s' not found in bin directory at `%s'", file, string_view{path}, }; return ret; }()} ,argv{[&] { std::vector ret { std::begin(arg), std::end(arg) }; ret.at(0) = binpath; return ret; }()} ,child { argv } ,worker_context { "m.app", 512_KiB, std::bind(&app::worker, this) } { } ircd::m::app::~app() noexcept { worker_context.interrupt(); } void ircd::m::app::worker() try { const auto event_id { m::event_id(event_idx) }; const auto room_id { m::room_id(event_idx) }; const auto user_id { m::get(event_idx, "sender") }; child.dock.wait([this] { return child.pid >= 0; }); log::info { log, "app:%lu starting %s in %s for %s @ `%s' id:%lu pid:%ld", event_idx, string_view{event_id}, string_view{room_id}, string_view{user_id}, argv.at(0), child.id, child.pid, }; char buf alignas(4096) [16_KiB]; for(run::barrier{};;) { window_buffer wb(buf); wb([](const mutable_buffer &buf) { return copy(buf, "
"_sv);
		});

		bool eof {false};
		wb([this, &eof](const mutable_buffer &buf)
		{
			const auto ret(read(this->child, buf));
			eof = empty(ret);
			return ret;
		});

		wb([](const mutable_buffer &buf)
		{
			return copy(buf, "
"_sv); }); const string_view &output { wb.completed() }; if(eof) { log::debug { log, "app:%lu :end of file", event_idx, }; return; } const string_view alt { "no alt text" }; const auto message_id { !ircd::write_avoid? m::msghtml(room_id, user_id, output, alt, "m.notice"): m::event::id::buf{} }; log::debug { log, "app:%lu output %zu bytes in %s to %s :%s%s", event_idx, size(output), string_view{message_id}, string_view{room_id}, trunc(output, 64), size(output) > 64? "...": "", }; } } catch(const std::exception &e) { log::error { log, "app:%lu (%p) worker fatal :%s", event_idx, this, e.what(), }; const ctx::exception_handler eh; child.join(); return; }