@@ 0,0 1,313 @@
+#define _GNU_SOURCE
+
+#include <poll.h>
+#include <signal.h>
+#include <sys/signalfd.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/stat.h>
+#include <string.h>
+
+#define LIBR_IMPLEMENTATION
+#include "r.h"
+
+#define ACTION_MAX (NAME_MAX)
+
+struct options {
+ char action[ACTION_MAX+1];
+
+ enum {
+ NOOP = 0,
+ BIND,
+ PRINT_PATH,
+ TRIGGER,
+ } cmd;
+};
+
+static void print_usage(int fd, const char* prog)
+{
+ dprintf(fd, "usage: %s [OPTION]... ACTION\n", prog);
+ dprintf(fd, "\n");
+ dprintf(fd, "commands:\n");
+ dprintf(fd, " -b bind action\n");
+ dprintf(fd, " -t trigger action\n");
+ dprintf(fd, " -p print socket path\n");
+ dprintf(fd, " -h print this message\n");
+}
+
+static void parse_options(struct options* o, int argc, char* argv[])
+{
+ memset(o, 0, sizeof(*o));
+
+ int res;
+ while((res = getopt(argc, argv, "btph")) != -1) {
+ switch(res) {
+ case 't': o->cmd = TRIGGER; break;
+ case 'b': o->cmd = BIND; break;
+ case 'p': o->cmd = PRINT_PATH; break;
+ case 'h':
+ default:
+ print_usage(res == 'h' ? 1 : 2, argv[0]);
+ exit(res == 'h' ? 0 : 2);
+ }
+ }
+
+ if(optind + 1 == argc) {
+ if(strnlen(argv[optind], ACTION_MAX) > ACTION_MAX) {
+ dprintf(2, "error: action too long\n");
+ exit(2);
+ }
+ strncpy(o->action, argv[optind], ACTION_MAX);
+ debug("action: %s", o->action);
+ } else {
+ if(optind >= argc) {
+ dprintf(2, "error: action not specified\n");
+ } else {
+ dprintf(2, "error: too many actions specified\n");
+ }
+ print_usage(2, argv[0]);
+ exit(2);
+ }
+
+ if(o->cmd == NOOP) {
+ dprintf(2, "error: command not specified\n");
+ print_usage(2, argv[0]);
+ exit(2);
+ }
+}
+
+struct msg {
+ char action[ACTION_MAX];
+ struct timespec timestamp;
+};
+
+static const char* iso8601_timespec(const struct timespec* ts)
+{
+ static char buf[21];
+ size_t r = strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%SZ", gmtime(&ts->tv_sec));
+ if(r <= 0) abort();
+ return buf;
+}
+
+static void prepare_sockaddr(struct sockaddr_un* addr, const char* socket_path)
+{
+ memset(addr, 0, sizeof(*addr));
+
+ addr->sun_family = AF_UNIX;
+
+ if(strlen(socket_path) >= sizeof(addr->sun_path)) {
+ failwith("socket path too long");
+ }
+
+ strncpy(addr->sun_path, socket_path, sizeof(addr->sun_path) - 1);
+}
+
+static int cmd_trigger(const struct options* o, const char* socket_path)
+{
+ debug("triggering: %s", o->action);
+
+ int fd = socket(AF_UNIX, SOCK_DGRAM, 0);
+
+ struct sockaddr_un addr;
+ prepare_sockaddr(&addr, socket_path);
+
+ struct msg msg = {0};
+ strncpy(msg.action, o->action, sizeof(msg.action));
+ int r = clock_gettime(CLOCK_REALTIME, &msg.timestamp);
+ CHECK(r, "clock_gettime(CLOCK_REALTIME)");
+
+ ssize_t s = sendto(fd,
+ &msg, sizeof(msg),
+ 0,
+ (const struct sockaddr*)&addr, sizeof(addr));
+ CHECK(s, "sendto(%s)", socket_path);
+
+ if(s != sizeof(msg)) {
+ failwith("unexpected partial sendto");
+ }
+
+ info("triggered: %s (%s)", o->action, iso8601_timespec(&msg.timestamp));
+
+ r = close(fd); CHECK(r, "close");
+ return 0;
+}
+
+struct state {
+ int running;
+ int exit_code;
+ int sfd;
+};
+
+static int signalfd_init(struct state* st)
+{
+ sigset_t m;
+ sigemptyset(&m);
+ sigaddset(&m, SIGINT);
+ sigaddset(&m, SIGTERM);
+
+ int fd = st->sfd = signalfd(-1, &m, 0);
+ CHECK(fd, "signalfd");
+
+ int r = sigprocmask(SIG_BLOCK, &m, NULL);
+ CHECK(r, "sigprocmask");
+
+ set_blocking(fd, 0);
+
+ return fd;
+}
+
+static void signalfd_deinit(struct state* st)
+{
+ int r = close(st->sfd); CHECK(r, "close");
+ st->sfd = -1;
+}
+
+static void signalfd_handle_event(struct state* st)
+{
+ while(1) {
+ struct signalfd_siginfo si;
+
+ ssize_t s = read(st->sfd, &si, sizeof(si));
+ if(s == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
+ break;
+ }
+ CHECK(s, "read");
+
+ if(s != sizeof(si)) {
+ failwith("unexpected partial read");
+ }
+
+ if(si.ssi_signo == SIGINT) {
+ debug("SIGINT");
+ st->running = 0;
+ st->exit_code = 1;
+ } else if(si.ssi_signo == SIGTERM) {
+ debug("SIGTERM");
+ st->running = 0;
+ st->exit_code = 1;
+ } else {
+ warning("unhandled signal: %u", si.ssi_signo);
+ }
+ }
+}
+
+static void prepare_socket_path(const char* socket_path)
+{
+ struct stat st;
+ int r = stat(socket_path, &st);
+ if(r == 0) {
+ if((st.st_mode & S_IFMT) == S_IFSOCK) {
+ debug("socket already exists, trying to unlink: %s", socket_path);
+ r = unlink(socket_path);
+ CHECK(r, "unlink(%s)", socket_path);
+ } else {
+ failwith("socket path already exists (and is not a socket): %s", socket_path);
+ }
+ } else if(r == -1 && errno != ENOENT) {
+ CHECK(r, "stat(%s)", socket_path);
+ } else {
+ xdg_makedirs(dirname(strdupa(socket_path)));
+ }
+}
+
+static int cmd_bind(const struct options* o, const char* socket_path)
+{
+ int fd = socket(AF_UNIX, SOCK_DGRAM, 0);
+ CHECK(fd, "socket(AF_UNIX, SOCK_DGRAM, 0)");
+
+ struct sockaddr_un addr;
+ prepare_sockaddr(&addr, socket_path);
+
+ prepare_socket_path(socket_path);
+
+ int r = bind(fd, (const struct sockaddr*)&addr, sizeof(addr));
+ CHECK(r, "bind(%s)", socket_path);
+
+ set_blocking(fd, 0);
+
+ struct state st = {
+ .running = 1,
+ .exit_code = 255,
+ };
+
+ struct pollfd fds[] = {
+ { .fd = signalfd_init(&st), .events = POLLIN },
+ { .fd = fd, .events = POLLIN },
+ };
+
+ while(st.running) {
+ int r = poll(fds, LENGTH(fds), -1);
+ CHECK(r, "poll");
+
+ if(fds[0].revents & POLLIN) {
+ signalfd_handle_event(&st);
+ fds[0].revents &= ~POLLIN;
+ }
+
+ if(fds[1].revents & POLLIN) {
+ struct msg msg;
+ ssize_t s = recvfrom(fd, &msg, sizeof(msg), 0, NULL, NULL);
+ CHECK(s, "recvfrom(%s)", socket_path);
+
+ if(s != sizeof(msg)) {
+ failwith("unexpected partial recvfrom");
+ }
+
+ if(strncmp(msg.action, o->action, sizeof(msg.action)) == 0) {
+ info("triggered: %s (%s)", o->action, iso8601_timespec(&msg.timestamp));
+ st.running = 0;
+ st.exit_code = 0;
+ }
+
+ fds[1].revents &= ~POLLIN;
+ }
+
+ for(size_t i = 0; i < LENGTH(fds); i++) {
+ if(fds[i].revents != 0) {
+ failwith("unhandled poll events: "
+ "fds[%zu] = { .fd = %d, .revents = %hd }",
+ i, fds[i].fd, fds[i].revents);
+ }
+ }
+ }
+
+ debug("graceful shutdown");
+ signalfd_deinit(&st);
+
+ r = close(fd); CHECK(r, "close");
+ r = unlink(socket_path);
+ CHECK(r, "unlink(%s)", socket_path);
+
+ return st.exit_code;
+}
+
+int main(int argc, char* argv[])
+{
+ struct options o;
+ parse_options(&o, argc, argv);
+
+ struct xdg* xdg = xdg_new("action");
+
+ char socket_path[PATH_MAX];
+ xdg_resolve(xdg, XDG_RUNTIME, LIT(socket_path), o.action, NULL);
+ debug("socket path: %s", socket_path);
+
+ int ec = 255;
+
+ if(o.cmd == PRINT_PATH) {
+ // ensure the path is a valid socket address
+ struct sockaddr_un addr;
+ prepare_sockaddr(&addr, socket_path);
+
+ printf("%s\n", socket_path);
+ ec = 0;
+ } else if(o.cmd == TRIGGER) {
+ ec = cmd_trigger(&o, socket_path);
+ } else if(o.cmd == BIND) {
+ ec = cmd_bind(&o, socket_path);
+ }
+
+ xdg_free(xdg);
+
+ return ec;
+}
@@ 0,0 1,806 @@
+// 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
+
+#define LIBR(x) x
+#define PRIVATE __attribute__((visibility("hidden")))
+#define PUBLIC __attribute__((visibility("default")))
+#define API PRIVATE
+
+
+// libr: fail.h
+
+#define CHECK(res, format, ...) CHECK_NOT(res, -1, format, ##__VA_ARGS__)
+
+#define CHECK_NOT(res, err, format, ...) \
+ CHECK_IF(res == err, format, ##__VA_ARGS__)
+
+#define CHECK_IF(cond, format, ...) do { \
+ if(cond) { \
+ LIBR(failwith0)(__extension__ __FUNCTION__, __extension__ __FILE__, \
+ __extension__ __LINE__, 1, \
+ format "\n", ##__VA_ARGS__); \
+ } \
+} while(0)
+
+#define CHECK_MALLOC(x) CHECK_NOT(x, NULL, "memory allocation failed")
+#define CHECK_MMAP(x) CHECK_NOT(x, MAP_FAILED, "memory mapping failed")
+
+#define failwith(format, ...) \
+ LIBR(failwith0)(__extension__ __FUNCTION__, __extension__ __FILE__, \
+ __extension__ __LINE__, 0, format "\n", ##__VA_ARGS__)
+
+#define not_implemented() LIBR(failwith0)("not implemented")
+
+void LIBR(failwith0)(
+ const char* const caller,
+ const char* const file,
+ const unsigned int line,
+ const int include_errno,
+ const char* const fmt, ...)
+__attribute__ ((noreturn, format (printf, 5, 6)));
+
+// libr: logging.h
+
+#include <stdarg.h>
+
+#define LOG_QUIET 0
+#define LOG_ERROR 1
+#define LOG_WARNING 2
+#define LOG_INFO 3
+#define LOG_DEBUG 4
+#define LOG_TRACE 5
+
+#ifndef LOG_LEVEL
+#define LOG_LEVEL LOG_INFO
+#endif
+
+extern int LIBR(logger_fd);
+
+#define __r_log(level, format, ...) do { \
+ LIBR(logger)(level, __extension__ __FUNCTION__, __extension__ __FILE__, \
+ __extension__ __LINE__, format "\n", ##__VA_ARGS__); \
+} while(0)
+
+#ifdef __cplusplus
+void LIBR(dummy)(...);
+#else
+void LIBR(dummy)();
+#endif
+
+#if LOG_LEVEL >= LOG_ERROR
+#define error(format, ...) __r_log(LOG_ERROR, format, ##__VA_ARGS__)
+#else
+#define error(format, ...) do { if(0) LIBR(dummy)(__VA_ARGS__); } while(0)
+#endif
+
+#if LOG_LEVEL >= LOG_WARNING
+#define warning(format, ...) __r_log(LOG_WARNING, format, ##__VA_ARGS__)
+#else
+#define warning(format, ...) do { if(0) LIBR(dummy)(__VA_ARGS__); } while(0)
+#endif
+
+#if LOG_LEVEL >= LOG_INFO
+#define info(format, ...) __r_log(LOG_INFO, format, ##__VA_ARGS__)
+#else
+#define info(format, ...) do { if(0) LIBR(dummy)(__VA_ARGS__); } while(0)
+#endif
+
+#if LOG_LEVEL >= LOG_DEBUG
+#define debug(format, ...) __r_log(LOG_DEBUG, format, ##__VA_ARGS__)
+#else
+#define debug(format, ...) do { if(0) LIBR(dummy)(__VA_ARGS__); } while(0)
+#endif
+
+#if LOG_LEVEL >= LOG_TRACE
+#define trace(format, ...) __r_log(LOG_TRACE, format, ##__VA_ARGS__)
+#else
+#define trace(format, ...) do { if(0) LIBR(dummy)(__VA_ARGS__); } while(0)
+#endif
+
+void LIBR(logger)(
+ int level,
+ const char* const caller,
+ const char* const file,
+ const unsigned int line,
+ const char* const fmt, ...)
+__attribute__ ((format (printf, 5, 6)));
+
+void LIBR(vlogger)(
+ int level,
+ const char* const caller,
+ const char* const file,
+ const unsigned int line,
+ const char* const fmt,
+ va_list vl
+);
+
+// libr: now.h
+
+// returns current time formated as compact ISO8601: 20190123T182628Z
+const char* LIBR(now_iso8601_compact)(void);
+
+// libr: util.h
+
+#ifndef LENGTH
+#define LENGTH(xs) (sizeof(xs)/sizeof((xs)[0]))
+#endif
+
+#ifndef LIT
+#define LIT(x) x,sizeof(x)
+#endif
+
+#ifndef STR
+#define STR(x) x,strlen(x)
+#endif
+
+#ifndef MAX
+#define MAX(a,b) ((a) > (b) ? (a) : (b))
+#endif
+
+#ifndef MIN
+#define MIN(a,b) ((a) < (b) ? (a) : (b))
+#endif
+
+// 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
+
+// libr: fail.c
+
+#include <stdlib.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+API void LIBR(failwith0)(
+ const char* const caller,
+ const char* const file,
+ const unsigned int line,
+ const int include_errno,
+ const char* const fmt, ...)
+{
+ va_list vl;
+ va_start(vl, fmt);
+
+ if(include_errno) {
+ LIBR(logger)(LOG_ERROR, caller, file, line, "(%s) ", strerror(errno));
+ if(vdprintf(LIBR(logger_fd), fmt, vl) < 0) {
+ abort();
+ }
+ } else {
+ LIBR(vlogger)(LOG_ERROR, caller, file, line, fmt, vl);
+ }
+ va_end(vl);
+
+ abort();
+}
+
+// libr: logging.c
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#ifdef __cplusplus
+API void LIBR(dummy)(...)
+#else
+API void LIBR(dummy)()
+#endif
+{
+ abort();
+}
+
+int LIBR(logger_fd) API = 2;
+
+API void LIBR(vlogger)(
+ int level,
+ const char* const caller,
+ const char* const file,
+ const unsigned int line,
+ const char* const fmt, va_list vl)
+{
+ int r = dprintf(LIBR(logger_fd), "%s:%d:%s:%s:%u ",
+ LIBR(now_iso8601_compact)(), getpid(), caller, file, line);
+ if(r < 0) {
+ abort();
+ }
+
+ r = vdprintf(LIBR(logger_fd), fmt, vl);
+ if(r < 0) {
+ abort();
+ }
+}
+
+API void LIBR(logger)(
+ int level,
+ const char* const caller,
+ const char* const file,
+ const unsigned int line,
+ const char* const fmt, ...)
+{
+ va_list vl;
+ va_start(vl, fmt);
+ LIBR(vlogger)(level, caller, file, line, fmt, vl);
+ va_end(vl);
+}
+
+// libr: now.c
+
+#include <time.h>
+#include <stdlib.h>
+
+PRIVATE const char* LIBR(now_iso8601_compact)(void)
+{
+ static char buf[17];
+ const time_t t = time(NULL);
+ size_t r = strftime(buf, sizeof(buf), "%Y%m%dT%H%M%SZ", gmtime(&t));
+ if(r <= 0) abort();
+ return buf;
+}
+
+// libr: nonblock.c
+
+#include <fcntl.h>
+
+API void LIBR(set_blocking)(int fd, int blocking)
+{
+ int fl = fcntl(fd, F_GETFL, 0);
+ if(blocking) {
+ fl &= ~O_NONBLOCK;
+ } else {
+ fl |= O_NONBLOCK;
+ }
+
+ 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