From 01eb1fe238371c3a29b22f1d06147f527285539e Mon Sep 17 00:00:00 2001 From: David Härdeman Date: Sun, 12 Jul 2020 23:58:21 +0200 Subject: Teach minecctl to delete servers on command --- minecctl/minecctl-commands.h | 5 ++ minecctl/minecctl.c | 18 +++++-- minecctl/minecctl.h | 2 +- minecctl/misc-commands.c | 121 +++++++++++++++++++++++++++++++++++++++++++ minecctl/misc-commands.h | 2 + minecctl/rcon-commands.c | 2 +- shared/utils.h | 6 +++ 7 files changed, 151 insertions(+), 5 deletions(-) diff --git a/minecctl/minecctl-commands.h b/minecctl/minecctl-commands.h index f50bc6c..b8a40a6 100644 --- a/minecctl/minecctl-commands.h +++ b/minecctl/minecctl-commands.h @@ -6,6 +6,7 @@ enum commands { CMD_INVALID = 0, CMD_INIT, CMD_NEW, + CMD_DELETE, CMD_LIST, CMD_LINT, CMD_INFO, @@ -29,6 +30,10 @@ static struct command_list { .name = "new", .cmd = CMD_NEW, }, + { + .name = "delete", + .cmd = CMD_DELETE, + }, { .name = "list", .cmd = CMD_LIST, diff --git a/minecctl/minecctl.c b/minecctl/minecctl.c index c2d6c19..51b106b 100644 --- a/minecctl/minecctl.c +++ b/minecctl/minecctl.c @@ -136,7 +136,7 @@ void dump_config(struct cfg *cfg) info("│ rcon_addrstr : %s", cfg->rcon_addrstr); info("│ mc_addrstr : %s", cfg->mc_addrstr); info("│ cmd : %p", cfg->cmd); - info("│ force stop : %s", cfg->force_stop ? "yes" : "no"); + info("│ force : %s", cfg->force ? "yes" : "no"); info("│ default set : %s", cfg->default_set ? "yes" : "no"); info("│ servers loaded: %s", cfg->server_list_loaded ? "yes" : "no"); info("│ commands%s", cfg->commands ? "" : " : none"); @@ -158,6 +158,7 @@ _noreturn_ static void usage(bool no_error) "Valid commands:\n" " init perform initial setup\n" " new SERVER create a new server\n" + " delete SERVER delete a server (-f to delete world as well)\n" " list list known servers\n" " lint check validity of server configuration files\n" " info [SERVER] show information about a running SERVER (or all known servers)\n" @@ -179,7 +180,7 @@ _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" - " -f, --force stop server even if it has players\n" + " -f, --force force stop/delete actions\n" " -v, --verbose enable extra logging\n" " -d, --debug enable debugging information\n" " -h, --help print this information\n" @@ -335,6 +336,17 @@ static void parse_command(struct cfg *cfg, char *const *argv) cfg->commands = strv_copy(argv); cfg->cmd = do_new; break; + case CMD_DELETE: + if (!*argv) { + error("Missing arguments"); + usage(false); + } else if (*(argv + 1)) { + error("Too many arguments"); + usage(false); + } + cfg->commands = strv_copy(argv); + cfg->cmd = do_delete; + break; case CMD_LIST: if (*argv) { error("Too many arguments"); @@ -460,7 +472,7 @@ static void parse_cmdline(struct cfg *cfg, int argc, char *const *argv) debug_mask = ~0; break; case 'f': - cfg->force_stop = true; + cfg->force = true; break; case 'h': usage(true); diff --git a/minecctl/minecctl.h b/minecctl/minecctl.h index 1d118d7..fd623df 100644 --- a/minecctl/minecctl.h +++ b/minecctl/minecctl.h @@ -18,7 +18,7 @@ struct cfg { uint16_t mc_port_max; uint16_t rcon_port_min; uint16_t rcon_port_max; - bool force_stop; + bool force; bool default_set; bool server_list_loaded; bool (*cmd)(struct cfg *cfg); diff --git a/minecctl/misc-commands.c b/minecctl/misc-commands.c index 752b0e9..ff802d7 100644 --- a/minecctl/misc-commands.c +++ b/minecctl/misc-commands.c @@ -240,6 +240,9 @@ static bool valid_name(const char *name) return false; } + if (*f == '.') + return false; + while (*f != '\0') { if ((*f >= 'a' && *f <= 'z') || (*f >= 'A' && *f <= 'Z') || @@ -543,6 +546,124 @@ bool do_new(struct cfg *cfg) return true; } +static bool recursive_unlink(int dfd) +{ + _cleanup_closedir_ DIR *dir = NULL; + struct dirent *dent; + int sdfd; + + dir = fdopendir(dfd); + if (!dir) { + error("fdopendir: %m"); + close(dfd); + return false; + } + + errno = 0; + while ((dent = readdir(dir))) { + if (streq(dent->d_name, ".") || streq(dent->d_name, "..")) + continue; + + switch (dent->d_type) { + case DT_DIR: + sdfd = openat(dfd, dent->d_name, O_RDONLY | O_CLOEXEC | + O_DIRECTORY | O_NOCTTY | O_NOFOLLOW | + O_NOATIME); + if (sdfd < 0) { + error("Unable to open subdir %s: %m", + dent->d_name); + return false; + } + + verbose("Checking subdir %s", dent->d_name); + if (!recursive_unlink(sdfd)) + return false; + + verbose("Deleting subdir %s", dent->d_name); + if (unlinkat(dfd, dent->d_name, AT_REMOVEDIR) != 0) { + error("Unable to unlink server subdir %s: %m", + dent->d_name); + return false; + } + + break; + + case DT_LNK: + _fallthrough_; + case DT_REG: + _fallthrough_; + case DT_UNKNOWN: + verbose("Unlinking file %s", dent->d_name); + if (unlinkat(dfd, dent->d_name, 0) != 0) { + error("Failed to unlink file: %s", + dent->d_name); + return false; + } + + break; + + default: + error("Unexpected file type in server directory: %s", + dent->d_name); + return false; + } + errno = 0; + } + + if (errno != 0) { + error("readdir: %m"); + return false; + } + + return true; +} + +bool do_delete(struct cfg *cfg) +{ + const char *name = cfg->commands[0]; + char filename[strlen(name) + STRLEN(".") + STRLEN(SERVER_CONFIG_FILE_SUFFIX) + 1]; + int dfd; + + sprintf(filename, "%s.%s", name, SERVER_CONFIG_FILE_SUFFIX); + + if (!valid_name(name)) + return false; + + server_load_all_known(cfg); + + /* FIXME: Stop server first */ + + if (unlinkat(dirfd(cfg->cfg_dir), filename, 0) != 0) { + error("Unable to remove \"%s\": %m", filename); + if (!cfg->force) + return false; + } else { + verbose("Removed \"%s\"", filename); + if (!cfg->force) + return true; + } + + dfd = openat(dirfd(cfg->data_dir), name, O_RDONLY | O_CLOEXEC | + O_DIRECTORY | O_NOCTTY | O_NOFOLLOW | O_NOATIME); + if (dfd < 0) { + error("Unable to open server directory: %m"); + return false; + } + + if (!recursive_unlink(dfd)) { + error("Failed to remove some file(s) in server directory"); + return false; + } + + verbose("Deleting server directory %s", name); + if (unlinkat(dirfd(cfg->data_dir), name, AT_REMOVEDIR)) { + error("Failed to remove server directory: %m"); + return false; + } + + return true; +} + bool do_list(struct cfg *cfg) { struct server *server; diff --git a/minecctl/misc-commands.h b/minecctl/misc-commands.h index d8f62ca..9798da9 100644 --- a/minecctl/misc-commands.h +++ b/minecctl/misc-commands.h @@ -6,6 +6,8 @@ bool do_init(struct cfg *cfg); bool do_new(struct cfg *cfg); +bool do_delete(struct cfg *cfg); + bool do_list(struct cfg *cfg); bool do_lint(struct cfg *cfg); diff --git a/minecctl/rcon-commands.c b/minecctl/rcon-commands.c index cdbf868..9719a01 100644 --- a/minecctl/rcon-commands.c +++ b/minecctl/rcon-commands.c @@ -340,7 +340,7 @@ static bool stop_one_server(struct cfg *cfg, struct server *server) return false; } - if (!cfg->force_stop) { + if (!cfg->force) { unsigned current; if (!get_player_count(fd, ¤t, NULL)) { diff --git a/shared/utils.h b/shared/utils.h index b6bf51c..2bf7aae 100644 --- a/shared/utils.h +++ b/shared/utils.h @@ -188,4 +188,10 @@ static inline void fclosep(FILE **f) { } #define _cleanup_fclose_ _cleanup_(fclosep) +static inline void closedirp(DIR **d) { + if (d && *d) + closedir(*d); +} +#define _cleanup_closedir_ _cleanup_(closedirp) + #endif -- cgit v1.2.3