0

I'm having trouble using kevent on mac with a USB serial console. I've narrowed it down to:

#include <errno.h>                                                                                                                                          
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/event.h>
#include <sys/ioctl.h>
#include <termios.h>

#define DEVICE "/dev/cu.usbserial-0011111D"

int main() {
  int kqueue_fd = kqueue();
  if (kqueue_fd < 0) {
    printf("Failed to open kqueue\n");
    return -1;
  }
  int device_fd = open(DEVICE, O_RDWR | O_NONBLOCK | O_NOCTTY);
  if (device_fd < 0) {
    printf("Failed to open device: %s\n", DEVICE);
    return -1;
  }
  printf("Opened %d\n", device_fd);

  enum { MAX_EVENTS = 1 };
  struct kevent events[MAX_EVENTS];

  EV_SET(&events[0], device_fd, EVFILT_READ, EV_ADD, 0, 0, NULL);

  int r = kevent(kqueue_fd, events, 1, NULL, 0, NULL);
  if (r < 0) {
    printf("kevent failed: %s\n", strerror(errno));
    return -1;
  }

  struct timespec sleep_time;
  sleep_time.tv_sec = 5;
  sleep_time.tv_nsec = 0;

  int ready = kevent(kqueue_fd, NULL, 0, (struct kevent*) &events,
                     MAX_EVENTS, &sleep_time);
  if (ready == 0) {
    printf("No event\n");
    return 0;
  }
  for (int i = 0; i < ready; i++) {
    printf(".ident %ld, .filter %d, .flags 0x%x, .fflags 0x%x, "
           ".data: %ld, .udata %p\n",
           events[i].ident,
           events[i].filter,
           events[i].flags,
           events[i].fflags,
           events[i].data,
           events[i].udata);

    int unread = 0;
    r = ioctl(events[i].ident, FIONREAD, &unread);
    if (r < 0) {
      printf("ioctl failed: %d: %s\n", errno, strerror(errno));
    }
  }
}

When I run this and unplug the USB device in the middle of the call to kevent(), I get:

Opened 4
.ident 4, .filter -1, .flags 0x1, .fflags 0x0, .data: 6, .udata 0x0
ioctl failed: 6: Device not configured

My understanding is that the contents of the event translates to:

FD 4, EVFILT_READ, EV_ADD, 6 bytes remaining on fd. But the ioctl() fails (since the device was removed), and errno is also 6, so it seems as if event.data is returning the errno, not the bytes remaining.

How can I differentiate between the normal read case and the case where the device has been removed? The filter, flags & fflags appear the same in both cases.

Additional Information

If I switch from opening the serial console to a pipe, and write a single byte followed by closing the write end, I get:

pipe() fd: 5 -> 4
.ident 4, .filter -1, .flags 0x1, .fflags 0x0, .data: 1, .udata 0x0
.ident 4, .filter -1, .flags 0x8001, .fflags 0x0, .data: 0, .udata 0x0

This is what I expect, since 0x8000 is EV_EOF.

  • It seems you've found the way to differentiate the cases: the behavior of a subsequent `ioctl()` operation. Why is that not good enough? – Ken Thomases Jun 20 '15 at 15:15
  • Well the point of returning information in the kevent struct is so that one can avoid the extra syscalls. – Simon Newton Jun 20 '15 at 17:59
  • Except disconnecting a device isn't an end-of-file condition. `kevent()` doesn't have any way to convey that subsequent attempts to read on the descriptor will produce errors. All it can do is tell you that there's a condition you have to read from the device to detect. – Ken Thomases Jun 20 '15 at 18:42
  • It appears that kevent is trying to return information about the condition in the data field, since it matches the errno from ioctl(). I'd expect EV_ERROR to be set. – Simon Newton Jun 20 '15 at 18:58
  • As I read the man page, `EV_ERROR` is about `kevent()` itself encountering an error processing `changelist`, which is `NULL` in your second call. It's not about an error on the things being monitored (a file descriptor in this case). Also, the semantics of `EVFILT_READ` for devices is not clear. They aren't sockets, vnodes, fifos, or pipes. So, I wouldn't put too much stuck in any one interpretation of the `data` field. – Ken Thomases Jun 20 '15 at 19:06

0 Answers0