Files
op-pedal/src/UnixSocket.cpp
T
Robin Davies 469b0c0c69 G++11 Errors
2022-04-23 12:38:12 -04:00

254 lines
6.9 KiB
C++

/*
* MIT License
*
* Copyright (c) 2022 Robin E. R. Davies
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do
* so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "UnixSocket.hpp"
#include <atomic>
#include <filesystem>
#include <grp.h>
#include <stdexcept>
#include "gsl/util"
#include <sys/socket.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/un.h>
#include <unistd.h>
#include <fcntl.h>
#include "ss.hpp"
using namespace std;
namespace pipedal
{
namespace detail
{
class UnixSocketData
{
public:
UnixSocketData()
{
memset(&localAddress, 0, sizeof(localAddress));
memset(&remoteAddress, 0, sizeof(remoteAddress));
localAddress.sun_family = AF_UNIX;
remoteAddress.sun_family = AF_UNIX;
}
int socket = -1;
struct sockaddr_un localAddress;
struct sockaddr_un remoteAddress;
};
}
}
using namespace pipedal;
using namespace pipedal::detail;
using namespace gsl;
bool UnixSocket::IsOpen() const { return data->socket != -1; }
UnixSocketException::UnixSocketException(int errno_)
: what_(strerror(errno_)), errno_(errno_)
{
}
UnixSocketException::UnixSocketException(const std::string what_, int errno_)
: what_(what_ + " " + strerror(errno_) + ")"), errno_(errno_)
{
}
UnixSocketException::UnixSocketException(const std::string what_)
: what_(what_), errno_(0)
{
}
void UnixSocket::Bind(const std::string &socketName, const std::string &permissionGroup)
{
if (socketName.length() >= sizeof(data->localAddress.sun_path) - 1)
{
throw UnixSocketException("Socket name too long.", E2BIG);
}
int s = socket(PF_UNIX, SOCK_DGRAM, 0);
auto socket_cleanup = finally([&]() {
if (s != -1)
{
close(s);
s = -1;
}
});
strcpy(data->localAddress.sun_path, socketName.c_str());
for (int retry = 0; /**/; ++retry)
{
int err = bind(s, (struct sockaddr *)&data->localAddress, sizeof(data->localAddress));
if (err < 0)
{
if (retry != 2)
{
unlink(data->localAddress.sun_path);
continue;
}
throw UnixSocketException("Can't bind socket " + socketName, errno);
}
else
{
break;
}
}
if (permissionGroup != "")
{
gid_t gid;
struct group *group_;
group_ = getgrnam(permissionGroup.c_str());
if (group_ == nullptr)
{
throw UnixSocketException("Group not found.");
}
gid = group_->gr_gid;
std::ignore = chown(data->localAddress.sun_path, -1, gid);
std::ignore = chmod(data->localAddress.sun_path, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
}
data->socket = s;
s = -1;
}
UnixSocket::UnixSocket()
{
data = new UnixSocketData();
}
void UnixSocket::Close()
{
if (data->socket != -1)
{
close(data->socket);
unlink(data->localAddress.sun_path);
}
}
UnixSocket::~UnixSocket()
{
Close();
delete data;
}
static std::atomic<uint64_t> instanceId;
void UnixSocket::Connect(const std::string &remoteSocketName, const std::string &permissionGroup)
{
const std::string localSocketName =
SS("/tmp/ppadmin-" << getpid() << "-" << (instanceId++));
if (localSocketName.length() >= sizeof(data->localAddress.sun_path) - 1)
{
throw UnixSocketException("Local socket path is too long. ", E2BIG);
}
strcpy(data->localAddress.sun_path, localSocketName.c_str());
int s = socket(PF_UNIX, SOCK_DGRAM, 0);
auto socket_cleanup = finally([&]() {
if (s != -1)
{
close(s);
s = -1;
unlink(data->localAddress.sun_path);
}
});
int e = bind(s, (sockaddr *)&data->localAddress, sizeof(data->localAddress));
if (permissionGroup.length() != 0)
{
struct group *g = getgrnam(permissionGroup.c_str());
if (g == nullptr)
{
throw UnixSocketException("Group not found");
}
gid_t gid = g->gr_gid;
std::ignore = chown(data->localAddress.sun_path, -1, gid);
std::ignore = chmod(data->localAddress.sun_path, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
}
strcpy(data->remoteAddress.sun_path, remoteSocketName.c_str());
e = connect(s, (sockaddr *)&data->remoteAddress, sizeof(data->remoteAddress));
if (e < 0)
{
throw UnixSocketException("Can't connect to socket " + remoteSocketName, errno);
}
data->socket = s;
s = -1;
}
size_t UnixSocket::Send(const void *buffer, size_t length)
{
ssize_t size = send(data->socket, buffer, length, 0);
if (size < 0)
{
throw UnixSocketException(errno);
}
return (size_t)size;
}
size_t UnixSocket::Receive(void *buffer, size_t length)
{
ssize_t received = recv(data->socket, buffer, length, 0);
if (received < 0)
{
throw UnixSocketException(errno);
}
return (size_t)received;
}
size_t UnixSocket::ReceiveFrom(void *buffer, size_t length, std::string *pFrom)
{
sockaddr_un sockAddr;
socklen_t sockAddrLen = sizeof(sockAddr);
ssize_t received = recvfrom(data->socket, buffer, length, 0, (sockaddr *)&sockAddr, &sockAddrLen);
if (received < 0)
{
throw UnixSocketException(errno);
}
*pFrom = std::string(sockAddr.sun_path);
return (size_t)received;
}
size_t UnixSocket::SendTo(const void *buffer, size_t length, const std::string &toAddress)
{
sockaddr_un sockAddr;
if (toAddress.length() >= sizeof(sockAddr.sun_path) - 1)
{
throw UnixSocketException("Address too long.", E2BIG);
}
strcpy(sockAddr.sun_path, toAddress.c_str());
sockAddr.sun_family = AF_UNIX;
ssize_t size = sendto(data->socket, buffer, length, 0, (sockaddr *)&sockAddr, sizeof(sockaddr_un));
if (size < 0)
{
throw UnixSocketException(errno);
}
return (size_t)size;
}