Add commandline arguments, implement --config, --version, --help

This commit is contained in:
Lion Kortlepel 2021-11-26 19:04:21 +01:00
parent 938774618c
commit eae27633db
No known key found for this signature in database
GPG Key ID: 4322FF2B4C71259B
9 changed files with 266 additions and 35 deletions

View File

@ -97,7 +97,8 @@ add_executable(BeamMP-Server
include/TNetwork.h src/TNetwork.cpp
include/LuaAPI.h src/LuaAPI.cpp
include/TScopedTimer.h src/TScopedTimer.cpp
include/SignalHandling.h src/SignalHandling.cpp)
include/SignalHandling.h src/SignalHandling.cpp
include/ArgsParser.h src/ArgsParser.cpp)
target_compile_definitions(BeamMP-Server PRIVATE SECRET_SENTRY_URL="${BEAMMP_SECRET_SENTRY_URL}")
include_directories(BeamMP-Server PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)

49
include/ArgsParser.h Normal file
View File

@ -0,0 +1,49 @@
#pragma once
#include <initializer_list>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
/*
* Allows syntax:
* --help : long flags
* --path=/home/lion : long assignments
*/
class ArgsParser {
public:
enum Flags : int {
NONE = 0,
REQUIRED = 1, // argument is required
HAS_VALUE = 2, // argument must have a value
};
ArgsParser() = default;
void Parse(const std::vector<std::string_view>& ArgList);
// prints errors if any errors occurred, in that case also returns false
bool Verify();
void RegisterArgument(std::vector<std::string>&& ArgumentNames, int Flags);
// pass all possible names for this argument (short, long, etc)
bool FoundArgument(const std::vector<std::string>& Names);
std::optional<std::string> GetValueOfArgument(const std::vector<std::string>& Names);
private:
void ConsumeLongAssignment(const std::string& Arg);
void ConsumeLongFlag(const std::string& Arg);
bool IsRegistered(const std::string& Name);
struct Argument {
std::string Name;
std::optional<std::string> Value;
};
struct RegisteredArgument {
std::vector<std::string> Names;
int Flags;
};
std::vector<RegisteredArgument> mRegisteredArguments;
std::vector<Argument> mFoundArgs;
};

View File

@ -8,7 +8,7 @@ namespace fs = std::filesystem;
class TConfig {
public:
explicit TConfig();
explicit TConfig(const std::string& ConfigFileName);
[[nodiscard]] bool Failed() const { return mFailed; }
@ -22,4 +22,5 @@ private:
void ParseOldFormat();
bool mFailed { false };
std::string mConfigFileName;
};

View File

@ -15,6 +15,7 @@ public:
void WriteRaw(const std::string& str);
void InitializeLuaConsole(TLuaEngine& Engine);
void BackupOldLog();
Commandline& Internal() { return mCommandline; }
private:
Commandline mCommandline;

View File

