support 2nd RCPT TO and PRDR
[ghsmtp.git] / POSIX.cpp
blob63e563611f0f6eabefaabbc89f78a3119cf9e1fb
1 #include "POSIX.hpp"
3 #include <glog/logging.h>
5 #include <fcntl.h>
6 #include <sys/select.h>
8 using std::chrono::duration_cast;
9 using std::chrono::milliseconds;
10 using std::chrono::seconds;
11 using std::chrono::system_clock;
12 using std::chrono::time_point;
14 void POSIX::set_nonblocking(int fd)
16 int flags;
17 PCHECK((flags = fcntl(fd, F_GETFL, 0)) != -1);
18 if (0 == (flags & O_NONBLOCK)) {
19 PCHECK(fcntl(fd, F_SETFL, flags | O_NONBLOCK) != -1);
23 bool POSIX::input_ready(int fd_in, milliseconds wait)
25 auto fds{fd_set{}};
26 FD_ZERO(&fds);
27 FD_SET(fd_in, &fds);
29 auto tv{timeval{}};
30 tv.tv_sec = duration_cast<seconds>(wait).count();
31 tv.tv_usec = (wait.count() % 1000) * 1000;
33 int puts;
34 PCHECK((puts = select(fd_in + 1, &fds, nullptr, nullptr, &tv)) != -1);
36 return 0 != puts;
39 bool POSIX::output_ready(int fd_out, milliseconds wait)
41 auto fds{fd_set{}};
42 FD_ZERO(&fds);
43 FD_SET(fd_out, &fds);
45 auto tv{timeval{}};
46 tv.tv_sec = duration_cast<seconds>(wait).count();
47 tv.tv_usec = (wait.count() % 1000) * 1000;
49 int puts;
50 PCHECK((puts = select(fd_out + 1, nullptr, &fds, nullptr, &tv)) != -1);
52 return 0 != puts;
55 std::streamsize POSIX::read(int fd,
56 char* s,
57 std::streamsize n,
58 std::function<void(void)> read_hook,
59 std::chrono::milliseconds timeout,
60 bool& t_o)
62 auto const start = system_clock::now();
63 auto const end_time = start + timeout;
65 for (;;) {
66 auto const n_ret = ::read(fd, static_cast<void*>(s), n);
68 if (n_ret == -1) {
69 switch (errno) {
70 case EINTR: break; // try read again
72 case ECONNRESET:
73 LOG(WARNING) << "read(2) raised ECONNRESET";
74 return -1;
76 default:
77 PCHECK((errno == EWOULDBLOCK) || (errno == EAGAIN))
78 << "error from read(2)";
81 else if (n_ret >= 0) {
82 return n_ret;
85 auto const now = system_clock::now();
86 if (now < end_time) {
87 auto const time_left = duration_cast<milliseconds>(end_time - now);
88 read_hook();
89 if (input_ready(fd, time_left))
90 continue; // try read again
92 t_o = true;
93 LOG(WARNING) << "read(2) timed out";
94 return -1;
98 std::streamsize POSIX::write(int fd,
99 const char* s,
100 std::streamsize n,
101 std::chrono::milliseconds timeout,
102 bool& t_o)
104 auto const start = system_clock::now();
105 auto const end_time = start + timeout;
107 auto written = std::streamsize{};
109 for (;;) {
110 auto const n_ret = ::write(fd, static_cast<const void*>(s), n - written);
112 if (n_ret == -1) {
113 switch (errno) {
114 case EINTR: break; // try write again
116 case ECONNRESET:
117 LOG(WARNING) << "write(2) raised ECONNRESET";
118 return -1;
120 default:
121 PCHECK((errno == EWOULDBLOCK) || (errno == EAGAIN))
122 << "error from write(2)";
125 else if (n_ret == 0) {
126 LOG(WARNING) << "zero write";
127 } else {
128 s += n_ret;
129 written += n_ret;
132 if (written == n)
133 return n;
135 auto const now = system_clock::now();
136 if (now < end_time) {
137 auto const time_left = duration_cast<milliseconds>(end_time - now);
138 if (output_ready(fd, time_left))
139 continue; // write some more
141 t_o = true;
142 LOG(WARNING) << "write(2) time out";
143 return -1;