How to get poll()'s POLLHUP equivalent with select()? - c++

I'm currently porting some code from Linux to Windows (with MinGW).
From what I understand, MinGW doesn't support poll(), which was used in the original, so I'm rewriting everything for select().
And now I stumbled upon if (pfd[i].revents & (POLLERR|POLLHUP))...
How can I get the equivalent of this condition with select() - or alternatively, with whatever the winsock api or MinGW provides? The POLLERR part is simple enough; if(FD_ISSET (i, &error_fd_set)) but I'm at loss about the POLLHUP part.

You can't. You have to use the "normal" way to find out whether the connection has been closed, that is reading from it.
In terms of code it'd be:
int rc = select(max_fd + 1, read_set, ..., ..., ...);
// check rc
for (int i = 0; i <= max_fd; ++i) {
if (FD_ISSET(i, &read_set)) { // data incoming on i
int rc = read(i, ..., ...); // or recv, if you use some flag
if (rc == 0) {
// i hung up
}
}
}
Otherwise, you can use WSAPoll which offers an API like the one you'd expect in UNIX-like systems.
Further information on WSAPoll.

According to my copy of The Linux Programming Interface, kernel poll events are mapped to select() events like so:
// Ready for reading
#define POLLIN_SET (POLLRDNORM | POLLRDBAND | POLLIN | POLLHUP | POLLERR)
// Ready for writing
#define POLLOUT_SET (POLLWRBAND | POLLWRNORM | POLLOUT | POLLERR)
// Exceptional condition
#define POLLEX_SET (POLLPRI)
So this suggests that you need to check the 'ready' event. To actually distinguish between POLLHUP, POLLIN, and POLLIN | POLLHUP, you can use the following chart from the book:
| ----- Condition or event ---- |
Data in pipe? | Write end open? | select() | poll()
no no r | POLLHUP
yes yes r | POLLIN
yes no r | POLLIN | POLLHUP

Related

Non blocking pseudo terminal, recovery after POLLHUP

I create a new pseudo-terminal by opening /dev/ptmx with open() function and O_RDWR | O_NOCTTY | O_NONBLOCK flags. Then I use poll() function to wait for incoming data from the remote end:
struct pollfd pollFileDescriptors[numberOfTerminals];
for (unsigned terminalIndex = 0; terminalIndex < numberOfTerminals; terminalIndex++) {
pollFileDescriptors[terminalIndex].fd = terminals[terminalIndex].getFileDescriptor();
pollFileDescriptors[terminalIndex].events = POLLIN | POLLPRI | POLLRDNORM | POLLRDBAND;
}
int ready = poll(pollFileDescriptors, terminals.getNumberOfTerminals(), timeoutMSec);
Everything works like a dream until the remote end closes connection. In such a case the poll() function returns all the time with POLLHUP revents flag. This is by design, however what could I do to make it operating as before i.e. waiting for another process to open and use the pseudo-terminal. I mean it waits, but returns immediately with POLLHUP set. On the other hand, if I close the file descriptor I have no guarantee of receiving the same pseudo-terminal-id that after reopening the /dev/ptmx. Is there any way to remove the POLLHUP revents flag?
I found a similar question: Poll() on Named Pipe returns with POLLHUP constantly and immediately , however I am using already O_RDWR as described there but it doesn't help as in case of named pipes.
The issue can be easily solved by reopening the pseudo-terminal right after it is created. The POLLHUP won't appear as long as at least one writer exists so we can do this by ourselves with open() and ptsname():
// Create a new pseudo terminal
int fileDescriptor = open("/dev/ptmx", O_RDWR | O_NOCTTY | O_NONBLOCK);
grantpt(fileDescriptor);
unlockpt(fileDescriptor);
// Reopen it for write
const char *targetPath = ptsname(fileDescriptor);
int dummyWriterFileDescriptor = open(fileName.c_str(), O_WRONLY | O_NOCTTY | O_NONBLOCK);

linux pseudo terminal (open select read)