@ -16,7 +16,7 @@ class TServer final {
public:
using TClientSet = std::unordered_set<std::shared_ptr<TClient>>;
TServer(int argc, char** argv);
TServer(const std::vector<std::string_view>& Arguments);
void InsertClient(const std::shared_ptr<TClient>& Ptr);
std::weak_ptr<TClient> InsertNewClient();

94
src/ArgsParser.cpp Normal file
View File

@ -0,0 +1,94 @@
#include "ArgsParser.h"
#include "Common.h"
#include <algorithm>
void ArgsParser::Parse(const std::vector<std::string_view>& ArgList) {
for (const auto& Arg : ArgList) {
if (Arg.size() > 2 && Arg.substr(0, 2) == "--") {
// long arg
if (Arg.find("=") != Arg.npos) {
ConsumeLongAssignment(std::string(Arg));
} else {
ConsumeLongFlag(std::string(Arg));
}
} else {
beammp_error("Error parsing commandline arguments: Supplied argument '" + std::string(Arg) + "' is not a valid argument and was ignored.");
}
}
}
bool ArgsParser::Verify() {
bool Ok = true;
for (const auto& RegisteredArg : mRegisteredArguments) {
if (RegisteredArg.Flags & Flags::REQUIRED && !FoundArgument(RegisteredArg.Names)) {
beammp_error("Error in commandline arguments: Argument '" + std::string(RegisteredArg.Names.at(0)) + "' is required but wasn't found.");
Ok = false;
continue;
} else if (FoundArgument(RegisteredArg.Names)) {
if (RegisteredArg.Flags & Flags::HAS_VALUE) {
if (!GetValueOfArgument(RegisteredArg.Names).has_value()) {
beammp_error("Error in commandline arguments: Argument '" + std::string(RegisteredArg.Names.at(0)) + "' expects a value, but no value was given.");
Ok = false;
}
} else if (GetValueOfArgument(RegisteredArg.Names).has_value()) {
beammp_error("Error in commandline arguments: Argument '" + std::string(RegisteredArg.Names.at(0)) + "' does not expect a value, but one was given.");
Ok = false;
}
}
}
return Ok;
}
void ArgsParser::RegisterArgument(std::vector<std::string>&& ArgumentNames, int Flags) {
mRegisteredArguments.push_back({ ArgumentNames, Flags });
}
bool ArgsParser::FoundArgument(const std::vector<std::string>& Names) {
// if any of the found args match any of the names
return std::any_of(mFoundArgs.begin(), mFoundArgs.end(),
[&Names](const Argument& Arg) -> bool {
// if any of the names match this arg's name
return std::any_of(Names.begin(), Names.end(), [&Arg](const std::string& Name) -> bool {
return Arg.Name == Name;
});
});
}
std::optional<std::string> ArgsParser::GetValueOfArgument(const std::vector<std::string>& Names) {
// finds an entry which has a name that is any of the names in 'Names'
auto Found = std::find_if(mFoundArgs.begin(), mFoundArgs.end(), [&Names](const Argument& Arg) -> bool {
return std::any_of(Names.begin(), Names.end(), [&Arg](const std::string_view& Name) -> bool {
return Arg.Name == Name;
});
});
if (Found != mFoundArgs.end()) {
// found
return Found->Value;
} else {
return std::nullopt;
}
}
bool ArgsParser::IsRegistered(const std::string& Name) {
return std::any_of(mRegisteredArguments.begin(), mRegisteredArguments.end(), [&Name](const RegisteredArgument& Arg) {
auto Iter = std::find(Arg.Names.begin(), Arg.Names.end(), Name);
return Iter != Arg.Names.end();
});
}
void ArgsParser::ConsumeLongAssignment(const std::string& Arg) {
auto Value = Arg.substr(Arg.rfind("=") + 1);
auto Name = Arg.substr(2, Arg.rfind("=") - 2);
if (!IsRegistered(Name)) {
beammp_warn("Argument '" + Name + "' was supplied but isn't a known argument, so it is likely being ignored.");
}
mFoundArgs.push_back({ Name, Value });
}
void ArgsParser::ConsumeLongFlag(const std::string& Arg) {
auto Name = Arg.substr(2, Arg.rfind("=") - 2);
mFoundArgs.push_back({ Name, std::nullopt });
if (!IsRegistered(Name)) {
beammp_warn("Argument '" + Name + "' was supplied but isn't a known argument, so it is likely being ignored.");
}
}

View File

@ -8,8 +8,6 @@
#include <istream>
#include <sstream>
static const char* ConfigFileName = static_cast<const char*>("ServerConfig.toml");
static constexpr std::string_view StrDebug = "Debug";
static constexpr std::string_view StrPrivate = "Private";
static constexpr std::string_view StrPort = "Port";
@ -23,19 +21,6 @@ static constexpr std::string_view StrAuthKey = "AuthKey";
static constexpr std::string_view StrSendErrors = "SendErrors";
static constexpr std::string_view StrSendErrorsMessageEnabled = "SendErrorsShowMessage";
TConfig::TConfig() {
if (!fs::exists(ConfigFileName) || !fs::is_regular_file(ConfigFileName)) {
beammp_info("No config file found! Generating one...");
CreateConfigFile(ConfigFileName);
}
if (!mFailed) {
if (fs::exists("Server.cfg")) {
beammp_warn("An old \"Server.cfg\" file still exists. Please note that this is no longer used. Instead, \"" + std::string(ConfigFileName) + "\" is used. You can safely delete the \"Server.cfg\".");
}
ParseFromFile(ConfigFileName);
}
}
void WriteSendErrors(const std::string& name) {
std::ofstream CfgFile { name, std::ios::out | std::ios::app };
CfgFile << "# You can turn on/off the SendErrors message you get on startup here" << std::endl
@ -45,8 +30,22 @@ void WriteSendErrors(const std::string& name) {
<< StrSendErrors << " = true" << std::endl;
}
TConfig::TConfig(const std::string& ConfigFileName)
: mConfigFileName(ConfigFileName) {
if (!fs::exists(mConfigFileName) || !fs::is_regular_file(mConfigFileName)) {
beammp_info("No config file found! Generating one...");
CreateConfigFile(mConfigFileName);
}
if (!mFailed) {
if (fs::exists("Server.cfg")) {
beammp_warn("An old \"Server.cfg\" file still exists. Please note that this is no longer used. Instead, \"" + std::string(mConfigFileName) + "\" is used. You can safely delete the \"Server.cfg\".");
}
ParseFromFile(mConfigFileName);
}
}
void TConfig::FlushToFile() {
auto data = toml::parse(ConfigFileName);
auto data = toml::parse(mConfigFileName);
data["General"] = toml::table();
data["General"][StrAuthKey.data()] = Application::Settings.Key;
data["General"][StrDebug.data()] = Application::Settings.DebugModeEnabled;
@ -60,7 +59,7 @@ void TConfig::FlushToFile() {
data["General"][StrResourceFolder.data()] = Application::Settings.Resource;
data["General"][StrSendErrors.data()] = Application::Settings.SendErrors;
data["General"][StrSendErrorsMessageEnabled.data()] = Application::Settings.SendErrorsMessageEnabled;
std::ofstream Stream(ConfigFileName);
std::ofstream Stream(mConfigFileName);
Stream << data << std::flush;
}
@ -104,7 +103,7 @@ void TConfig::CreateConfigFile(std::string_view name) {
"# IMPORTANT: Fill in the AuthKey with the key you got from `https://beammp.com/k/dashboard` on the left under \"Keys\"\n"
<< '\n';
ofs << data << '\n';
beammp_error("There was no \"" + std::string(ConfigFileName) + "\" file (this is normal for the first time running the server), so one was generated for you. It was automatically filled with the settings from your Server.cfg, if you have one. Please open ServerConfig.toml and ensure your AuthKey and other settings are filled in and correct, then restart the server. The old Server.cfg file will no longer be used and causes a warning if it exists from now on.");
beammp_error("There was no \"" + std::string(mConfigFileName) + "\" file (this is normal for the first time running the server), so one was generated for you. It was automatically filled with the settings from your Server.cfg, if you have one. Please open ServerConfig.toml and ensure your AuthKey and other settings are filled in and correct, then restart the server. The old Server.cfg file will no longer be used and causes a warning if it exists from now on.");
mFailed = true;
ofs.close();
// FIXME
@ -143,7 +142,7 @@ void TConfig::ParseFromFile(std::string_view name) {
PrintDebug();
// all good so far, let's check if there's a key
if (Application::Settings.Key.empty()) {
beammp_error("No AuthKey specified in the \"" + std::string(ConfigFileName) + "\" file. Please get an AuthKey, enter it into the config file, and restart this server.");
beammp_error("No AuthKey specified in the \"" + std::string(mConfigFileName) + "\" file. Please get an AuthKey, enter it into the config file, and restart this server.");
mFailed = true;
}
}

View File

@ -15,10 +15,10 @@
namespace json = rapidjson;
TServer::TServer(int argc, char** argv) {
TServer::TServer(const std::vector<std::string_view>& Arguments) {
beammp_info("BeamMP Server v" + Application::ServerVersionString());
if (argc > 1) {
Application::Settings.CustomIP = argv[1];
if (Arguments.size() > 1) {
Application::Settings.CustomIP = Arguments[0];
size_t n = std::count(Application::Settings.CustomIP.begin(), Application::Settings.CustomIP.end(), '.');
auto p = Application::Settings.CustomIP.find_first_not_of(".0123456789");
if (p != std::string::npos || n != 3 || Application::Settings.CustomIP.substr(0, 3) == "127") {
@ -67,7 +67,7 @@ size_t TServer::ClientCount() const {
void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::string Packet, TPPSMonitor& PPSMonitor, TNetwork& Network) {
if (Packet.find("Zp") != std::string::npos && Packet.size() > 500) {
//abort();
// abort();
}
if (Packet.substr(0, 4) == "ABG:") {
Packet = DeComp(Packet.substr(4));
@ -84,7 +84,7 @@ void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::string Pac
std::any Res;
char Code = Packet.at(0);
//V to Z
// V to Z
if (Code <= 90 && Code >= 86) {
PPSMonitor.IncrementInternalPPS();
Network.SendToAll(LockedClient.get(), Packet, false, false);
@ -200,7 +200,7 @@ void TServer::ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Networ
int PID = -1;
int VID = -1, Pos;
std::string Data = Packet.substr(3), pid, vid;
switch (Code) { //Spawned Destroyed Switched/Moved NotFound Reset
switch (Code) { // Spawned Destroyed Switched/Moved NotFound Reset
case 's':
beammp_trace(std::string(("got 'Os' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
if (Data.at(0) == '0') {
@ -370,6 +370,6 @@ void TServer::Apply(TClient& c, int VID, const std::string& pckt) {
void TServer::InsertClient(const std::shared_ptr<TClient>& NewClient) {
beammp_debug("inserting client (" + std::to_string(ClientCount()) + ")");
WriteLock Lock(mClientsMutex); //TODO why is there 30+ threads locked here
WriteLock Lock(mClientsMutex); // TODO why is there 30+ threads locked here
(void)mClients.insert(NewClient);
}

View File

@ -1,5 +1,6 @@
#include "TSentry.h"
#include "ArgsParser.h"
#include "Common.h"
#include "CustomAssert.h"
#include "Http.h"
@ -17,15 +18,103 @@
#include <iostream>
#include <thread>
static const std::string sCommandlineArguments = R"(
USAGE:
BeamMP-Server [arguments]
ARGUMENTS:
--help
Displays this help and exits.
--ip=<ip>
Asks the server to bind to the
specified IPv4 address. ONLY allows
IPv4 addresses of the format
A.B.C.D, where A-D are between 0 and
255 each.
--config=/path/to/ServerConfig.toml
Absolute or relative path to the
Server Config file, including the
filename. For paths and filenames with
spaces, put quotes around the path.
--version
Prints version info and exits.
EXAMPLES:
BeamMP-Server --ip=203.0.113.0
Runs the BeamMP-Server and binds its address to the
specified IP '203.0.113.0'.
BeamMP-Server --config=../MyWestCoastServerConfig.toml
Runs the BeamMP-Server and uses the server config file
which is one directory above it and is named
'MyWestCoastServerConfig.toml'.
)";
// this is provided by the build system, leave empty for source builds
// global, yes, this is ugly, no, it cant be done another way
TSentry Sentry {};
int main(int argc, char** argv) try {
struct MainArguments {
int argc {};
char** argv {};
std::vector<std::string_view> List;
std::string InvokedAs;
};
int BeamMPServerMain(MainArguments Arguments);
int main(int argc, char** argv) {
MainArguments Args { argc, argv, {}, argv[0] };
Args.List.reserve(argc);
for (int i = 1; i < argc; ++i) {
Args.List.push_back(argv[i]);
}
int MainRet = 0;
try {
MainRet = BeamMPServerMain(std::move(Args));
} catch (const std::exception& e) {
beammp_error("A fatal exception has occurred and the server is forcefully shutting down.");
beammp_error(e.what());
Sentry.LogException(e, _file_basename, _line);
MainRet = -1;
}
return MainRet;
}
int BeamMPServerMain(MainArguments Arguments) {
setlocale(LC_ALL, "C");
SetupSignalHandlers();
ArgsParser Parser;
Parser.RegisterArgument({ "help" }, ArgsParser::NONE);
Parser.RegisterArgument({ "version" }, ArgsParser::NONE);
Parser.RegisterArgument({ "config" }, ArgsParser::HAS_VALUE);
Parser.RegisterArgument({ "ip" }, ArgsParser::HAS_VALUE);
Parser.Parse(Arguments.List);
if (!Parser.Verify()) {
return 1;
}
if (Parser.FoundArgument({ "help" })) {
Application::Console().Internal().set_prompt("");
Application::Console().WriteRaw(sCommandlineArguments);
return 0;
}
if (Parser.FoundArgument({ "version" })) {
Application::Console().Internal().set_prompt("");
Application::Console().WriteRaw("BeamMP-Server v" + Application::ServerVersionString());
return 0;
}
std::string ConfigPath = "ServerConfig.toml";
if (Parser.FoundArgument({ "config" })) {
auto MaybeConfigPath = Parser.GetValueOfArgument({ "config" });
if (MaybeConfigPath.has_value()) {
ConfigPath = MaybeConfigPath.value();
beammp_info("Custom config requested via commandline: '" + ConfigPath + "'");
}
}
bool Shutdown = false;
Application::RegisterShutdownHandler([&Shutdown] { Shutdown = true; });
Application::RegisterShutdownHandler([] {
@ -33,8 +122,8 @@ int main(int argc, char** argv) try {
TLuaEngine::WaitForAll(Futures);
});
TServer Server(argc, argv);
TConfig Config;
TServer Server(Arguments.List);
TConfig Config(ConfigPath);
TLuaEngine LuaEngine;
LuaEngine.SetServer(&Server);
@ -67,7 +156,4 @@ int main(int argc, char** argv) try {
}
beammp_info("Shutdown.");
return 0;
} catch (const std::exception& e) {
beammp_error(e.what());
Sentry.LogException(e, _file_basename, _line);
}