Understand V4L2 request buffer number - c

I'm newbie in using V4L2 on embedded device and I have two questions related to V4L2 library usage:
First is the buffer allocation for v4l2_requestbuffers:
struct v4l2_requestbuffers req;
CLEAR(req);
req.count = 4;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
and later it is used by:
if (req.count < 2) {
fprintf(stderr, "Insufficient buffer memory on %s\n",
dev_name);
exit(EXIT_FAILURE);
}
buffers = calloc(req.count, sizeof(*buffers));
if (!buffers) {
fprintf(stderr, "Out of memory\n");
exit(EXIT_FAILURE);
}
for (n_buffers = 0; n_buffers < req.count; ++n_buffers) {
struct v4l2_buffer buf;
CLEAR(buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = n_buffers;
if (-1 == xioctl(fd, VIDIOC_QUERYBUF, &buf))
errno_exit("VIDIOC_QUERYBUF");
buffers[n_buffers].length = buf.length;
buffers[n_buffers].start =
mmap(NULL /* start anywhere */,
buf.length,
PROT_READ | PROT_WRITE /* required */,
MAP_SHARED /* recommended */,
fd, buf.m.offset);
if (MAP_FAILED == buffers[n_buffers].start)
errno_exit("mmap");
}
For capturing still image, req.count is 1. For capturing video, do we need to tune the req.count number to a number that we get good fps? That's mean we have to change the req.count from 2 (in this example) to a suitable number to get acceptable fps?
Second question is the timeout setting, as used in the mainloop:
static void mainloop(void)
{
unsigned int count;
count = frame_count;
while (count-- > 0) {
for (;;) {
fd_set fds;
struct timeval tv;
int r;
FD_ZERO(&fds);
FD_SET(fd, &fds);
/* Timeout. */
tv.tv_sec = 2;
tv.tv_usec = 0;
r = select(fd + 1, &fds, NULL, NULL, &tv);
if (-1 == r) {
if (EINTR == errno)
continue;
errno_exit("select");
}
if (0 == r) {
fprintf(stderr, "select timeout\n");
exit(EXIT_FAILURE);
}
if (read_frame())
break;
/* EAGAIN - continue select loop. */
}
}
}
If I set
/* Timeout. */
tv.tv_sec = 0;
tv.tv_usec = 0;
r = select(fd + 1, &fds, NULL, NULL, &tv);
My CPU load is 100%, I have to put it is 1 or 2 to make decrease the CPU load. If I remove timeout select() function, my CPU load is 100% again.
From my reading, the select() operation cost more CPU cycles but how can we remove select() and timeout setting in the mainloop()? Because when I look into other V4L2 capturing code from senior software engineer, I don't see they use timeout function as in the example above.
So my second question is how to avoid using timeout select() but still keep low CPU load .

Related

VFIO interrupts using eventfd: can eventfd semaphore behaviour be maintained?

I have a program running on a QEMU VM. The program running inside this VM gets notified by a program on the host via interrupts and using QEMU ivshmem. The program on the host creates an eventfd and sends this file descriptor to QEMU when the VM starts. The program in the guest then opens a VFIO group device and sets an interrupt request fd on this device. We can then add the interrupt fd to epoll and epoll_wait to wait for notifications from the host.
The thing is that I want a 1-1 matching between the times the host writes to the eventfd and the number of events that are signaled in epoll_wait. For this I decided to use EFD_SEMAPHORE for the evenfds on the host and the guest. From my understanding, every time I write an 8 byte integer with value 1, the eventfd_counter is incremented by 1. Then every time the eventfd is read, the counter is decremented by 1 (different from a regular eventfd where each read clears the whole counter). For some reason, I am not getting the desired behaviour, so I was wondering if either eventfds with the EFD_SEMAPHORE flags are not properly supported by VFIO or QEMUs ivshmem.
Below is a simplified version of the parts I think are relevant and how I setup the notification system. I hope the code below is not too verbose. I tried to reduce the number of irrelevant parts (there is too much other code in the middle that is not particularly relevant to the problem) but not 100% sure what might be relevant or not.
Code host uses to signal guest
int ivshmem_uxsocket_send_int(int fd, int64_t i)
{
int n;
struct iovec iov = {
.iov_base = &i,
.iov_len = sizeof(i),
};
struct msghdr msg = {
.msg_name = NULL,
.msg_namelen = 0,
.msg_iov = &iov,
.msg_iovlen = 1,
.msg_control = NULL,
.msg_controllen = 0,
.msg_flags = 0,
};
if ((n = sendmsg(fd, &msg, 0)) != sizeof(int64_t))
{
return -1;
}
return n;
}
int ivshmem_uxsocket_sendfd(int uxfd, int fd, int64_t i)
{
int n;
struct cmsghdr *chdr;
/* Need to pass at least one byte of data to send control data */
struct iovec iov = {
.iov_base = &i,
.iov_len = sizeof(i),
};
/* Allocate a char array but use a union to ensure that it
is aligned properly */
union {
char buf[CMSG_SPACE(sizeof(fd))];
struct cmsghdr align;
} cmsg;
memset(&cmsg, 0, sizeof(cmsg));
/* Add control data (file descriptor) to msg */
struct msghdr msg = {
.msg_name = NULL,
.msg_namelen = 0,
.msg_iov = &iov,
.msg_iovlen = 1,
.msg_control = &cmsg,
.msg_controllen = sizeof(cmsg),
.msg_flags = 0,
};
/* Set message header to describe ancillary data */
chdr = CMSG_FIRSTHDR(&msg);
chdr->cmsg_level = SOL_SOCKET;
chdr->cmsg_type = SCM_RIGHTS;
chdr->cmsg_len = CMSG_LEN(sizeof(int));
memcpy(CMSG_DATA(chdr), &fd, sizeof(fd));
if ((n = sendmsg(uxfd, &msg, 0)) != sizeof(i))
{
return -1;
}
return n;
}
/* SETUP IVSHMEM WITH QEMU AND PASS THE EVENTFD USED TO
NOTIFY THE GUEST */
int ivshmem_uxsocket_accept()
{
int ret;
int cfd, ifd, nfd;
int64_t version = IVSHMEM_PROTOCOL_VERSION;
uint64_t hostid = HOST_PEERID;
int vmid = 0
/* Accept connection from qemu ivshmem */
if ((cfd = accept(uxfd, NULL, NULL)) < 0)
{
return -1;
}
/* Send protocol version as required by qemu ivshmem */
ret = ivshmem_uxsocket_send_int(cfd, version);
if (ret < 0)
{
return -1;
}
/* Send vm id to qemu */
ret = ivshmem_uxsocket_send_int(cfd, vmid);
if (ret < 0)
{
return -1;
}
/* Send shared memory fd to qemu */
ret = ivshmem_uxsocket_sendfd(cfd, shm_fd, -1);
if (ret < 0)
{
return -1;
}
/* Eventfd used by guest to notify host */
if ((nfd = eventfd(0, EFD_SEMAPHORE | EFD_NONBLOCK)) < 0)
{
return -1;
}
/* Ivshmem protocol requires to send host id
with the notify fd */
ret = ivshmem_uxsocket_sendfd(cfd, nfd, hostid);
if (ret < 0)
{
return -1;
}
/* THIS IS THE EVENTFD OF INTEREST TO US: USED BY HOST
TO NOTIFY GUEST */
if ((ifd = eventfd(0, EFD_SEMAPHORE | EFD_NONBLOCK)) < 0)
{
return -1;
}
ret = ivshmem_uxsocket_sendfd(cfd, ifd, vmid);
if (ret < 0)
{
return -1;
}
if (epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev) < 0)
{
return -1;
}
return 0;
}
/* NOW EVERY TIME WE WANT TO NOTIFY THE GUEST
WE CALL THE FOLLOWING FUNCTION */
int notify_guest(int fd)
{
int ret;
uint64_t buf = 1;
ret = write(fd, &buf, sizeof(uint64_t));
if (ret < sizeof(uint64_t))
{
return -1;
}
return 0;
}
Code guest uses to receive notifications from host
/* THIS FUNCTION SETS THE IRQ THAT RECEIVES THE
NOTIFICATIONS FROM THE HOST */
int vfio_set_irq(int dev)
{
int fd;
struct vfio_irq_set *irq_set;
char buf[sizeof(struct vfio_irq_set) + sizeof(int)];
if ((fd = eventfd(0, EFD_SEMAPHORE | EFD_NONBLOCK)) < 0)
{
return -1;
}
irq_set = (struct vfio_irq_set *) buf;
irq_set->argsz = sizeof(buf);
irq_set->flags = VFIO_IRQ_SET_DATA_EVENTFD | VFIO_IRQ_SET_ACTION_TRIGGER;
irq_set->index = 2;
irq_set->start = 0;
irq_set->count = 1;
memcpy(&irq_set->data, &fd, sizeof(int));
if (ioctl(dev, VFIO_DEVICE_SET_IRQS, irq_set) < 0)
{
return -1;
}
return irq_fd;
}
/* The guest sets up the ivshmem region from QEMU and sets the
interrupt request. */
int vfio_init()
{
int cont, group, irq_fd;
struct epoll_event ev;
struct vfio_group_status g_status = { .argsz = sizeof(g_status) };
struct vfio_device_info device_info = { .argsz = sizeof(device_info) };
/* Create vfio container */
if ((cont = open("/dev/vfio/vfio", O_RDWR)) < 0)
{
return -1;
}
/* Check API version of container */
if (ioctl(cont, VFIO_GET_API_VERSION) != VFIO_API_VERSION)
{
return -1;
}
if (!ioctl(cont, VFIO_CHECK_EXTENSION, VFIO_NOIOMMU_IOMMU))
{
return -1;
}
/* Open the vfio group */
if((group = open(VFIO_GROUP, O_RDWR)) < 0)
{
return -1;
}
/* Test if group is viable and available */
ioctl(group, VFIO_GROUP_GET_STATUS, &g_status);
if (!(g_status.flags & VFIO_GROUP_FLAGS_VIABLE))
{
return -1;
}
/* Add group to container */
if (ioctl(group, VFIO_GROUP_SET_CONTAINER, &cont) < 0)
{
return -1;
}
/* Enable desired IOMMU model */
if (ioctl(cont, VFIO_SET_IOMMU, VFIO_NOIOMMU_IOMMU) < 0)
{
return -1;
}
/* Get file descriptor for device */
if ((dev = ioctl(group, VFIO_GROUP_GET_DEVICE_FD, VFIO_PCI_DEV)) < 0)
{
return -1;
}
/* Get device info */
if (ioctl(dev, VFIO_DEVICE_GET_INFO, &device_info) < 0)
{
return -1;
}
/* Set interrupt request fd */
if ((irq_fd = vfio_set_irq(dev)) < 0)
{
return -1
}
/* Add interrupt request fd to interest list */
if (vfio_subscribe_irq() < 0)
{
return -1;
}
/* Do other shm setup stuff not related to the interrupt
request */
ev.events = EPOLLIN;
ev.data.ptr = EP_NOTIFY;
ev.data.fd = irq_fd;
if (epoll_ctl(epfd, EPOLL_CTL_ADD, irq_fd, &ev) != 0)
{
return -1;
}
return 0;
}
int ivshmem_drain_evfd(int fd)
{
int ret;
uint64_t buf;
ret = read(fd, &buf, sizeof(uint64_t));
if (ret == 0)
{
return -1;
}
return ret;
}
/* I should get every notification from the host here,
but it seems that not all notifications are going
through. The number of calls to notify_guest does not
match the number of events received from epoll_wait
here */
int notify_poll()
{
int i, n;
struct epoll_event evs[32];
n = epoll_wait(epfd, evs, 32, 0);
for (i = 0; i < n; i++)
{
if (evs[i].events & EPOLLIN)
{
/* Drain evfd */
drain_evfd(irq_fd);
/* Handle notification ... */
handle();
}
}
}

