~rootmos/action

54b4d988f40540019727bf6e71346d15498b7903 — Gustav Behm 5 months ago 47cf241
Construct the expected socket path
2 files changed, 559 insertions(+), 3 deletions(-)

M c-impl/action.c
M c-impl/r.h
M c-impl/action.c => c-impl/action.c +14 -1
@@ 11,6 11,7 @@ struct options {
    enum {
        NOOP = 0,
        BIND,
        PRINT_PATH,
        TRIGGER,
    } cmd;
};


@@ 22,6 23,7 @@ static void print_usage(int fd, const char* prog)
    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");
}



@@ 30,10 32,11 @@ static void parse_options(struct options* o, int argc, char* argv[])
    memset(o, 0, sizeof(*o));

    int res;
    while((res = getopt(argc, argv, "bth")) != -1) {
    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]);


@@ 128,6 131,13 @@ int main(int argc, char* argv[])
    struct options o;
    parse_options(&o, argc, argv);

    struct xdg* xdg = xdg_new("action");

    if(o.cmd == PRINT_PATH) {
        printf("%s/%s\n", xdg_runtime(xdg), o.action);
        goto exit;
    }

    struct state st = {
        .running = 1,
    };


@@ 159,5 169,8 @@ int main(int argc, char* argv[])
    debug("graceful shutdown");
    signalfd_deinit(&st);

exit:
    xdg_free(xdg);

    return 0;
}

M c-impl/r.h => c-impl/r.h +545 -2
@@ 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