diff --git a/lix/libexec/common.hh b/lix/libexec/common.hh index 5dce83043..2fc647318 100644 --- a/lix/libexec/common.hh +++ b/lix/libexec/common.hh @@ -1,15 +1,19 @@ #pragma once ///@file common setup/utility header for libexec helpers +#include #include +#include #include #include #include #include #include // IWYU pragma: keep +#include #include #include // IWYU pragma: keep #include +#include #include /// file descriptor of the error reporting pipe. anything written to this pipe @@ -37,6 +41,34 @@ inline void die(std::string_view msg) exit(252); } +/// converts an argument to an integer or dies with a message. +template + requires std::is_integral_v +T argToInt(const char (&argName)[N], const char * str) +{ + // this should really just wrap std::from_chars, but macos doesn't have it. + for (const auto c : std::string_view(str)) { + if (c != '-' && !std::isdigit(c)) { + die(std::format("invalid {} argument", argName)); + } + } + + char * end = nullptr; + const auto tmp = [&] { + if constexpr (std::is_signed_v) { + return std::strtoimax(str, &end, 10); // NOLINT(lix-unsafe-c-calls): str is a C string + } else { + return std::strtoumax(str, &end, 10); // NOLINT(lix-unsafe-c-calls): str is a C string + } + }(); + + if (!end || *end || tmp < std::numeric_limits::min() || tmp > std::numeric_limits::max()) { + die(std::format("invalid {} argument", argName)); + } + + return tmp; +} + /// check syscall result and immediately terminate with a message on failure. #define DIE_UNLESS_SYS(name, expr) \ ([&] { \ diff --git a/lix/libexec/meson.build b/lix/libexec/meson.build index bc9651f9c..cef364819 100644 --- a/lix/libexec/meson.build +++ b/lix/libexec/meson.build @@ -11,3 +11,10 @@ run_pager = executable( install : true, install_dir : libexecdir / 'lix', ) + +unix_bind_connect = executable( + 'unix-bind-connect', + files('unix-bind-connect.cc'), + install : true, + install_dir : libexecdir / 'lix', +) diff --git a/lix/libexec/unix-bind-connect.cc b/lix/libexec/unix-bind-connect.cc new file mode 100644 index 000000000..0d6d8f022 --- /dev/null +++ b/lix/libexec/unix-bind-connect.cc @@ -0,0 +1,34 @@ +#include "common.hh" +#include +#include + +LIBEXEC_HELPER(4) + +int helperMain(const char *, std::span args) noexcept +{ + int socket = argToInt("socket", args[0]); + std::string_view method = args[1]; + const auto dir = args[2]; + const auto name = args[3]; + + DIE_UNLESS_SYS("chdir", chdir(dir)); + + struct sockaddr_un addr; + addr.sun_family = AF_UNIX; + + if (auto nameLen = strlen(name); nameLen + 1 >= sizeof(addr.sun_path)) { + die(std::format("socket path {}/{} is too long", dir, name)); + } else { + memcpy(addr.sun_path, name, nameLen + 1); + } + + if (method == "bind") { + DIE_UNLESS_SYS("bind", bind(socket, reinterpret_cast(&addr), sizeof(addr))); + } else if (method == "connect") { + DIE_UNLESS_SYS("connect", connect(socket, reinterpret_cast(&addr), sizeof(addr))); + } else { + die(std::format("invalid method %s", method)); + } + + return 0; +} diff --git a/lix/libutil/unix-domain-socket.cc b/lix/libutil/unix-domain-socket.cc index 5f1b38b91..3adc601a2 100644 --- a/lix/libutil/unix-domain-socket.cc +++ b/lix/libutil/unix-domain-socket.cc @@ -4,6 +4,7 @@ #include "lix/libutil/unix-domain-socket.hh" #include "lix/libutil/strings.hh" +#include #include #include #include @@ -64,36 +65,12 @@ static void bindConnectProcHelper( auto * psaddr = reinterpret_cast(&addr); if (path.size() + 1 >= sizeof(addr.sun_path)) { - Pipe pipe; - pipe.create(); - Pid pid{startProcess([&] { - try { - pipe.readSide.close(); - Path dir = dirOf(path); - if (sys::chdir(dir) == -1) { - throw SysError("chdir to '%s' failed", dir); - } - std::string base(baseNameOf(path)); - if (base.size() + 1 >= sizeof(addr.sun_path)) - throw Error("socket path '%s' is too long", base); - memcpy(addr.sun_path, base.c_str(), base.size() + 1); - if (operation(fd, psaddr, sizeof(addr)) == -1) - throw SysError("cannot %s to socket at '%s'", operationName, path); - writeFull(pipe.writeSide.get(), "0\n"); - } catch (SysError & e) { - writeFull(pipe.writeSide.get(), fmt("%d\n", e.errNo)); - } catch (...) { - writeFull(pipe.writeSide.get(), "-1\n"); - } - })}; - pipe.writeSide.close(); - auto errNo = string2Int(chomp(drainFD(pipe.readSide.get()))); - if (!errNo || *errNo == -1) - throw Error("cannot %s to socket at '%s'", operationName, path); - else if (*errNo > 0) { - errno = *errNo; - throw SysError("cannot %s to socket at '%s'", operationName, path); - } + runHelper( + "unix-bind-connect", + {.args = + {std::to_string(fd), std::string(operationName), dirOf(path), std::string(baseNameOf(path))}, + .redirections = {{.dup = fd, .from = fd}}} + ).waitAndCheck(); } else { memcpy(addr.sun_path, path.c_str(), path.size() + 1); if (operation(fd, psaddr, sizeof(addr)) == -1)