@@ 1,5 1,5 @@
-// libr 0.5.0 (ec3c888754c9966a470ff5a8d1baf1cfaa3045d2) (https://github.com/rootmos/libr.git) (2024-03-29T04:14:36+01:00)
-// modules: fail logging now util nonblock
+// libr 0.5.0 (ec3c888754c9966a470ff5a8d1baf1cfaa3045d2) (https://github.com/rootmos/libr.git) (2024-03-29T04:47:48+01:00)
+// modules: fail logging now util nonblock xdg path
#ifndef LIBR_HEADER
#define LIBR_HEADER
@@ 147,6 147,68 @@ const char* LIBR(now_iso8601_compact)(void);
// libr: nonblock.h
void LIBR(set_blocking)(int fd, int blocking);
+
+// libr: xdg.h
+
+#include <stddef.h>
+#include <stdarg.h>
+
+// https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
+
+struct xdg;
+
+enum xdg_kind {
+ XDG_HOME = 0,
+ XDG_DATA,
+ XDG_CONFIG,
+ XDG_STATE,
+ XDG_CACHE,
+ XDG_RUNTIME,
+ XDG_KINDS,
+};
+
+struct xdg* LIBR(xdg_new)(const char* app);
+void LIBR(xdg_free)(struct xdg* xdg);
+
+const char* LIBR(xdg_dir)(struct xdg* xdg, enum xdg_kind k);
+const char** LIBR(xdg_dirs)(struct xdg* xdg, enum xdg_kind k);
+
+const char* LIBR(xdg_home)(struct xdg* xdg);
+const char* LIBR(xdg_data_home)(struct xdg* xdg);
+const char* LIBR(xdg_config_home)(struct xdg* xdg);
+const char* LIBR(xdg_state_home)(struct xdg* xdg);
+const char* LIBR(xdg_cache_home)(struct xdg* xdg);
+const char* LIBR(xdg_runtime)(struct xdg* xdg);
+
+const char** LIBR(xdg_data_dirs)(struct xdg* xdg);
+const char** LIBR(xdg_config_dirs)(struct xdg* xdg);
+
+const char* LIBR(xdg_resolve)(struct xdg* xdg, enum xdg_kind k, char* buf, size_t L, ...);
+const char* LIBR(xdg_resolvev)(struct xdg* xdg, enum xdg_kind k, char* buf, size_t L, va_list ps);
+const char* LIBR(xdg_resolves)(struct xdg* xdg, enum xdg_kind k, ...);
+const char* LIBR(xdg_resolvevs)(struct xdg* xdg, enum xdg_kind k, va_list ps);
+
+const char* LIBR(xdg_preparev)(struct xdg* xdg, enum xdg_kind k, char* buf, size_t L, va_list ps);
+const char* LIBR(xdg_prepare)(struct xdg* xdg, enum xdg_kind k, char* buf, size_t L, ...);
+const char* LIBR(xdg_preparevs)(struct xdg* xdg, enum xdg_kind k, va_list ps);
+const char* LIBR(xdg_prepares)(struct xdg* xdg, enum xdg_kind k, ...);
+
+// libr: path.h
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <sys/types.h>
+
+size_t LIBR(path_join)(char* buf, size_t L, const char* p0, ...);
+size_t LIBR(path_joinv)(char* buf, size_t L, const char* p0, va_list ps);
+
+// using static buffer
+#ifdef failwith
+const char* LIBR(path_joins)(const char* p0, ...);
+const char* LIBR(path_joinvs)(const char* p0, va_list ps);
+#endif
+
+int LIBR(makedirs)(const char* path, mode_t mode);
#endif // LIBR_HEADER
#ifdef LIBR_IMPLEMENTATION
@@ 260,4 322,485 @@ API void LIBR(set_blocking)(int fd, int blocking)
int r = fcntl(fd, F_SETFL, fl);
CHECK(r, "fcntl(%d, F_SETFL, %d)", fd, fl);
}
+
+// libr: xdg.c
+
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <libgen.h>
+
+struct xdg {
+ char app[NAME_MAX];
+
+ char* dir[XDG_KINDS];
+ char** dirs[XDG_KINDS];
+};
+
+API struct xdg* LIBR(xdg_new)(const char* app)
+{
+ struct xdg* xdg = calloc(1, sizeof(*xdg));
+ CHECK_MALLOC(xdg);
+
+ if(app) {
+ const size_t N = sizeof(xdg->app);
+ size_t n = strnlen(app, N);
+ if(n >= N) {
+ failwith("application name truncated at %zu characters", n);
+ }
+
+ if(n > 0) {
+ memcpy(xdg->app, app, n);
+ }
+ }
+
+ return xdg;
+}
+
+API void LIBR(xdg_free)(struct xdg* xdg)
+{
+ if(xdg == NULL) return;
+
+ for(int i = 0; i < XDG_KINDS; i++) {
+ free(xdg->dir[i]);
+ free(xdg->dirs[i]);
+ }
+
+ free(xdg);
+}
+
+static size_t xdg_check_path(const char* p)
+{
+ if(!p) return 0;
+ size_t r = strnlen(p, PATH_MAX);
+ if(r >= PATH_MAX) return 0;
+ if(r == 0) return 0;
+ if(p[0] != '/') return 0;
+ return r;
+}
+
+static size_t xdg_append_app(const struct xdg* xdg, char* buf, size_t L, const char* p)
+{
+ if(xdg->app[0]) {
+ size_t l = LIBR(path_join)(buf, L, p, xdg->app, NULL);
+ if(l >= L) {
+ failwith("buffer overflow");
+ }
+
+ return l;
+ } else {
+ return strlen(buf);
+ }
+}
+
+static size_t LIBR(xdg_fallback_runtime_dir)(char* buf, size_t L)
+{
+ char template[NAME_MAX];
+ size_t n = snprintf(LIT(template), "/tmp/xdg-runtime-fallback-%d-XXXXXX", geteuid());
+ if(n >= sizeof(template)) {
+ failwith("buffer overflow");
+ }
+
+ // mkdtemp(3): "The directory is then created with permissions 0700."
+ char* tmp = mkdtemp(template);
+ CHECK_NOT(tmp, NULL, "mkdtemp(%s)", template);
+ warning("using fallback directory for an unset XDG_RUNTIME_DIR: %s", tmp);
+ return snprintf(buf, L, "%s", tmp);
+}
+
+API const char* LIBR(xdg_dir)(struct xdg* xdg, enum xdg_kind k)
+{
+ if(xdg->dir[k]) return xdg->dir[k];
+
+ if(k == XDG_HOME) {
+ char* home = getenv("HOME");
+ if(!home) {
+ failwith("unable to resolve HOME variable");
+ }
+
+ xdg->dir[k] = strdup(home);
+ CHECK_MALLOC(xdg->dir[k]);
+ return xdg->dir[k];
+ }
+
+ char buf[PATH_MAX];
+ size_t l;
+
+ const char* v = NULL;
+ switch(k) {
+ case XDG_DATA: v = "XDG_DATA_HOME"; break;
+ case XDG_CONFIG: v = "XDG_CONFIG_HOME"; break;
+ case XDG_STATE: v = "XDG_STATE_HOME"; break;
+ case XDG_CACHE: v = "XDG_CACHE_HOME"; break;
+ case XDG_RUNTIME: v = "XDG_RUNTIME_DIR"; break;
+ default: failwith("unexpected kind: %d", k);
+ }
+
+ const char* e = getenv(v);
+ if(xdg_check_path(e)) {
+ l = LIBR(path_join)(LIT(buf), e, NULL);
+ } else {
+ switch(k) {
+ case XDG_DATA:
+ l = LIBR(path_join)(LIT(buf), LIBR(xdg_home)(xdg), ".local", "share", NULL);
+ break;
+ case XDG_CONFIG:
+ l = LIBR(path_join)(LIT(buf), LIBR(xdg_home)(xdg), ".config", NULL);
+ break;
+ case XDG_STATE:
+ l = LIBR(path_join)(LIT(buf), LIBR(xdg_home)(xdg), ".local", "state", NULL);
+ break;
+ case XDG_CACHE:
+ l = LIBR(path_join)(LIT(buf), LIBR(xdg_home)(xdg), ".cache", NULL);
+ break;
+ case XDG_RUNTIME:
+ l = LIBR(xdg_fallback_runtime_dir)(LIT(buf));
+ break;
+ default:
+ failwith("unexpected kind: %d", k);
+ }
+ }
+ if(l >= sizeof(buf)) {
+ failwith("buffer overflow");
+ }
+
+ xdg_append_app(xdg, LIT(buf), buf);
+
+ xdg->dir[k] = strdup(buf);
+ CHECK_MALLOC(xdg->dir[k]);
+
+ return xdg->dir[k];
+}
+
+API const char* LIBR(xdg_home)(struct xdg* xdg)
+{
+ return LIBR(xdg_dir)(xdg, XDG_HOME);
+}
+
+API const char* LIBR(xdg_data_home)(struct xdg* xdg)
+{
+ return LIBR(xdg_dir)(xdg, XDG_DATA);
+}
+
+API const char* LIBR(xdg_config_home)(struct xdg* xdg)
+{
+ return LIBR(xdg_dir)(xdg, XDG_CONFIG);
+}
+
+API const char* LIBR(xdg_state_home)(struct xdg* xdg)
+{
+ return LIBR(xdg_dir)(xdg, XDG_STATE);
+}
+
+API const char* LIBR(xdg_cache_home)(struct xdg* xdg)
+{
+ return LIBR(xdg_dir)(xdg, XDG_CACHE);
+}
+
+API const char* LIBR(xdg_runtime)(struct xdg* xdg)
+{
+ return LIBR(xdg_dir)(xdg, XDG_RUNTIME);
+}
+
+API const char** LIBR(xdg_dirs)(struct xdg* xdg, enum xdg_kind k)
+{
+ if(xdg->dirs[k]) return (const char**)xdg->dirs[k];
+
+ const char* e = NULL;
+ if(k == XDG_DATA || k == XDG_CONFIG) {
+ const char* v = NULL;
+ if(k == XDG_DATA) {
+ v = "XDG_DATA_DIRS";
+ } else if(k == XDG_CONFIG) {
+ v = "XDG_CONFIG_DIRS";
+ }
+
+ e = getenv(v);
+ if(e == NULL || *e == 0) {
+ if(k == XDG_DATA) {
+ e = "/usr/local/share:/usr/share";
+ } else if(k == XDG_CONFIG) {
+ e = "/etc/xdg:/etc";
+ }
+ }
+ }
+
+ struct {
+ char* path;
+ size_t len;
+ void* next;
+ }* dirs;
+
+ {
+ dirs = alloca(sizeof(*dirs));
+ const char* h = LIBR(xdg_dir)(xdg, k);
+ dirs->len = strlen(h);
+ dirs->path = alloca(dirs->len+1);
+ memcpy(dirs->path, h, dirs->len+1);
+ dirs->next = NULL;
+ }
+ typeof(*dirs)** tail = (void*)&dirs->next;
+
+ size_t n = 1;
+ if(e) {
+ const size_t L = strlen(e);
+ char buf[L+1];
+ memcpy(buf, e, L+1);
+ size_t a = 0, b = 0;
+ do {
+ for(; buf[b] != ':' && buf[b] != 0; b++);
+ buf[b] = 0;
+ char* p = &buf[a];
+ if(xdg_check_path(p)) {
+ char q[PATH_MAX];
+ size_t l = LIBR(xdg_append_app)(xdg, LIT(q), p);
+ if(l >= sizeof(q)) {
+ failwith("buffer overflow");
+ }
+
+ typeof(*dirs)* t = alloca(sizeof(*dirs));
+ t->len = l;
+ t->path = alloca(l+1);
+ memcpy(t->path, q, l+1);
+ t->next = NULL;
+
+ *tail = t;
+ tail = (void*)&t->next;
+ n += 1;
+ }
+ b += 1;
+ a = b;
+ } while(b < L);
+ }
+
+ size_t N = 0;
+ for(typeof(*dirs)* p = dirs; p != NULL; p = p->next) {
+ N += sizeof(char*);
+ N += p->len+1;
+ }
+ N += sizeof(char*); // add space for the NULL guard
+
+ void* buf = malloc(N);
+ CHECK_MALLOC(buf);
+ char** index = buf;
+
+ index[n] = NULL;
+ char* strings = (char*)&index[n+1];
+
+ size_t i = 0;
+ for(typeof(*dirs)* p = dirs; p != NULL; i++, p = p->next) {
+ index[i] = strings;
+ memcpy(strings, p->path, p->len + 1);
+ strings += p->len + 1;
+ }
+
+ xdg->dirs[k] = buf;
+ return buf;
+}
+
+API const char** LIBR(xdg_data_dirs)(struct xdg* xdg)
+{
+ return LIBR(xdg_dirs)(xdg, XDG_DATA);
+}
+
+API const char** LIBR(xdg_config_dirs)(struct xdg* xdg)
+{
+ return LIBR(xdg_dirs)(xdg, XDG_CONFIG);
+}
+
+API const char* LIBR(xdg_resolvev)(struct xdg* xdg, enum xdg_kind k, char* buf, size_t L, va_list ps)
+{
+ const char** dirs = LIBR(xdg_dirs)(xdg, k);
+ for(size_t i = 0; dirs[i]; i++) {
+ va_list qs;
+ va_copy(qs, ps);
+ size_t l = LIBR(path_joinv)(buf, L, dirs[i], qs);
+ if(l >= L) {
+ failwith("buffer overflow");
+ }
+ va_end(qs);
+
+ struct stat st;
+ int r = stat(buf, &st);
+ if(r == -1 && (errno == EACCES || errno == ENOENT || errno == ENOTDIR)) {
+ continue;
+ }
+ CHECK(r, "stat(%s)", buf);
+
+ return buf;
+ }
+
+ return NULL;
+}
+
+API const char* LIBR(xdg_resolve)(struct xdg* xdg, enum xdg_kind k, char* buf, size_t L, ...)
+{
+ va_list ps; va_start(ps, L);
+ const char* p = LIBR(xdg_resolvev)(xdg, k, buf, L, ps);
+ return va_end(ps), p;
+}
+
+API const char* LIBR(xdg_resolvevs)(struct xdg* xdg, enum xdg_kind k, va_list ps)
+{
+ static char buf[PATH_MAX];
+ return LIBR(xdg_resolvev)(xdg, k, LIT(buf), ps);
+}
+
+API const char* LIBR(xdg_resolves)(struct xdg* xdg, enum xdg_kind k, ...)
+{
+ va_list ps; va_start(ps, k);
+ const char* p = LIBR(xdg_resolvevs)(xdg, k, ps);
+ return va_end(ps), p;
+}
+
+API void LIBR(xdg_makedirs)(const char* path)
+{
+ int r = LIBR(makedirs)(path, 0700);
+ CHECK(r, "makedirs(%s, 0700)", path);
+}
+
+API const char* LIBR(xdg_preparev)(struct xdg* xdg, enum xdg_kind k, char* buf, size_t L, va_list ps)
+{
+ size_t l = LIBR(path_joinv)(buf, L, LIBR(xdg_dir)(xdg, k), ps);
+ if(l >= L) {
+ failwith("buffer overflow");
+ }
+
+ struct stat st;
+ int r = stat(buf, &st);
+ if(r == -1 && errno == ENOENT) {
+ char d[l+1];
+ memcpy(d, buf, l+1);
+ LIBR(xdg_makedirs)(dirname(d));
+ return buf;
+ }
+ CHECK(r, "stat(%s)", buf);
+
+ return buf;
+}
+
+API const char* LIBR(xdg_prepare)(struct xdg* xdg, enum xdg_kind k, char* buf, size_t L, ...)
+{
+ va_list ps; va_start(ps, L);
+ const char* p = LIBR(xdg_preparev)(xdg, k, buf, L, ps);
+ return va_end(ps), p;
+}
+
+API const char* LIBR(xdg_preparevs)(struct xdg* xdg, enum xdg_kind k, va_list ps)
+{
+ static char buf[PATH_MAX];
+ return LIBR(xdg_preparev)(xdg, k, LIT(buf), ps);
+}
+
+API const char* LIBR(xdg_prepares)(struct xdg* xdg, enum xdg_kind k, ...)
+{
+ va_list ps; va_start(ps, k);
+ const char* p = LIBR(xdg_preparevs)(xdg, k, ps);
+ return va_end(ps), p;
+}
+
+// libr: path.c
+
+#include <errno.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <limits.h>
+
+API size_t LIBR(path_joinv)(char* buf, size_t L, const char* p0, va_list ps)
+{
+ size_t n = strlen(p0);
+ if(n < L) {
+ memmove(buf, p0, n);
+ }
+
+ for(;;) {
+ const char* p = va_arg(ps, const char*);
+ if(!p) break;
+
+ if(n < L) {
+ buf[n] = '/';
+ }
+ n += 1;
+
+ size_t l = strlen(p);
+ if(n + l < L) {
+ memcpy(&buf[n], p, l);
+ }
+ n += l;
+ }
+
+ if(n < L) {
+ buf[n] = 0;
+ }
+
+ return n;
+}
+
+API size_t LIBR(path_join)(char* buf, size_t L, const char* p0, ...)
+{
+ va_list ps; va_start(ps, p0);
+ size_t n = LIBR(path_joinv)(buf, L, p0, ps);
+ return va_end(ps), n;
+}
+
+#ifdef failwith
+API const char* LIBR(path_joinvs)(const char* p0, va_list ps)
+{
+ static char buf[PATH_MAX];
+ size_t l = LIBR(path_joinv)(buf, sizeof(buf), p0, ps);
+ if(l >= sizeof(buf)) {
+ failwith("buffer overflow");
+ }
+
+ return buf;
+}
+
+API const char* LIBR(path_joins)(const char* p0, ...)
+{
+ va_list ps; va_start(ps, p0);
+ const char* p = LIBR(path_joinvs)(p0, ps);
+ return va_end(ps), p;
+}
+#endif
+
+API int LIBR(makedirs)(const char* path, mode_t mode)
+{
+ const size_t L = strlen(path);
+ char buf[L+1];
+ memcpy(buf, path, L+1);
+
+ for(size_t l = (buf[0] == '/' ? 1 : 0); l <= L; l++) {
+ if(buf[l] == '/') {
+ buf[l] = 0;
+ }
+
+ if(buf[l] == 0) {
+ int r = mkdir(buf, mode);
+ if(r == -1) {
+ if(errno != EEXIST) {
+ return -1;
+ }
+ }
+
+ if(l < L) {
+ buf[l] = '/';
+ }
+ }
+ }
+
+ struct stat st;
+ int r = stat(buf, &st);
+ if(r != 0) return -1;
+
+ if(!(st.st_mode & S_IFDIR)) {
+ errno = EEXIST;
+ return -1;
+ }
+
+ return 0;
+}
#endif // LIBR_IMPLEMENTATION