diff options
-rw-r--r-- | minecctl/misc-commands.c | 9 | ||||
-rw-r--r-- | minecctl/misc.c | 69 | ||||
-rw-r--r-- | minecctl/misc.h | 14 | ||||
-rw-r--r-- | minecctl/server.c | 46 | ||||
-rw-r--r-- | minecproxy/main.c | 96 | ||||
-rw-r--r-- | minecproxy/main.h | 30 | ||||
-rw-r--r-- | minecproxy/server-config.c | 13 | ||||
-rw-r--r-- | shared/config-parser.c | 1 | ||||
-rw-r--r-- | shared/systemd.c | 1 | ||||
-rw-r--r-- | shared/utils.c | 176 | ||||
-rw-r--r-- | 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 |