From 6586ea650597ae0563c4234a7658499ce1e0117b Mon Sep 17 00:00:00 2001
From: David Härdeman <david@hardeman.nu>
Date: Sun, 12 Jul 2020 14:04:40 +0200
Subject: Teach minecproxy to use same dirs as minecctl, step 1

---
 minecctl/misc-commands.c   |   9 +--
 minecctl/misc.c            |  69 ------------------
 minecctl/misc.h            |  14 ----
 minecctl/server.c          |  46 ++----------
 minecproxy/main.c          |  96 +++++++++++++------------
 minecproxy/main.h          |  30 ++------
 minecproxy/server-config.c |  13 +---
 shared/config-parser.c     |   1 -
 shared/systemd.c           |   1 -
 shared/utils.c             | 176 +++++++++++++++++++++++++++++++++++++++++++++
 shared/utils.h             |  44 +++++++++++-
 11 files changed, 291 insertions(+), 208 deletions(-)

diff --git a/minecctl/misc-commands.c b/minecctl/misc-commands.c
index db9b937..3ad9792 100644
--- a/minecctl/misc-commands.c
+++ b/minecctl/misc-commands.c
@@ -69,6 +69,7 @@ static bool write_cfg_file(int dfd, const char *name,
 static bool find_user_service(int xfd, const char *service)
 {
 	_cleanup_close_ int dfd = -1;
+	_cleanup_free_ char *dpath = NULL;
 	/* FIXME: Make this a macro, make paths #defines */
 	char sub_path[STRLEN("systemd/user/") + strlen(service) + 1];
 	char etc_path[STRLEN("/etc/systemd/user/") + strlen(service) + 1];
@@ -98,11 +99,11 @@ static bool find_user_service(int xfd, const char *service)
 		return true;
 	}
 
-	dfd = open_xdg_data_dir(false);
+	dfd = open_xdg_data_dir(false, &dpath);
 	if (dfd >= 0) {
 		if (faccessat(dfd, sub_path, R_OK, 0) == 0) {
 			info("User service %s already installed in "
-			     "$XDG_DATA_HOME/systemd/user/", service);
+			     "%s/systemd/user/", service, dpath);
 			return true;
 		}
 	}
@@ -187,7 +188,7 @@ bool do_init(_unused_ struct cfg *cfg)
 	_cleanup_close_ int xdfd = -1;
 	_cleanup_close_ int mdfd = -1;
 
-	xcfd = open_xdg_cfg_dir(true);
+	xcfd = open_xdg_cfg_dir(true, NULL);
 	if (xcfd < 0)
 		return false;
 
@@ -215,7 +216,7 @@ bool do_init(_unused_ struct cfg *cfg)
 				 ___examples_minecproxy_service_len))
 		return false;
 
-	xdfd = open_xdg_data_dir(true);
+	xdfd = open_xdg_data_dir(true, NULL);
 	if (xdfd < 0)
 		return false;
 
diff --git a/minecctl/misc.c b/minecctl/misc.c
index 90189dc..72c711a 100644
--- a/minecctl/misc.c
+++ b/minecctl/misc.c
@@ -8,81 +8,12 @@
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>
-#include <pwd.h>
 #include <errno.h>
 
 #include "shared/utils.h"
 #include "misc.h"
 #include "minecctl.h"
 
