summaryrefslogtreecommitdiff
path: root/minecctl
diff options
context:
space:
mode:
Diffstat (limited to 'minecctl')
-rw-r--r--minecctl/minecctl.c17
-rw-r--r--minecctl/minecctl.h4
-rw-r--r--minecctl/misc-commands.c136
-rw-r--r--minecctl/misc.h4
-rw-r--r--minecctl/rcon-commands.c36
-rw-r--r--minecctl/server.c244
6 files changed, 281 insertions, 160 deletions
diff --git a/minecctl/minecctl.c b/minecctl/minecctl.c
index 344d3d7..4a6bf0f 100644
--- a/minecctl/minecctl.c
+++ b/minecctl/minecctl.c
@@ -130,8 +130,8 @@ void dump_config(struct cfg *cfg)
info("Configuration");
info("┌────────────");
- info("│ dir_path : %s", cfg->dir_path);
- info("│ dir : %p", cfg->dir);
+ info("│ cfg_dir : %p", cfg->cfg_dir);
+ info("│ data_dir : %p", cfg->data_dir);
info("│ rcon_password : %s", cfg->rcon_password);
info("│ rcon_addrstr : %s", cfg->rcon_addrstr);
info("│ mc_addrstr : %s", cfg->mc_addrstr);
@@ -179,7 +179,6 @@ _noreturn_ static void usage(bool no_error)
" -m, --mc-address=ADDR connect to Minecraft server at ADDR\n"
" (only relevant for some commands, can also\n"
" use environment variable MC_ADDRESS)\n"
- " -c, --cfgdir=DIR look for configuration files in DIR\n"
" -f, --force stop server even if it has players\n"
" -v, --verbose enable extra logging\n"
" -d, --debug enable debugging information\n"
@@ -430,7 +429,6 @@ static void parse_cmdline(struct cfg *cfg, int argc, char *const *argv)
{ "rcon-password", required_argument, 0, 'p' },
{ "rcon-address", required_argument, 0, 'r' },
{ "mc-address", required_argument, 0, 'm' },
- { "cfgdir", required_argument, 0, 'c' },
{ "verbose", no_argument, 0, 'v' },
{ "debug", no_argument, 0, 'd' },
{ "force", no_argument, 0, 'f' },
@@ -439,7 +437,7 @@ static void parse_cmdline(struct cfg *cfg, int argc, char *const *argv)
};
/* clang-format on */
- c = getopt_long(argc, argv, ":p:r:m:c:vdfh", long_options,
+ c = getopt_long(argc, argv, ":p:r:m:vdfh", long_options,
&option_index);
if (c == -1)
@@ -455,9 +453,6 @@ static void parse_cmdline(struct cfg *cfg, int argc, char *const *argv)
case 'm':
cfg->mc_addrstr = xstrdup(optarg);
break;
- case 'c':
- cfg->dir_path = optarg;
- break;
case 'v':
debug_mask |= DBG_VERBOSE;
break;
@@ -532,7 +527,9 @@ out:
strv_free(cfg.commands);
xfree(cfg.rcon_addrstr);
xfree(cfg.mc_addrstr);
- if (cfg.dir)
- closedir(cfg.dir);
+ if (cfg.cfg_dir)
+ closedir(cfg.cfg_dir);
+ if (cfg.data_dir)
+ closedir(cfg.data_dir);
exit(success ? EXIT_SUCCESS : EXIT_FAILURE);
}
diff --git a/minecctl/minecctl.h b/minecctl/minecctl.h
index 5778d13..1d118d7 100644
--- a/minecctl/minecctl.h
+++ b/minecctl/minecctl.h
@@ -6,8 +6,8 @@
#include <dirent.h>
struct cfg {
- const char *dir_path;
- DIR *dir;
+ DIR *cfg_dir;
+ DIR *data_dir;
char *rcon_password;
char *rcon_addrstr;
char *mc_addrstr;
diff --git a/minecctl/misc-commands.c b/minecctl/misc-commands.c
index f3ddff2..8a54e64 100644
--- a/minecctl/misc-commands.c
+++ b/minecctl/misc-commands.c
@@ -21,11 +21,11 @@
#include "examples/eula.txt.h"
#include "examples/example.mcserver.h"
+#include "examples/minecctl.conf.h"
#include "examples/minecproxy.conf.h"
#include "examples/minecproxy.service.h"
#include "examples/minecserver@.service.h"
#include "examples/README.TXT.h"
-#include "examples/server.properties.h"
static bool create_link(int dfd, const char *source, const char *target)
{
@@ -99,7 +99,7 @@ static bool find_user_service(int xfd, const char *service)
return true;
}
- dfd = open_xdg_shared_dir();
+ dfd = open_xdg_data_dir(false);
if (dfd >= 0) {
if (faccessat(dfd, sub_path, R_OK, 0) == 0) {
info("User service %s already installed in "
@@ -142,105 +142,92 @@ static bool create_user_service(int xfd, const char *service,
return true;
}
-static bool create_server_cfg(int sfd, int cfd, const char *name,
+static bool create_server_cfg(struct cfg *cfg, const char *name,
const char *filename,
const unsigned char *properties,
size_t properties_len)
{
- _cleanup_close_ int efd = -1;
+ _cleanup_close_ int sfd = -1;
+ int cfd, dfd;
- efd = open_subdir(sfd, name, true);
- if (efd < 0) {
- error("Failed to create configuration directory \"%s\"", name);
+ if (!cfg->cfg_dir || !cfg->data_dir)
return false;
- }
- if (!write_cfg_file(efd, "eula.txt", ___examples_eula_txt,
- ___examples_eula_txt_len))
- return false;
-
- if (!write_cfg_file(efd, "server.properties",
- properties, properties_len))
- return false;
-
- if (!create_link(efd, "server.jar", "../server.jar"))
- return false;
+ cfd = dirfd(cfg->cfg_dir);
+ dfd = dirfd(cfg->data_dir);
if (!write_cfg_file(cfd, filename,
___examples_example_mcserver,
___examples_example_mcserver_len))
return false;
- return true;
-}
-
-static bool create_config_tree(int xfd)
-{
- _cleanup_close_ int mfd = -1;
- _cleanup_close_ int sfd = -1;
- _cleanup_close_ int cfd = -1;
-
- mfd = open_subdir(xfd, "minecproxy", true);
- if (mfd < 0)
- return false;
-
- sfd = open_subdir(mfd, "servers", true);
- if (sfd < 0)
- return false;
-
- if (!write_cfg_file(sfd, "README.TXT", ___examples_README_TXT,
- ___examples_README_TXT_len))
+ sfd = open_subdir(dfd, name, true);
+ if (sfd < 0) {
+ error("Failed to create server directory \"%s\"", name);
return false;
+ }
- cfd = open_subdir(mfd, "config", true);
- if (cfd < 0)
+ if (!write_cfg_file(sfd, "eula.txt", ___examples_eula_txt,
+ ___examples_eula_txt_len))
return false;
- if (!write_cfg_file(cfd, "minecproxy.conf", ___examples_minecproxy_conf,
- ___examples_minecproxy_conf_len))
+ if (!write_cfg_file(sfd, "server.properties",
+ properties, properties_len))
return false;
- if (!create_server_cfg(sfd, cfd, "example", "example.mcserver",
- ___examples_server_properties,
- ___examples_server_properties_len))
+ if (!create_link(sfd, "server.jar", "../server.jar"))
return false;
- info("Created configuration in $XDG_CONFIG_HOME/minecproxy");
return true;
}
-bool do_init(struct cfg *cfg)
+bool do_init(_unused_ struct cfg *cfg)
{
- _cleanup_close_ int xfd = -1;
+ _cleanup_close_ int xcfd = -1;
+ _cleanup_close_ int mcfd = -1;
+ _cleanup_close_ int xdfd = -1;
+ _cleanup_close_ int mdfd = -1;
- if (cfg->dir_path) {
- xfd = open(cfg->dir_path, O_PATH | O_CLOEXEC | O_DIRECTORY);
- if (xfd < 0)
- error("Failed to open dir %s: %m", cfg->dir_path);
- } else
- xfd = open_xdg_cfg_dir(true);
+ xcfd = open_xdg_cfg_dir(true);
+ if (xcfd < 0)
+ return false;
- if (xfd < 0)
+ mcfd = open_subdir(xcfd, "minecproxy", true);
+ if (mcfd < 0)
return false;
- if (!create_config_tree(xfd))
+ if (!write_cfg_file(mcfd, "minecproxy.conf",
+ ___examples_minecproxy_conf,
+ ___examples_minecproxy_conf_len))
return false;
- if (cfg->dir_path) {
- info("Option -c used, not creating systemd services");
- return true;
- }
+ if (!write_cfg_file(mcfd, "minecctl.conf",
+ ___examples_minecctl_conf,
+ ___examples_minecctl_conf_len))
+ return false;
- if (!create_user_service(xfd, "minecserver@.service",
+ if (!create_user_service(xcfd, "minecserver@.service",
___examples_minecserver__service,
___examples_minecserver__service_len))
return false;
- if (!create_user_service(xfd, "minecproxy.service",
+ if (!create_user_service(xcfd, "minecproxy.service",
___examples_minecproxy_service,
___examples_minecproxy_service_len))
return false;
+ xdfd = open_xdg_data_dir(true);
+ if (xdfd < 0)
+ return false;
+
+ mdfd = open_subdir(xdfd, "minecproxy", true);
+ if (mdfd < 0)
+ return false;
+
+ if (!write_cfg_file(mdfd, "README.TXT", ___examples_README_TXT,
+ ___examples_README_TXT_len))
+ return false;
+
return true;
}
@@ -390,12 +377,12 @@ static bool saddr_port_match(struct list_head *list, uint16_t port)
list_for_each_entry(a, list, list) {
switch (a->st.ss_family) {
case AF_INET:
- if (a->in4.sin_port == port)
+ if (htons(a->in4.sin_port) == port)
return true;
break;
case AF_INET6:
- if (a->in6.sin6_port == port)
+ if (htons(a->in6.sin6_port) == port)
return true;
break;
@@ -446,7 +433,9 @@ static bool select_free_ports(struct cfg *cfg, uint16_t *listen_port,
}
}
- if (!used ) {
+ if (!used) {
+ error("Found unused port, %" PRIu16, lport);
+
if (listen_port && *listen_port == 0)
*listen_port = lport;
@@ -470,7 +459,6 @@ bool do_new(struct cfg *cfg)
struct server *defserver = NULL;
_cleanup_free_ unsigned char *properties = NULL;
size_t properties_len;
- _cleanup_close_ int sfd = -1;
uint16_t rcon_port = 0, mc_port = 0;
const char *rcon_password = NULL;
@@ -480,9 +468,13 @@ bool do_new(struct cfg *cfg)
char filename[strlen(name) + STRLEN(".mcserver") + 1];
sprintf(filename, "%s.mcserver", name);
- server_load_all_known(cfg);
+ if (!server_read_all_configs(cfg, false)) {
+ error("Failed to read all existing server configurations, "
+ "try running the \"lint\" command.");
+ return false;
+ }
- if (!cfg->dir)
+ if (!cfg->cfg_dir || !cfg->data_dir)
return false;
list_for_each_entry(server, &cfg->servers, list) {
@@ -505,17 +497,13 @@ bool do_new(struct cfg *cfg)
} else if (cfg->rcon_password)
rcon_password = cfg->rcon_password;
+ dump_config(cfg);
+
if (!select_free_ports(cfg, NULL, &mc_port, &rcon_port)) {
error("Failed to find a free port");
return false;
}
- sfd = open_subdir(dirfd(cfg->dir), "../servers", false);
- if (sfd < 0) {
- error("Failed to open configuration directory \"servers\"");
- return false;
- }
-
properties = create_mc_properties(name, mc_port, rcon_port,
rcon_password, &properties_len);
if (!properties)
@@ -523,7 +511,7 @@ bool do_new(struct cfg *cfg)
error("Created config file (%zu):\n%s", properties_len, properties);
- if (!create_server_cfg(sfd, dirfd(cfg->dir), name, filename, properties,
+ if (!create_server_cfg(cfg, name, filename, properties,
properties_len))
return false;
diff --git a/minecctl/misc.h b/minecctl/misc.h
index d1c6816..02c01ea 100644
--- a/minecctl/misc.h
+++ b/minecctl/misc.h
@@ -6,9 +6,9 @@ int open_subdir(int dfd, const char *subdir, bool nofail);
int open_xdg_dir(const char *envname, const char *altpath, bool nofail);
-static inline int open_xdg_shared_dir()
+static inline int open_xdg_data_dir(bool nofail)
{
- return open_xdg_dir("XDG_DATA_HOME", ".local/share", false);
+ return open_xdg_dir("XDG_DATA_HOME", ".local/share", nofail);
}
static inline int open_xdg_cfg_dir(bool nofail)
diff --git a/minecctl/rcon-commands.c b/minecctl/rcon-commands.c
index 44bb286..cb6ea45 100644
--- a/minecctl/rcon-commands.c
+++ b/minecctl/rcon-commands.c
@@ -21,18 +21,13 @@ static void send_packet(int sfd, const char *buf, size_t len)
size_t off = 0;
ssize_t r;
- while (true) {
+ for (off = 0; off < len; off += r) {
r = write(sfd, buf + off, len - off);
if (r < 0) {
- if (errno == EINTR)
+ if (errno == EAGAIN || errno == EINTR)
continue;
- else
- die("Failed to write packet: %m");
+ die("Failed to write packet: %m");
}
-
- off += r;
- if (off == len)
- break;
}
}
@@ -40,31 +35,26 @@ static void send_packet(int sfd, const char *buf, size_t len)
static void read_packet(int sfd, char *buf, size_t len, int32_t *id,
int32_t *type, const char **msg)
{
- size_t off = 0;
+ size_t off;
ssize_t r;
const char *error;
- while (true) {
+ for (off = 0; off < len; off += r) {
+ if (rcon_protocol_packet_complete(buf, off))
+ break;
+
r = read(sfd, buf + off, len - off);
if (r < 0) {
if (errno == EINTR)
continue;
- else
- die("Failed to read reply: %m");
- }
-
- if (r == 0)
+ die("Failed to read reply: %m");
+ } else if (r == 0)
die("Failed, connection closed");
-
- off += r;
- if (rcon_protocol_packet_complete(buf, off))
- break;
-
- if (off >= len)
- die("Reply too large %zu and %zu", off, len);
}
- if (!rcon_protocol_read_packet(buf, off, id, type, msg, &error))
+ if (off >= len)
+ die("Reply too large %zu and %zu", off, len);
+ else if (!rcon_protocol_read_packet(buf, off, id, type, msg, &error))
die("Failed to parse response: %s", error);
}
diff --git a/minecctl/server.c b/minecctl/server.c
index 91e7842..3743526 100644
--- a/minecctl/server.c
+++ b/minecctl/server.c
@@ -3,6 +3,7 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
+#include <errno.h>
#include "shared/utils.h"
#include "minecctl.h"
@@ -12,37 +13,39 @@
#define INVALID(msg) do { *error = (msg); return false; } while(0)
-static DIR *open_cfg_dir(const char *cmdline_path)
+static DIR *__open_dir(int (*base_dir)(bool nofail), const char *fallback)
{
- int dfd, mfd;
+ _cleanup_close_ int xfd = -1;
+ _cleanup_close_ int mfd = -1;
DIR *dir;
- if (cmdline_path) {
- dir = opendir(cmdline_path);
- if (!dir)
- error("opendir(%s): %m", cmdline_path);
- return dir;
- }
-
/* First, attempt per-user config dir... */
- dfd = open_xdg_cfg_dir(false);
- if (dfd >= 0) {
- mfd = openat(dfd, "minecproxy/config",
- O_CLOEXEC | O_DIRECTORY | O_RDONLY);
- close(dfd);
- if (mfd >= 0) {
- dir = fdopendir(mfd);
- if (!dir)
- error("fdopendir: %m");
- return dir;
- }
- }
+ xfd = base_dir(false);
+ if (xfd < 0)
+ goto fallback;
- /* ...and fallback on the system dir */
- dir = opendir(DEFAULT_CFG_DIR);
- if (!dir)
- error("Failed to open configuration directory: %m");
+ mfd = openat(xfd, "minecproxy", O_CLOEXEC | O_DIRECTORY | O_RDONLY);
+ if (mfd < 0)
+ goto fallback;
+
+ dir = fdopendir(mfd);
+ if (dir)
+ mfd = -1;
return dir;
+
+ /* ...and fallback on the system dir */
+fallback:
+ return opendir(fallback);
+}
+
+static inline DIR *open_cfg_dir()
+{
+ return __open_dir(open_xdg_cfg_dir, DEFAULT_CFG_DIR);
+}
+
+static inline DIR *open_data_dir()
+{
+ return __open_dir(open_xdg_data_dir, DEFAULT_DATA_DIR);
}
void server_load_all_known(struct cfg *cfg)
@@ -53,13 +56,20 @@ void server_load_all_known(struct cfg *cfg)
return;
cfg->server_list_loaded = true;
- cfg->dir = open_cfg_dir(cfg->dir_path);
- if (!cfg->dir) {
+
+ cfg->cfg_dir = open_cfg_dir();
+ if (!cfg->cfg_dir) {
error("Failed to open config directory");
return;
}
- while ((dent = readdir(cfg->dir))) {
+ cfg->data_dir = open_data_dir();
+ if (!cfg->data_dir) {
+ error("Failed to open server directory");
+ return;
+ }
+
+ while ((dent = readdir(cfg->cfg_dir))) {
struct server *server;
char *suffix;
@@ -77,13 +87,149 @@ void server_load_all_known(struct cfg *cfg)
}
}
+static bool read_file(int dfd, const char *filename, char *buf, size_t len,
+ const char **error)
+{
+ _cleanup_close_ int fd = -1;
+ size_t off;
+ ssize_t r;
+
+ fd = openat(dfd, filename, O_RDONLY | O_CLOEXEC);
+ if (fd < 0)
+ INVALID("failed to open file");
+
+ for (off = 0; off < len; off += r) {
+ r = read(fd, buf + off, len - off);
+ if (r < 0) {
+ if (errno == EAGAIN || errno == EINTR)
+ continue;
+ INVALID("failed to read file");
+ } else if (r == 0)
+ break;
+ }
+
+ if (off >= len - 1)
+ INVALID("file too large");
+
+ buf[off] = '\0';
+ return true;
+}
+
+enum sprop_keys {
+ SPROP_KEY_INVALID = 0,
+ SPROP_KEY_SERVER_PORT,
+ SPROP_KEY_RCON_PORT,
+ SPROP_KEY_RCON_PASSWORD,
+};
+
+struct cfg_key_value_map sprop_key_map[] = {
+ {
+ .key_name = "server-port",
+ .key_value = SPROP_KEY_SERVER_PORT,
+ .value_type = CFG_VAL_TYPE_ADDRS,
+ },
+ {
+ .key_name = "rcon.port",
+ .key_value = SPROP_KEY_RCON_PORT,
+ .value_type = CFG_VAL_TYPE_ADDRS,
+ },
+ {
+ .key_name = "rcon.password",
+ .key_value = SPROP_KEY_RCON_PASSWORD,
+ .value_type = CFG_VAL_TYPE_STRING,
+ }
+};
+
+static void sprop_parse(struct server_config *scfg, char *buf,
+ unsigned *lineno, const char **error)
+{
+ char *pos = buf;
+ struct saddr *saddr, *tmp;
+
+ *lineno = 0;
+
+ while (true) {
+ int key;
+ const char *keyname;
+ struct cfg_value value;
+
+ if (!config_parse_line("server.properties", &pos, sprop_key_map,
+ &key, &keyname, &value, false, lineno,
+ error))
+ break;
+
+ switch (key) {
+ case SPROP_KEY_SERVER_PORT:
+ error("Got a server port");
+
+ /* FIXME: these should use scfg_queue_dns */
+ if (value.type != CFG_VAL_TYPE_ADDRS) {
+ error("Got async DNS results!?");
+ break;
+ }
+
+ if (!list_empty(&scfg->remotes)) {
+ error("mc server address set both in %s and "
+ "server.properties", scfg->filename);
+ break;
+ }
+
+ list_for_each_entry_safe(saddr, tmp, &value.saddrs, list) {
+ list_del(&saddr->list);
+ list_add(&saddr->list, &scfg->remotes);
+ }
+
+ break;
+
+ case SPROP_KEY_RCON_PORT:
+ error("Got a rcon port");
+
+ if (value.type != CFG_VAL_TYPE_ADDRS) {
+ error("Got async DNS results!?");
+ break;
+ }
+
+ if (!list_empty(&scfg->rcons)) {
+ error("rcon address set both in %s and "
+ "server.properties", scfg->filename);
+ break;
+ }
+
+ list_for_each_entry_safe(saddr, tmp, &value.saddrs, list) {
+ list_del(&saddr->list);
+ list_add(&saddr->list, &scfg->rcons);
+ }
+
+ break;
+
+ case SPROP_KEY_RCON_PASSWORD:
+ error("Got an rcon password");
+
+ if (scfg->rcon_password) {
+ error("rcon password set both in %s and "
+ "server.properties (%smatching)",
+ scfg->filename,
+ streq(scfg->rcon_password, value.str) ?
+ "" : "not");
+ break;
+ }
+
+ scfg->rcon_password = xstrdup(value.str);
+ break;
+
+ case SPROP_KEY_INVALID:
+ _fallthrough_;
+ default:
+ break;
+ }
+ }
+}
+
bool server_read_config(struct cfg *cfg, struct server *server,
unsigned *lineno, const char **error)
{
+ _cleanup_close_ int dfd = -1;
char buf[4096];
- size_t off = 0;
- ssize_t r;
- int fd;
if (!error)
return false;
@@ -100,34 +246,34 @@ bool server_read_config(struct cfg *cfg, struct server *server,
if (server->file_read)
return true;
- if (!cfg->dir)
+ if (!cfg->cfg_dir)
INVALID("Configuration directory not opened");
+ if (!cfg->data_dir)
+ INVALID("Server directory not opened");
+
*lineno = 0;
server->file_read = true;
- fd = openat(dirfd(cfg->dir), server->scfg.filename, O_RDONLY | O_CLOEXEC);
- if (fd < 0)
- INVALID("failed to open configuration file");
+ if (!read_file(dirfd(cfg->cfg_dir), server->scfg.filename, buf,
+ sizeof(buf), error))
+ return false;
- while (true) {
- r = read(fd, buf + off, sizeof(buf) - off - 1);
- if (r < 0)
- INVALID("failed to read configuration file");
- else if (r == 0)
- break;
+ if (!scfg_parse(&server->scfg, buf, false, lineno, error))
+ return false;
- off += r;
- if (off == sizeof(buf) - 1)
- INVALID("invalid, file too large");
- }
+ /* fill in missing parameters from server.properties */
+ dfd = open_subdir(dirfd(cfg->data_dir), server->name, false);
+ if (dfd < 0)
+ goto out;
- buf[off] = '\0';
- close(fd);
+ if (!read_file(dfd, "server.properties", buf, sizeof(buf),
+ error))
+ goto out;
- if (!scfg_parse(&server->scfg, buf, NULL, lineno, error))
- return false;
+ sprop_parse(&server->scfg, buf, lineno, error);
+out:
if (!server->scfg.rcon_password)
verbose("rcon password not set");