#pragma once
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <sys/un.h>
|
|
#include <string>
|
|
#include <sstream>
|
|
#include <atomic>
|
|
#include <array>
|
|
#include <variant>
|
|
#include <cassert>
|
|
|
|
namespace gp{
|
|
using open_opt_flags = int;
|
|
|
|
enum class open_options : open_opt_flags {
|
|
append = O_APPEND,
|
|
create = O_CREAT,
|
|
async = O_ASYNC,
|
|
direct = O_DIRECT,
|
|
data_sync = O_DSYNC,
|
|
file_sync = O_SYNC,
|
|
exclusive = O_EXCL,
|
|
no_access_time = O_NOATIME,
|
|
no_follow = O_NOFOLLOW,
|
|
read = O_RDONLY,
|
|
write = O_WRONLY,
|
|
#ifdef O_TEMP
|
|
temporary = O_TEMP,
|
|
#endif
|
|
#ifdef O_TRUC
|
|
truncate = O_TRUC,
|
|
#endif
|
|
};
|
|
|
|
using open_opt_mode = int;
|
|
|
|
enum class open_modes : open_opt_mode {
|
|
user_read = S_IRUSR,
|
|
user_write = S_IWUSR,
|
|
user_exec = S_IXUSR,
|
|
group_read = S_IRGRP,
|
|
group_write = S_IWGRP,
|
|
group_exec = S_IXGRP,
|
|
other_read = S_IROTH,
|
|
other_write = S_IWOTH,
|
|
other_exec = S_IXOTH,
|
|
set_uid = S_ISUID,
|
|
set_gid = S_ISGID,
|
|
sticky_bit = S_ISVTX,
|
|
};
|
|
|
|
enum class socket_domain : int {
|
|
ip4 = AF_INET,
|
|
ip6 = AF_INET6,
|
|
unix = AF_UNIX
|
|
};
|
|
|
|
enum class socket_protocol : int {
|
|
tcp_like = SOCK_STREAM,
|
|
udp_like = SOCK_DGRAM
|
|
};
|
|
|
|
|
|
using socket_opt_flags = int;
|
|
|
|
enum class net_socket_opt_flags : socket_opt_flags {
|
|
non_blocking = SOCK_NONBLOCK,
|
|
close_on_exec = SOCK_CLOEXEC,
|
|
};
|
|
|
|
using socket_opt_flags = int;
|
|
|
|
enum class unix_socket_opt_flags : socket_opt_flags {
|
|
non_blocking = SOCK_NONBLOCK,
|
|
close_on_exec = SOCK_CLOEXEC,
|
|
};
|
|
|
|
using address = std::variant<sockaddr_in, sockaddr_in6, sockaddr_un>;
|
|
|
|
template<typename T>
|
|
struct stream_expect{
|
|
const T v;
|
|
stream_expect(T value)
|
|
: v(value)
|
|
{}
|
|
};
|
|
|
|
template<typename T>
|
|
std::istream& operator>>(std::istream& in, const stream_expect<T>& expector)
|
|
{
|
|
T vin;
|
|
in >> vin;
|
|
if(vin != expector.v)
|
|
throw std::runtime_error("Expector failed");
|
|
return in;
|
|
}
|
|
|
|
address make_ipv4(const std::string_view addr, const uint16_t port)
|
|
{
|
|
std::stringstream din;
|
|
din<<addr;
|
|
std::array<uint8_t, 4> address{0};
|
|
int ex;
|
|
din>>ex
|
|
>>stream_expect<char>('.');
|
|
address[0]=ex;
|
|
din>>ex
|
|
>>stream_expect<char>('.');
|
|
address[1]=ex;
|
|
din>>ex
|
|
>>stream_expect<char>('.');
|
|
address[2]=ex;
|
|
din>>ex;
|
|
address[3]=ex;
|
|
|
|
sockaddr_in ret;
|
|
in_addr ret_addr;
|
|
ret_addr.s_addr = *((uint32_t*)&address);
|
|
ret.sin_family = AF_INET;
|
|
ret.sin_addr = ret_addr;
|
|
ret.sin_port = htons(port);
|
|
return ret;
|
|
}
|
|
|
|
// TODO: make an IPv6 parser
|
|
//address make_ipv6(const std::string_view addr, const uint16_t port){}
|
|
|
|
address make_unix(const std::string_view filename)
|
|
{
|
|
sockaddr_un ret;
|
|
if(filename.size()>(sizeof(ret.sun_path)-1))
|
|
throw std::runtime_error("Filename too long");
|
|
|
|
ret.sun_family = AF_UNIX;
|
|
*std::copy(filename.begin(), filename.end(), ret.sun_path) = '\0';
|
|
return ret;
|
|
}
|
|
|
|
class shared_fd {
|
|
int fd;
|
|
std::atomic_int* guard;
|
|
int last_error;
|
|
shared_fd(int fd_v)
|
|
: fd(fd_v)
|
|
, guard(new std::atomic_int{1})
|
|
, last_error(0)
|
|
{}
|
|
|
|
public:
|
|
shared_fd()
|
|
: fd(-1)
|
|
, guard(nullptr)
|
|
, last_error(0)
|
|
{}
|
|
|
|
shared_fd(const shared_fd& oth)
|
|
: fd(oth.fd)
|
|
, guard(oth.guard)
|
|
, last_error(0)
|
|
{
|
|
if(guard)
|
|
guard->fetch_add(1, std::memory_order_acq_rel);
|
|
}
|
|
|
|
shared_fd(shared_fd&& oth)
|
|
: last_error(0)
|
|
{
|
|
fd=oth.fd;
|
|
guard=oth.guard;
|
|
oth.fd=-1;
|
|
oth.guard=nullptr;
|
|
}
|
|
|
|
const int get() const {
|
|
return fd;
|
|
}
|
|
|
|
void operator=(const shared_fd& oth)
|
|
{
|
|
this->~shared_fd();
|
|
fd = oth.fd;
|
|
guard = oth.guard;
|
|
if(guard)
|
|
guard->fetch_add(1, std::memory_order_acq_rel);
|
|
}
|
|
|
|
void operator=(shared_fd&& oth)
|
|
{
|
|
std::swap(fd,oth.fd);
|
|
std::swap(guard,oth.guard);
|
|
}
|
|
|
|
static shared_fd open(const std::string& filename, const open_opt_flags& flags, const open_opt_mode& mode = 0)
|
|
{
|
|
shared_fd ret{::open(filename.c_str(), flags, mode)};
|
|
return ret;
|
|
}
|
|
|
|
static shared_fd create(const std::string& filename, const open_opt_mode& mode)
|
|
{
|
|
shared_fd ret{::creat(filename.c_str(), mode)};
|
|
return ret;
|
|
}
|
|
|
|
static shared_fd tcp_socket()
|
|
{
|
|
shared_fd ret;
|
|
|
|
auto res = ::socket(AF_INET, SOCK_STREAM, 0);
|
|
if(res >= 0)
|
|
{
|
|
ret = shared_fd{res};
|
|
fcntl(res, F_SETFL, O_NONBLOCK);
|
|
}
|
|
else
|
|
ret.last_error = errno;
|
|
return ret;
|
|
}
|
|
|
|
static shared_fd socket(const socket_domain& dom, const socket_protocol& proto)//, const net_socket_opt_flags& flags)
|
|
{
|
|
shared_fd ret;
|
|
|
|
auto res = ::socket((int)dom, (int)proto, (int)0);
|
|
if(res >= 0)
|
|
{
|
|
ret = shared_fd{res};
|
|
}
|
|
else
|
|
ret.last_error = errno;
|
|
return ret;
|
|
}
|
|
|
|
static shared_fd unix_socket(const socket_protocol& proto, const socket_opt_flags flags) {
|
|
shared_fd ret;
|
|
|
|
auto res = ::socket((int)AF_UNIX, (int)proto, (int)flags);
|
|
if(res >= 0)
|
|
ret = shared_fd{res};
|
|
|
|
return ret;
|
|
}
|
|
|
|
static std::pair<shared_fd,shared_fd> unix_socket_pair(const socket_protocol& proto, const net_socket_opt_flags flags) {
|
|
std::pair<gp::shared_fd, gp::shared_fd> ret;
|
|
int fds[2];
|
|
auto result = ::socketpair((int)AF_UNIX, (int)proto, (int)flags, fds);
|
|
if(result != 0)
|
|
return ret;
|
|
ret.first = shared_fd(fds[0]);
|
|
ret.second = shared_fd(fds[1]);
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool is_valid() const {
|
|
return guard && (fd>=0);
|
|
}
|
|
|
|
bool has_failed() const {
|
|
return last_error;
|
|
}
|
|
|
|
bool was_connection_refused() const {
|
|
return last_error==ECONNREFUSED;
|
|
}
|
|
|
|
bool in_progress() const {
|
|
return last_error==EINPROGRESS;
|
|
}
|
|
|
|
bool must_retry() const {
|
|
return last_error==EAGAIN;
|
|
}
|
|
|
|
int error() const {
|
|
return last_error;
|
|
}
|
|
|
|
void bind(const address& addr)
|
|
{
|
|
int ret;
|
|
std::visit([&](const auto& v){
|
|
ret = ::bind(fd, (struct sockaddr*)&v, sizeof(v));
|
|
}, addr);
|
|
if(ret==0)
|
|
{
|
|
last_error = 0;
|
|
return;
|
|
}
|
|
else
|
|
last_error = errno;
|
|
}
|
|
|
|
void connect(const address& addr)
|
|
{
|
|
int ret;
|
|
std::visit([&](const auto& v){
|
|
ret = ::connect(fd, (struct sockaddr*)&v, sizeof(v));
|
|
}, addr);
|
|
if(ret!=0)
|
|
{
|
|
last_error = errno;
|
|
return;
|
|
}
|
|
last_error = 0;
|
|
}
|
|
|
|
void listen(const int& backlog = 16)
|
|
{
|
|
int ret = 0;
|
|
std::visit([&](){
|
|
ret = ::listen(fd, backlog);
|
|
});
|
|
if(ret!=0)
|
|
{
|
|
last_error = errno;
|
|
return;
|
|
}
|
|
last_error = 0;
|
|
}
|
|
|
|
shared_fd accept()
|
|
{
|
|
int ret = 0;
|
|
std::array<uint8_t, 256> buffer;
|
|
socklen_t len;
|
|
ret = ::accept(fd,(sockaddr*)buffer.begin(), &len);
|
|
if(ret!=0)
|
|
{
|
|
last_error = errno;
|
|
return shared_fd{};
|
|
}
|
|
last_error = 0;
|
|
return shared_fd{ret};
|
|
}
|
|
|
|
std::string_view read(const std::string_view buffer)
|
|
{
|
|
int sz = ::read(fd, (void*)(buffer.begin()), buffer.size());
|
|
if(sz<0)
|
|
last_error = errno;
|
|
else
|
|
last_error = 0;
|
|
sz = sz<0?0:sz;
|
|
return std::string_view(buffer.begin(), sz);
|
|
}
|
|
|
|
std::string_view write(const std::string_view buffer)
|
|
{
|
|
int sz = ::write(fd, (void*)(buffer.begin()), buffer.size());
|
|
if(sz<0)
|
|
last_error = errno;
|
|
else
|
|
last_error = 0;
|
|
sz = sz<0?0:sz;
|
|
return std::string_view(buffer.begin()+sz, buffer.size()-sz);
|
|
}
|
|
|
|
~shared_fd()
|
|
{
|
|
if(guard)
|
|
if(guard->fetch_sub(1, std::memory_order_acq_rel) == 1)
|
|
{
|
|
assert(guard->load() == 0);
|
|
assert(fd >= 0);
|
|
delete guard;
|
|
guard = nullptr;
|
|
if(fd >= 0)
|
|
{
|
|
::close(fd);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|