You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
lokinet/daemon/lokinet.cpp

722 lines
19 KiB
C++

#include <llarp/config/config.hpp> // for ensure_config
#include <llarp/constants/version.hpp>
#include <llarp.hpp>
#include <llarp/util/lokinet_init.h>
#include <llarp/util/fs.hpp>
#include <llarp/util/logging/logger.hpp>
#include <llarp/util/logging/ostream_logger.hpp>
#include <llarp/util/str.hpp>
#ifdef _WIN32
#include <dbghelp.h>
#endif
#ifdef __APPLE__
#include <llarp/util/logging/apple_logger.hpp>
#endif
#include <csignal>
#include <cxxopts.hpp>
6 years ago
#include <string>
#include <iostream>
#include <future>
#ifdef USE_JEMALLOC
#include <new>
#include <jemalloc/jemalloc.h>
void*
operator new(std::size_t sz)
{
void* ptr = malloc(sz);
if (ptr)
return ptr;
else
throw std::bad_alloc{};
}
void
operator delete(void* ptr) noexcept
{
free(ptr);
}
void
operator delete(void* ptr, size_t) noexcept
{
free(ptr);
}
#endif
int
lokinet_main(int, char**);
#ifdef _WIN32
#include <strsafe.h>
5 years ago
extern "C" LONG FAR PASCAL
win32_signal_handler(EXCEPTION_POINTERS*);
extern "C" VOID FAR PASCAL
win32_daemon_entry(DWORD, LPTSTR*);
BOOL ReportSvcStatus(DWORD, DWORD, DWORD);
VOID
insert_description();
SERVICE_STATUS SvcStatus;
SERVICE_STATUS_HANDLE SvcStatusHandle;
bool start_as_daemon = false;
#endif
std::shared_ptr<llarp::Context> ctx;
std::promise<int> exit_code;
6 years ago
void
handle_signal(int sig)
6 years ago
{
if (ctx)
ctx->loop->call([sig] { ctx->HandleSignal(sig); });
else
std::cerr << "Received signal " << sig << ", but have no context yet. Ignoring!" << std::endl;
6 years ago
}
#ifdef _WIN32
int
startWinsock()
{
WSADATA wsockd;
int err;
err = ::WSAStartup(MAKEWORD(2, 2), &wsockd);
if (err)
{
perror("Failed to start Windows Sockets");
return err;
}
::CreateMutex(nullptr, FALSE, "lokinet_win32_daemon");
return 0;
}
extern "C" BOOL FAR PASCAL
handle_signal_win32(DWORD fdwCtrlType)
{
UNREFERENCED_PARAMETER(fdwCtrlType);
handle_signal(SIGINT);
return TRUE; // probably unreachable
}
void
install_win32_daemon()
{
SC_HANDLE schSCManager;
SC_HANDLE schService;
std::array<char, 1024> szPath{};
if (!GetModuleFileName(nullptr, szPath.data(), MAX_PATH))
{
llarp::LogError("Cannot install service ", GetLastError());
return;
}
// just put the flag here. we eat it later on and specify the
// config path in the daemon entry point
StringCchCat(szPath.data(), 1024, " --win32-daemon");
// Get a handle to the SCM database.
schSCManager = OpenSCManager(
nullptr, // local computer
nullptr, // ServicesActive database
SC_MANAGER_ALL_ACCESS); // full access rights
if (nullptr == schSCManager)
{
llarp::LogError("OpenSCManager failed ", GetLastError());
return;
}
// Create the service
schService = CreateService(
schSCManager, // SCM database
"lokinet", // name of service
"Lokinet for Windows", // service name to display
SERVICE_ALL_ACCESS, // desired access
SERVICE_WIN32_OWN_PROCESS, // service type
SERVICE_DEMAND_START, // start type
SERVICE_ERROR_NORMAL, // error control type
szPath.data(), // path to service's binary
nullptr, // no load ordering group
nullptr, // no tag identifier
nullptr, // no dependencies
nullptr, // LocalSystem account
nullptr); // no password
if (schService == nullptr)
{
llarp::LogError("CreateService failed ", GetLastError());
CloseServiceHandle(schSCManager);
return;
}
else
llarp::LogInfo("Service installed successfully");
CloseServiceHandle(schService);
CloseServiceHandle(schSCManager);
insert_description();
}
VOID
insert_description()
{
SC_HANDLE schSCManager;
SC_HANDLE schService;
SERVICE_DESCRIPTION sd;
LPTSTR szDesc =
"LokiNET is a free, open source, private, "
"decentralized, \"market based sybil resistant\" "
"and IP based onion routing network";
// Get a handle to the SCM database.
schSCManager = OpenSCManager(
NULL, // local computer
NULL, // ServicesActive database
SC_MANAGER_ALL_ACCESS); // full access rights
if (nullptr == schSCManager)
{
llarp::LogError("OpenSCManager failed ", GetLastError());
return;
}
// Get a handle to the service.
schService = OpenService(
schSCManager, // SCM database
"lokinet", // name of service
SERVICE_CHANGE_CONFIG); // need change config access
if (schService == nullptr)
{
llarp::LogError("OpenService failed ", GetLastError());
CloseServiceHandle(schSCManager);
return;
}
// Change the service description.
sd.lpDescription = szDesc;
if (!ChangeServiceConfig2(
schService, // handle to service
SERVICE_CONFIG_DESCRIPTION, // change: description
&sd)) // new description
{
llarp::LogError("ChangeServiceConfig2 failed");
}
else
llarp::LogInfo("Service description updated successfully.");
CloseServiceHandle(schService);
CloseServiceHandle(schSCManager);
}
void
uninstall_win32_daemon()
{
SC_HANDLE schSCManager;
SC_HANDLE schService;
// Get a handle to the SCM database.
schSCManager = OpenSCManager(
nullptr, // local computer
nullptr, // ServicesActive database
SC_MANAGER_ALL_ACCESS); // full access rights
if (nullptr == schSCManager)
{
llarp::LogError("OpenSCManager failed ", GetLastError());
return;
}
// Get a handle to the service.
schService = OpenService(
schSCManager, // SCM database
"lokinet", // name of service
0x10000); // need delete access
if (schService == nullptr)
{
llarp::LogError("OpenService failed ", GetLastError());
CloseServiceHandle(schSCManager);
return;
}
// Delete the service.
if (!DeleteService(schService))
{
llarp::LogError("DeleteService failed ", GetLastError());
}
else
llarp::LogInfo("Service deleted successfully\n");
CloseServiceHandle(schService);
CloseServiceHandle(schSCManager);
}
#endif
/// this sets up, configures and runs the main context
static void
run_main_context(std::optional<fs::path> confFile, const llarp::RuntimeOptions opts)
{
llarp::LogTrace("start of run_main_context()");
try
{
std::shared_ptr<llarp::Config> conf;
if (confFile.has_value())
{
llarp::LogInfo("Using config file: ", *confFile);
conf = std::make_shared<llarp::Config>(confFile->parent_path());
}
else
{
conf = std::make_shared<llarp::Config>(llarp::GetDefaultDataDir());
}
if (!conf->Load(confFile, opts.isSNode))
Config file improvements (#1397) * Config file API/comment improvements API improvements: ================= Make the config API use position-independent tag parameters (Required, Default{123}, MultiValue) rather than a sequence of bools with overloads. For example, instead of: conf.defineOption<int>("a", "b", false, true, 123, [] { ... }); you now write: conf.defineOption<int>("a", "b", MultiValue, Default{123}, [] { ... }); The tags are: - Required - MultiValue - Default{value} plus new abilities (see below): - Hidden - RelayOnly - ClientOnly - Comment{"line1", "line2", "line3"} Made option definition more powerful: ===================================== - `Hidden` allows you to define an option that won't show up in the generated config file if it isn't set. - `RelayOnly`/`ClientOnly` sets up an option that is only accepted and only shows up for relay or client configs. (If neither is specified the option shows up in both modes). - `Comment{...}` lets the option comments be specified as part of the defineOption. Comment improvements ==================== - Rewrote comments for various options to expand on details. - Inlined all the comments with the option definitions. - Several options that were missing comments got comments added. - Made various options for deprecated and or internal options hidden by default so that they don't show up in a default config file. - show the section comment (but not option comments) *after* the [section] tag instead of before it as it makes more sense that way (particularly for the [bind] section which has a new long comment to describe how it works). Disable profiling by default ============================ We had this weird state where we use and store profiling by default but never *load* it when starting up. This commit makes us just not use profiling at all unless explicitly enabled. Other misc changes: =================== - change default worker threads to 0 (= num cpus) instead of 1, and fix it to allow 0. - Actually apply worker-threads option - fixed default data-dir value erroneously having quotes around it - reordered ifname/ifaddr/mapaddr (was previously mapaddr/ifaddr/ifname) as mapaddr is a sort of specialization of ifaddr and so makes more sense to come after it (particularly because it now references ifaddr in its help message). - removed peer-stats option (since we always require it for relays and never use it for clients) - removed router profiles filename option (this doesn't need to be configurable) - removed defunct `service-node-seed` option - Change default logging output file to "" (which means stdout), and also made "-" work for stdout. * Router hive compilation fixes * Comments for SNApp SRV settings in ini file * Add extra blank line after section comments * Better deprecated option handling Allow {client,relay}-only options in {relay,client} configs to be specified as implicitly deprecated options: they warn, and don't set anything. Add an explicit `Deprecated` tag and move deprecated option handling into definition.cpp. * Move backwards compat options into section definitions Keep the "addBackwardsCompatibleConfigOptions" only for options in sections that no longer exist. * Fix INI parsing issues & C++17-ify - don't allow inline comments because it seems they aren't allowed in ini formats in general, and is going to cause problems if there is a comment character in a value (e.g. an exit auth string). Additionally it was breaking on a line such as: # some comment; see? because it was treating only `; see?` as the comment and then producing an error message about the rest of the line being invalid. - make section parsing stricter: the `[` and `]` have to be at the beginning at end of the line now (after stripping whitespace). - Move whitespace stripping to the top since everything in here does it. - chop off string_view suffix/prefix rather than maintaining position values - fix potential infinite loop/segfault when given a line such as `]foo[` * Make config parsing failure fatal Load() LogError's and returns false on failure, so we weren't aborting on config file errors. * Formatting: allow `{}` for empty functions/structs Instead of using two lines when empty: { } * Make default dns bind 127.0.0.1 on non-Linux * Don't show empty section; fix tests We can conceivably have sections that only make sense for clients or relays, and so want to completely omit that section if we have no options for the type of config being generated. Also fixes missing empty lines between tests. Co-authored-by: Thomas Winget <tewinget@gmail.com>
4 years ago
throw std::runtime_error{"Config file parsing failed"};
ctx = std::make_shared<llarp::Context>();
ctx->Configure(std::move(conf));
signal(SIGINT, handle_signal);
signal(SIGTERM, handle_signal);
#ifndef _WIN32
signal(SIGHUP, handle_signal);
signal(SIGUSR1, handle_signal);
#endif
ctx->Setup(opts);
llarp::util::SetThreadName("llarp-mainloop");
auto result = ctx->Run(opts);
exit_code.set_value(result);
}
catch (std::exception& e)
{
llarp::LogError("Fatal: caught exception while running: ", e.what());
exit_code.set_exception(std::current_exception());
}
catch (...)
{
llarp::LogError("Fatal: caught non-standard exception while running");
exit_code.set_exception(std::current_exception());
}
}
#ifdef _WIN32
void
TellWindowsServiceStopped()
{
::WSACleanup();
if (not start_as_daemon)
return;
llarp::LogInfo("Telling Windows the service has stopped.");
if (not ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0))
{
auto error_code = GetLastError();
if (error_code == ERROR_INVALID_DATA)
llarp::LogError(
"SetServiceStatus failed: \"The specified service status structure is invalid.\"");
else if (error_code == ERROR_INVALID_HANDLE)
llarp::LogError("SetServiceStatus failed: \"The specified handle is invalid.\"");
else
llarp::LogError("SetServiceStatus failed with an unknown error.");
}
llarp::LogContext::Instance().ImmediateFlush();
}
class WindowsServiceStopped
{
public:
WindowsServiceStopped() = default;
~WindowsServiceStopped()
{
TellWindowsServiceStopped();
}
};
/// minidump generation for windows jizz
/// will make a coredump when there is an unhandled exception
LONG
GenerateDump(EXCEPTION_POINTERS* pExceptionPointers)
{
3 years ago
const DWORD flags = MiniDumpWithFullMemory | MiniDumpWithFullMemoryInfo | MiniDumpWithHandleData
| MiniDumpWithUnloadedModules | MiniDumpWithThreadInfo;
std::stringstream ss;
ss << "C:\\ProgramData\\lokinet\\crash-" << llarp::time_now_ms().count() << ".dmp";
const std::string fname = ss.str();
HANDLE hDumpFile;
SYSTEMTIME stLocalTime;
GetLocalTime(&stLocalTime);
3 years ago
MINIDUMP_EXCEPTION_INFORMATION ExpParam{};
hDumpFile = CreateFile(
fname.c_str(),
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_WRITE | FILE_SHARE_READ,
0,
CREATE_ALWAYS,
0,
0);
ExpParam.ExceptionPointers = pExceptionPointers;
ExpParam.ClientPointers = TRUE;
MiniDumpWriteDump(
3 years ago
GetCurrentProcess(), GetCurrentProcessId(), hDumpFile, flags, &ExpParam, NULL, NULL);
return 1;
}
#endif
int
main(int argc, char* argv[])
{
#ifndef _WIN32
return lokinet_main(argc, argv);
#else
SERVICE_TABLE_ENTRY DispatchTable[] = {
{"lokinet", (LPSERVICE_MAIN_FUNCTION)win32_daemon_entry}, {NULL, NULL}};
if (lstrcmpi(argv[1], "--win32-daemon") == 0)
{
start_as_daemon = true;
StartServiceCtrlDispatcher(DispatchTable);
}
else
return lokinet_main(argc, argv);
#endif
}
int
lokinet_main(int argc, char* argv[])
{
auto result = Lokinet_INIT();
if (result)
{
return result;
}
#ifdef __APPLE__
llarp::LogContext::Instance().logStream.reset(new llarp::NSLogStream{});
#endif
llarp::RuntimeOptions opts;
#ifdef _WIN32
WindowsServiceStopped stopped_raii;
if (startWinsock())
return -1;
SetConsoleCtrlHandler(handle_signal_win32, TRUE);
5 years ago
// SetUnhandledExceptionFilter(win32_signal_handler);
#endif
cxxopts::Options options(
"lokinet",
"LokiNET is a free, open source, private, "
"decentralized, \"market based sybil resistant\" "
"and IP based onion routing network");
// clang-format off
options.add_options()
("v,verbose", "Verbose", cxxopts::value<bool>())
#ifdef _WIN32
("install", "install win32 daemon to SCM", cxxopts::value<bool>())
("remove", "remove win32 daemon from SCM", cxxopts::value<bool>())
#endif
("h,help", "help", cxxopts::value<bool>())("version", "version", cxxopts::value<bool>())
("g,generate", "generate client config", cxxopts::value<bool>())
("r,router", "run as router instead of client", cxxopts::value<bool>())
("f,force", "overwrite", cxxopts::value<bool>())
("c,colour", "colour output", cxxopts::value<bool>()->default_value("true"))
("b,background", "background mode (start, but do not connect to the network)",
cxxopts::value<bool>())
("config", "path to configuration file", cxxopts::value<std::string>())
;
// clang-format on
options.parse_positional("config");
bool genconfigOnly = false;
bool overwrite = false;
std::optional<fs::path> configFile;
try
{
auto result = options.parse(argc, argv);
if (result.count("verbose") > 0)
{
SetLogLevel(llarp::eLogDebug);
llarp::LogDebug("debug logging activated");
}
if (!result["colour"].as<bool>())
{
llarp::LogContext::Instance().logStream =
std::make_unique<llarp::OStreamLogStream>(false, std::cerr);
}
if (result.count("help"))
{
std::cout << options.help() << std::endl;
return 0;
}
if (result.count("version"))
{
std::cout << llarp::VERSION_FULL << std::endl;
return 0;
}
#ifdef _WIN32
if (result.count("install"))
{
install_win32_daemon();
return 0;
}
if (result.count("remove"))
{
uninstall_win32_daemon();
return 0;
}
#endif
if (result.count("generate") > 0)
{
genconfigOnly = true;
}
if (result.count("background") > 0)
{
opts.background = true;
}
if (result.count("router") > 0)
{
opts.isSNode = true;
}
if (result.count("force") > 0)
{
overwrite = true;
}
if (result.count("config") > 0)
{
auto arg = result["config"].as<std::string>();
if (!arg.empty())
{
configFile = arg;
}
}
}
catch (const cxxopts::option_not_exists_exception& ex)
{
std::cerr << ex.what();
std::cout << options.help() << std::endl;
return 1;
}
if (configFile.has_value())
{
// when we have an explicit filepath
fs::path basedir = configFile->parent_path();
if (genconfigOnly)
{
try
{
llarp::ensureConfig(basedir, *configFile, overwrite, opts.isSNode);
}
catch (std::exception& ex)
{
LogError("cannot generate config at ", *configFile, ": ", ex.what());
return 1;
}
}
else
{
try
{
if (!fs::exists(*configFile))
{
llarp::LogError("Config file not found ", *configFile);
return 1;
}
}
catch (std::exception& ex)
{
LogError("cannot check if ", *configFile, " exists: ", ex.what());
return 1;
}
}
}
else
{
try
{
llarp::ensureConfig(
llarp::GetDefaultDataDir(), llarp::GetDefaultConfigPath(), overwrite, opts.isSNode);
}
catch (std::exception& ex)
{
llarp::LogError("cannot ensure config: ", ex.what());
return 1;
}
configFile = llarp::GetDefaultConfigPath();
}
if (genconfigOnly)
{
return 0;
}
#ifdef _WIN32
SetUnhandledExceptionFilter(&GenerateDump);
#endif
std::thread main_thread{[&] { run_main_context(configFile, opts); }};
auto ftr = exit_code.get_future();
#ifdef _WIN32
ReportSvcStatus(SERVICE_RUNNING, NO_ERROR, 0);
#endif
do
{
// do periodic non lokinet related tasks here
if (ctx and ctx->IsUp() and not ctx->LooksAlive())
{
for (const auto& wtf :
{"you have been visited by the mascott of the deadlocked router.",
"⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⣀⣴⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣄⠄⠄⠄⠄",
"⠄⠄⠄⠄⠄⢀⣀⣀⡀⠄⠄⠄⡠⢲⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⡀⠄⠄",
"⠄⠄⠄⠔⣈⣀⠄⢔⡒⠳⡴⠊⠄⠸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠿⣿⣿⣧⠄⠄",
"⠄⢜⡴⢑⠖⠊⢐⣤⠞⣩⡇⠄⠄⠄⠙⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣆⠄⠝⠛⠋⠐",
"⢸⠏⣷⠈⠄⣱⠃⠄⢠⠃⠐⡀⠄⠄⠄⠄⠙⠻⢿⣿⣿⣿⣿⣿⣿⣿⡿⠛⠸⠄⠄⠄⠄",
"⠈⣅⠞⢁⣿⢸⠘⡄⡆⠄⠄⠈⠢⡀⠄⠄⠄⠄⠄⠄⠉⠙⠛⠛⠛⠉⠉⡀⠄⠡⢀⠄⣀",
"⠄⠙⡎⣹⢸⠄⠆⢘⠁⠄⠄⠄⢸⠈⠢⢄⡀⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠃⠄⠄⠄⠄⠄",
"⠄⠄⠑⢿⠈⢆⠘⢼⠄⠄⠄⠄⠸⢐⢾⠄⡘⡏⠲⠆⠠⣤⢤⢤⡤⠄⣖⡇⠄⠄⠄⠄⠄",
"⣴⣶⣿⣿⣣⣈⣢⣸⠄⠄⠄⠄⡾⣷⣾⣮⣤⡏⠁⠘⠊⢠⣷⣾⡛⡟⠈⠄⠄⠄⠄⠄⠄",
"⣿⣿⣿⣿⣿⠉⠒⢽⠄⠄⠄⠄⡇⣿⣟⣿⡇⠄⠄⠄⠄⢸⣻⡿⡇⡇⠄⠄⠄⠄⠄⠄⠄",
"⠻⣿⣿⣿⣿⣄⠰⢼⠄⠄⠄⡄⠁⢻⣍⣯⠃⠄⠄⠄⠄⠈⢿⣻⠃⠈⡆⡄⠄⠄⠄⠄⠄",
"⠄⠙⠿⠿⠛⣿⣶⣤⡇⠄⠄⢣⠄⠄⠈⠄⢠⠂⠄⠁⠄⡀⠄⠄⣀⠔⢁⠃⠄⠄⠄⠄⠄",
"⠄⠄⠄⠄⠄⣿⣿⣿⣿⣾⠢⣖⣶⣦⣤⣤⣬⣤⣤⣤⣴⣶⣶⡏⠠⢃⠌⠄⠄⠄⠄⠄⠄",
"⠄⠄⠄⠄⠄⠿⠿⠟⠛⡹⠉⠛⠛⠿⠿⣿⣿⣿⣿⣿⡿⠂⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄",
"⠠⠤⠤⠄⠄⣀⠄⠄⠄⠑⠠⣤⣀⣀⣀⡘⣿⠿⠙⠻⡍⢀⡈⠂⠄⠄⠄⠄⠄⠄⠄⠄⠄",
"⠄⠄⠄⠄⠄⠄⠑⠠⣠⣴⣾⣿⣿⣿⣿⣿⣿⣇⠉⠄⠻⣿⣷⣄⡀⠄⠄⠄⠄⠄⠄⠄⠄",
"file a bug report now or be cursed with this "
"annoying image in your syslog for all time."})
{
LogError(wtf);
llarp::LogContext::Instance().ImmediateFlush();
}
#ifdef _WIN32
TellWindowsServiceStopped();
#endif
std::abort();
}
} while (ftr.wait_for(std::chrono::seconds(1)) != std::future_status::ready);
main_thread.join();
int code = 0;
try
{
code = ftr.get();
}
catch (const std::exception& e)
{
std::cerr << "main thread threw exception: " << e.what() << std::endl;
code = 1;
}
catch (...)
{
std::cerr << "main thread threw non-standard exception" << std::endl;
code = 2;
}
llarp::LogContext::Instance().ImmediateFlush();
if (ctx)
{
ctx.reset();
}
return code;
7 years ago
}
#ifdef _WIN32
BOOL
ReportSvcStatus(DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwWaitHint)
{
static DWORD dwCheckPoint = 1;
// Fill in the SERVICE_STATUS structure.
SvcStatus.dwCurrentState = dwCurrentState;
SvcStatus.dwWin32ExitCode = dwWin32ExitCode;
SvcStatus.dwWaitHint = dwWaitHint;
if (dwCurrentState == SERVICE_START_PENDING)
SvcStatus.dwControlsAccepted = 0;
else
SvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
if ((dwCurrentState == SERVICE_RUNNING) || (dwCurrentState == SERVICE_STOPPED))
SvcStatus.dwCheckPoint = 0;
else
SvcStatus.dwCheckPoint = dwCheckPoint++;
// Report the status of the service to the SCM.
return SetServiceStatus(SvcStatusHandle, &SvcStatus);
}
VOID FAR PASCAL
SvcCtrlHandler(DWORD dwCtrl)
{
// Handle the requested control code.
switch (dwCtrl)
{
case SERVICE_CONTROL_STOP:
ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0);
// Signal the service to stop.
handle_signal(SIGINT);
return;
case SERVICE_CONTROL_INTERROGATE:
break;
default:
break;
}
}
// The win32 daemon entry point is just a trampoline that returns control
// to the original lokinet entry
// and only gets called if we get --win32-daemon in the command line
VOID FAR PASCAL
win32_daemon_entry(DWORD argc, LPTSTR* argv)
{
// Register the handler function for the service
SvcStatusHandle = RegisterServiceCtrlHandler("lokinet", SvcCtrlHandler);
if (!SvcStatusHandle)
{
llarp::LogError("failed to register daemon control handler");
return;
}
// These SERVICE_STATUS members remain as set here
SvcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
SvcStatus.dwServiceSpecificExitCode = 0;
// Report initial status to the SCM
ReportSvcStatus(SERVICE_START_PENDING, NO_ERROR, 3000);
// SCM clobbers startup args, regenerate them here
argc = 2;
argv[1] = "c:/programdata/lokinet/lokinet.ini";
argv[2] = nullptr;
lokinet_main(argc, argv);
}
#endif