Skip to main content
clarify code (I have not tried to compile this either)
Source Link
grawity
  • 16.3k
  • 1
  • 34
  • 54

Use /usr/bin/systemd-socket-activate to mimic systemd's socket activation protocol, or implement a custom tool that does the same. (This specific tool only supports delay-starting, i.e. when the first client connects, not immediately, as testing that was its original purpose. Shouldn't be a problem – if you want music to play on boot, you can just follow it up with an mpc call to immediately poke the socket and cause mpd to be activated.)

AnIn some distributions, systemd-socket-activate might be in /usr/lib(exec) instead.

If you don't want to have the systemd tools installed either, then an equivalent might be:

#define _GNU_SOURCE
#include <err.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>

#define LISTEN_FDS_START 3

int main(int argc, char *argv) {
    char *basedir, *mpddir, *addr, *pidstr;
    struct sockaddr_un sa = { .sun_family = AF_UNIX };
    int r, sock; 

    /* The intended usage is "./mytool /usr/bin/mpd --systemd --blah" */
    if (argc < 2)
        errx(1, "mpd executable and args not provided");

    basedir = getenv("XDG_RUNTIME_DIR");
    if (!basedir || !basedir[0])
        errx(1, "XDG_RUNTIME_DIR not set");

    r = asprintf(&mpddir, "%s/mpd", basedir);
    if (r < 0)
        exit(3);

    r = mkdir(mpddir, 0700);
    if (r < 0 && errno != EEXIST)
        err(1, "mkdir(%s)" failed", mpddir);

    r = snprintf(sa.sun_path, sizeof sa.sun_path, "%s/mpd/socket", basedir);
    if (r < 0)
        exitabort(3);
    if (r >= 108)
        errx(1, "socket address too long");

    sock = socket(AF_UNIX, SOCK_STREAM, 0);
    if (r < 0)
        err(1, "socket""socket(AF_UNIX) failed");

    r = bind(sock, (struct sockaddr *) &sa, sizeof sa);
    if (r < 0)
        err(1, "bind""bind(%s/mpd/socket) failed", basedir);

    /* Systemd socket activation protocol defines that FDs start from 3.
     * Normally this little program will only have 0-2 when it starts, as
     * usual, so the socket() call will also issue a FD starting from 3
     * but it's better to make sure and move the socket to 3 if needed.
     */
    if (sock != 3LISTEN_FDS_START) {
        r = dup2(sock, 3LISTEN_FDS_START);
        if (r < 0)
            err(1, "dup""dup failed");
        close(sock);
        sock = 3;LISTEN_FDS_START;
    }

    r = asprintf(&pidstr, "%d", getpid());
    if (r < 0)
        exitabort(3);

    setenv("LISTEN_FDS", "1");
    setenv("LISTEN_PID", pidstr);

    execvp(argv[1], argv+1);
}

Use /usr/bin/systemd-socket-activate to mimic systemd's socket activation protocol, or implement a custom tool that does the same. (This specific tool only supports delay-starting, i.e. when the first client connects, not immediately, as testing that was its original purpose.)

An equivalent might be:

#define _GNU_SOURCE
#include <err.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>

int main(int argc, char *argv) {
    char *basedir, *mpddir, *addr, *pidstr;
    struct sockaddr_un sa = { .sun_family = AF_UNIX };
    int r, sock;

    if (argc < 2)
        errx(1, "mpd executable and args not provided");

    basedir = getenv("XDG_RUNTIME_DIR");
    if (!basedir || !basedir[0])
        errx(1, "XDG_RUNTIME_DIR not set");

    r = asprintf(&mpddir, "%s/mpd", basedir);
    if (r < 0)
        exit(3);

    r = mkdir(mpddir, 0700);
    if (r < 0 && errno != EEXIST)
        err(1, "mkdir(%s)", mpddir);

    r = snprintf(sa.sun_path, sizeof sa.sun_path, "%s/mpd/socket", basedir);
    if (r < 0)
        exit(3);
    if (r >= 108)
        errx(1, "socket address too long");

    sock = socket(AF_UNIX, SOCK_STREAM, 0);
    if (r < 0)
        err(1, "socket");

    r = bind(sock, (struct sockaddr *) &sa, sizeof sa);
    if (r < 0)
        err(1, "bind");

    if (sock != 3) {
        r = dup2(sock, 3);
        if (r < 0)
            err(1, "dup");
        close(sock);
        sock = 3;
    }

    r = asprintf(&pidstr, "%d", getpid());
    if (r < 0)
        exit(3);

    setenv("LISTEN_FDS", "1");
    setenv("LISTEN_PID", pidstr);

    execvp(argv[1], argv+1);
}

Use /usr/bin/systemd-socket-activate to mimic systemd's socket activation protocol, or implement a custom tool that does the same. (This specific tool only supports delay-starting, i.e. when the first client connects, not immediately, as testing that was its original purpose. Shouldn't be a problem – if you want music to play on boot, you can just follow it up with an mpc call to immediately poke the socket and cause mpd to be activated.)

