123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530 |
- /** @file net.c
- * @brief Network related functions.
- */
- // For ppol()
- #define _GNU_SOURCE
- #include <stdlib.h>
- #include <unistd.h>
- #include <assert.h>
- #include <errno.h>
- #include <stdio.h>
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <sys/uio.h>
- #include <signal.h>
- #include <poll.h>
- #include "faux/faux.h"
- #include "faux/time.h"
- #include "faux/net.h"
- #ifdef HAVE_PTHREAD
- #define setsigmask pthread_sigmask
- #else
- #define setsigmask sigprocmask
- #endif
- /** @brief Sends data to socket. Uses timeout and signal mask.
- *
- * The function acts like a pselect(). It gets timeout interval to interrupt
- * too long sending. It gets signal mask to atomically set it while blocking
- * within select()-like function. But it doesn't blocks signals before it.
- * User code must do it. The function can be interrupted by unblocked signal or
- * by timeout. Else it will send() all data given. Function can return sent
- * size less than required by user. It can happen due to errors.
- *
- * @param [in] fd Socket.
- * @param [in] buf Buffer to write.
- * @param [in] n Number of bytes to write.
- * @param [in] timeout Send timeout.
- * @param [in] sigmask Signal mask to set while pselect() call.
- * @return Number of bytes written or < 0 on error.
- */
- ssize_t faux_send(int fd, const void *buf, size_t n,
- const struct timespec *timeout, const sigset_t *sigmask)
- {
- size_t total_written = 0;
- size_t left = n;
- const void *data = buf;
- struct timespec now = {};
- struct timespec deadline = {};
- assert(fd != -1);
- assert(buf);
- if ((-1 == fd) || !buf)
- return -1;
- if (0 == n)
- return 0;
- // Calculate deadline - the time when timeout must occur.
- if (timeout) {
- faux_timespec_now(&now);
- faux_timespec_sum(&deadline, &now, timeout);
- }
- do {
- ssize_t bytes_written = 0;
- struct pollfd fds = {};
- struct timespec *poll_timeout = NULL;
- struct timespec to = {};
- int sn = 0;
- if (timeout) {
- if (faux_timespec_before_now(&deadline))
- break; // Timeout already occured
- faux_timespec_now(&now);
- faux_timespec_diff(&to, &deadline, &now);
- poll_timeout = &to;
- }
- // Handlers for poll()
- faux_bzero(&fds, sizeof(fds));
- fds.fd = fd;
- fds.events = POLLOUT;
- sn = ppoll(&fds, 1, poll_timeout, sigmask);
- // When kernel can't allocate some internal structures it can
- // return EAGAIN so retry.
- if ((sn < 0) && (EAGAIN == errno))
- continue;
- // All unneded signals are masked so don't process EINTR
- // in special way. Just break the loop
- if (sn < 0)
- break;
- // Timeout: break the loop. User don't want to wait any more
- if (0 == sn)
- break;
- // Some unknown event (not POLLOUT). So retry polling
- if (!(fds.revents & POLLOUT))
- continue;
- // The send() call is non-blocking but it's not obvious that
- // it can't return EINTR. Probably it can. Due to the fact the
- // call is non-blocking re-send() on any signal i.e. any EINTR.
- do {
- bytes_written = send(fd, data, left, MSG_DONTWAIT | MSG_NOSIGNAL);
- } while ((bytes_written < 0) && (EINTR == errno));
- if (bytes_written < 0)
- break;
- // Insufficient space
- if (0 == bytes_written)
- break;
- data += bytes_written;
- left = left - bytes_written;
- total_written += bytes_written;
- } while (left > 0);
- return total_written;
- }
- /** @brief Sends data to socket. It blocks signals and removes races.
- *
- * The function is like a faux_send() but it blocks all the signals and then
- * checks for "isbreak_func()" function to interrupt function call in a case
- * when isbreak_func() returns not-null value. See pselect() manpage for race
- * conditions explanation. It's needed for consistent signal handling.
- *
- * Usually signal hanler sets global volatile var to inform main programm about
- * event. For example it can be SIGINT to stop the programm. Programm analyzes
- * this var and can exit. Library function knows nothing about global vars or
- * signal handlers so user can create special function that returns 0 if nothing
- * is happened and !=0 if system call (or function in our case) must be
- * interrupted. So isbreak_func() is a such function. Library function will call
- * it to determine if it must to be interrupted due to signal. Additionally
- * sigmask allows only interested signals while data sending.
- *
- * @sa faux_send()
- * @sa pselect()
- * @param [in] fd Socket.
- * @param [in] buf Buffer to write.
- * @param [in] n Number of bytes to write.
- * @param [in] timeout Send timeout.
- * @param [in] sigmask Signal mask to set while pselect() call.
- * @param [in] isbreak_func Function returns !=0 if call must be interrupted.
- * @return Number of bytes written or < 0 on error.
- */
- ssize_t faux_send_block(int fd, const void *buf, size_t n,
- const struct timespec *timeout, const sigset_t *sigmask,
- int (*isbreak_func)(void))
- {
- sigset_t all_sigmask = {}; // All signals mask
- sigset_t orig_sigmask = {}; // Saved signal mask
- ssize_t bytes_num = 0;
- assert(fd != -1);
- assert(buf);
- if ((-1 == fd) || !buf)
- return -1;
- if (0 == n)
- return 0;
- // Block signals to prevent race conditions right before pselect()
- // Catch signals while pselect() only
- // Now blocks all signals
- sigfillset(&all_sigmask);
- setsigmask(SIG_SETMASK, &all_sigmask, &orig_sigmask);
- // Signal handler can set var to interrupt exchange.
- // Get value of this var by special callback function.
- if (isbreak_func && isbreak_func())
- return -1;
- bytes_num = faux_send(fd, buf, n, timeout, sigmask);
- setsigmask(SIG_SETMASK, &orig_sigmask, NULL);
- return bytes_num;
- }
- /** @brief Sends "struct iovec" data blocks to socket.
- *
- * This function is like a faux_send() function but uses scatter/gather.
- *
- * @see faux_send().
- * @param [in] fd Socket.
- * @param [in] iov Array of "struct iovec" structures.
- * @param [in] iovcnt Number of iov array members.
- * @param [in] timeout Send timeout.
- * @param [in] sigmask Signal mask to set while pselect() call.
- * @return Number of bytes written.
- * < total_length then insufficient space, timeout or
- * error (but some data were already sent).
- * < 0 - error.
- */
- ssize_t faux_sendv(int fd, const struct iovec *iov, int iovcnt,
- const struct timespec *timeout, const sigset_t *sigmask)
- {
- size_t total_written = 0;
- int i = 0;
- struct timespec now = {};
- struct timespec deadline = {};
- assert(fd != -1);
- if (fd == -1)
- return -1;
- if (!iov)
- return -1;
- if (iovcnt == 0)
- return 0;
- // Calculate deadline - the time when timeout must occur.
- if (timeout) {
- faux_timespec_now(&now);
- faux_timespec_sum(&deadline, &now, timeout);
- }
- for (i = 0; i < iovcnt; i++) {
- ssize_t bytes_written = 0;
- struct timespec *send_timeout = NULL;
- struct timespec to = {};
- if (timeout) {
- if (faux_timespec_before_now(&deadline))
- break; // Timeout already occured
- faux_timespec_now(&now);
- faux_timespec_diff(&to, &deadline, &now);
- send_timeout = &to;
- }
- if (iov[i].iov_len == 0)
- continue;
- bytes_written = faux_send(fd, iov[i].iov_base, iov[i].iov_len,
- send_timeout, sigmask);
- if (bytes_written < 0) { // Error
- if (total_written != 0)
- break;
- return -1;
- }
- if (0 == bytes_written) // Insufficient space or timeout
- break;
- total_written += bytes_written;
- }
- return total_written;
- }
- /** @brief Sends "struct iovec" data blocks to socket. It removes signal races.
- *
- * This function is like a faux_send_block() function but uses scatter/gather.
- *
- * @see faux_send_block().
- * @param [in] fd Socket.
- * @param [in] iov Array of "struct iovec" structures.
- * @param [in] iovcnt Number of iov array members.
- * @param [in] timeout Send timeout.
- * @param [in] sigmask Signal mask to set while pselect() call.
- * @param [in] isbreak_func Function returns !=0 if call must be interrupted.
- * @return Number of bytes written.
- * < total_length then insufficient space, timeout or
- * error (but some data were already sent).
- * < 0 - error.
- */
- ssize_t faux_sendv_block(int fd, const struct iovec *iov, int iovcnt,
- const struct timespec *timeout, const sigset_t *sigmask,
- int (*isbreak_func)(void))
- {
- sigset_t all_sigmask = {}; // All signals mask
- sigset_t orig_sigmask = {}; // Saved signal mask
- ssize_t bytes_num = 0;
- assert(fd != -1);
- if ((-1 == fd))
- return -1;
- if (!iov)
- return -1;
- if (iovcnt == 0)
- return 0;
- // Block signals to prevent race conditions right before pselect()
- // Catch signals while pselect() only
- // Now blocks all signals
- sigfillset(&all_sigmask);
- setsigmask(SIG_SETMASK, &all_sigmask, &orig_sigmask);
- // Signal handler can set var to interrupt exchange.
- // Get value of this var by special callback function.
- if (isbreak_func && isbreak_func())
- return -1;
- bytes_num = faux_sendv(fd, iov, iovcnt, timeout, sigmask);
- setsigmask(SIG_SETMASK, &orig_sigmask, NULL);
- return bytes_num;
- }
- /** @brief Receives data from the socket.
- *
- * Function has the same parameters and features like faux_send() function
- * but it receives data.
- *
- * @sa faux_send()
- */
- ssize_t faux_recv(int fd, void *buf, size_t n,
- const struct timespec *timeout, const sigset_t *sigmask)
- {
- size_t total_readed = 0;
- size_t left = n;
- void *data = buf;
- struct timespec now = {};
- struct timespec deadline = {};
- assert(fd != -1);
- assert(buf);
- if ((-1 == fd) || !buf)
- return -1;
- if (0 == n)
- return 0;
- // Calculate deadline - the time when timeout must occur.
- if (timeout) {
- faux_timespec_now(&now);
- faux_timespec_sum(&deadline, &now, timeout);
- }
- do {
- ssize_t bytes_readed = 0;
- struct pollfd fds = {};
- struct timespec *poll_timeout = NULL;
- struct timespec to = {};
- int sn = 0;
- if (timeout) {
- if (faux_timespec_before_now(&deadline))
- break; // Timeout already occured
- faux_timespec_now(&now);
- faux_timespec_diff(&to, &deadline, &now);
- poll_timeout = &to;
- }
- // Handlers for poll()
- faux_bzero(&fds, sizeof(fds));
- fds.fd = fd;
- fds.events = POLLIN;
- sn = ppoll(&fds, 1, poll_timeout, sigmask);
- // When kernel can't allocate some internal structures it can
- // return EAGAIN so retry.
- if ((sn < 0) && (EAGAIN == errno))
- continue;
- // All unneded signals are masked so don't process EINTR
- // in special way. Just break the loop
- if (sn < 0)
- break;
- // Timeout: break the loop. User don't want to wait any more
- if (0 == sn)
- break;
- // Some unknown event (not POLLIN). So retry polling
- if (!(fds.revents & POLLIN))
- continue;
- // The send() call is non-blocking but it's not obvious that
- // it can't return EINTR. Probably it can. Due to the fact the
- // call is non-blocking re-send() on any signal i.e. any EINTR.
- do {
- bytes_readed = recv(fd, data, left, MSG_DONTWAIT | MSG_NOSIGNAL);
- } while ((bytes_readed < 0) && (EINTR == errno));
- if (bytes_readed < 0)
- break;
- // EOF
- if (0 == bytes_readed)
- break;
- data += bytes_readed;
- left = left - bytes_readed;
- total_readed += bytes_readed;
- } while (left > 0);
- return total_readed;
- }
- /** @brief Receives data from the socket. It removes signal races.
- *
- * Function has the same parameters and features like faux_send_block() function
- * but it receives data.
- *
- * @sa faux_send_block()
- */
- ssize_t faux_recv_block(int fd, void *buf, size_t n,
- const struct timespec *timeout, const sigset_t *sigmask,
- int (*isbreak_func)(void))
- {
- sigset_t all_sigmask = {}; // All signals mask
- sigset_t orig_sigmask = {}; // Saved signal mask
- ssize_t bytes_num = 0;
- assert(fd != -1);
- assert(buf);
- if ((-1 == fd) || !buf)
- return -1;
- if (0 == n)
- return 0;
- // Block signals to prevent race conditions right before pselect()
- // Catch signals while pselect() only
- // Now blocks all signals
- sigfillset(&all_sigmask);
- setsigmask(SIG_SETMASK, &all_sigmask, &orig_sigmask);
- // Signal handler can set var to interrupt exchange.
- // Get value of this var by special callback function.
- if (isbreak_func && isbreak_func())
- return -1;
- bytes_num = faux_recv(fd, buf, n, timeout, sigmask);
- setsigmask(SIG_SETMASK, &orig_sigmask, NULL);
- return bytes_num;
- }
- /** @brief Receives data from the socket. Uses scatter/gather.
- *
- * Function has the same parameters and features like faux_sendv() function
- * but it receives data.
- *
- * @sa faux_sendv()
- */
- ssize_t faux_recvv(int fd, struct iovec *iov, int iovcnt,
- const struct timespec *timeout, const sigset_t *sigmask)
- {
- size_t total_readed = 0;
- int i = 0;
- struct timespec now = {};
- struct timespec deadline = {};
- assert(fd != -1);
- if (fd == -1)
- return -1;
- if (!iov)
- return -1;
- if (iovcnt == 0)
- return 0;
- // Calculate deadline - the time when timeout must occur.
- if (timeout) {
- faux_timespec_now(&now);
- faux_timespec_sum(&deadline, &now, timeout);
- }
- for (i = 0; i < iovcnt; i++) {
- ssize_t bytes_readed = 0;
- struct timespec *recv_timeout = NULL;
- struct timespec to = {};
- if (timeout) {
- if (faux_timespec_before_now(&deadline))
- break; // Timeout already occured
- faux_timespec_now(&now);
- faux_timespec_diff(&to, &deadline, &now);
- recv_timeout = &to;
- }
- if (iov[i].iov_len == 0)
- continue;
- bytes_readed = faux_recv(fd, iov[i].iov_base, iov[i].iov_len,
- recv_timeout, sigmask);
- if (bytes_readed < 0) { // Error
- if (total_readed != 0)
- break;
- return -1;
- }
- if (0 == bytes_readed) // EOF or timeout
- break;
- total_readed += bytes_readed;
- }
- return total_readed;
- }
- /** @brief Receives data from the socket. Uses sactter/gather, removes races.
- *
- * Function has the same parameters and features like faux_sendv_block()
- * function but it receives data.
- *
- * @sa faux_sendv_block()
- */
- ssize_t faux_recvv_block(int fd, struct iovec *iov, int iovcnt,
- const struct timespec *timeout, const sigset_t *sigmask,
- int (*isbreak_func)(void))
- {
- sigset_t all_sigmask = {}; // All signals mask
- sigset_t orig_sigmask = {}; // Saved signal mask
- ssize_t bytes_num = 0;
- assert(fd != -1);
- if ((-1 == fd))
- return -1;
- if (!iov)
- return -1;
- if (iovcnt == 0)
- return 0;
- // Block signals to prevent race conditions right before pselect()
- // Catch signals while pselect() only
- // Now blocks all signals
- sigfillset(&all_sigmask);
- setsigmask(SIG_SETMASK, &all_sigmask, &orig_sigmask);
- // Signal handler can set var to interrupt exchange.
- // Get value of this var by special callback function.
- if (isbreak_func && isbreak_func())
- return -1;
- bytes_num = faux_recvv(fd, iov, iovcnt, timeout, sigmask);
- setsigmask(SIG_SETMASK, &orig_sigmask, NULL);
- return bytes_num;
- }
|