Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 27ffe16

Browse files
committedApr 19, 2025·
FEAT(server,cli): parse options via CLI11
1 parent ed52120 commit 27ffe16

File tree

1 file changed

+202
-160
lines changed

1 file changed

+202
-160
lines changed
 

‎src/murmur/main.cpp

+202-160
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@
3737
# include <sys/syslog.h>
3838
#endif
3939

40+
#include <optional>
41+
#include <tuple>
42+
43+
#include <CLI/CLI.hpp>
44+
4045
extern QFile *qfLog;
4146

4247
static bool bVerbose = false;
@@ -186,6 +191,135 @@ void cleanup(int signum) {
186191
exit(signum);
187192
}
188193

194+
class CLIOptions {
195+
public:
196+
bool quit = false;
197+
std::optional< std::string > ini_file;
198+
std::optional< std::string > db_dump_path;
199+
std::optional< std::string > db_import_path;
200+
std::tuple< std::string, std::optional< unsigned int > > supw_srv;
201+
std::optional< unsigned int > disable_su_srv;
202+
bool verbose_logging = false;
203+
bool cli_detach = detach;
204+
bool wipe_ssl = false;
205+
bool wipe_logs = false;
206+
bool log_groups = false;
207+
bool log_acls = false;
208+
209+
bool print_authors = false;
210+
bool print_license = false;
211+
bool print_3rd_party_licenses = false;
212+
213+
#ifdef Q_OS_UNIX
214+
bool limits = false;
215+
std::optional< unsigned int > read_supw_srv;
216+
#endif
217+
218+
static constexpr const char *const CLI_ABOUT_SECTION = "About";
219+
static constexpr const char *const CLI_LOGGING_SECTION = "Logging";
220+
static constexpr const char *const CLI_ADMINISTRATION_SECTION = "Administration";
221+
static constexpr const char *const CLI_CONFIGURATION_SECTION = "Configuration";
222+
static constexpr const char *const CLI_TESTING_SECTION = "Testing";
223+
};
224+
225+
CLIOptions parseCLI(int argc, char **argv) {
226+
CLIOptions options;
227+
228+
CLI::App app;
229+
app.set_version_flag("--version", Version::getRelease().toStdString());
230+
231+
app.add_option_no_stream("-i,--ini", options.ini_file, "Specify ini file to use.")
232+
->option_text("<inifile>")
233+
->expected(1, 2)
234+
->check(CLI::ExistingFile)
235+
->group(CLIOptions::CLI_CONFIGURATION_SECTION);
236+
237+
app.add_option("-w,--supw", options.supw_srv, "Set password for 'SuperUser' account on server srv.")
238+
->option_text("<pw> [srv]")
239+
->allow_extra_args()
240+
->expected(0, 1)
241+
->group(CLIOptions::CLI_ADMINISTRATION_SECTION);
242+
243+
#ifdef Q_OS_UNIX
244+
app.add_option_no_stream("-r,--readsupw", options.read_supw_srv,
245+
"Reads password for server srv from standard input.")
246+
->option_text("[srv]")
247+
->default_val(1)
248+
->expected(0, 1)
249+
->group(CLIOptions::CLI_ADMINISTRATION_SECTION);
250+
251+
app.add_flag("-l,--limits", options.limits,
252+
"Tests and shows how many file descriptors and threads can be created.\n"
253+
"The purpose of this option is to test how many clients Mumble server can handle.\n"
254+
"Mumble server will exit after this test.")
255+
->group(CLIOptions::CLI_TESTING_SECTION);
256+
#endif
257+
258+
app.add_option_no_stream("-d,--disablesu", options.disable_su_srv,
259+
"Disable password for 'SuperUser' account on server srv.")
260+
->option_text("[srv]")
261+
->expected(0, 1)
262+
->group(CLIOptions::CLI_ADMINISTRATION_SECTION);
263+
app.add_flag("-s,--wipessl", options.wipe_ssl, "Remove SSL certificates from database.")
264+
->group(CLIOptions::CLI_ADMINISTRATION_SECTION);
265+
app.add_flag("--db-json-dump", options.db_dump_path,
266+
"Requests a JSON dump of the database to be written to the given file")
267+
->option_text("[file]")
268+
->expected(0, 1)
269+
->group(CLIOptions::CLI_ADMINISTRATION_SECTION);
270+
app.add_flag("--db-json-import", options.db_import_path,
271+
"Reads in the provide JSON file and imports its contents into the database")
272+
->option_text("[file]")
273+
->expected(0, 1)
274+
->group(CLIOptions::CLI_ADMINISTRATION_SECTION);
275+
276+
app.add_flag("-v,--verbose", options.verbose_logging, "Use verbose logging (include debug-logs).")
277+
->group(CLIOptions::CLI_LOGGING_SECTION);
278+
app.add_flag("!-f,!--force-fg", options.cli_detach,
279+
#ifdef Q_OS_UNIX
280+
"Don't detach from console."
281+
#else
282+
"Don't write to the log file."
283+
#endif
284+
)
285+
->group(CLIOptions::CLI_LOGGING_SECTION);
286+
287+
app.add_flag("-p,--wipelogs", options.wipe_logs, "Remove all log entries from database.")
288+
->group(CLIOptions::CLI_LOGGING_SECTION);
289+
app.add_flag("-g,--loggroups", options.log_groups, "Turns on logging for group changes for all servers.")
290+
->group(CLIOptions::CLI_LOGGING_SECTION);
291+
app.add_flag("-a,--logacls", options.log_acls, "Turns on logging for ACL changes for all servers.")
292+
->group(CLIOptions::CLI_LOGGING_SECTION);
293+
294+
295+
app.add_flag("--authors", options.print_authors, "Show Mumble server's authors.")
296+
->group(CLIOptions::CLI_ABOUT_SECTION);
297+
app.add_flag("--license", options.print_license, "Show Mumble server's license.")
298+
->group(CLIOptions::CLI_ABOUT_SECTION);
299+
app.add_flag("--3rd-party-licenses", options.print_3rd_party_licenses,
300+
"Show licenses for third-party software used by Mumble server.")
301+
->group(CLIOptions::CLI_ABOUT_SECTION);
302+
303+
304+
app.footer("If no inifile is provided, Mumble server will search for one in\ndefault locations.");
305+
306+
try {
307+
(app).parse(argc, argv);
308+
} catch (const CLI::ParseError &e) {
309+
std::stringstream info_stream, error_stream;
310+
app.exit(e, info_stream, error_stream);
311+
312+
if (e.get_exit_code() != static_cast< int >(CLI::ExitCodes::Success)) {
313+
qFatal("%s", error_stream.str().c_str());
314+
} else {
315+
qInfo("%s", info_stream.str().c_str());
316+
}
317+
options.quit = true;
318+
}
319+
320+
return options;
321+
}
322+
189323
int main(int argc, char **argv) {
190324
// Check for SSE and MMX, but only in the windows binaries
191325
#ifdef Q_OS_WIN
@@ -234,178 +368,86 @@ int main(int argc, char **argv) {
234368

235369
MumbleSSL::initialize();
236370

237-
QString inifile;
238-
QString supw;
239-
QString dbDumpPath;
240-
QString dbImportPath;
241-
bool disableSu = false;
242-
bool wipeSsl = false;
243-
bool wipeLogs = false;
244-
unsigned int sunum = 0;
245-
#ifdef Q_OS_UNIX
246-
bool readPw = false;
247-
#endif
248-
bool logGroups = false;
249-
bool logACL = false;
250-
251-
252371
qInstallMessageHandler(murmurMessageOutputWithContext);
253372

254373
#ifdef Q_OS_WIN
255374
Tray tray(nullptr, &le);
256375
#endif
376+
CLIOptions cli_options = parseCLI(argc, argv);
377+
if (cli_options.quit)
378+
return 0;
257379

258-
QStringList args = a.arguments();
259-
for (int i = 1; i < args.size(); i++) {
260-
bool bLast = false;
261-
QString arg = args.at(i).toLower();
262-
if ((arg == "-supw")) {
263-
detach = false;
264-
if (i + 1 < args.size()) {
265-
i++;
266-
supw = args.at(i);
267-
if (i + 1 < args.size()) {
268-
i++;
269-
sunum = args.at(i).toUInt();
270-
}
271-
bLast = true;
272-
} else {
273-
#ifdef Q_OS_UNIX
274-
qFatal("-supw expects the password on the command line - maybe you meant -readsupw?");
275-
#else
276-
qFatal("-supw expects the password on the command line");
277-
#endif
278-
}
279-
#ifdef Q_OS_UNIX
280-
} else if ((arg == "-readsupw")) {
281-
// Note that it is essential to set detach = false here. If this is ever to be changed, the code part
282-
// handling the readPw = true part has to be moved up so that it is executed before fork is called on Unix
283-
// systems.
284-
detach = false;
285-
readPw = true;
286-
if (i + 1 < args.size()) {
287-
i++;
288-
sunum = args.at(i).toUInt();
289-
}
290-
bLast = true;
291-
#endif
292-
} else if ((arg == "-disablesu")) {
293-
detach = false;
294-
disableSu = true;
295-
if (i + 1 < args.size()) {
296-
i++;
297-
sunum = args.at(i).toUInt();
298-
}
299-
bLast = true;
300-
} else if ((arg == "-ini") && (i + 1 < args.size())) {
301-
i++;
302-
inifile = args.at(i);
303-
} else if ((arg == "-wipessl")) {
304-
wipeSsl = true;
305-
} else if ((arg == "-wipelogs")) {
306-
wipeLogs = true;
307-
} else if ((arg == "-fg")) {
308-
detach = false;
309-
} else if ((arg == "-v")) {
310-
bVerbose = true;
311-
} else if ((arg == "-version") || (arg == "--version")) {
312-
// Print version and exit (print to regular std::cout to avoid adding any useless meta-information from
313-
// using e.g. qWarning
314-
std::cout << "Mumble server version " << Version::getRelease().toStdString() << std::endl;
315-
return 0;
316-
} else if (args.at(i) == QLatin1String("-license") || args.at(i) == QLatin1String("--license")) {
380+
if (cli_options.print_license) {
317381
#ifdef Q_OS_WIN
318-
AboutDialog ad(nullptr, AboutDialogOptionsShowLicense);
319-
ad.exec();
320-
return 0;
382+
AboutDialog ad(nullptr, AboutDialogOptionsShowLicense);
383+
ad.exec();
384+
return 0;
321385
#else
322-
qInfo("%s\n", qPrintable(License::license()));
323-
return 0;
386+
qInfo("%s\n", qPrintable(License::license()));
387+
return 0;
324388
#endif
325-
} else if (args.at(i) == QLatin1String("-authors") || args.at(i) == QLatin1String("--authors")) {
389+
} else if (cli_options.print_authors) {
326390
#ifdef Q_OS_WIN
327-
AboutDialog ad(nullptr, AboutDialogOptionsShowAuthors);
328-
ad.exec();
329-
return 0;
391+
AboutDialog ad(nullptr, AboutDialogOptionsShowAuthors);
392+
ad.exec();
393+
return 0;
330394
#else
331-
qInfo("%s\n",
332-
"For a list of authors, please see https://github.com/mumble-voip/mumble/graphs/contributors");
333-
return 0;
395+
qInfo("%s\n", "For a list of authors, please see https://github.com/mumble-voip/mumble/graphs/contributors");
396+
return 0;
334397
#endif
335-
} else if (args.at(i) == QLatin1String("-third-party-licenses")
336-
|| args.at(i) == QLatin1String("--third-party-licenses")) {
398+
} else if (cli_options.print_3rd_party_licenses) {
337399
#ifdef Q_OS_WIN
338-
AboutDialog ad(nullptr, AboutDialogOptionsShowThirdPartyLicenses);
339-
ad.exec();
340-
return 0;
400+
AboutDialog ad(nullptr, AboutDialogOptionsShowThirdPartyLicenses);
401+
ad.exec();
402+
return 0;
341403
#else
342-
qInfo("%s", qPrintable(License::printableThirdPartyLicenseInfo()));
343-
return 0;
344-
#endif
345-
} else if (arg == "--db-json-dump") {
346-
++i;
347-
dbDumpPath = args.at(i);
348-
} else if (arg == "--db-json-import") {
349-
++i;
350-
dbImportPath = args.at(i);
351-
} else if ((arg == "-h") || (arg == "-help") || (arg == "--help")) {
352-
detach = false;
353-
qInfo(
354-
"Usage: %s [-ini <inifile>] [-supw <password>]\n"
355-
" --version Print version information and exit\n"
356-
" -ini <inifile> Specify ini file to use.\n"
357-
" -supw <pw> [srv] Set password for 'SuperUser' account on server srv.\n"
358-
#ifdef Q_OS_UNIX
359-
" -readsupw [srv] Reads password for server srv from standard input.\n"
360-
#endif
361-
" -disablesu [srv] Disable password for 'SuperUser' account on server srv.\n"
362-
#ifdef Q_OS_UNIX
363-
" -limits Tests and shows how many file descriptors and threads can be created.\n"
364-
" The purpose of this option is to test how many clients Murmur can handle.\n"
365-
" Murmur will exit after this test.\n"
404+
qInfo("%s", qPrintable(License::printableThirdPartyLicenseInfo()));
405+
return 0;
366406
#endif
367-
" -v Use verbose logging (include debug-logs).\n"
407+
}
408+
409+
detach = cli_options.cli_detach;
410+
QString inifile = QString::fromStdString(cli_options.ini_file.value_or(""));
411+
QString supw;
412+
bool disableSu = false;
413+
bool wipeSsl = cli_options.wipe_ssl;
414+
bool wipeLogs = cli_options.wipe_logs;
415+
unsigned int sunum = 0;
368416
#ifdef Q_OS_UNIX
369-
" -fg Don't detach from console.\n"
370-
#else
371-
" -fg Don't write to the log file.\n"
417+
bool readPw = false;
372418
#endif
373-
" -wipessl Remove SSL certificates from database.\n"
374-
" -wipelogs Remove all log entries from database.\n"
375-
" -loggroups Turns on logging for group changes for all servers.\n"
376-
" -logacls Turns on logging for ACL changes for all servers.\n"
377-
" -version Show version information.\n"
378-
" --db-json-dump [file] Requests a JSON dump of the database to be written to the given file\n"
379-
" --db-json-import [file] Reads in the provide JSON file and imports its contents into the database\n"
380-
"\n"
381-
" -license Show Murmur's license.\n"
382-
" -authors Show Murmur's authors.\n"
383-
" -third-party-licenses Show licenses for third-party software used by Murmur.\n"
384-
"\n"
385-
"If no inifile is provided, murmur will search for one in \n"
386-
"default locations.",
387-
qPrintable(args.at(0)));
388-
return 0;
389-
#ifdef Q_OS_UNIX
390-
} else if (arg == "-limits") {
391-
detach = false;
392-
Meta::mp->read(inifile);
393-
unixhandler.setuid();
394-
unixhandler.finalcap();
395-
LimitTest::testLimits(a);
419+
bool logGroups = cli_options.log_groups;
420+
bool logACL = cli_options.log_acls;
421+
422+
bVerbose = cli_options.verbose_logging;
423+
424+
if (cli_options.disable_su_srv) {
425+
detach = false;
426+
disableSu = true;
427+
sunum = *cli_options.disable_su_srv;
428+
}
429+
430+
if (!std::get< 0 >(cli_options.supw_srv).empty()) {
431+
supw = QString::fromStdString(std::get< 0 >(cli_options.supw_srv));
432+
sunum = std::get< 1 >(cli_options.supw_srv).value_or< unsigned int >(1);
433+
#ifdef Q_OS_LINUX
434+
} else if (cli_options.read_supw_srv) {
435+
// Note that it is essential to set detach = false here. If this is ever to be changed, the code part
436+
// handling the readPw = true part has to be moved up so that it is executed before fork is called on Unix
437+
// systems.
438+
439+
detach = false;
440+
readPw = true;
441+
sunum = *cli_options.read_supw_srv;
442+
}
443+
444+
if (cli_options.limits) {
445+
detach = false;
446+
Meta::mp->read(inifile);
447+
unixhandler.setuid();
448+
unixhandler.finalcap();
449+
LimitTest::testLimits(a);
396450
#endif
397-
} else if (arg == "-loggroups") {
398-
logGroups = true;
399-
} else if (arg == "-logacls") {
400-
logACL = true;
401-
} else {
402-
detach = false;
403-
qFatal("Unknown argument %s", qPrintable(args.at(i)));
404-
}
405-
if (bLast && (i + 1 != args.size())) {
406-
detach = false;
407-
qFatal("Password arguments must be last.");
408-
}
409451
}
410452

411453
if (QSslSocket::supportsSsl()) {
@@ -420,22 +462,22 @@ int main(int argc, char **argv) {
420462

421463
Meta::mp->read(inifile);
422464

423-
if (!dbDumpPath.isEmpty()) {
465+
if (cli_options.db_dump_path) {
424466
DBWrapper wrapper(Meta::getConnectionParameter());
425467

426-
std::ofstream file(dbDumpPath.toStdString());
468+
std::ofstream file(*cli_options.db_dump_path);
427469
file << wrapper.exportDBToJSON().dump(2);
428470

429-
qInfo("Dumped JSON representation of database contents to '%s'", qPrintable(dbDumpPath));
471+
qInfo("Dumped JSON representation of database contents to '%s'", cli_options.db_dump_path->c_str());
430472

431473
return 0;
432474
}
433475

434-
if (!dbImportPath.isEmpty()) {
435-
qInfo("Importing contents of '%s' into database", qPrintable(dbImportPath));
476+
if (cli_options.db_import_path) {
477+
qInfo("Importing contents of '%s' into database", cli_options.db_import_path->c_str());
436478
DBWrapper wrapper(Meta::getConnectionParameter());
437479

438-
std::ifstream file(dbImportPath.toStdString());
480+
std::ifstream file(*cli_options.db_import_path);
439481

440482
nlohmann::json json;
441483
file >> json;

0 commit comments

Comments
 (0)
Please sign in to comment.