In some distributions, systemd-socket-activate might be in /usr/lib(exec) instead.

If you don't want to have the systemd tools installed either, then an equivalent might be:

#define _GNU_SOURCE
#include <err.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>

#define LISTEN_FDS_START 3

int main(int argc, char *argv) {
    char *basedir, *mpddir, *addr, *pidstr;
    struct sockaddr_un sa = { .sun_family = AF_UNIX };
    int r, sock; 

    /* The intended usage is "./mytool /usr/bin/mpd --systemd --blah" */
    if (argc < 2)
        errx(1, "mpd executable and args not provided");

    basedir = getenv("XDG_RUNTIME_DIR");
    if (!basedir || !basedir[0])
        errx(1, "XDG_RUNTIME_DIR not set");

    r = asprintf(&mpddir, "%s/mpd", basedir);
    if (r < 0)
        exit(3);

    r = mkdir(mpddir, 0700);
    if (r < 0 && errno != EEXIST)
        err(1, "mkdir(%s) failed", mpddir);

    r = snprintf(sa.sun_path, sizeof sa.sun_path, "%s/mpd/socket", basedir);
    if (r < 0)
        abort();
    if (r >= 108)
        errx(1, "socket address too long");

    sock = socket(AF_UNIX, SOCK_STREAM, 0);
    if (r < 0)
        err(1, "socket(AF_UNIX) failed");

    r = bind(sock, (struct sockaddr *) &sa, sizeof sa);
    if (r < 0)
        err(1, "bind(%s/mpd/socket) failed", basedir);

    /* Systemd socket activation protocol defines that FDs start from 3.
     * Normally this little program will only have 0-2 when it starts, as
     * usual, so the socket() call will also issue a FD starting from 3
     * but it's better to make sure and move the socket to 3 if needed.
     */
    if (sock != LISTEN_FDS_START) {
        r = dup2(sock, LISTEN_FDS_START);
        if (r < 0)
            err(1, "dup failed");
        close(sock);
        sock = LISTEN_FDS_START;
    }

    r = asprintf(&pidstr, "%d", getpid());
    if (r < 0)
        abort();

    setenv("LISTEN_FDS", "1");
    setenv("LISTEN_PID", pidstr);

    execvp(argv[1], argv+1);
}
Source Link
grawity
  • 16.3k
  • 1
  • 34
  • 54

Is there a way to make mpd start only a UNIX socket (and ideally read $XDG_RUNTIME_DIR for that) using plain mpd without systemd?

Use /usr/bin/systemd-socket-activate to mimic systemd's socket activation protocol, or implement a custom tool that does the same. (This specific tool only supports delay-starting, i.e. when the first client connects, not immediately, as testing that was its original purpose.)

mkdir -p "$XDG_RUNTIME_DIR/mpd"
systemd-socket-activate -l "$XDG_RUNTIME_DIR/mpd/socket" mpd --stderr [--no-daemon --systemd]

An equivalent might be:

#define _GNU_SOURCE
#include <err.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>

int main(int argc, char *argv) {
    char *basedir, *mpddir, *addr, *pidstr;
    struct sockaddr_un sa = { .sun_family = AF_UNIX };
    int r, sock;

    if (argc < 2)
        errx(1, "mpd executable and args not provided");

    basedir = getenv("XDG_RUNTIME_DIR");
    if (!basedir || !basedir[0])
        errx(1, "XDG_RUNTIME_DIR not set");

    r = asprintf(&mpddir, "%s/mpd", basedir);
    if (r < 0)
        exit(3);

    r = mkdir(mpddir, 0700);
    if (r < 0 && errno != EEXIST)
        err(1, "mkdir(%s)", mpddir);

    r = snprintf(sa.sun_path, sizeof sa.sun_path, "%s/mpd/socket", basedir);
    if (r < 0)
        exit(3);
    if (r >= 108)
        errx(1, "socket address too long");

    sock = socket(AF_UNIX, SOCK_STREAM, 0);
    if (r < 0)
        err(1, "socket");

    r = bind(sock, (struct sockaddr *) &sa, sizeof sa);
    if (r < 0)
        err(1, "bind");

    if (sock != 3) {
        r = dup2(sock, 3);
        if (r < 0)
            err(1, "dup");
        close(sock);
        sock = 3;
    }

    r = asprintf(&pidstr, "%d", getpid());
    if (r < 0)
        exit(3);

    setenv("LISTEN_FDS", "1");
    setenv("LISTEN_PID", pidstr);

    execvp(argv[1], argv+1);
}

(I did not try to compile this. Would've used python subprocess.run but)

it only works if the socket directory is already created

You can literally mkdir the socket directory as part of the startup script.