-static const char *get_homedir()
-{
-	const char *e;
-        struct passwd *passwd;
-        uid_t uid;
-
-	e = getenv("HOME");
-	if (e && e[0] == '/')
-		return e;
-
-	uid = getuid();
-	if (uid == 0)
-		return "/root";
-
-        passwd = getpwuid(uid);
-	if (passwd && passwd->pw_dir[0] == '/')
-		return passwd->pw_dir;
-
-	return NULL;
-}
-
-int open_subdir(int dfd, const char *subdir, bool nofail)
-{
-	int sfd;
-
-	if (nofail && mkdirat(dfd, subdir, 0777) < 0 && errno != EEXIST) {
-		error("Unable to create subdirectory %s: %m", subdir);
-		return -1;
-	}
-
-	sfd = openat(dfd, subdir, O_PATH | O_CLOEXEC | O_DIRECTORY);
-	if (sfd < 0 && nofail)
-		error("Unable to open subdirectory %s: %m", subdir);
-
-	return sfd;
-}
-
-int open_xdg_dir(const char *envname, const char *altpath, bool nofail)
-{
-        const char *e;
-	const char *h;
-	int dfd, hfd;
-
-	e = getenv(envname);
-	if (e && e[0] == '/') {
-		dfd = open(e, O_PATH | O_CLOEXEC | O_DIRECTORY);
-		if (dfd < 0)
-			error("Unable to open $%s(%s): %m", envname, e);
-		return dfd;
-	}
-
-	h = get_homedir();
-	if (!h) {
-		error("Unable to determine home directory");
-		return -1;
-	}
-
-	hfd = open(h, O_PATH | O_CLOEXEC | O_DIRECTORY);
-	if (hfd < 0) {
-		error("Unable to open $HOME(%s): %m", h);
-		return -1;
-	}
-
-	dfd = open_subdir(hfd, altpath, nofail);
-	close(hfd);
-	return dfd;
-}
-
 /* FIXME: Can be shared */
 void set_use_colors()
 {
diff --git a/minecctl/misc.h b/minecctl/misc.h
index 02c01ea..3182ef8 100644
--- a/minecctl/misc.h
+++ b/minecctl/misc.h
@@ -2,20 +2,6 @@
 #ifndef foomischfoo
 #define foomischfoo
 
-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_data_dir(bool nofail)
-{
-	return open_xdg_dir("XDG_DATA_HOME", ".local/share", nofail);
-}
-
-static inline int open_xdg_cfg_dir(bool nofail)
-{
-	return open_xdg_dir("XDG_CONFIG_HOME", ".config", nofail);
-}
-
 void set_use_colors();
 
 char **strv_copy(char *const *strv);
diff --git a/minecctl/server.c b/minecctl/server.c
index 3acc537..e50e10c 100644
--- a/minecctl/server.c
+++ b/minecctl/server.c
@@ -9,63 +9,29 @@
 #include "minecctl.h"
 #include "server.h"
 #include "misc.h"
-#include "config.h"
 
 #define INVALID(msg) do { *error = (msg); return false; } while(0)
 
-static DIR *__open_dir(int (*base_dir)(bool nofail), const char *fallback)
-{
-	_cleanup_close_ int xfd = -1;
-	_cleanup_close_ int mfd = -1;
-	DIR *dir;
-
-	/* First, attempt per-user config dir... */
-	xfd = base_dir(false);
-	if (xfd < 0)
-		goto fallback;
-
-	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)
 {
 	struct dirent *dent;
+	_cleanup_free_ char *cpath = NULL;
+	_cleanup_free_ char *dpath = NULL;
 
 	if (cfg->server_list_loaded)
 		return;
 
 	cfg->server_list_loaded = true;
 
-	cfg->cfg_dir = open_cfg_dir();
+	cfg->cfg_dir = open_cfg_dir(NULL, &cpath);
 	if (!cfg->cfg_dir) {
-		error("Failed to open config directory");
+		error("Failed to open config directory %s", cpath);
 		return;
 	}
 
-	cfg->data_dir = open_data_dir();
+	cfg->data_dir = open_data_dir(NULL, &dpath);
 	if (!cfg->data_dir) {
-		error("Failed to open server directory");
+		error("Failed to open server directory %s", dpath);
 		return;
 	}
 
diff --git a/minecproxy/main.c b/minecproxy/main.c
index 0406976..48bb1fa 100644
--- a/minecproxy/main.c
+++ b/minecproxy/main.c
@@ -179,6 +179,8 @@ static void cfg_free(struct uring_task *task)
 	debug(DBG_SIG, "called");
 	systemd_delete();
 	xfree(cfg->igmp_iface);
+	xfree(cfg->data_real_path);
+	xfree(cfg->cfg_real_path);
 	cfg->igmp_iface = NULL;
 	exiting = true;
 	/* The cfg struct is free:d in main() */
@@ -258,54 +260,45 @@ struct cfg_key_value_map mcfg_key_map[] = {
 
 static void cfg_read()
 {
-	FILE *cfgfile;
-	const char *path;
 	char buf[4096];
-	char *pos = buf;
-	size_t rd = 0;
+	char *pos;
+	size_t off;
 	size_t r;
 	unsigned lineno;
+	_cleanup_close_ int fd = -1;
+	_cleanup_fclose_ FILE *cfgfile = NULL;
 
 	assert_return(cfg);
 
-	if (cfg->cfg_file)
-		path = cfg->cfg_file;
-	else
-		path = DEFAULT_MAIN_CFG_FILE;
-
-	cfgfile = fopen(path, "re");
-	if (!cfgfile) {
-		/* ENOENT is only an error with an explicitly set path */
-		if (errno == ENOENT && !cfg->cfg_file)
-			return;
-		else if (errno == ENOENT)
-			die("main config file (%s) missing", path);
-		else
-			die("fopen(%s): %m", path);
-	}
+	fd = openat(dirfd(cfg->cfg_dir), DEFAULT_MAIN_CFG_FILE,
+		    O_RDONLY | O_CLOEXEC | O_NOCTTY);
+	if (fd < 0)
+		return;
+
+	cfgfile = fdopen(fd, "re");
+	if (!cfgfile)
+		return;
 
-	debug(DBG_CFG, "opened main config file (%s)", path);
+	fd = -1;
+	debug(DBG_CFG, "opened main config file %s/%s", cfg->cfg_real_path,
+	      DEFAULT_MAIN_CFG_FILE);
 
-	while (rd < sizeof(buf)) {
-		r = fread(pos, 1, sizeof(buf) - rd - 1, cfgfile);
+	for (off = 0; off < sizeof(buf); off += r) {
+		r = fread(buf + off, 1, sizeof(buf) - off, cfgfile);
 		if (r == 0)
 			break;
-		rd += r;
-		pos += r;
 	}
 
-	if (rd == 0)
-		die("main config file (%s) zero size", path);
+	if (off >= sizeof(buf) - 1)
+		die("main config file %s/%s too large", cfg->cfg_real_path,
+		    DEFAULT_MAIN_CFG_FILE);
 
-	if (rd >= sizeof(buf))
-		die("main config file (%s) too large", path);
-
-	fclose(cfgfile);
-	*pos = '\0';
+	buf[off] = '\0';
 	pos = buf;
 
 	if (!config_parse_header("mcproxy", &pos, &lineno))
-		die("main config file (%s) missing/invalid header", path);
+		die("main config file %s/%s missing/invalid header",
+		    cfg->cfg_real_path, DEFAULT_MAIN_CFG_FILE);
 
 	while (true) {
 		int key;
@@ -313,13 +306,15 @@ static void cfg_read()
 		struct cfg_value value;
 		const char *error;
 
-		if (!config_parse_line(path, &pos, mcfg_key_map, &key, &keyname,
+		if (!config_parse_line(DEFAULT_MAIN_CFG_FILE, &pos,
+				       mcfg_key_map, &key, &keyname,
 				       &value, false, &lineno, &error))
 			break;
 
 		if (key == MCFG_KEY_INVALID)
-			die("main config file (%s) invalid: line %u: %s", path,
-			    lineno, error);
+			die("main config file %s/%s invalid: line %u: %s",
+			    cfg->cfg_real_path, DEFAULT_MAIN_CFG_FILE, lineno,
+			    error);
 
 		debug(DBG_CFG, "main cfg: key %s", keyname);
 
@@ -369,7 +364,7 @@ static void cfg_read()
 
 		case MCFG_KEY_INVALID:
 		default:
-			die("main config file (%s) invalid", path);
+			die("main config file invalid");
 		}
 	}
 }
@@ -436,8 +431,8 @@ _noreturn_ static void usage(bool invalid)
 	info("Usage: %s [OPTIONS]\n"
 	     "\n"
 	     "Valid options:\n"
-	     "  -c, --cfgdir=DIR\tlook for server configuration files in DIR\n"
-	     "  -C, --cfgfile=PATH\tuse PATH as the main configuration file\n"
+	     "  -c, --cfgdir=DIR\tuse DIR for configuration files\n"
+	     "  -C, --datadir=DIR\tuse DIR for server data\n"
 	     "  -u, --user=USER\trun as USER\n"
 	     "  -D, --daemonize\trun in daemon mode (disables stderr output)\n"
 	     "  -l, --logfile=FILE\tlog to FILE instead of stderr\n"
@@ -465,7 +460,12 @@ static void cfg_init(int argc, char **argv)
 	uring_task_init(&cfg->task, "main", NULL, cfg_free);
 	INIT_LIST_HEAD(&cfg->servers);
 
-	cfg->cfg_dir = DEFAULT_CFG_DIR;
+	cfg->cfg_path = NULL;
+	cfg->cfg_real_path = NULL;
+	cfg->cfg_dir = NULL;
+	cfg->data_path = NULL;
+	cfg->data_real_path = NULL;
+	cfg->data_dir = NULL;
 	cfg->announce_interval = DEFAULT_ANNOUNCE_INTERVAL;
 	cfg->proxy_connection_interval = DEFAULT_PROXY_CONN_INTERVAL;
 	cfg->proxy_connection_attempts = DEFAULT_PROXY_CONN_ATTEMPTS;
@@ -482,7 +482,7 @@ static void cfg_init(int argc, char **argv)
 		/* clang-format off */
 		static struct option long_options[] = {
 			{ "cfgdir",	required_argument,	0, 'c' },
-			{ "cfgfile",	required_argument,	0, 'C' },
+			{ "datadir",	required_argument,	0, 'C' },
 			{ "user",	required_argument,	0, 'u' },
 			{ "daemonize",	no_argument,		0, 'D' },
 			{ "logfile",	required_argument,	0, 'l' },
@@ -500,11 +500,11 @@ static void cfg_init(int argc, char **argv)
 
 		switch (c) {
 		case 'c':
-			cfg->cfg_dir = optarg;
+			cfg->cfg_path = optarg;
 			break;
 
 		case 'C':
-			cfg->cfg_file = optarg;
+			cfg->data_path = optarg;
 			break;
 
 		case 'v':
@@ -618,8 +618,16 @@ static void cfg_apply()
 	 * Do this after caps have been dropped to make sure we're not
 	 * accessing a directory we should have permissions to.
 	 */
-	if (chdir(cfg->cfg_dir))
-		die("chdir(%s): %m", cfg->cfg_dir);
+	cfg->cfg_dir = open_cfg_dir(cfg->cfg_path, &cfg->cfg_real_path);
+	if (!cfg->cfg_dir)
+		die("Unable to open configuration directory");
+
+	cfg->data_dir = open_data_dir(cfg->data_path, &cfg->data_real_path);
+	if (!cfg->data_dir)
+		die("Unable to open server directory");
+
+	if (fchdir(dirfd(cfg->data_dir)))
+		die("Unable to chdir to server directory: %m");
 
 	if (debug_enabled(DBG_VERBOSE)) {
 		char *wd;
diff --git a/minecproxy/main.h b/minecproxy/main.h
index 2c7be83..f7dd547 100644
--- a/minecproxy/main.h
+++ b/minecproxy/main.h
@@ -14,28 +14,6 @@ struct uring_task;
 extern struct cfg *cfg;
 extern bool exiting;
 
-/*
-enum debug_lvl {
-	DBG_ERROR	= (0x1 << 1),
-	DBG_INFO	= (0x1 << 2),
-	DBG_VERBOSE	= (0x1 << 3),
-	DBG_CFG		= (0x1 << 4),
-	DBG_REF		= (0x1 << 5),
-	DBG_MALLOC	= (0x1 << 6),
-	DBG_ANN		= (0x1 << 7),
-	DBG_SIG		= (0x1 << 8),
-	DBG_UR		= (0x1 << 9),
-	DBG_SRV		= (0x1 << 10),
-	DBG_PROXY	= (0x1 << 11),
-	DBG_RCON	= (0x1 << 12),
-	DBG_IDLE	= (0x1 << 13),
-	DBG_IGMP	= (0x1 << 14),
-	DBG_SYSD	= (0x1 << 15),
-	DBG_DNS		= (0x1 << 16),
-	DBG_TIMER	= (0x1 << 17),
-};
-*/
-
 void dump_tree();
 
 /* To save typing in all the function definitions below */
@@ -75,10 +53,14 @@ struct uring_task {
 
 struct cfg {
 	/* Options */
+	const char *cfg_path;
+	char *cfg_real_path;
+	DIR *cfg_dir;
+	const char *data_path;
+	char *data_real_path;
+	DIR *data_dir;
 	uid_t uid;
 	gid_t gid;
-	const char *cfg_dir;
-	const char *cfg_file;
 	bool do_igmp;
 	char *igmp_iface;
 	bool splice_supported;
diff --git a/minecproxy/server-config.c b/minecproxy/server-config.c
index 959f1d0..2e7f277 100644
--- a/minecproxy/server-config.c
+++ b/minecproxy/server-config.c
@@ -215,11 +215,10 @@ void server_cfg_monitor_init()
 	int ifd;
 	int iwd;
 	struct server_cfg_monitor *scfgm;
-	DIR *dir;
 	struct dirent *dent;
 	struct server *server;
 
-	assert_return(!cfg->server_cfg_monitor);
+	assert_return(!cfg->server_cfg_monitor && cfg->cfg_dir);
 
 	scfgm = zmalloc(sizeof(*scfgm));
 	if (!scfgm)
@@ -231,7 +230,7 @@ void server_cfg_monitor_init()
 
 	/* ln = IN_CREATE, cp/vi/mv = IN_CREATE, IN_OPEN, IN_CLOSE_WRITE */
 	iwd = inotify_add_watch(
-		ifd, ".",
+		ifd, cfg->cfg_real_path,
 		IN_CLOSE_WRITE | IN_DELETE | IN_CREATE | IN_DELETE_SELF |
 			IN_MOVE_SELF | IN_MOVED_TO | IN_MOVED_FROM |
 			IN_DONT_FOLLOW | IN_EXCL_UNLINK | IN_ONLYDIR);
@@ -244,11 +243,7 @@ void server_cfg_monitor_init()
 	cfg->server_cfg_monitor = scfgm;
 	uring_read(&scfgm->task, scfgm->buf, sizeof(scfgm->buf), inotify_cb);
 
-	dir = opendir(".");
-	if (!dir)
-		die("opendir(%s): %m", cfg->cfg_dir);
-
-	while ((dent = readdir(dir)) != NULL) {
+	while ((dent = readdir(cfg->cfg_dir)) != NULL) {
 		if (!config_valid_server_filename(dent, NULL))
 			continue;
 
@@ -257,6 +252,4 @@ void server_cfg_monitor_init()
 			uring_openat(&server->task, server->scfg.filename,
 				     server_cfg_open_cb);
 	}
-
-	closedir(dir);
 }
diff --git a/shared/config-parser.c b/shared/config-parser.c
index 4dc446c..909c558 100644
--- a/shared/config-parser.c
+++ b/shared/config-parser.c
@@ -14,7 +14,6 @@
 #include "config-parser.h"
 #include "server-config-options.h"
 #include "server-properties-options.h"
-#include "config.h"
 
 static bool handle_addrinfo_results(struct addrinfo *results,
 				    struct list_head *target_list,
diff --git a/shared/systemd.c b/shared/systemd.c
index c45c7c5..4edc055 100644
--- a/shared/systemd.c
+++ b/shared/systemd.c
@@ -7,7 +7,6 @@
 #include "utils.h"
 #include "config-parser.h"
 #include "systemd.h"
-#include "config.h"
 
 static sd_bus *bus = NULL;
 static bool bus_failed = false;
diff --git a/shared/utils.c b/shared/utils.c
index 99fcaa3..f41a8a1 100644
--- a/shared/utils.c
+++ b/shared/utils.c
@@ -5,6 +5,7 @@
 #include <limits.h>
 #include <arpa/inet.h>
 #include <string.h>
+#include <sys/stat.h>
 #include <sys/types.h>
 #include <dirent.h>
 #include <fcntl.h>
@@ -14,11 +15,156 @@
 #include <netinet/tcp.h>
 #include <inttypes.h>
 #include <stdarg.h>
+#include <pwd.h>
 
 #include "utils.h"
 
 unsigned debug_mask = 0;
 
+static const char *get_homedir()
+{
+	const char *e;
+        struct passwd *passwd;
+        uid_t uid;
+
+	e = getenv("HOME");
+	if (e && e[0] == '/')
+		return e;
+
+	uid = getuid();
+	if (uid == 0)
+		return "/root";
+
+        passwd = getpwuid(uid);
+	if (passwd && passwd->pw_dir[0] == '/')
+		return passwd->pw_dir;
+
+	return NULL;
+}
+
+int open_subdir(int dfd, const char *subdir, bool nofail)
+{
+	int sfd;
+
+	if (nofail && mkdirat(dfd, subdir, 0777) < 0 && errno != EEXIST) {
+		error("Unable to create subdirectory %s: %m", subdir);
+		return -1;
+	}
+
+	sfd = openat(dfd, subdir, O_PATH | O_CLOEXEC | O_DIRECTORY);
+	if (sfd < 0 && nofail)
+		error("Unable to open subdirectory %s: %m", subdir);
+
+	return sfd;
+}
+
+int open_xdg_dir(const char *envname, const char *altpath, bool nofail,
+		 char **rpath)
+{
+        const char *e;
+	const char *h;
+	int dfd, hfd;
+
+	e = getenv(envname);
+	if (e && e[0] == '/') {
+		dfd = open(e, O_PATH | O_CLOEXEC | O_DIRECTORY);
+		if (dfd < 0)
+			error("Unable to open $%s(%s): %m", envname, e);
+		else if (rpath) {
+			*rpath = xstrdup(e);
+			if (!rpath) {
+				error("xstrdup: %m");
+				close(dfd);
+				return -1;
+			}
+		}
+		return dfd;
+	}
+
+	h = get_homedir();
+	if (!h) {
+		error("Unable to determine home directory");
+		return -1;
+	}
+
+	hfd = open(h, O_PATH | O_CLOEXEC | O_DIRECTORY);
+	if (hfd < 0) {
+		error("Unable to open $HOME(%s): %m", h);
+		return -1;
+	}
+
+	dfd = open_subdir(hfd, altpath, nofail);
+	close(hfd);
+
+	if (dfd >= 0 && rpath) {
+		*rpath = xstrcat(h, "/", altpath, NULL);
+		if (!*rpath) {
+			error("xstrcat: %m");
+			close(dfd);
+			return -1;
+		}
+	}
+	return dfd;
+}
+
+DIR *__open_dir(const char *user_override,
+		int (*base_dir)(bool nofail, char **rpath),
+		const char *fallback, char **rpath)
+{
+	_cleanup_close_ int xfd = -1;
+	_cleanup_close_ int mfd = -1;
+	_cleanup_free_ char *tmp = NULL;
+	DIR *dir;
+
+	/* First, use explicitly set path... */
+	if (user_override) {
+		dir = opendir(user_override);
+		if (dir && rpath) {
+			*rpath = xstrdup(user_override);
+			if (!*rpath) {
+				closedir(dir);
+				return NULL;
+			}
+		}
+		return dir;
+	}
+
+	/* ...second, attempt per-user config dir... */
+	xfd = base_dir(false, &tmp);
+	if (xfd < 0)
+		goto fallback;
+
+	mfd = openat(xfd, "minecproxy", O_CLOEXEC | O_DIRECTORY | O_RDONLY);
+	if (mfd < 0)
+		goto fallback;
+
+	dir = fdopendir(mfd);
+	if (dir)
+		mfd = -1;
+
+	if (rpath) {
+		*rpath = xstrcat(tmp, "/minecproxy", NULL);
+		if (!*rpath) {
+			closedir(dir);
+			return NULL;
+		}
+	}
+
+	return dir;
+
+	/* ...third, fallback on the system dir */
+fallback:
+	dir = opendir(fallback);
+	if (dir && rpath) {
+		*rpath = xstrdup(fallback);
+		if (!*rpath) {
+			closedir(dir);
+			return NULL;
+		}
+	}
+	return dir;
+}
+
 const char *ansi_red = "";
 const char *ansi_green = "";
 const char *ansi_yellow = "";
@@ -252,3 +398,33 @@ char *xsprintf(size_t *rlen, const char *fmt, ...)
 
 	return str;
 }
+
+char *xstrcat(const char *a, ...) {
+	size_t len;
+	const char *b;
+	char *str;
+	char *to;
+	va_list ap;
+
+	if (!a)
+		return NULL;
+
+	len = strlen(a) + 1;
+
+	va_start(ap, a);
+	while ((b = va_arg(ap, const char *)))
+		len += strlen(b);
+	va_end(ap);
+
+	str = zmalloc(len);
+	if (!str)
+		return NULL;
+	
+	to = stpcpy(str, a);
+	va_start(ap, a);
+	while ((b = va_arg(ap, const char *)))
+		to = stpcpy(to, b);
+	va_end(ap);
+
+	return str;
+}
diff --git a/shared/utils.h b/shared/utils.h
index 1291d21..b6bf51c 100644
--- a/shared/utils.h
+++ b/shared/utils.h
@@ -8,9 +8,11 @@
 #include <stdlib.h>
 #include <linux/if_packet.h>
 #include <sys/socket.h>
+#include <sys/types.h>
 #include <netinet/in.h>
 #include <netinet/ip.h>
 #include <unistd.h>
+#include <dirent.h>
 
 extern unsigned debug_mask;
 
@@ -42,6 +44,7 @@ extern unsigned debug_mask;
 #include "list.h"
 #include "debug.h"
 #include "external.h"
+#include "config.h"
 #include "ansi-colors.h"
 
 /* Length of longest DNS name = 253 + trailing dot */
@@ -68,6 +71,37 @@ struct saddr {
 	struct list_head list;
 };
 
+int open_subdir(int dfd, const char *subdir, bool nofail);
+
+int open_xdg_dir(const char *envname, const char *altpath, bool nofail,
+		 char **rpath);
+
+static inline int open_xdg_data_dir(bool nofail, char **rpath)
+{
+	return open_xdg_dir("XDG_DATA_HOME", ".local/share", nofail, rpath);
+}
+
+static inline int open_xdg_cfg_dir(bool nofail, char **rpath)
+{
+	return open_xdg_dir("XDG_CONFIG_HOME", ".config", nofail, rpath);
+}
+
+DIR *__open_dir(const char *user_override,
+		int (*base_dir)(bool nofail, char **rpath),
+		const char *fallback, char **rpath);
+
+static inline DIR *open_cfg_dir(const char *user_override, char **rpath)
+{
+	return __open_dir(user_override, open_xdg_cfg_dir, DEFAULT_CFG_DIR,
+			  rpath);
+}
+
+static inline DIR *open_data_dir(const char *user_override, char **rpath)
+{
+	return __open_dir(user_override, open_xdg_data_dir, DEFAULT_DATA_DIR,
+			  rpath);
+}
+
 void enable_colors();
 
 void free_password(char **password);
@@ -91,6 +125,8 @@ int strtou16_strict(const char *str, uint16_t *result);
 
 char *xsprintf(size_t *rlen, const char *fmt, ...) _printf_(2, 3);
 
+char *xstrcat(const char *a, ...);
+
 static inline bool empty_str(const char *str)
 {
 	if (!str || str[0] == '\0')
@@ -136,7 +172,7 @@ static inline bool strcaseeq(const char *a, const char *b)
 
 static inline void closep(int *fd)
 {
-	if (*fd && *fd >= 0)
+	if (fd && *fd >= 0)
 		close(*fd);
 }
 #define _cleanup_close_ _cleanup_(closep)
@@ -146,4 +182,10 @@ static inline void freep(void *p) {
 }
 #define _cleanup_free_ _cleanup_(freep)
 
+static inline void fclosep(FILE **f) {
+	if (f && *f)
+		fclose(*f);
+}
+#define _cleanup_fclose_ _cleanup_(fclosep)
+
 #endif
-- 
cgit v1.2.3