From 27eaddb6b9524b43448f4e5c88ac74bbe8fdb3a5 Mon Sep 17 00:00:00 2001 From: C. McEnroe Date: Wed, 5 Feb 2020 22:49:56 -0500 Subject: Use getopt_config to load options I'm really getting a lot of use out of this config.c huh. --- config.c | 178 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 config.c (limited to 'config.c') diff --git a/config.c b/config.c new file mode 100644 index 0000000..b3e42f9 --- /dev/null +++ b/config.c @@ -0,0 +1,178 @@ +/* Copyright (C) 2019 C. McEnroe + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "chat.h" + +#define CONFIG_DIR "catgirl" + +FILE *configOpen(const char *path, const char *mode) { + if (path[0] == '/' || path[0] == '.') goto local; + + const char *home = getenv("HOME"); + const char *configHome = getenv("XDG_CONFIG_HOME"); + const char *configDirs = getenv("XDG_CONFIG_DIRS"); + + char buf[PATH_MAX]; + if (configHome) { + snprintf(buf, sizeof(buf), "%s/" CONFIG_DIR "/%s", configHome, path); + } else { + if (!home) goto local; + snprintf(buf, sizeof(buf), "%s/.config/" CONFIG_DIR "/%s", home, path); + } + FILE *file = fopen(buf, mode); + if (file) return file; + if (errno != ENOENT) return NULL; + + if (!configDirs) configDirs = "/etc/xdg"; + while (*configDirs) { + size_t len = strcspn(configDirs, ":"); + snprintf( + buf, sizeof(buf), "%.*s/" CONFIG_DIR "/%s", + (int)len, configDirs, path + ); + file = fopen(buf, mode); + if (file) return file; + if (errno != ENOENT) return NULL; + configDirs += len; + if (*configDirs) configDirs++; + } + +local: + return fopen(path, mode); +} + +#define WS "\t " + +static const char *path; +static FILE *file; +static size_t num; +static char *line; +static size_t cap; + +static int clean(int opt) { + if (file) fclose(file); + free(line); + line = NULL; + cap = 0; + return opt; +} + +int getopt_config( + int argc, char *const *argv, + const char *optstring, const struct option *longopts, int *longindex +) { + static int opt; + if (opt >= 0) { + opt = getopt_long(argc, argv, optstring, longopts, longindex); + } + if (opt >= 0) return opt; + + for (;;) { + if (!file) { + if (optind < argc) { + num = 0; + path = argv[optind++]; + file = configOpen(path, "r"); + if (!file) { + warn("%s", path); + return clean('?'); + } + } else { + return clean(-1); + } + } + + for (;;) { + ssize_t llen = getline(&line, &cap, file); + if (ferror(file)) { + warn("%s", path); + return clean('?'); + } + if (llen <= 0) break; + if (line[llen - 1] == '\n') line[llen - 1] = '\0'; + num++; + + char *name = line + strspn(line, WS); + size_t len = strcspn(name, WS "="); + if (!name[0] || name[0] == '#') continue; + + const struct option *option; + for (option = longopts; option->name; ++option) { + if (strlen(option->name) != len) continue; + if (!strncmp(option->name, name, len)) break; + } + if (!option->name) { + warnx( + "%s:%zu: unrecognized option `%.*s'", + path, num, (int)len, name + ); + return clean('?'); + } + + char *equal = &name[len] + strspn(&name[len], WS); + if (*equal && *equal != '=') { + warnx( + "%s:%zu: option `%s' missing equals sign", + path, num, option->name + ); + return clean('?'); + } + if (option->has_arg == no_argument && *equal) { + warnx( + "%s:%zu: option `%s' doesn't allow an argument", + path, num, option->name + ); + return clean('?'); + } + if (option->has_arg == required_argument && !*equal) { + warnx( + "%s:%zu: option `%s' requires an argument", + path, num, option->name + ); + return clean(':'); + } + + optarg = NULL; + if (*equal) { + char *arg = &equal[1] + strspn(&equal[1], WS); + optarg = strdup(arg); + if (!optarg) { + warn("getopt_config"); + return clean('?'); + } + } + + if (longindex) *longindex = option - longopts; + if (option->flag) { + *option->flag = option->val; + return 0; + } else { + return option->val; + } + } + + fclose(file); + file = NULL; + } +} -- cgit 1.4.1-2-gfad0 From b59431bb15ec74f05119a7c710a1f6a21e702bad Mon Sep 17 00:00:00 2001 From: C. McEnroe Date: Mon, 10 Feb 2020 19:40:13 -0500 Subject: Add -s to save and load buffers --- catgirl.1 | 27 +++++++++- chat.c | 17 +++++- chat.h | 4 ++ config.c | 8 ++- ui.c | 176 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 220 insertions(+), 12 deletions(-) (limited to 'config.c') diff --git a/catgirl.1 b/catgirl.1 index 15b387b..00f875b 100644 --- a/catgirl.1 +++ b/catgirl.1 @@ -20,6 +20,7 @@ .Op Fl n Ar nick .Op Fl p Ar port .Op Fl r Ar real +.Op Fl s Ar save .Op Fl u Ar user .Op Fl w Ar pass .Op Ar config ... @@ -123,6 +124,18 @@ Set realname to .Ar real . The default realname is the same as the nickname. . +.It Fl s Ar name , Cm save = Ar name +Load and save the contents of windows from +.Ar name +in +.Pa $XDG_DATA_DIRS/catgirl , +or an absolute or relative path if +.Ar name +starts with +.Ql / +or +.Ql \&. . +. .It Fl u Ar user , Cm user = Ar user Set username to .Ar user . @@ -324,7 +337,7 @@ The color numbers are as follows: .Sh FILES .Bl -tag -width Ds .It Pa $XDG_CONFIG_DIRS/catgirl -Configuration files are search for first in +Configuration files are searched for first in .Ev $XDG_CONFIG_HOME , usually .Pa ~/.config , @@ -334,6 +347,18 @@ usually .Pa /etc/xdg . .It Pa ~/.config/catgirl The most likely location of configuration files. +. +.It Pa $XDG_DATA_DIRS/catgirl +Save files are searched for first in +.Ev $XDG_DATA_HOME , +usually +.Pa ~/.local/share , +followed by the colon-separated list of paths +.Ev $XDG_DATA_DIRS , +usually +.Pa /usr/local/share:/usr/share . +.It Pa ~/.local/share/catgirl +The most likely location of save files. .El . .Sh EXAMPLES diff --git a/chat.c b/chat.c index c58fdc5..e8713bb 100644 --- a/chat.c +++ b/chat.c @@ -47,6 +47,15 @@ size_t idNext = Network + 1; struct Self self = { .color = Default }; +static const char *save; +static void exitSave(void) { + int error = uiSave(save); + if (error) { + warn("%s", save); + _exit(EX_IOERR); + } +} + uint32_t hashInit; int procPipe[2] = { -1, -1 }; @@ -84,7 +93,7 @@ int main(int argc, char *argv[]) { const char *user = NULL; const char *real = NULL; - const char *Opts = "!C:H:O:a:c:eh:j:k:n:p:r:u:vw:"; + const char *Opts = "!C:H:O:a:c:eh:j:k:n:p:r:s:u:vw:"; const struct option LongOpts[] = { { "insecure", no_argument, NULL, '!' }, { "copy", required_argument, NULL, 'C' }, @@ -99,6 +108,7 @@ int main(int argc, char *argv[]) { { "nick", required_argument, NULL, 'n' }, { "port", required_argument, NULL, 'p' }, { "real", required_argument, NULL, 'r' }, + { "save", required_argument, NULL, 's' }, { "user", required_argument, NULL, 'u' }, { "debug", no_argument, NULL, 'v' }, { "pass", required_argument, NULL, 'w' }, @@ -121,6 +131,7 @@ int main(int argc, char *argv[]) { break; case 'n': nick = optarg; break; case 'p': port = optarg; break; case 'r': real = optarg; + break; case 's': save = optarg; break; case 'u': user = optarg; break; case 'v': self.debug = true; break; case 'w': pass = optarg; @@ -154,6 +165,10 @@ int main(int argc, char *argv[]) { if (privFile) fclose(privFile); uiInit(); + if (save) { + uiLoad(save); + atexit(exitSave); + } uiShowID(Network); uiFormat(Network, Cold, NULL, "Traveling..."); uiDraw(); diff --git a/chat.h b/chat.h index 13319da..47a6163 100644 --- a/chat.h +++ b/chat.h @@ -26,6 +26,8 @@ #define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0])) #define BIT(x) x##Bit, x = 1 << x##Bit, x##Bit_ = x##Bit +#define XDG_SUBDIR "catgirl" + typedef unsigned char byte; int procPipe[2]; @@ -144,6 +146,8 @@ void uiWrite(size_t id, enum Heat heat, const time_t *time, const char *str); void uiFormat( size_t id, enum Heat heat, const time_t *time, const char *format, ... ) __attribute__((format(printf, 4, 5))); +void uiLoad(const char *name); +int uiSave(const char *name); enum Edit { EditHead, diff --git a/config.c b/config.c index b3e42f9..3bf56c0 100644 --- a/config.c +++ b/config.c @@ -24,8 +24,6 @@ #include "chat.h" -#define CONFIG_DIR "catgirl" - FILE *configOpen(const char *path, const char *mode) { if (path[0] == '/' || path[0] == '.') goto local; @@ -35,10 +33,10 @@ FILE *configOpen(const char *path, const char *mode) { char buf[PATH_MAX]; if (configHome) { - snprintf(buf, sizeof(buf), "%s/" CONFIG_DIR "/%s", configHome, path); + snprintf(buf, sizeof(buf), "%s/" XDG_SUBDIR "/%s", configHome, path); } else { if (!home) goto local; - snprintf(buf, sizeof(buf), "%s/.config/" CONFIG_DIR "/%s", home, path); + snprintf(buf, sizeof(buf), "%s/.config/" XDG_SUBDIR "/%s", home, path); } FILE *file = fopen(buf, mode); if (file) return file; @@ -48,7 +46,7 @@ FILE *configOpen(const char *path, const char *mode) { while (*configDirs) { size_t len = strcspn(configDirs, ":"); snprintf( - buf, sizeof(buf), "%.*s/" CONFIG_DIR "/%s", + buf, sizeof(buf), "%.*s/" XDG_SUBDIR "/%s", (int)len, configDirs, path ); file = fopen(buf, mode); diff --git a/ui.c b/ui.c index 9a070a6..57ef322 100644 --- a/ui.c +++ b/ui.c @@ -20,11 +20,14 @@ #include #include #include +#include +#include #include #include #include #include #include +#include #include #include #include @@ -66,6 +69,13 @@ static void bufferPush(struct Buffer *buffer, time_t time, const char *line) { if (!buffer->lines[i]) err(EX_OSERR, "strdup"); } +static time_t bufferTime(const struct Buffer *buffer, size_t i) { + return buffer->times[(buffer->len + i) % BufferCap]; +} +static const char *bufferLine(const struct Buffer *buffer, size_t i) { + return buffer->lines[(buffer->len + i) % BufferCap]; +} + enum { WindowLines = BufferCap }; struct Window { size_t id; @@ -532,9 +542,8 @@ static void reflow(struct Window *window) { werase(window->pad); wmove(window->pad, WindowLines - 1, 0); window->unreadLines = 0; - struct Buffer *buffer = &window->buffer; for (size_t i = 0; i < BufferCap; ++i) { - char *line = buffer->lines[(buffer->len + i) % BufferCap]; + const char *line = bufferLine(&window->buffer, i); if (!line) continue; waddch(window->pad, '\n'); if (i >= (size_t)(BufferCap - window->unreadCount)) { @@ -557,12 +566,12 @@ static void resize(void) { statusUpdate(); } -static void bufferList(struct Buffer *buffer) { +static void bufferList(const struct Buffer *buffer) { uiHide(); waiting = true; for (size_t i = 0; i < BufferCap; ++i) { - time_t time = buffer->times[(buffer->len + i) % BufferCap]; - const char *line = buffer->lines[(buffer->len + i) % BufferCap]; + time_t time = bufferTime(buffer, i); + const char *line = bufferLine(buffer, i); if (!line) continue; struct tm *tm = localtime(&time); @@ -848,3 +857,160 @@ void uiRead(void) { } inputUpdate(); } + +static FILE *dataOpen(const char *path, const char *mode) { + if (path[0] == '/' || path[0] == '.') goto local; + + const char *home = getenv("HOME"); + const char *dataHome = getenv("XDG_DATA_HOME"); + const char *dataDirs = getenv("XDG_DATA_DIRS"); + + char homePath[PATH_MAX]; + if (dataHome) { + snprintf( + homePath, sizeof(homePath), + "%s/" XDG_SUBDIR "/%s", dataHome, path + ); + } else { + if (!home) goto local; + snprintf( + homePath, sizeof(homePath), + "%s/.local/share/" XDG_SUBDIR "/%s", home, path + ); + } + FILE *file = fopen(homePath, mode); + if (file) return file; + if (errno != ENOENT) { + warn("%s", homePath); + return NULL; + } + + char buf[PATH_MAX]; + if (!dataDirs) dataDirs = "/usr/local/share:/usr/share"; + while (*dataDirs) { + size_t len = strcspn(dataDirs, ":"); + snprintf( + buf, sizeof(buf), "%.*s/" XDG_SUBDIR "/%s", + (int)len, dataDirs, path + ); + file = fopen(buf, mode); + if (file) return file; + if (errno != ENOENT) { + warn("%s", buf); + return NULL; + } + dataDirs += len; + if (*dataDirs) dataDirs++; + } + + if (mode[0] != 'r') { + char *base = strrchr(homePath, '/'); + *base = '\0'; + int error = mkdir(homePath, S_IRWXU); + if (error && errno != EEXIST) { + warn("%s", homePath); + return NULL; + } + *base = '/'; + file = fopen(homePath, mode); + if (!file) warn("%s", homePath); + return file; + } + +local: + file = fopen(path, mode); + if (!file) warn("%s", path); + return file; +} + +static const size_t Signatures[] = { + 0x6C72696774616301, +}; + +static size_t signatureVersion(size_t signature) { + for (size_t i = 0; i < ARRAY_LEN(Signatures); ++i) { + if (signature == Signatures[i]) return i; + } + err(EX_DATAERR, "unknown file signature %zX", signature); +} + +static int writeSize(FILE *file, size_t value) { + return (fwrite(&value, sizeof(value), 1, file) ? 0 : -1); +} +static int writeTime(FILE *file, time_t time) { + return (fwrite(&time, sizeof(time), 1, file) ? 0 : -1); +} +static int writeString(FILE *file, const char *str) { + return (fwrite(str, strlen(str) + 1, 1, file) ? 0 : -1); +} + +int uiSave(const char *name) { + FILE *file = dataOpen(name, "w"); + if (!file) return -1; + + if (writeSize(file, Signatures[0])) return -1; + const struct Window *window; + for (window = windows.head; window; window = window->next) { + if (writeString(file, idNames[window->id])) return -1; + for (size_t i = 0; i < BufferCap; ++i) { + time_t time = bufferTime(&window->buffer, i); + const char *line = bufferLine(&window->buffer, i); + if (!line) continue; + if (writeTime(file, time)) return -1; + if (writeString(file, line)) return -1; + } + if (writeTime(file, 0)) return -1; + } + return fclose(file); +} + +static size_t readSize(FILE *file) { + size_t value; + fread(&value, sizeof(value), 1, file); + if (ferror(file)) err(EX_IOERR, "fread"); + if (feof(file)) errx(EX_DATAERR, "unexpected eof"); + return value; +} +static time_t readTime(FILE *file) { + time_t time; + fread(&time, sizeof(time), 1, file); + if (ferror(file)) err(EX_IOERR, "fread"); + if (feof(file)) errx(EX_DATAERR, "unexpected eof"); + return time; +} +static ssize_t readString(FILE *file, char **buf, size_t *cap) { + ssize_t len = getdelim(buf, cap, '\0', file); + if (len < 0 && !feof(file)) err(EX_IOERR, "getdelim"); + return len; +} + +void uiLoad(const char *name) { + FILE *file = dataOpen(name, "r"); + if (!file) { + if (errno != ENOENT) exit(EX_NOINPUT); + file = dataOpen(name, "w"); + if (!file) exit(EX_CANTCREAT); + fclose(file); + return; + } + + size_t signature = readSize(file); + signatureVersion(signature); + + char *buf = NULL; + size_t cap = 0; + while (0 < readString(file, &buf, &cap)) { + struct Window *window = windowFor(idFor(buf)); + for (;;) { + time_t time = readTime(file); + if (!time) break; + readString(file, &buf, &cap); + bufferPush(&window->buffer, time, buf); + } + reflow(window); + // TODO: Place some marker of end of save. + } + + free(buf); + fclose(file); +} -- cgit 1.4.1-2-gfad0 From 99480a42e56e70707822934ffeb56f0454afc127 Mon Sep 17 00:00:00 2001 From: C. McEnroe Date: Mon, 10 Feb 2020 19:57:10 -0500 Subject: Factor out XDG base directory code And add warnings to configOpen, since that's the only way to be accurate if a weird error occurs. --- Makefile | 1 + chat.c | 4 +- chat.h | 2 + config.c | 41 +------------------ ui.c | 67 -------------------------------- xdg.c | 134 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 140 insertions(+), 109 deletions(-) create mode 100644 xdg.c (limited to 'config.c') diff --git a/Makefile b/Makefile index 89af9b3..b1ffede 100644 --- a/Makefile +++ b/Makefile @@ -15,6 +15,7 @@ OBJS += handle.o OBJS += irc.o OBJS += ui.o OBJS += url.o +OBJS += xdg.o dev: tags all diff --git a/chat.c b/chat.c index e8713bb..1ae7090 100644 --- a/chat.c +++ b/chat.c @@ -154,11 +154,11 @@ int main(int argc, char *argv[]) { FILE *privFile = NULL; if (cert) { certFile = configOpen(cert, "r"); - if (!certFile) err(EX_NOINPUT, "%s", cert); + if (!certFile) return EX_NOINPUT; } if (priv) { privFile = configOpen(priv, "r"); - if (!privFile) err(EX_NOINPUT, "%s", priv); + if (!privFile) return EX_NOINPUT; } ircConfig(insecure, certFile, privFile); if (certFile) fclose(certFile); diff --git a/chat.h b/chat.h index 47a6163..03a0a50 100644 --- a/chat.h +++ b/chat.h @@ -189,6 +189,8 @@ void urlOpenMatch(size_t id, const char *str); void urlCopyMatch(size_t id, const char *str); FILE *configOpen(const char *path, const char *mode); +FILE *dataOpen(const char *path, const char *mode); + int getopt_config( int argc, char *const *argv, const char *optstring, const struct option *longopts, int *longindex diff --git a/config.c b/config.c index 3bf56c0..3a87948 100644 --- a/config.c +++ b/config.c @@ -24,42 +24,6 @@ #include "chat.h" -FILE *configOpen(const char *path, const char *mode) { - if (path[0] == '/' || path[0] == '.') goto local; - - const char *home = getenv("HOME"); - const char *configHome = getenv("XDG_CONFIG_HOME"); - const char *configDirs = getenv("XDG_CONFIG_DIRS"); - - char buf[PATH_MAX]; - if (configHome) { - snprintf(buf, sizeof(buf), "%s/" XDG_SUBDIR "/%s", configHome, path); - } else { - if (!home) goto local; - snprintf(buf, sizeof(buf), "%s/.config/" XDG_SUBDIR "/%s", home, path); - } - FILE *file = fopen(buf, mode); - if (file) return file; - if (errno != ENOENT) return NULL; - - if (!configDirs) configDirs = "/etc/xdg"; - while (*configDirs) { - size_t len = strcspn(configDirs, ":"); - snprintf( - buf, sizeof(buf), "%.*s/" XDG_SUBDIR "/%s", - (int)len, configDirs, path - ); - file = fopen(buf, mode); - if (file) return file; - if (errno != ENOENT) return NULL; - configDirs += len; - if (*configDirs) configDirs++; - } - -local: - return fopen(path, mode); -} - #define WS "\t " static const char *path; @@ -92,10 +56,7 @@ int getopt_config( num = 0; path = argv[optind++]; file = configOpen(path, "r"); - if (!file) { - warn("%s", path); - return clean('?'); - } + if (!file) return clean('?'); } else { return clean(-1); } diff --git a/ui.c b/ui.c index ecf7e60..9601aaa 100644 --- a/ui.c +++ b/ui.c @@ -21,13 +21,11 @@ #include #include #include -#include #include #include #include #include #include -#include #include #include #include @@ -858,71 +856,6 @@ void uiRead(void) { inputUpdate(); } -static FILE *dataOpen(const char *path, const char *mode) { - if (path[0] == '/' || path[0] == '.') goto local; - - const char *home = getenv("HOME"); - const char *dataHome = getenv("XDG_DATA_HOME"); - const char *dataDirs = getenv("XDG_DATA_DIRS"); - - char homePath[PATH_MAX]; - if (dataHome) { - snprintf( - homePath, sizeof(homePath), - "%s/" XDG_SUBDIR "/%s", dataHome, path - ); - } else { - if (!home) goto local; - snprintf( - homePath, sizeof(homePath), - "%s/.local/share/" XDG_SUBDIR "/%s", home, path - ); - } - FILE *file = fopen(homePath, mode); - if (file) return file; - if (errno != ENOENT) { - warn("%s", homePath); - return NULL; - } - - char buf[PATH_MAX]; - if (!dataDirs) dataDirs = "/usr/local/share:/usr/share"; - while (*dataDirs) { - size_t len = strcspn(dataDirs, ":"); - snprintf( - buf, sizeof(buf), "%.*s/" XDG_SUBDIR "/%s", - (int)len, dataDirs, path - ); - file = fopen(buf, mode); - if (file) return file; - if (errno != ENOENT) { - warn("%s", buf); - return NULL; - } - dataDirs += len; - if (*dataDirs) dataDirs++; - } - - if (mode[0] != 'r') { - char *base = strrchr(homePath, '/'); - *base = '\0'; - int error = mkdir(homePath, S_IRWXU); - if (error && errno != EEXIST) { - warn("%s", homePath); - return NULL; - } - *base = '/'; - file = fopen(homePath, mode); - if (!file) warn("%s", homePath); - return file; - } - -local: - file = fopen(path, mode); - if (!file) warn("%s", path); - return file; -} - static const size_t Signatures[] = { 0x6C72696774616301, }; diff --git a/xdg.c b/xdg.c new file mode 100644 index 0000000..6e33210 --- /dev/null +++ b/xdg.c @@ -0,0 +1,134 @@ +/* Copyright (C) 2019, 2020 C. McEnroe + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "chat.h" + +FILE *configOpen(const char *path, const char *mode) { + if (path[0] == '/' || path[0] == '.') goto local; + + const char *home = getenv("HOME"); + const char *configHome = getenv("XDG_CONFIG_HOME"); + const char *configDirs = getenv("XDG_CONFIG_DIRS"); + + char buf[PATH_MAX]; + if (configHome) { + snprintf(buf, sizeof(buf), "%s/" XDG_SUBDIR "/%s", configHome, path); + } else { + if (!home) goto local; + snprintf(buf, sizeof(buf), "%s/.config/" XDG_SUBDIR "/%s", home, path); + } + FILE *file = fopen(buf, mode); + if (file) return file; + if (errno != ENOENT) { + warn("%s", buf); + return NULL; + } + + if (!configDirs) configDirs = "/etc/xdg"; + while (*configDirs) { + size_t len = strcspn(configDirs, ":"); + snprintf( + buf, sizeof(buf), "%.*s/" XDG_SUBDIR "/%s", + (int)len, configDirs, path + ); + file = fopen(buf, mode); + if (file) return file; + if (errno != ENOENT) { + warn("%s", buf); + return NULL; + } + configDirs += len; + if (*configDirs) configDirs++; + } + +local: + file = fopen(path, mode); + if (!file) warn("%s", path); + return file; +} + +FILE *dataOpen(const char *path, const char *mode) { + if (path[0] == '/' || path[0] == '.') goto local; + + const char *home = getenv("HOME"); + const char *dataHome = getenv("XDG_DATA_HOME"); + const char *dataDirs = getenv("XDG_DATA_DIRS"); + + char homePath[PATH_MAX]; + if (dataHome) { + snprintf( + homePath, sizeof(homePath), + "%s/" XDG_SUBDIR "/%s", dataHome, path + ); + } else { + if (!home) goto local; + snprintf( + homePath, sizeof(homePath), + "%s/.local/share/" XDG_SUBDIR "/%s", home, path + ); + } + FILE *file = fopen(homePath, mode); + if (file) return file; + if (errno != ENOENT) { + warn("%s", homePath); + return NULL; + } + + char buf[PATH_MAX]; + if (!dataDirs) dataDirs = "/usr/local/share:/usr/share"; + while (*dataDirs) { + size_t len = strcspn(dataDirs, ":"); + snprintf( + buf, sizeof(buf), "%.*s/" XDG_SUBDIR "/%s", + (int)len, dataDirs, path + ); + file = fopen(buf, mode); + if (file) return file; + if (errno != ENOENT) { + warn("%s", buf); + return NULL; + } + dataDirs += len; + if (*dataDirs) dataDirs++; + } + + if (mode[0] != 'r') { + char *base = strrchr(homePath, '/'); + *base = '\0'; + int error = mkdir(homePath, S_IRWXU); + if (error && errno != EEXIST) { + warn("%s", homePath); + return NULL; + } + *base = '/'; + file = fopen(homePath, mode); + if (!file) warn("%s", homePath); + return file; + } + +local: + file = fopen(path, mode); + if (!file) warn("%s", path); + return file; +} -- cgit 1.4.1-2-gfad0