how linux select() system call monitor two files(file descriptors) and how to use timer argument

I need to use select system call where I have to open two files (file descriptors) and do read operation on those files which are ready, I need to use some timeout after every 5ms and read from those files
Here is my sample code:
int main()
{
fd_set readfds,writefds;
ssize_t nbytes,bytes_read;
char buf[20];
int fd_1,fd_2,retval;
struct timeval tv;
fd_1 = open("test1.txt", O_RDWR);
if(fd_1 < 0) {
printf("Cannot open file...\n");
return 0;
}
fd_2 = open("test2.txt", O_RDWR);
if(fd_2 < 0) {
printf("Cannot open file_2...\n");
return 0;
}
/* Wait up to five seconds. */
tv.tv_sec = 5;
tv.tv_usec = 0;
for(;;)
{
retval = select(FD_SETSIZE, &readfds, &writefds, NULL, &tv);
printf("select");
perror("select");
exit(EXIT_FAILURE);
//}
for(int i=0; i < FD_SETSIZE; i++)
{
FD_ZERO(&readfds);
if (FD_ISSET(i, &readfds))
{
// read call happens here //
nbytes = sizeof(buf);
bytes_read = read(i, buf, nbytes);
printf("%ld", bytes_read);
}
else
{
perror("read");
exit(EXIT_FAILURE);
}
}
}
You need to initialize the file descriptors sets before calling select(). With your current code select ends up with EBADF.
Something like this should do it:
FD_ZERO (&writefds);
for(;;)
{
FD_ZERO (&readfds);
FD_SET(fd_1, &readfds);
FD_SET(fd_2, &readfds);
retval = select(FD_SETSIZE, &readfds, &writefds, NULL, &tv);
if (retval < 0) {
perror("select");
exit(EXIT_FAILURE);
}
for(int i=0; i<FD_SETSIZE; i++)
{
if (FD_ISSET(i, &readfds))
{
// read call happens here //
printf("reading from file: %d\n", i);
nbytes = sizeof(buf);
bytes_read = read(i, buf, nbytes);
printf("read %ld bytes\n", bytes_read);
}
}
}
And also you may want to check that for(;;) loop and only exit on error. Once your select behaves properly you can proceed with debugging inside your second for loop. You don't seem to use writefds here, so you may also just set it to NULL in select.
One more note: since you only have two files in the read set you could simply check FD_ISSET(fd_1) / FD_ISSET(fd_2) instead of iterating over all FD_SETSIZE entries.

Slow network connection speed with berkeley sockets

My friend and I have wrote a small download manager in C that splits the target file into several parts and downloads each part using a single posix thread. Everything seems to work fine, except that it is very slow compared to other download managers like wget (which as I know, does not split the file into several chunks). In every thread, we use a simple loop to download each part from a socket:
while ((nrecv = recv(sockfd, downbuf, sizeof(downbuf), 0)) > 0)
{
if ((nwrite = write(fd, downbuf, nrecv)) != nrecv)
die("write");
totalrw += nwrite;
}
/* ... */
I've tried with several different sizes for "downbuf", like 2014, 2048, 4096 and 8192, but with not much difference. It takes almost 45 seconds to download a 270 MB file, while wget downloads the same file in just 5 seconds. Both server and client are on the same host. Why is the difference so vast? Could you please tell me what trick wget uses?
This is how I make the request to the server:
sockfd = make_conn(website);
hdr.rq_buf = headerbuf; /* buffer to save response header */
hdr.rq_bufsize = sizeof(headerbuf);
hdr.rq_host = website;
hdr.rq_fpath = filepath; /* target file */
hdr.rq_flags = S_HEADFLAG; /* use head method at this moment
to get the total file size */
error = headerinit(hdr);
if (error)
{
die("headerinit()");
}
send(sockfd, headerbuf, strlen(headerbuf), 0); /* send the initial request */
recv(sockfd, respbuf, sizeof(respbuf), 0);
if (-1 == response_proc(respbuf, strlen(respbuf), &resp))
{
myperror("response_proc()");
exit(EXIT_FAILURE);
} /* process the header */
size_t sz = (size_t)strtol(resp.rs_content_length, NULL, 10);
divide(sz, chunks, numcons); /* divide the file into several parts */
for (int i = 0; i < numcons; i++)
{
/* populate data needed for threads */
args[i].t_hdr.rq_offset.c_start = chunks[i].c_start; /* where to start */
args[i].t_hdr.rq_offset.c_end = chunks[i].c_end; /* download up to this point */
args[i].t_hdr.rq_host = strdup(website);
args[i].t_hdr.rq_fpath = strdup(filepath);
snprintf(args[i].t_fname, BUFSIZ, "%sp%i", outfile, i);
args[i].t_order = i;
}
for (i = 0; i < numcons; i++)
{
if (0 != pthread_create(&threads[i], NULL, thread_main,
&args[i]))
{
die("pthread_create()");
}
}
for (i = 0; i < numcons; i++)
{
if (0 != pthread_join(threads[i], &thread_status))
{
die("pthread_join()");
}
}
http_request_header_t is defined as:
typedef struct {
void *rq_buf;
size_t rq_bufsize;
char *rq_host;
char *rq_fpath;
chunk_t rq_offset;
int rq_flags;
} http_request_header_t;
and http_response_header_t is defined as:
typedef struct {
#ifdef WITH_EXTRA_HEADERS
char *rs_version;
#endif
char *rs_status;
char *rs_date;
char *rs_server;
char *rs_last_modified;
char *rs_accept_ranges;
char *rs_content_length;
char *rs_connection;
char *rs_content_type;
} http_response_header_t;
This is the main routine that every thread use:
void *
thread_main(void *arg_orig)
{
thr_arg_t *arg = (thr_arg_t*)arg_orig;
int fd, sockfd;
http_response_header_t resp;
size_t totalrw = 0;
ssize_t nrecv;
char *line = malloc(BUFSIZ * sizeof(char));
char hdrbuf[BUFSIZ];
char respbuf[BUFSIZ];
mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP;
ssize_t nwrite = 0;
void *downbuf = malloc(DOWNBUF * sizeof(char));
sockfd = make_conn(arg->t_hdr.rq_host);
fd = open(arg->t_fname, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, mode);
if (-1 == fd)
{
die("thread_open(): fd");
}
arg->t_hdr.rq_flags = S_OFFSET;
arg->t_hdr.rq_buf = hdrbuf;
arg->t_hdr.rq_bufsize = sizeof(hdrbuf);
headerinit(arg->t_hdr);
//printf("%s\n", arg->t_hdr.rq_buf);
sendn(sockfd, hdrbuf, strlen(hdrbuf), 0);
/* first, read the header */
while ((nrecv = readheader(sockfd, &line, BUFSIZ)) > 0)
{
strncpy(respbuf + nwrite, line, sizeof(respbuf) - nwrite);
nwrite += nrecv;
}
nwrite = 0;
//printf("\n\n%s\n\n", respbuf);
if (-1 == response_proc(respbuf, strlen(respbuf), &resp))
{
myperror("thread_response_proc()");
exit(EXIT_FAILURE);
}
if (strncmp(resp.rs_status, "416", 3) == 0)
{
fprintf(stderr, "Partial content is not supported by the server\n");
exit(EXIT_FAILURE);
}
/* now read the actual data */
while ((nrecv = recv(sockfd, downbuf, sizeof(downbuf), 0)) > 0)
{
if ((nwrite = write(fd, downbuf, nrecv)) != nrecv)
die("write");
totalrw += nwrite;
}
if(-1 == nrecv)
{
die("recv()");
}
close(sockfd);
close(fd);
idxwr(arg->t_fname, arg->t_order, totalrw);
return ((void*)0);
}
You haven't posted enough here, but usually the cause of an unexpected slowdown in TCP will be Nagle's algorithm. This is triggered when you write small chunks of data to a socket. These would be inefficient to put on the wire on their own so the TCP stack waits for the user program to add more data before it sends out the packet. Only if nothing is added for 'a while' does it actually send an incomplete packet.
This can be disabled, but as your aim is an efficient bulk transfer you probably shouldn't.

read()/ioctl disturbs GPIO signal?

I connect a Linux embedded board(based on imx233) and a MSP430 MCU. They are connected via 4 pin SPI, but I use a GPIO for the chip select purpose on the Linux board. What I do is to use poll to detect falling edge of the GPIO(nr 52) then perform SPI reading either ioctl or read()
int main(void)
{
/********************************LINUX SCHEDULING**********************************/
sp.sched_priority = sched_get_priority_max(SCHED_FIFO); //scheduling
sched_setscheduler(0, SCHED_FIFO, &sp); //scheduling
/********************************LINUX SCHEDULING_END******************************/
struct pollfd fdset[2]; //declare the poll to be used in interrupt catching
int nfds = 2;
int gpio_fd, timeout, rc;
char *buf[MAX_BUF]; //max=64byte
int len;
initialize(); //gpio's are set to SPI_SLAVE
// spi_init();
gpio_fd = gpio_fd_open(CHIP_SELECT_PIN); //the CS(SS) pin is opened
timeout = POLL_TIMEOUT; //timeout 3 sec is set
// uint8_t voidFirstDetection = 1;
while (1) {
memset((void*)fdset, 0, sizeof(fdset));
fdset[0].fd = NULL;
fdset[0].events = POLLIN;
fdset[1].fd = gpio_fd;
fdset[1].events = POLLPRI;
/*** POLL starts to detect chipselects****/
rc = poll(fdset, nfds, timeout);
if (rc < 0) {
printf("\npoll() failed!\n");
return -1;
}
if (rc == 0) {
printf(".");
}
if (fdset[1].revents & POLLPRI ) { //HERE I need to run SPI_read
len = read(fdset[1].fd, buf, MAX_BUF);
/* if(voidFirstDetection){
voidFirstDetection = 0;
}else{*/
printf("\npoll() GPIO %d interrupt occurred\n", CHIP_SELECT_PIN);
int fd = open(device, O_RDWR);
if (fd < 0){
// snprintf(systemlogmsg, sizeof(systemlogmsg), "[1181]: errno:%s Cannot open /dev/spidev ", strerror(errno));
// error_logging(systemlogmsg, LOGLEVEL_ERROR);
printf("error spi recive\n");
}
//spi_transfer(fd);
do_read(fd);
close(fd);
// }
}
}
gpio_fd_close(gpio_fd);
return 0;
}
Above code works fine that it generates an interrupt only at the falling edge of the signal. I use the either of the below code when the interrupt is detected to read the /dev/spidev1-0
static void do_read(int fd)
{
unsigned char buf[1], *bp;
int status;
int len = 1;
/* read at least 2 bytes, no more than 32 */
memset(buf, 0, sizeof buf);
status = read(fd, buf, len);
if (status < 0) {
perror("read");
return;
}
if (status != len) {
fprintf(stderr, "short read\n");
return;
}
printf("read(%2d, %2d): %02x %02x,", len, status,
buf[0], buf[1]);
status -= 2;
bp = buf + 2;
while (status-- > 0)
printf(" %02x", *bp++);
printf("\n");
}
static void spi_transfer(int fd)
{
int ret;
uint8_t tx[2];
uint8_t rx[3] = {0 };
struct spi_ioc_transfer tr = {
.tx_buf = 0,
.rx_buf = (unsigned long)rx,
.len = ARRAY_SIZE(tx),
.delay_usecs = delay,
.speed_hz = speed,
.bits_per_word = bits,
};
ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr);
if (ret < 1){
printf("can't send spi message");
exit(1);
}
for (ret = 0; ret < ARRAY_SIZE(tx); ret++) {
if (!(ret % 6))
puts("");
printf("%.2X ", rx[ret]);
}
puts("");
}
Whenever the either ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr); line on spi_transfer() or status = read(fd, buf, len); on do_read() is executed, I see an infinite loop that detects an interrupt on the GPIO52 (chipselect). I try the observe the GPIO via oscilloscope but I could not see any signal change (it might be a spike that my oscilloscope cannot detect), however, when I connect the chipselect to the Vcc, it does not get the infinite loop. As I am on the early stage, I set one of GPIO of the MCU as an output and a constant logic high. I use GPIO52 (Chip select) as an input because my aim is to transfer data from MCU to the linux board.
I guess, the read() and ioctl somehow effects the GPIO to sink more current than the GPIO can provide. If it is the problem, what can I do that ioctl or read() would not disturb GPIO. Or do you think something else could be a problem?
I was lucky that I found the problem quick. I tied the grounds of both boards and now it works fine. I will keep the post as someone else might have the same problem. But I am still curious how ioctl or read disturbs the GPIO signal level

