1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
#include "c-calls.hh"
#include "lix/libutil/file-system.hh"
#include "lix/libutil/processes.hh"
#include "lix/libutil/unix-domain-socket.hh"
#include "lix/libutil/strings.hh"

#include <string>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>

namespace nix {

AutoCloseFD createUnixDomainSocket()
{
    AutoCloseFD fdSocket{socket(PF_UNIX, SOCK_STREAM
        #ifdef SOCK_CLOEXEC
        | SOCK_CLOEXEC
        #endif
        , 0)};
    if (!fdSocket)
        throw SysError("cannot create Unix domain socket");
    closeOnExec(fdSocket.get());
    return fdSocket;
}


AutoCloseFD createUnixDomainSocket(const Path & path, mode_t mode)
{
    auto fdSocket = nix::createUnixDomainSocket();

    bind(fdSocket.get(), path);

    chmodPath(path, mode);

    if (listen(fdSocket.get(), 100) == -1)
        throw SysError("cannot listen on socket '%1%'", path);

    return fdSocket;
}

/**
 * Workaround for the max length of Unix socket names being between 102
 * (darwin) and 108 (Linux), which is extremely short. This limitation is
 * caused by historical restrictions on sizeof(struct sockaddr):
 * https://unix.stackexchange.com/a/367012.
 *
 * Our solution here is to start a process inheriting the socket, chdir into
 * the directory of the socket, then connect with just the filename. This is
 * rather silly but it works around working directory being process-wide state,
 * and is as clearly sound as possible.
 */
static void bindConnectProcHelper(
    std::string_view operationName, auto && operation,
    int fd, const std::string & path)
{
    struct sockaddr_un addr;
    addr.sun_family = AF_UNIX;

    // Casting between types like these legacy C library interfaces
    // require is forbidden in C++. To maintain backwards
    // compatibility, the implementation of the bind/connect functions
    // contains some hints to the compiler that allow for this
    // special case.
    auto * psaddr = reinterpret_cast<struct sockaddr *>(&addr);

    Pipe resultFd;
    resultFd.create();

    if (path.size() + 1 >= sizeof(addr.sun_path)) {
        runHelper(
            "unix-bind-connect",
            {.args =
                 {
                     std::to_string(fd),
                     std::string(operationName),
                     dirOf(path),
                     std::string(baseNameOf(path)),
                     std::to_string(resultFd.writeSide.get()),
                 },
             .redirections = {
                 {.dup = fd, .from = fd},
                 {.dup = resultFd.writeSide.get(), .from = resultFd.writeSide.get()},
             }}
        ).waitAndCheck();
        resultFd.writeSide.close();
        auto resultRaw = drainFD(resultFd.readSide.get());
        int result;
        if (resultRaw.size() != sizeof(result)) {
            throw Error("bind-connect helper returned bad result! bailing");
        }
        memcpy(&result, resultRaw.data(), sizeof(result));
        if (result != 0) {
            throw SysError(result, "cannot %s to socket at '%s'", operationName, path);
        }
    } else {
        memcpy(addr.sun_path, path.c_str(), path.size() + 1);
        if (operation(fd, psaddr, sizeof(addr)) == -1)
            throw SysError("cannot %s to socket at '%s'", operationName, path);
    }
}


void bind(int fd, const std::string & path)
{
    (void) sys::unlink(path);

    bindConnectProcHelper("bind", ::bind, fd, path);
}


void connect(int fd, const std::string & path)
{
    bindConnectProcHelper("connect", ::connect, fd, path);
}

}