From b56b003fc13a4e12f97c6cfd5dd650e928d6e016 Mon Sep 17 00:00:00 2001 From: David Härdeman Date: Sun, 12 Jul 2020 00:00:26 +0200 Subject: Teach minecctl to split things into data and cfg dir, also read config variables from the server.properties file --- minecctl/minecctl.c | 17 ++-- minecctl/minecctl.h | 4 +- minecctl/misc-commands.c | 136 ++++++++++++-------------- minecctl/misc.h | 4 +- minecctl/rcon-commands.c | 36 +++---- minecctl/server.c | 244 +++++++++++++++++++++++++++++++++++++---------- 6 files changed, 281 insertions(+), 160 deletions(-) (limited to 'minecctl') 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 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 #include #include +#include #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"); -- cgit v1.2.3