I have a following scenario: Someone creates a pseudo terminal via opening /dev/ptmx. New terminal is created and named for example /dev/pts/2. Then, in my program I open /dev/pts/2 for reading. But I also open other devices for reading and use select() function to wait for any incoming data. The select have also some timeout specified for performing other stuff when no data arrives for too long. After successful select i read data using read() function and then print it on the screen.
I encountered an issue if the pseudo terminal is closed by the one who created it. In this case select function ends immediately indicating success as well as read ends indicating "no data" by returning zero. The issue imho is that neither select nor read returns error in such case. How should I handle this to detect that the terminal is no longer existing?
Status processData()
{
fd_set readFileDescriptorSet; // defined somewhere else
int maxFileDescriptor; // defined somewhere else
struct timeval timeout; // defined somewhere else
int ret = select(maxFileDescriptor + 1, &readFileDescriptorSet, nullptr, nullptr, &timeout);
if (!ret) // timeout
return Status::success();
if (ret < 0) // error from select()
return Status::error("select error");
ssize_t rd;
char buff[10];
do {
rd = read(interfaces.serialPort.getFileDescriptor(), buff, sizeof(buff) - 1);
if (rd > 0) { // some data has been read
buff[rd] = '\0';
std::cout << buff;
}
} while (rd > 0);
if (rd < 0) // error from read()
return Status::error("read error");
return Status::success();
}
While the way I open the pseudo terminal is following:
Status internalOpen(std::string fileName)
{
close();
fileDescriptor = ::open(fileName.c_str(), O_RDWR | O_NOCTTY | O_NONBLOCK);
if (fileDescriptor == -1)
return Status::error("Terminal::internalOpen::open('" + fileName + "')");
struct termios attributes;
if (tcgetattr(fileDescriptor, &attributes))
return Status::error("Terminal::internalOpen::tcgetattr()");
setAttributes(attributes);
if (tcsetattr(fileDescriptor, TCSANOW, &attributes))
return Status::error("Terminal::internalOpen::tcsetattr()");
return Status::success();
}
void setAttributes(struct termios &attributes)
{
cfmakeraw(&attributes);
cfsetspeed(&attributes, Config::baudRate);
attributes.c_iflag &= ~(IXOFF | IXANY);
attributes.c_oflag &= ~(ONLCR);
attributes.c_lflag &= ~(ECHOE);
attributes.c_cflag &= ~(CSTOPB | CRTSCTS);
attributes.c_cflag |= CREAD | CLOCAL;
attributes.c_cc[VMIN] = 0;
attributes.c_cc[VTIME] = 0;
}
After select() returns indicating that there's something to be read, the shown code loops repeatedly trying to read() from the non-blocking file descriptor until it is 0:
do {
rd = read( ...
} while (rd > 0);
That's certainly reasonable. Except that the closed connection results in the very first read() returning 0, which the shown logic cannot discriminate.
All that's really needed here is to keep track of whether anything has been read, prior read() returning 0. But if read() returned 0 right off the bat, your goose is cooked.
Additionally, there a few other improvements will make things more robust.
After select() returns, actually check if the file descriptor's bit remains set in the readFileDescriptorSet. The shown logic simply assumes that it is, by checking for all other possibilities. Still, this is somewhat fragile. It's easy to forget this assumption if something tangentially related gets modified (i.e., another fle descriptor gets thrown into the mix).
Use poll() instead of select(), and explicitly check for POLLHUP|POLLRDHUP in revents. The file descriptor closure condition is more explicitly called out, in the poll() interface.

Hardware flow control with termios (CRTSCTS) for UART Device

right now I'm communicating with a device over UART in C++ on a Udoo Neo. For this I'm using termios to set up the connection and write data to the device.
For this purpose I want to use hardware flow control and have set the flag (CRTSCTS) with termios.
For the hardaware flow control I've connected the device RTS line to the boards CTS and I've also checked via oscilloscope, that the device is giving me an active high, if it is not ready to read.
The problem is that I still loose bytes in the following example of just spamming the device with numbers, but the boards output says that everything was written correctly.
I thought the UART would be blocked when using HW flow control, so that no information is lost. Am I not understanding this correctly - or is there an error in the code?
Thanks for the help
const char dev[] = "/dev/ttymxc4";
int main(int argc, char **argv) {
int fd;
struct termios t; ///< control structure for a general asynchronous interface
// edited code
tcgetattr(fd, &t);
t.c_iflag &= ~(IGNBRK | BRKINT | ICRNL |
INLCR | PARMRK | INPCK | ISTRIP | IXON);
t.c_oflag = 0;
t.c_cflag &= ~(CSIZE | PARENB);
t.c_cflag |= (CS8 | CRTSCTS);
// edited code
t.c_cflag |= (CLOCAL | CREAD);
t.c_lflag &= ~(ECHO | ECHONL | ICANON | IEXTEN | ISIG);
t.c_cc[VMIN] = 0;
t.c_cc[VTIME] = 0;
cfsetispeed(&t,B57600); /* normal shall be: B115200 Baud */
fd = ::open(dev, O_RDWR);
if (fd==-1) {
printf("UART: cannot open file: %s\n",dev);
return -1;
}
tcsetattr(fd,TCSANOW, &t);
// edited code
fcntl(fd, F_SETFL, 0);
int count = 0;
while (true) {
count++;
std::stringstream output;
output << count << ",";
::write(fd, output.str().c_str(), output.str().length());
printf("%d, writing: %s\n", fd, output.str().c_str());
usleep(10000);
}
return 0;
}
Referring to the links by #sawdust, the HW flow control is manipulated via
CCTS_OFLOW and CRTS_IFLOW via libc-doc
Macro: tcflag_t CCTS_OFLOW
If this bit is set, enable flow control of output based on the CTS wire (RS232 protocol).
Macro: tcflag_t CRTS_IFLOW
If this bit is set, enable flow control of input based on the RTS wire (RS232 protocol).
CNEW_RTSCTS and CRTSCTS via SerProgGuide
Some versions of UNIX support hardware flow control using the CTS
(Clear To Send) and RTS (Request To Send) signal lines. If the
CNEW_RTSCTS or CRTSCTS constants are defined on your system then
hardware flow control is probably supported. Do the following to
enable hardware flow control:
options.c_cflag |= CNEW_RTSCTS; /* Also called CRTSCTS */
Note the "Some versions..." and "...is probably supported."
On my particular cross compilation toolchain (Linaro GCC 6.5-2018.12) if I grep for these values, CRTSCTS is not documented, but defined, CCTS_OFLOW is in a lot of info files, but in no header files...
libc/usr/include/bits/termios.h:
174:# define CRTSCTS 020000000000 /* flow control */
As you have said in your comment
... I just thought this would be handled by the kernel?
I am seeing the phenomenon, that even if I add the relevant rts/cts properties in the device-tree ({rts,cts}-gpios or uart-has-rtscts), the command stty -a -F /dev/ttyS still reports back -crtscts meaning that the RTS/CTS handshake is disabled, so even without the userspace application this doesn't seem to be a trivial config. (Kernel 5.4)

mq_receive returning EAGAIN when data is present

I have an mqueue which has 2 messages in it but the call to mq_receive will not pull data. I don't have any previous experience with this message queue so forgive my ignorance. I believe the send side is working fine as echoing the "file" does show information.
QSIZE:48 NOTIFY:1 SIGNO:0 NOTIFY_PID:5741
This is 2 messages without a message signal being generated. The PID is the process that will be calling mq_receive.
For reference, here is the open code.
msgq_attr.mq_maxmsg = MAX_NUM_MESSAGES; // 20
msgq_attr.mq_msgsize = MAX_MSG_SIZE; // 256
mqrcv_id = mq_open(queue_name, O_RDONLY | O_NONBLOCK,
S_IRWXU | S_IRWXO, msgq_attr);
notify.sigev_notify = SIGEV_NONE;
notify.sigev_notify_attributes = NULL;
mq_notify(mqrcv_id, &notify);
And here is the receive.
int msgSize = mq_receive(mqrcv_id, buffer,
MAX_MSG_SIZE, &msgprio);
msgSize always returns -1 with errno EAGAIN. From the documentation, this should mean that the queue has no messages in it. Note that it is NONBLOCKING.
The issue is a code bug. The call to open should be
mqrcv_id = mq_open(queue_name, O_RDONLY | O_NONBLOCK,
S_IRWXU | S_IRWXO, &msgq_attr);

Serial port is stuck at wait(4,

I have this implementation in C++ for linux for initializing a serial port:
void setupSerialPort()
{
fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK);
memset(&tio, 0, sizeof(tio));
tio.c_iflag = 0;
tio.c_oflag = 0;
tio.c_cflag = CS8|CREAD|CLOCAL;
tio.c_lflag = 0;
tio.c_cc[VMIN] = 0;
tio.c_cc[VTIME] = 0;
fd = open(SERIALPORT, O_RDWR | O_NONBLOCK) ;
fcntl(fd , F_SETFL, 0); // read data in the buffer per chunk
cfsetospeed(&tio, B4800); // 4800 baud
cfsetispeed(&tio, B4800); // 4800 baud
tcsetattr(fd , TCSANOW, &tio);
}
Sometimes, the serial port reading is stuck and I use 'strace' to see what is going on. the problem is:
strace -p 9454
Process 9454 attached - interrupt to quit
wait4(-1, ^C <unfinished ...>
Process 9454 detached
How can I avoid the problem (it does not happen all the time)?
The problem occurring randomly indicates that the call to open is failing. So check the return values from open call and other remaining calls.
if (fd < 0) return;
Also ensure clean close of the serial port.
Syscalls involving IO could get stuck specially with network IO. Since you are using NON_BLOCKING mode that shouldnt be the case , The complete code would have been helpful here. Personally i had this situation before and I used timer to generate SIG_USR which used to end the blocking call with an error. Pseudo code for that algo is as follows:
initiateTimer ( timeout)
myfunctionCall(){
someSysCall();
}
if(!signalled){
disableTimer();
}else{
checkState();
if(fatal)
die;
}
My suggestions... Open the device with:
int fd = TEMP_FAILURE_RETRY(open(ttyName, O_RDWR | O_NOCTTY | O_NONBLOCK));
The macro TEMP_FAILURE_RETRY is needed to protect open, close, read and select from signal interruptions. Read about the macro if you use libc. Include O_NOCTTY flag to avoid the device becoming the controlling port of the process. Check return value for valid fd.
C.