2020-01-19 16:32:32 +01:00
|
|
|
/*
|
|
|
|
** Copyright (C) 2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
|
|
|
**
|
|
|
|
** This program is free software; you can redistribute it and/or modify it
|
|
|
|
** under the terms of the GNU General Public License as published by the
|
|
|
|
** Free Software Foundation; either version 3, or (at your option) any
|
|
|
|
** later version.
|
|
|
|
**
|
|
|
|
** This program is distributed in the hope that it will be useful,
|
|
|
|
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
** GNU General Public License for more details.
|
|
|
|
**
|
|
|
|
** You should have received a copy of the GNU General Public License
|
|
|
|
** along with this program; if not, write to the Free Software Foundation,
|
|
|
|
** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
**
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "config.h"
|
|
|
|
|
|
|
|
#include <string>
|
|
|
|
#include <algorithm>
|
2020-02-04 00:04:28 +01:00
|
|
|
#include <atomic>
|
2020-12-06 23:35:30 +01:00
|
|
|
#include <cstdio>
|
|
|
|
|
2020-10-31 08:41:21 +01:00
|
|
|
#include <unistd.h>
|
2020-01-19 16:32:32 +01:00
|
|
|
|
2020-11-07 13:06:23 +01:00
|
|
|
#include "mu-runtime.hh"
|
2020-06-08 22:04:05 +02:00
|
|
|
#include "mu-cmd.hh"
|
2020-10-31 08:41:21 +01:00
|
|
|
#include "mu-server.hh"
|
|
|
|
|
2020-01-19 16:32:32 +01:00
|
|
|
#include "utils/mu-utils.hh"
|
|
|
|
#include "utils/mu-command-parser.hh"
|
2020-05-25 17:32:10 +02:00
|
|
|
#include "utils/mu-readline.hh"
|
2020-01-19 16:32:32 +01:00
|
|
|
|
|
|
|
using namespace Mu;
|
2022-05-30 19:30:33 +02:00
|
|
|
static std::atomic<int> MuTerminate{0};
|
2021-10-20 11:18:15 +02:00
|
|
|
static bool tty;
|
2020-02-04 00:04:28 +01:00
|
|
|
|
2022-01-30 13:34:10 +01:00
|
|
|
static void
|
|
|
|
sig_handler(int sig)
|
|
|
|
{
|
2022-05-30 19:30:33 +02:00
|
|
|
MuTerminate = sig;
|
2022-01-30 13:34:10 +01:00
|
|
|
}
|
|
|
|
|
2020-02-04 00:04:28 +01:00
|
|
|
static void
|
2021-10-20 11:18:15 +02:00
|
|
|
install_sig_handler(void)
|
2020-02-04 00:04:28 +01:00
|
|
|
{
|
2022-01-30 13:34:10 +01:00
|
|
|
static struct sigaction action;
|
|
|
|
int i, sigs[] = {SIGINT, SIGHUP, SIGTERM, SIGPIPE};
|
2020-02-04 00:04:28 +01:00
|
|
|
|
2022-05-30 19:30:33 +02:00
|
|
|
MuTerminate = 0;
|
2020-02-04 00:04:28 +01:00
|
|
|
|
2022-01-30 13:34:10 +01:00
|
|
|
action.sa_handler = sig_handler;
|
2021-10-20 11:18:15 +02:00
|
|
|
sigemptyset(&action.sa_mask);
|
|
|
|
action.sa_flags = SA_RESETHAND;
|
2020-02-04 00:04:28 +01:00
|
|
|
|
2021-10-20 11:18:15 +02:00
|
|
|
for (i = 0; i != G_N_ELEMENTS(sigs); ++i)
|
|
|
|
if (sigaction(sigs[i], &action, NULL) != 0)
|
2022-01-30 13:34:10 +01:00
|
|
|
g_critical("set sigaction for %d failed: %s",
|
2022-05-06 21:07:15 +02:00
|
|
|
sigs[i], g_strerror(errno));
|
2020-02-04 00:04:28 +01:00
|
|
|
}
|
|
|
|
|
2020-01-19 16:32:32 +01:00
|
|
|
/*
|
|
|
|
* Markers for/after the length cookie that precedes the expression we write to
|
|
|
|
* output. We use octal 376, 377 (ie, 0xfe, 0xff) as they will never occur in
|
|
|
|
* utf8 */
|
|
|
|
|
2020-10-31 08:41:21 +01:00
|
|
|
#define COOKIE_PRE "\376"
|
|
|
|
#define COOKIE_POST "\377"
|
2020-01-19 16:32:32 +01:00
|
|
|
|
|
|
|
static void
|
2020-10-31 08:41:21 +01:00
|
|
|
cookie(size_t n)
|
2020-01-19 16:32:32 +01:00
|
|
|
{
|
2021-10-20 11:18:15 +02:00
|
|
|
const auto num{static_cast<unsigned>(n)};
|
2020-11-01 13:20:12 +01:00
|
|
|
|
2021-10-20 11:18:15 +02:00
|
|
|
if (tty) // for testing.
|
|
|
|
::printf("[%x]", num);
|
|
|
|
else
|
|
|
|
::printf(COOKIE_PRE "%x" COOKIE_POST, num);
|
2020-01-19 16:32:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2022-05-06 21:07:15 +02:00
|
|
|
output_sexp_stdout(Sexp&& sexp, Server::OutputFlags flags)
|
2020-01-19 16:32:32 +01:00
|
|
|
{
|
2022-05-06 21:07:15 +02:00
|
|
|
/* if requested, insert \n between list elements; note:
|
|
|
|
* is _not_ inherited by children */
|
|
|
|
if (any_of(flags & Server::OutputFlags::SplitList))
|
|
|
|
sexp.formatting_opts |= Sexp::FormattingOptions::SplitList;
|
|
|
|
|
2021-10-20 11:18:15 +02:00
|
|
|
const auto str{sexp.to_sexp_string()};
|
2022-05-06 21:07:15 +02:00
|
|
|
|
2021-10-20 11:18:15 +02:00
|
|
|
cookie(str.size() + 1);
|
|
|
|
if (G_UNLIKELY(::puts(str.c_str()) < 0)) {
|
|
|
|
g_critical("failed to write output '%s'", str.c_str());
|
|
|
|
::raise(SIGTERM); /* terminate ourselves */
|
|
|
|
}
|
2022-02-03 21:58:53 +01:00
|
|
|
|
2022-05-06 21:07:15 +02:00
|
|
|
if (any_of(flags & Server::OutputFlags::Flush))
|
2022-02-20 14:02:25 +01:00
|
|
|
std::fflush(stdout);
|
2020-01-19 16:32:32 +01:00
|
|
|
}
|
|
|
|
|
2021-07-31 00:48:24 +02:00
|
|
|
static void
|
|
|
|
report_error(const Mu::Error& err) noexcept
|
|
|
|
{
|
2021-10-20 11:18:15 +02:00
|
|
|
Sexp::List e;
|
2021-07-31 00:48:24 +02:00
|
|
|
|
2021-10-20 11:18:15 +02:00
|
|
|
e.add_prop(":error", Sexp::make_number(static_cast<size_t>(err.code())));
|
|
|
|
e.add_prop(":message", Sexp::make_string(err.what()));
|
2021-07-31 00:48:24 +02:00
|
|
|
|
2022-05-06 21:07:15 +02:00
|
|
|
output_sexp_stdout(Sexp::make_list(std::move(e)),
|
|
|
|
Server::OutputFlags::Flush);
|
2021-07-31 00:48:24 +02:00
|
|
|
}
|
|
|
|
|
2022-05-12 07:48:28 +02:00
|
|
|
|
|
|
|
Result<void>
|
|
|
|
Mu::mu_cmd_server(const MuConfig* opts) try {
|
|
|
|
|
|
|
|
auto store = Store::make(mu_runtime_path(MU_RUNTIME_PATH_XAPIANDB),
|
|
|
|
Store::Options::Writable);
|
2022-05-09 19:58:35 +02:00
|
|
|
if (!store)
|
2022-05-12 07:48:28 +02:00
|
|
|
return Err(store.error());
|
2021-10-20 11:18:15 +02:00
|
|
|
|
2022-05-09 19:58:35 +02:00
|
|
|
Server server{*store, output_sexp_stdout};
|
2022-05-30 19:30:33 +02:00
|
|
|
g_message("created server with store @ %s; maildir @ %s; debug-mode %s"
|
|
|
|
"readline: %s",
|
2022-05-09 19:58:35 +02:00
|
|
|
store->properties().database_path.c_str(),
|
|
|
|
store->properties().root_maildir.c_str(),
|
2022-05-30 19:30:33 +02:00
|
|
|
opts->debug ? "yes" : "no",
|
|
|
|
have_readline() ? "yes" : "no");
|
2021-10-20 11:18:15 +02:00
|
|
|
|
|
|
|
tty = ::isatty(::fileno(stdout));
|
|
|
|
const auto eval = std::string{opts->commands ? "(help :full t)"
|
2022-05-06 21:07:15 +02:00
|
|
|
: opts->eval ? opts->eval
|
|
|
|
: ""};
|
2021-10-20 11:18:15 +02:00
|
|
|
if (!eval.empty()) {
|
|
|
|
server.invoke(eval);
|
2022-05-12 07:48:28 +02:00
|
|
|
return Ok();
|
2021-10-20 11:18:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Note, the readline stuff is inactive unless on a tty.
|
|
|
|
const auto histpath{std::string{mu_runtime_path(MU_RUNTIME_PATH_CACHE)} + "/history"};
|
|
|
|
setup_readline(histpath, 50);
|
|
|
|
|
|
|
|
install_sig_handler();
|
|
|
|
std::cout << ";; Welcome to the " << PACKAGE_STRING << " command-server\n"
|
|
|
|
<< ";; Use (help) to get a list of commands, (quit) to quit.\n";
|
|
|
|
|
|
|
|
bool do_quit{};
|
|
|
|
while (!MuTerminate && !do_quit) {
|
|
|
|
std::fflush(stdout); // Needed for Windows, see issue #1827.
|
|
|
|
const auto line{read_line(do_quit)};
|
|
|
|
if (line.find_first_not_of(" \t") == std::string::npos)
|
|
|
|
continue; // skip whitespace-only lines
|
|
|
|
|
|
|
|
do_quit = server.invoke(line) ? false : true;
|
|
|
|
save_line(line);
|
|
|
|
}
|
2022-05-30 19:30:33 +02:00
|
|
|
|
|
|
|
if (MuTerminate != 0)
|
|
|
|
g_message ("shutting down due to signal %d", MuTerminate.load());
|
|
|
|
|
2021-10-20 11:18:15 +02:00
|
|
|
shutdown_readline();
|
|
|
|
|
2022-05-12 07:48:28 +02:00
|
|
|
return Ok();
|
2020-02-21 00:13:29 +01:00
|
|
|
|
2020-01-19 16:32:32 +01:00
|
|
|
} catch (const Error& er) {
|
2021-10-20 11:18:15 +02:00
|
|
|
/* note: user-level error, "OK" for mu */
|
|
|
|
report_error(er);
|
|
|
|
g_warning("server caught exception: %s", er.what());
|
2022-05-12 07:48:28 +02:00
|
|
|
return Ok();
|
2020-01-19 16:32:32 +01:00
|
|
|
} catch (...) {
|
2021-10-20 11:18:15 +02:00
|
|
|
g_critical("server caught exception");
|
2022-05-12 07:48:28 +02:00
|
|
|
return Err(Error::Code::Internal, "caught exception");
|
2020-01-19 16:32:32 +01:00
|
|
|
}
|