Windows API: Wait for data to be available on non-GUI console input (PIPE-based STDIN)

Background
I am currently working on a Windows select-like function that not only supports SOCKET handles, but also other kinds of waitable handles. My goal is to wait on standard console handles in order to provide select-functionality to the curl testsuite.
The related program can be found in the curl git repository: sockfilt.c
Question
Is it possible to wait for data to be available on a non-GUI-based console input? The issue is that WaitFor* methods do not support PIPE handles and therefore STDIN is not supported if the process input is fed from another process, e.g. using the pipe | functionality of cmd.
The following example program illustrates the problem: select_ws.c
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <windows.h>
#include <winsock2.h>
#include <malloc.h>
#include <conio.h>
#include <fcntl.h>
#define SET_SOCKERRNO(x) (WSASetLastError((int)(x)))
typedef SOCKET curl_socket_t;
/*
* select function with support for WINSOCK2 sockets and all
* other handle types supported by WaitForMultipleObjectsEx.
* http://msdn.microsoft.com/en-us/library/windows/desktop/ms687028.aspx
* http://msdn.microsoft.com/en-us/library/windows/desktop/ms741572.aspx
*/
static int select_ws(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout)
{
long networkevents;
DWORD milliseconds, wait, idx, avail, events, inputs;
WSAEVENT wsaevent, *wsaevents;
WSANETWORKEVENTS wsanetevents;
INPUT_RECORD *inputrecords;
HANDLE handle, *handles;
curl_socket_t sock, *fdarr, *wsasocks;
int error, fds;
DWORD nfd = 0, wsa = 0;
int ret = 0;
/* check if the input value is valid */
if(nfds < 0) {
SET_SOCKERRNO(EINVAL);
return -1;
}
/* check if we got descriptors, sleep in case we got none */
if(!nfds) {
Sleep((timeout->tv_sec * 1000) + (timeout->tv_usec / 1000));
return 0;
}
/* allocate internal array for the original input handles */
fdarr = malloc(nfds * sizeof(curl_socket_t));
if(fdarr == NULL) {
SET_SOCKERRNO(ENOMEM);
return -1;
}
/* allocate internal array for the internal event handles */
handles = malloc(nfds * sizeof(HANDLE));
if(handles == NULL) {
SET_SOCKERRNO(ENOMEM);
return -1;
}
/* allocate internal array for the internal socket handles */
wsasocks = malloc(nfds * sizeof(curl_socket_t));
if(wsasocks == NULL) {
SET_SOCKERRNO(ENOMEM);
return -1;
}
/* allocate internal array for the internal WINSOCK2 events */
wsaevents = malloc(nfds * sizeof(WSAEVENT));
if(wsaevents == NULL) {
SET_SOCKERRNO(ENOMEM);
return -1;
}
/* loop over the handles in the input descriptor sets */
for(fds = 0; fds < nfds; fds++) {
networkevents = 0;
handles[nfd] = 0;
if(FD_ISSET(fds, readfds))
networkevents |= FD_READ|FD_ACCEPT|FD_CLOSE;
if(FD_ISSET(fds, writefds))
networkevents |= FD_WRITE|FD_CONNECT;
if(FD_ISSET(fds, exceptfds))
networkevents |= FD_OOB;
/* only wait for events for which we actually care */
if(networkevents) {
fdarr[nfd] = (curl_socket_t)fds;
if(fds == fileno(stdin)) {
handles[nfd] = GetStdHandle(STD_INPUT_HANDLE);
}
else if(fds == fileno(stdout)) {
handles[nfd] = GetStdHandle(STD_OUTPUT_HANDLE);
}
else if(fds == fileno(stderr)) {
handles[nfd] = GetStdHandle(STD_ERROR_HANDLE);
}
else {
wsaevent = WSACreateEvent();
if(wsaevent != WSA_INVALID_EVENT) {
error = WSAEventSelect(fds, wsaevent, networkevents);
if(error != SOCKET_ERROR) {
handles[nfd] = wsaevent;
wsasocks[wsa] = (curl_socket_t)fds;
wsaevents[wsa] = wsaevent;
wsa++;
}
else {
handles[nfd] = (HANDLE)fds;
WSACloseEvent(wsaevent);
}
}
}
nfd++;
}
}
/* convert struct timeval to milliseconds */
if(timeout) {
milliseconds = ((timeout->tv_sec * 1000) + (timeout->tv_usec / 1000));
}
else {
milliseconds = INFINITE;
}
/* wait for one of the internal handles to trigger */
wait = WaitForMultipleObjectsEx(nfd, handles, FALSE, milliseconds, FALSE);
/* loop over the internal handles returned in the descriptors */
for(idx = 0; idx < nfd; idx++) {
fds = fdarr[idx];
handle = handles[idx];
sock = (curl_socket_t)fds;
/* check if the current internal handle was triggered */
if(wait != WAIT_FAILED && (wait - WAIT_OBJECT_0) >= idx &&
WaitForSingleObjectEx(handle, 0, FALSE) == WAIT_OBJECT_0) {
/* try to handle the event with STD* handle functions */
if(fds == fileno(stdin)) {
/* check if there is no data in the input buffer */
if(!stdin->_cnt) {
/* check if we are getting data from a PIPE */
if(!GetConsoleMode(handle, &avail)) {
/* check if there is no data from PIPE input */
if(!PeekNamedPipe(handle, NULL, 0, NULL, &avail, NULL))
avail = 0;
if(!avail)
FD_CLR(sock, readfds);
} /* check if there is no data from keyboard input */
else if (!_kbhit()) {
/* check if there are INPUT_RECORDs in the input buffer */
if(GetNumberOfConsoleInputEvents(handle, &events)) {
if(events > 0) {
/* remove INPUT_RECORDs from the input buffer */
inputrecords = (INPUT_RECORD*)malloc(events *
sizeof(INPUT_RECORD));
if(inputrecords) {
if(!ReadConsoleInput(handle, inputrecords,
events, &inputs))
inputs = 0;
free(inputrecords);
}
/* check if we got all inputs, otherwise clear buffer */
if(events != inputs)
FlushConsoleInputBuffer(handle);
}
}
/* remove from descriptor set since there is no real data */
FD_CLR(sock, readfds);
}
}
/* stdin is never ready for write or exceptional */
FD_CLR(sock, writefds);
FD_CLR(sock, exceptfds);
}
else if(fds == fileno(stdout) || fds == fileno(stderr)) {
/* stdout and stderr are never ready for read or exceptional */
FD_CLR(sock, readfds);
FD_CLR(sock, exceptfds);
}
else {
/* try to handle the event with the WINSOCK2 functions */
error = WSAEnumNetworkEvents(fds, NULL, &wsanetevents);
if(error != SOCKET_ERROR) {
/* remove from descriptor set if not ready for read/accept/close */
if(!(wsanetevents.lNetworkEvents & (FD_READ|FD_ACCEPT|FD_CLOSE)))
FD_CLR(sock, readfds);
/* remove from descriptor set if not ready for write/connect */
if(!(wsanetevents.lNetworkEvents & (FD_WRITE|FD_CONNECT)))
FD_CLR(sock, writefds);
/* remove from descriptor set if not exceptional */
if(!(wsanetevents.lNetworkEvents & FD_OOB))
FD_CLR(sock, exceptfds);
}
}
/* check if the event has not been filtered using specific tests */
if(FD_ISSET(sock, readfds) || FD_ISSET(sock, writefds) ||
FD_ISSET(sock, exceptfds)) {
ret++;
}
}
else {
/* remove from all descriptor sets since this handle did not trigger */
FD_CLR(sock, readfds);
FD_CLR(sock, writefds);
FD_CLR(sock, exceptfds);
}
}
for(idx = 0; idx < wsa; idx++) {
WSAEventSelect(wsasocks[idx], NULL, 0);
WSACloseEvent(wsaevents[idx]);
}
free(wsaevents);
free(wsasocks);
free(handles);
free(fdarr);
return ret;
}
int main(void)
{
WORD wVersionRequested;
WSADATA wsaData;
SOCKET sock[4];
struct sockaddr_in sockaddr[4];
fd_set readfds;
fd_set writefds;
fd_set exceptfds;
SOCKET maxfd = 0;
int selfd = 0;
void *buffer = malloc(1024);
ssize_t nread;
setmode(fileno(stdin), O_BINARY);
wVersionRequested = MAKEWORD(2, 2);
WSAStartup(wVersionRequested, &wsaData);
sock[0] = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
sockaddr[0].sin_family = AF_INET;
sockaddr[0].sin_addr.s_addr = inet_addr("74.125.134.26");
sockaddr[0].sin_port = htons(25);
connect(sock[0], (struct sockaddr *) &sockaddr[0], sizeof(sockaddr[0]));
sock[1] = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
sockaddr[1].sin_family = AF_INET;
sockaddr[1].sin_addr.s_addr = inet_addr("74.125.134.27");
sockaddr[1].sin_port = htons(25);
connect(sock[1], (struct sockaddr *) &sockaddr[1], sizeof(sockaddr[1]));
sock[2] = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
sockaddr[2].sin_family = AF_INET;
sockaddr[2].sin_addr.s_addr = inet_addr("127.0.0.1");
sockaddr[2].sin_port = htons(1337);
printf("bind = %d\n", bind(sock[2], (struct sockaddr *) &sockaddr[2], sizeof(sockaddr[2])));
printf("listen = %d\n", listen(sock[2], 5));
sock[3] = INVALID_SOCKET;
while(1) {
FD_ZERO(&readfds);
FD_ZERO(&writefds);
FD_ZERO(&exceptfds);
FD_SET(sock[0], &readfds);
FD_SET(sock[0], &exceptfds);
maxfd = maxfd > sock[0] ? maxfd : sock[0];
FD_SET(sock[1], &readfds);
FD_SET(sock[1], &exceptfds);
maxfd = maxfd > sock[1] ? maxfd : sock[1];
FD_SET(sock[2], &readfds);
FD_SET(sock[2], &exceptfds);
maxfd = maxfd > sock[2] ? maxfd : sock[2];
FD_SET((SOCKET)fileno(stdin), &readfds);
maxfd = maxfd > (SOCKET)fileno(stdin) ? maxfd : (SOCKET)fileno(stdin);
printf("maxfd = %d\n", maxfd);
selfd = select_ws(maxfd + 1, &readfds, &writefds, &exceptfds, NULL);
printf("selfd = %d\n", selfd);
if(FD_ISSET(sock[0], &readfds)) {
printf("read sock[0]\n");
nread = recv(sock[0], buffer, 1024, 0);
printf("read sock[0] = %d\n", nread);
}
if(FD_ISSET(sock[0], &exceptfds)) {
printf("exception sock[0]\n");
}
if(FD_ISSET(sock[1], &readfds)) {
printf("read sock[1]\n");
nread = recv(sock[1], buffer, 1024, 0);
printf("read sock[1] = %d\n", nread);
}
if(FD_ISSET(sock[1], &exceptfds)) {
printf("exception sock[1]\n");
}
if(FD_ISSET(sock[2], &readfds)) {
if(sock[3] != INVALID_SOCKET)
closesocket(sock[3]);
printf("accept sock[2] = %d\n", sock[2]);
nread = sizeof(sockaddr[3]);
printf("WSAGetLastError = %d\n", WSAGetLastError());
sock[3] = accept(sock[2], (struct sockaddr *) &sockaddr[3], &nread);
printf("WSAGetLastError = %d\n", WSAGetLastError());
printf("accept sock[2] = %d\n", sock[3]);
}
if(FD_ISSET(sock[2], &exceptfds)) {
printf("exception sock[2]\n");
}
if(FD_ISSET(fileno(stdin), &readfds)) {
printf("read fileno(stdin)\n");
nread = read(fileno(stdin), buffer, 1024);
printf("read fileno(stdin) = %d\n", nread);
}
}
WSACleanup();
free(buffer);
}
Compile using MinGW with the following command:
mingw32-gcc select_ws.c -Wl,-lws2_32 -g -o select_ws.exe
Running the program directly from the console using the following command works:
select_ws.exe
But doing the same with a pipe will constantly signal WaitForMultipleObjectsEx:
ping -t 8.8.8.8 | select_ws.exe
The pipe is ready to read until the parent process is finished, e.g.:
ping 8.8.8.8 | select_ws.exe
Is there a compatible way to simulate a blocking wait on the PIPE-based console input handle together with the other handles? The use of threads should be avoided.
You are welcome to contribute changes to the example program in this gist.
Thanks in advance!
I actually found a way to make it work using a separate waiting-thread. Please see the following commit in the curl repository on github.com.
Thanks for your comments!
Use GetStdHandle(STD_INPUT_HANDLE) to get the STDIN pipe handle, then use ReadFile/Ex() with an OVERLAPPED structure whose hEvent member is set to a manual-reset event from CreateEvent(). You can then use any of the WaitFor*() functions to wait on the event. If it times out, call CancelIo() to abort the read operation.

Resources