diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/.gitignore | 2 | ||||
| -rw-r--r-- | src/Makefile.am | 13 | ||||
| -rw-r--r-- | src/sender.cc | 215 | ||||
| -rw-r--r-- | src/sender_client.cc | 200 | ||||
| -rw-r--r-- | src/sender_client.hh | 14 | ||||
| -rw-r--r-- | src/sockutils.cc | 36 | ||||
| -rw-r--r-- | src/sockutils.hh | 62 | ||||
| -rw-r--r-- | src/test_sender.cc | 41 |
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; +} |
