summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/.gitignore2
-rw-r--r--src/Makefile.am13
-rw-r--r--src/sender.cc215
-rw-r--r--src/sender_client.cc200
-rw-r--r--src/sender_client.hh14
-rw-r--r--src/sockutils.cc36
-rw-r--r--src/sockutils.hh62
-rw-r--r--src/test_sender.cc41
8 files changed, 430 insertions, 153 deletions
diff --git a/src/.gitignore b/src/.gitignore
index 85ad493..146a3bd 100644
--- a/src/.gitignore
+++ b/src/.gitignore
@@ -5,5 +5,7 @@
/libcgi.la
/libdb.la
/libutil.la
+/libsender_client.la
/event
/sender
+/test-sender
diff --git a/src/Makefile.am b/src/Makefile.am
index 06647f5..c85aca3 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -4,16 +4,19 @@ AM_CPPFLAGS = @DEFINES@ -DLOCALSTATEDIR='"@localstatedir@/stuff"' \
-DSYSCONFDIR='"@sysconfdir@/stuff"'
bin_PROGRAMS = event sender
-noinst_LTLIBRARIES = libdb.la libcgi.la libutil.la
+noinst_PROGRAMS = test-sender
+noinst_LTLIBRARIES = libdb.la libcgi.la libutil.la libsender_client.la
-event_SOURCES = event.cc event.hh event_main.cc common.hh cgi.hh db.hh \
- sender_client.cc sender_client.hh
-event_LDADD = libdb.la libcgi.la
+event_SOURCES = event.cc event.hh event_main.cc common.hh cgi.hh db.hh
+event_LDADD = libdb.la libcgi.la libsender_client.la
sender_SOURCES = common.hh sender.cc json.hh json.cc
sender_CPPFLAGS = $(AM_CPPFLAGS) @CURL_CFLAGS@
sender_LDADD = libutil.la @CURL_LIBS@
+test_sender_SOURCES = common.hh test_sender.cc sender_client.hh
+test_sender_LDADD = libsender_client.la
+
libcgi_la_SOURCES = cgi.hh common.hh cgi.cc \
query_parser.hh query_parser.cc \
header_parser.hh header_parser.cc \
@@ -30,3 +33,5 @@ libdb_la_LIBADD = @SQLITE3_LIBS@
libutil_la_SOURCES = common.hh fsutils.cc fsutils.hh config.cc config.hh \
strutils.hh strutils.cc sockutils.hh sockutils.cc
+libsender_client_la_SOURCES = common.h sender_client.cc sender_client.hh
+libsender_client_la_LIBADD = libutil.la
diff --git a/src/sender.cc b/src/sender.cc
index a248cbf..75be2d7 100644
--- a/src/sender.cc
+++ b/src/sender.cc
@@ -9,6 +9,7 @@
#include <csignal>
#include <iostream>
#include <netdb.h>
+#include <syslog.h>
#include <time.h>
#include <unistd.h>
#include <vector>
@@ -45,20 +46,14 @@ public:
: sock_(sock), fill_(0), have_channel_(false), size_(0) {
}
- ~Client() {
- if (sock_ != -1) {
- close(sock_);
- }
- }
-
int sock() const {
- return sock_;
+ return sock_.get();
}
bool read() {
while (true) {
char buf[1024];
- auto ret = ::read(sock_, buf, sizeof(buf));
+ auto ret = ::read(sock_.get(), buf, sizeof(buf));
if (ret < 0) {
if (errno == EINTR) continue;
return errno == EWOULDBLOCK || errno == EAGAIN;
@@ -133,7 +128,7 @@ private:
message_.clear();
}
- int sock_;
+ sockguard sock_;
char buf_[4];
size_t fill_;
@@ -222,56 +217,23 @@ void queue_message(const std::string& channel, const std::string& message) {
g_info.requests.emplace_back(g_info.multi, g_info.url, obj->str());
}
-bool make_nonblock(int sock) {
- int flags = fcntl(sock, F_GETFL, 0);
- if (flags < 0) {
- return false;
- }
- if (!(flags & O_NONBLOCK)) {
- flags |= O_NONBLOCK;
- if (fcntl(sock, F_SETFL, flags) < 0) {
- return false;
- }
- }
- return true;
-}
-
-
-} // namespace
-
-int main() {
- auto cfg = Config::create();
- if (!cfg->load("./sender.config")) {
- cfg->load(SYSCONFDIR "/sender.config");
- }
- g_info.username = cfg->get("username", "stuff-sender-bot");
- g_info.url = cfg->get("url", "");
- if (g_info.url.empty()) {
- std::cerr << "No url configured" << std::endl;
- return EXIT_FAILURE;
- }
- g_info.icon_url = cfg->get("icon_url", "");
- g_info.icon_emoji = cfg->get("icon_emoji", "");
- auto const& listener = cfg->get("listener", "");
- if (listener.empty()) {
- std::cerr << "No listener configured" << std::endl;
- return EXIT_FAILURE;
- }
+int run(const std::string& listener, int fd) {
+ openlog("sender", LOG_PID, LOG_DAEMON);
if (curl_global_init(CURL_GLOBAL_ALL)) {
- std::cerr << "CURL failed to initialize" << std::endl;
+ syslog(LOG_ERR, "CURL failed to initialize");
return EXIT_FAILURE;
}
g_info.multi = curl_multi_init();
if (!g_info.multi) {
- std::cerr << "CURL didn't initialize" << std::endl;
+ syslog(LOG_ERR, "CURL did not to initialize");
return EXIT_FAILURE;
}
std::vector<Client> clients;
int still_running;
int exitvalue;
- int sock_ = -1;
+ sockguard sock_;
size_t pos = listener.find(':');
if (pos != std::string::npos) {
// [host]:port
@@ -283,57 +245,67 @@ int main() {
hints.ai_flags = AI_PASSIVE;
if (getaddrinfo(pos == 0 ? nullptr : listener.substr(0, pos).c_str(),
listener.substr(pos + 1).c_str(), &hints, &res)) {
- std::cerr << "Invalid host or port in: " << listener << std::endl;
+ syslog(LOG_ERR, "Invalid host or port in: %s", listener.c_str());
goto error;
}
for (auto ptr = res; ptr; ptr = ptr->ai_next) {
- sock_ = socket(ptr->ai_family, ptr->ai_socktype,
- ptr->ai_protocol);
- if (sock_ == -1) continue;
- if (bind(sock_, res->ai_addr, res->ai_addrlen)) {
- close(sock_);
- sock_ = -1;
+ sock_.reset(socket(ptr->ai_family, ptr->ai_socktype,
+ ptr->ai_protocol));
+ if (!sock_) continue;
+ if (bind(sock_.get(), res->ai_addr, res->ai_addrlen)) {
+ sock_.reset();
continue;
}
break;
}
freeaddrinfo(res);
- if (sock_ == -1) {
- std::cerr << "Unable to bind: " << listener << std::endl;
+ if (!sock_) {
+ syslog(LOG_ERR, "Unable to bind: %s", listener.c_str());
goto error;
}
} else {
// socket
- sock_ = socket(PF_LOCAL, SOCK_STREAM, 0);
- if (sock_ == -1) {
- std::cerr << "Unable to create local socket: " << strerror(errno)
- << std::endl;
+ sock_.reset(socket(PF_LOCAL, SOCK_STREAM, 0));
+ if (!sock_) {
+ syslog(LOG_ERR, "Unable to create a unix socket: %s",
+ strerror(errno));
goto error;
}
struct sockaddr_un name;
name.sun_family = AF_LOCAL;
strncpy(name.sun_path, listener.c_str(), sizeof(name.sun_path));
name.sun_path[sizeof(name.sun_path) - 1] = '\0';
- if (bind(sock_, reinterpret_cast<struct sockaddr*>(&name),
- SUN_LEN(&name))) {
- std::cerr << "Bind failed: " << strerror(errno) << std::endl;
+ while (true) {
+ if (bind(sock_.get(), reinterpret_cast<struct sockaddr*>(&name),
+ SUN_LEN(&name)) == 0) {
+ break;
+ }
+ if (errno == EADDRINUSE) {
+ if (unlink(listener.c_str()) == 0) {
+ continue;
+ }
+ errno = EADDRINUSE;
+ }
+ syslog(LOG_ERR, "Bind failed: %s", strerror(errno));
goto error;
}
}
- if (listen(sock_, 10)) {
- std::cerr << "Listen failed: " << strerror(errno) << std::endl;
+ if (listen(sock_.get(), 10)) {
+ syslog(LOG_ERR, "Listen failed: %s", strerror(errno));
goto error;
}
- make_nonblocking(sock_);
+ make_nonblocking(sock_.get());
{
int value = 1;
#ifdef SO_REUSEPORT
- setsockopt(sock_, SOL_SOCKET, SO_REUSEPORT, &value, sizeof(value));
+ setsockopt(sock_.get(), SOL_SOCKET, SO_REUSEPORT,
+ &value, sizeof(value));
#else
- setsockopt(sock_, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value));
+ setsockopt(sock_.get(), SOL_SOCKET, SO_REUSEADDR,
+ &value, sizeof(value));
#endif
}
@@ -341,6 +313,11 @@ int main() {
signal(SIGINT, quit);
signal(SIGTERM, quit);
+ while (true) {
+ if (write(fd, "", 1) != -1 || errno != EINTR) break;
+ }
+ close(fd);
+
while (!g_quit) {
curl_multi_perform(g_info.multi, &still_running);
@@ -371,12 +348,12 @@ int main() {
}
if (curl_multi_fdset(g_info.multi, &read_set, &write_set, &err_set,
&max) != CURLM_OK) {
- std::cerr << "curl_multi_fdset failed" << std::endl;
+ syslog(LOG_ERR, "curl_multi_fdset failed");
goto error;
}
- max = std::max(max, sock_);
- FD_SET(sock_, &read_set);
+ max = std::max(max, sock_.get());
+ FD_SET(sock_.get(), &read_set);
for (auto it = clients.begin(); it != clients.end();) {
if (it->sock() == -1) {
it = clients.erase(it);
@@ -389,7 +366,7 @@ int main() {
auto ret = select(max + 1, &read_set, &write_set, &err_set, to);
if (ret < 0) {
if (errno == EINTR) continue;
- std::cerr << "Select failed: " << strerror(errno);
+ syslog(LOG_ERR, "Select failed: %s", strerror(errno));
goto error;
}
for (auto it = clients.begin(); ret > 0 && it != clients.end();) {
@@ -404,23 +381,19 @@ int main() {
++it;
}
}
- if (ret > 0 && FD_ISSET(sock_, &read_set)) {
+ if (ret > 0 && FD_ISSET(sock_.get(), &read_set)) {
ret--;
- auto sock = accept(sock_, nullptr, nullptr);
- if (sock < 0) {
+ sockguard sock(accept(sock_.get(), nullptr, nullptr));
+ if (!sock) {
if (errno == EINTR) continue;
if (errno == EWOULDBLOCK || errno == EAGAIN) continue;
- std::cerr << "Accept failed: " << strerror(errno);
- goto error;
- }
- if (!make_nonblocking(sock)) {
- close(sock);
- } else {
+ syslog(LOG_WARNING, "Accept failed: %s", strerror(errno));
+ } else if (make_nonblocking(sock.get())) {
if (clients.size() == MAX_CLIENTS) {
// Remove oldest
clients.erase(clients.begin());
}
- clients.emplace_back(sock);
+ clients.emplace_back(sock.release());
}
}
}
@@ -435,6 +408,82 @@ int main() {
g_info.requests.clear();
curl_multi_cleanup(g_info.multi);
curl_global_cleanup();
- close(sock_);
+ unlink(listener.c_str());
+ closelog();
return exitvalue;
}
+
+} // namespace
+
+int main() {
+ auto cfg = Config::create();
+ if (!cfg->load("./sender.config")) {
+ cfg->load(SYSCONFDIR "/sender.config");
+ }
+ g_info.username = cfg->get("username", "stuff-sender-bot");
+ g_info.url = cfg->get("url", "");
+ if (g_info.url.empty()) {
+ std::cerr << "No url configured" << std::endl;
+ return EXIT_FAILURE;
+ }
+ g_info.icon_url = cfg->get("icon_url", "");
+ g_info.icon_emoji = cfg->get("icon_emoji", "");
+ auto const& listener = cfg->get("listener", "");
+ if (listener.empty()) {
+ std::cerr << "No listener configured" << std::endl;
+ return EXIT_FAILURE;
+ }
+ cfg.reset();
+
+ int fd[2];
+ if (pipe(fd)) {
+ std::cerr << "Unable to create pipe: " << strerror(errno) << std::endl;
+ return EXIT_FAILURE;
+ }
+
+ auto pid = fork();
+ if (pid < 0) {
+ std::cerr << "Unable to fork: " << strerror(errno) << std::endl;
+ close(fd[0]);
+ close(fd[1]);
+ return EXIT_FAILURE;
+ }
+ if (pid == 0) {
+ close(fd[0]);
+ setpgrp();
+ if (listener.find(':') != std::string::npos ||
+ listener.front() == '/') {
+ chdir("/");
+ }
+ close(STDIN_FILENO);
+ close(STDOUT_FILENO);
+ close(STDERR_FILENO);
+ int ret = run(listener, fd[1]);
+ while (true) {
+ if (write(fd[1], "", 1) != -1 || errno != EINTR) break;
+ }
+ close(fd[1]);
+ _exit(ret);
+ } else {
+ close(fd[1]);
+ char c;
+ while (true) {
+ auto ret = read(fd[0], &c, 1);
+ if (ret == 1) {
+ break;
+ }
+ if (ret < 0 && errno == EINTR) {
+ continue;
+ }
+ c = '1';
+ break;
+ }
+ if (c) {
+ std::cerr << "Failed to start, see syslog for details" << std::endl;
+ close(fd[0]);
+ return EXIT_FAILURE;
+ }
+ close(fd[0]);
+ return EXIT_SUCCESS;
+ }
+}
diff --git a/src/sender_client.cc b/src/sender_client.cc
index 69899ec..df83a74 100644
--- a/src/sender_client.cc
+++ b/src/sender_client.cc
@@ -4,6 +4,7 @@
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/un.h>
+#include <sys/wait.h>
#include <cerrno>
#include <cstring>
#include <fcntl.h>
@@ -18,28 +19,30 @@ namespace stuff {
namespace {
+long WRITE_TIMEOUT = 5;
+long CONNECT_TIMEOUT = 5;
+
class SenderClientImpl : public SenderClient {
public:
- SenderClientImpl()
- : sock_(-1) {
- }
- ~SenderClientImpl() override {
- if (sock_ != -1) {
- close(sock_);
- }
+ SenderClientImpl(std::shared_ptr<Error> error)
+ : error_(error) {
}
bool open(const Config* config) {
if (!config) return false;
sender_ = config->get("sender", "");
- if (sender_.empty()) return false;
+ if (sender_.empty()) {
+ if (error_) error_->error("Config missing sender");
+ return false;
+ }
+ sender_bin_ = config->get("sender_bin", "");
return true;
}
void send(const std::string& channel, const std::string& message) override {
struct timeval target;
gettimeofday(&target, NULL);
- target.tv_sec += 5;
+ target.tv_sec += WRITE_TIMEOUT;
send(channel, message, &target);
}
@@ -47,7 +50,7 @@ public:
private:
void send(const std::string& channel, const std::string& message,
const struct timeval* target) {
- if (sock_ == -1) {
+ if (!sock_) {
if (!setup()) return;
}
@@ -60,7 +63,7 @@ private:
ssize_t ret;
if (pos < 4) {
size_t const avail = 4 - pos;
- ret = write(sock_,
+ ret = write(sock_.get(),
reinterpret_cast<char*>(&size1) + pos, avail);
if (ret > 0) {
pos += ret;
@@ -68,14 +71,14 @@ private:
}
} else if (pos < 4 + channel.size()) {
size_t const avail = 4 + channel.size() - pos;
- ret = write(sock_, channel.data() + pos - 4, avail);
+ ret = write(sock_.get(), channel.data() + pos - 4, avail);
if (ret > 0) {
pos += ret;
if (static_cast<size_t>(ret) == avail) continue;
}
} else if (pos < 8 + channel.size()) {
size_t const avail = 8 + channel.size() - pos;
- ret = write(sock_, reinterpret_cast<char*>(&size2)
+ ret = write(sock_.get(), reinterpret_cast<char*>(&size2)
+ pos - 4 - channel.size(), avail);
if (ret > 0) {
pos += ret;
@@ -83,7 +86,8 @@ private:
}
} else {
size_t const avail = len - pos;
- ret = write(sock_, message.data() + pos - 8 - channel.size(),
+ ret = write(sock_.get(),
+ message.data() + pos - 8 - channel.size(),
avail);
if (ret > 0) {
pos += ret;
@@ -94,45 +98,26 @@ private:
if (ret < 0) {
if (errno == EINTR) continue;
if (errno != EAGAIN && errno != EWOULDBLOCK) {
- close(sock_);
- sock_ = -1;
+ sock_.reset();
return send(channel, message);
}
}
fd_set write_set;
FD_ZERO(&write_set);
- FD_SET(sock_, &write_set);
+ FD_SET(sock_.get(), &write_set);
while (true) {
struct timeval timeout;
- gettimeofday(&timeout, NULL);
- if (target->tv_sec == timeout.tv_sec) {
- timeout.tv_sec = 0;
- if (target->tv_usec > timeout.tv_usec) {
- timeout.tv_usec = target->tv_usec - timeout.tv_usec;
- } else {
- timeout.tv_usec = 0;
- }
- } else if (target->tv_sec > timeout.tv_sec) {
- timeout.tv_sec = target->tv_sec - timeout.tv_sec;
- if (target->tv_usec >= timeout.tv_usec) {
- timeout.tv_usec = target->tv_usec - timeout.tv_usec;
- } else {
- timeout.tv_sec--;
- timeout.tv_usec =
- 1000000l + target->tv_usec - timeout.tv_usec;
- }
- } else {
+ if (!calc_timeout(target, &timeout)) {
timeout.tv_sec = 0;
timeout.tv_usec = 0;
}
- auto ret = select(sock_ + 1, nullptr, &write_set, nullptr,
+ auto ret = select(sock_.get() + 1, nullptr, &write_set, nullptr,
&timeout);
if (ret < 0 && errno == EINTR) continue;
if (ret <= 0) {
// Timeout or error
- close(sock_);
- sock_ = -1;
+ sock_.reset();
return send(channel, message);
}
break;
@@ -140,8 +125,81 @@ private:
}
}
+ bool connect_timeout(int sock, struct sockaddr* addr, socklen_t addrlen,
+ const struct timeval* target) {
+ if (!make_nonblocking(sock)) {
+ error_->error("Unable to make non-blocking socket", errno);
+ return false;
+ }
+ while (true) {
+ if (connect(sock, addr, addrlen) == 0) {
+ return true;
+ }
+ if (errno == EINTR) continue;
+ if (errno != EINPROGRESS) return false;
+ fd_set write_set;
+ FD_ZERO(&write_set);
+ FD_SET(sock, &write_set);
+ while (true) {
+ struct timeval timeout;
+ if (!calc_timeout(target, &timeout)) {
+ timeout.tv_sec = 0;
+ timeout.tv_usec = 0;
+ }
+ auto ret = select(sock + 1, nullptr, &write_set, nullptr,
+ &timeout);
+ if (ret < 0) {
+ if (errno == EINTR) continue;
+ return false;
+ }
+ if (ret == 0) {
+ errno = ETIMEDOUT;
+ return false;
+ }
+ int err;
+ socklen_t len = sizeof(int);
+ if (getsockopt(sock, SOL_SOCKET, SO_ERROR, &err, &len)) {
+ return false;
+ }
+ if (err != 0) {
+ errno = err;
+ return false;
+ }
+ return true;
+ }
+ }
+ }
+
+ bool setup_start() {
+ if (sender_bin_.empty()) return false;
+ auto pid = fork();
+ if (pid < 0) {
+ if (error_) error_->error("Error forking", errno);
+ return false;
+ }
+ if (pid == 0) {
+ char* argv[2];
+ argv[0] = const_cast<char*>(sender_bin_.c_str());
+ argv[1] = nullptr;
+ _exit(execv(argv[0], argv));
+ } else {
+ int status;
+ auto ret = waitpid(pid, &status, 0);
+ if (ret == -1) {
+ if (error_) {
+ error_->error("Error waiting for sender bin", errno);
+ }
+ }
+ return true;
+ }
+ }
+
bool setup() {
- size_t pos = sender_.find(':');
+ struct timeval target;
+ gettimeofday(&target, NULL);
+ target.tv_sec += CONNECT_TIMEOUT;
+
+ auto pos = sender_.find(':');
if (pos != std::string::npos) {
// host:port
struct addrinfo hints, *res;
@@ -151,57 +209,69 @@ private:
hints.ai_protocol = IPPROTO_TCP;
if (getaddrinfo(sender_.substr(0, pos).c_str(),
sender_.substr(pos + 1).c_str(), &hints, &res)) {
+ if (error_) error_->error("Error resolving: " + sender_);
return false;
}
for (auto ptr = res; ptr; ptr = ptr->ai_next) {
- sock_ = socket(ptr->ai_family, ptr->ai_socktype,
- ptr->ai_protocol);
- if (sock_ == -1) continue;
- // TODO: Make async
- if (connect(sock_, res->ai_addr, res->ai_addrlen)) {
- close(sock_);
- sock_ = -1;
+ sock_.reset(socket(ptr->ai_family, ptr->ai_socktype,
+ ptr->ai_protocol));
+ if (!sock_) continue;
+ if (!connect_timeout(sock_.get(), res->ai_addr, res->ai_addrlen,
+ &target)) {
+ sock_.reset();
continue;
}
break;
- }
- freeaddrinfo(res);
- if (sock_ == -1) return false;
+ }
+ freeaddrinfo(res);
+ if (!sock_) {
+ if (errno == ECONNREFUSED && setup_start()) {
+ return setup();
+ }
+ if (error_) error_->error("Socket/Connect failed", errno);
+ return false;
+ }
} else {
// socket
- sock_ = socket(PF_LOCAL, SOCK_STREAM, 0);
- if (sock_ == -1) return false;
+ sock_.reset(socket(PF_LOCAL, SOCK_STREAM, 0));
+ if (!sock_) {
+ if (error_) {
+ error_->error("Unable to create unix socket", errno);
+ }
+ return false;
+ }
struct sockaddr_un name;
name.sun_family = AF_LOCAL;
strncpy(name.sun_path, sender_.c_str(), sizeof(name.sun_path));
name.sun_path[sizeof(name.sun_path) - 1] = '\0';
- // TODO: Make async
- if (connect(sock_, reinterpret_cast<struct sockaddr*>(&name),
- SUN_LEN(&name))) {
- close(sock_);
- sock_ = 1;
+ if (!connect_timeout(sock_.get(),
+ reinterpret_cast<struct sockaddr*>(&name),
+ SUN_LEN(&name), &target)) {
+ if ((errno == ECONNREFUSED ||
+ errno == ENOENT) && setup_start()) {
+ return setup();
+ }
+ if (error_) error_->error("Connect failed", errno);
+ sock_.reset();
return false;
}
}
- if (!make_nonblocking(sock_)) {
- close(sock_);
- sock_ = -1;
- return false;
- }
-
return true;
}
std::string sender_;
- int sock_;
+ std::string sender_bin_;
+ std::shared_ptr<Error> error_;
+ sockguard sock_;
};
} // namespace
// static
-std::unique_ptr<SenderClient> SenderClient::create(const Config* config) {
- std::unique_ptr<SenderClientImpl> ret(new SenderClientImpl());
+std::unique_ptr<SenderClient> SenderClient::create(
+ const Config* config, std::shared_ptr<Error> error) {
+ std::unique_ptr<SenderClientImpl> ret(new SenderClientImpl(error));
if (!ret->open(config)) return nullptr;
return std::move(ret);
}
diff --git a/src/sender_client.hh b/src/sender_client.hh
index 5d0bd35..54339cf 100644
--- a/src/sender_client.hh
+++ b/src/sender_client.hh
@@ -10,12 +10,24 @@ class Config;
class SenderClient {
public:
+ class Error {
+ public:
+ virtual ~Error() {}
+
+ virtual void error(const std::string& message) = 0;
+ virtual void error(const std::string& message, int error) = 0;
+
+ protected:
+ Error() {}
+ };
+
virtual ~SenderClient() {}
virtual void send(const std::string& channel,
const std::string& message) = 0;
- static std::unique_ptr<SenderClient> create(const Config* config);
+ static std::unique_ptr<SenderClient> create(
+ const Config* config, std::shared_ptr<Error> error = nullptr);
protected:
SenderClient() {}
diff --git a/src/sockutils.cc b/src/sockutils.cc
index 53e3ef0..1bca300 100644
--- a/src/sockutils.cc
+++ b/src/sockutils.cc
@@ -1,6 +1,8 @@
#include "common.hh"
#include <fcntl.h>
+#include <sys/time.h>
+#include <unistd.h>
#include "sockutils.hh"
@@ -20,4 +22,38 @@ bool make_nonblocking(int sock) {
return true;
}
+bool calc_timeout(const struct timeval* target, struct timeval* timeout) {
+ gettimeofday(timeout, nullptr);
+ if (target->tv_sec == timeout->tv_sec) {
+ timeout->tv_sec = 0;
+ if (target->tv_usec >= timeout->tv_usec) {
+ timeout->tv_usec = target->tv_usec - timeout->tv_usec;
+ } else {
+ return false;
+ }
+ } else if (target->tv_sec > timeout->tv_sec) {
+ timeout->tv_sec = target->tv_sec - timeout->tv_sec;
+ if (target->tv_usec >= timeout->tv_usec) {
+ timeout->tv_usec = target->tv_usec - timeout->tv_usec;
+ } else {
+ timeout->tv_sec--;
+ timeout->tv_usec = 1000000l + target->tv_usec - timeout->tv_usec;
+ }
+ } else {
+ return false;
+ }
+ return true;
+}
+
+// static
+void sockguard::close(int sock) {
+ ::close(sock);
+}
+
} // namespace stuff
+
+namespace std {
+void swap(stuff::sockguard& s1, stuff::sockguard& s2) noexcept {
+ s1.swap(s2);
+}
+} // namespace std
diff --git a/src/sockutils.hh b/src/sockutils.hh
index 2c47daa..9ad85ab 100644
--- a/src/sockutils.hh
+++ b/src/sockutils.hh
@@ -5,6 +5,68 @@ namespace stuff {
bool make_nonblocking(int sock);
+bool calc_timeout(const struct timeval* target, struct timeval* timeout);
+
+class sockguard {
+public:
+ sockguard()
+ : sock_(-1) {
+ }
+ explicit sockguard(int sock)
+ : sock_(sock) {
+ }
+ sockguard(sockguard&& sock)
+ : sock_(sock.sock_) {
+ }
+ ~sockguard() {
+ reset();
+ }
+ sockguard& operator=(sockguard&& sock) {
+ reset(sock.sock_);
+ return *this;
+ }
+ void reset() {
+ if (sock_ != -1) {
+ close(sock_);
+ sock_ = -1;
+ }
+ }
+ void reset(int sock) {
+ if (sock_ != -1 && sock_ != sock) {
+ close(sock_);
+ }
+ sock_ = sock;
+ }
+ void swap(sockguard& sock) {
+ auto tmp = sock.sock_;
+ sock.sock_ = sock_;
+ sock_ = tmp;
+ }
+ operator bool() const {
+ return sock_ != -1;
+ }
+ int get() const {
+ return sock_;
+ }
+ int release() {
+ auto ret = sock_;
+ sock_ = -1;
+ return ret;
+ }
+
+protected:
+ sockguard(const sockguard&) = delete;
+ sockguard& operator=(const sockguard&) = delete;
+ static void close(int sock);
+
+private:
+ int sock_;
+};
+
} // namespace stuff
+namespace std {
+void swap(stuff::sockguard& s1, stuff::sockguard& s2) noexcept;
+} // namespace std
+
#endif /* SOCKUTILS_HH */
diff --git a/src/test_sender.cc b/src/test_sender.cc
new file mode 100644
index 0000000..6622ed1
--- /dev/null
+++ b/src/test_sender.cc
@@ -0,0 +1,41 @@
+#include "common.hh"
+
+#include <cstdlib>
+#include <cstring>
+#include <iostream>
+
+#include "config.hh"
+#include "sender_client.hh"
+
+using namespace stuff;
+
+int main(int argc, char** argv) {
+ class StdError : public SenderClient::Error {
+ public:
+ StdError() {
+ }
+ void error(const std::string& message) override {
+ std::cerr << message << std::endl;
+ }
+ void error(const std::string& message, int error) override {
+ std::cerr << message << ": " << strerror(error) << std::endl;
+ }
+ };
+
+ if (argc != 4) {
+ std::cerr << "Usage: `test_sender CONFIG CHANNEL MESSAGE`" << std::endl;
+ return EXIT_FAILURE;
+ }
+ auto config = Config::create();
+ if (!config->load(argv[1])) {
+ std::cerr << "Error loading config: " << argv[1] << std::endl;
+ return EXIT_FAILURE;
+ }
+ auto error = std::make_shared<StdError>();
+ auto client = SenderClient::create(config.get(), error);
+ if (!client) {
+ return EXIT_FAILURE;
+ }
+ client->send(argv[2], argv[3]);
+ return EXIT_SUCCESS;
+}