Skip to content

Commit 44dddce

Browse files
committed
macOS poll work-around
Since polling of character devices is not supported on macOS, add the following work-around: - While stdin has not reached EOF, put the tty in non-blocking mode in order to simulate polling by peeking for new data. - Once stdin has reached EOF, just do a blocking read of the tty.
1 parent 87bc7f2 commit 44dddce

File tree

7 files changed

+176
-19
lines changed

7 files changed

+176
-19
lines changed

Makefile.am

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ AM_CFLAGS=-Wall -Wextra
44
AM_CPPFLAGS=-D_GNU_SOURCE
55

66
bin_PROGRAMS=pick
7-
pick_SOURCES=pick.c compat-reallocarray.c compat-strtonum.c compat.h
7+
pick_SOURCES=compat-reallocarray.c \
8+
compat-strtonum.c \
9+
compat-xpoll.c \
10+
compat.h \
11+
pick.c
812
pick_CPPFLAGS=$(AM_CPPFLAGS) $(NCURSES_CFLAGS)
913
pick_LDADD=$(NCURSES_LIBS)
1014

compat-xpoll.c

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
#ifdef HAVE_CONFIG_H
2+
#include "config.h"
3+
#endif
4+
5+
#include <stdlib.h>
6+
#include <poll.h>
7+
8+
#include "compat.h"
9+
10+
#ifdef HAVE_BROKEN_POLL
11+
12+
#include <assert.h>
13+
#include <errno.h>
14+
#include <fcntl.h>
15+
#include <unistd.h>
16+
17+
static int tty_peekc(int);
18+
19+
int
20+
xpoll(struct pollfd *fds, nfds_t nfds, int timeout)
21+
{
22+
static int state;
23+
int flags, inready, ttyready;
24+
int nready = 0;
25+
26+
/* XXX filter_choices() poll disabled */
27+
if (nfds == 1 && timeout == 0)
28+
return 0;
29+
30+
if (state == 0) {
31+
assert(nfds == 2);
32+
33+
flags = fcntl(fds[0].fd, F_GETFL, 0);
34+
if (flags == -1)
35+
return -1;
36+
flags |= O_NONBLOCK;
37+
if (fcntl(fds[0].fd, F_SETFL, flags) == -1)
38+
return -1;
39+
state++;
40+
}
41+
42+
if (state == 1) {
43+
if (timeout < 0)
44+
timeout = 100;
45+
while (nready == 0) {
46+
if (nfds == 1) {
47+
state++;
48+
break;
49+
}
50+
51+
inready = poll(&fds[1], 1, timeout);
52+
if (inready == -1)
53+
return -1;
54+
nready += inready;
55+
56+
ttyready = tty_peekc(fds[0].fd);
57+
if (ttyready == -1) {
58+
return -1;
59+
} else if (ttyready > 0) {
60+
fds[0].revents = POLLIN;
61+
nready++;
62+
} else {
63+
fds[0].revents = 0;
64+
}
65+
}
66+
}
67+
68+
if (state == 2) {
69+
flags = fcntl(fds[0].fd, F_GETFL, 0);
70+
if (flags == -1)
71+
return -1;
72+
flags &= ~O_NONBLOCK;
73+
if (fcntl(fds[0].fd, F_SETFL, flags) == -1)
74+
return -1;
75+
state++;
76+
}
77+
78+
if (state == 3) {
79+
ttyready = tty_peekc(fds[0].fd);
80+
if (ttyready == -1) {
81+
return -1;
82+
} else if (ttyready > 0) {
83+
fds[0].revents = POLLIN;
84+
nready++;
85+
} else {
86+
fds[0].revents = 0;
87+
}
88+
}
89+
90+
return nready;
91+
}
92+
93+
static int
94+
tty_peekc(int fd)
95+
{
96+
extern int tty_getc_peek;
97+
ssize_t n;
98+
unsigned char c;
99+
100+
n = read(fd, &c, sizeof(c));
101+
if (n == -1) {
102+
if (errno == EAGAIN)
103+
return 0;
104+
return -1;
105+
}
106+
if (n > 0) {
107+
assert(tty_getc_peek == -1);
108+
tty_getc_peek = c;
109+
}
110+
return n;
111+
}
112+
113+
#else
114+
115+
int
116+
xpoll(struct pollfd *fds, nfds_t nfds, int timeout)
117+
{
118+
return poll(fds, nfds, timeout);
119+
}
120+
121+
#endif /* HAVE_BROKEN_POLL */

compat.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,5 @@ long long strtonum(const char *, long long, long long, const char **);
3232
#endif /* !HAVE_STRTONUM */
3333

3434
#endif /* COMPAT_H */
35+
36+
int xpoll(struct pollfd *fds, nfds_t nfds, int timeout);

config.h.in

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
/* config.h.in. Generated from configure.ac by autoheader. */
22

3+
/* Define if poll does not support character devices */
4+
#undef HAVE_BROKEN_POLL
5+
36
/* Define if ncursesw is available */
47
#undef HAVE_NCURSESW_H
58

configure.ac

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ AC_PREREQ([2.61])
22
AC_INIT([pick], [2.0.2], [[email protected]])
33
AM_INIT_AUTOMAKE([subdir-objects])
44
AC_CONFIG_HEADERS([config.h])
5+
AC_CANONICAL_HOST
56
AC_PROG_CC
67
AM_PROG_CC_C_O
78
AC_CHECK_FUNCS([pledge reallocarray strtonum])
@@ -16,7 +17,6 @@ AC_SEARCH_LIBS([setupterm], [curses], [], [
1617
)
1718
])
1819
AC_DEFUN([AC_MALLOC_OPTIONS], [
19-
AC_CANONICAL_HOST
2020
AC_MSG_CHECKING([for $host_os malloc hardening options])
2121
case "$host_os" in
2222
openbsd*) malloc_options="RS";;
@@ -30,5 +30,20 @@ AC_DEFUN([AC_MALLOC_OPTIONS], [
3030
AC_SUBST([MALLOC_OPTIONS], [$malloc_options])
3131
])
3232
AC_MALLOC_OPTIONS
33+
AC_DEFUN([AC_BROKEN_POLL], [
34+
AC_MSG_CHECKING([for broken poll implementation])
35+
case "$host_os" in
36+
darwin*) broken=1;;
37+
*) broken=0;;
38+
esac
39+
if test "$broken" -eq 1; then
40+
AC_MSG_RESULT([yes])
41+
AC_DEFINE([HAVE_BROKEN_POLL], [1], [
42+
Define if poll does not support character devices])
43+
else
44+
AC_MSG_RESULT([no])
45+
fi
46+
])
47+
AC_BROKEN_POLL
3348
AC_CONFIG_FILES([Makefile])
3449
AC_OUTPUT

pick.c

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#endif
44

55
#include <sys/ioctl.h>
6+
#include <sys/types.h>
67

78
#include <ctype.h>
89
#include <err.h>
@@ -70,13 +71,15 @@ struct choice {
7071
double score;
7172
};
7273

74+
int tty_getc_peek = -1;
75+
7376
static int choice_cmp(const void *, const void *);
7477
static const char *choice_description(const struct choice *);
7578
static const char *choice_string(const struct choice *);
7679
static void delete_between(char *, size_t, size_t, size_t);
7780
static char *eager_strpbrk(const char *, const char *);
7881
static int filter_choices(size_t);
79-
static size_t get_choices(int, size_t);
82+
static size_t get_choices(int, ssize_t);
8083
static enum key get_key(const char **);
8184
static void handle_sigwinch(int);
8285
static int isu8cont(unsigned char);
@@ -224,7 +227,7 @@ usage(int status)
224227
}
225228

226229
size_t
227-
get_choices(int fd, size_t insert)
230+
get_choices(int fd, ssize_t insert)
228231
{
229232
static const char *ifs;
230233
static char *buf;
@@ -291,7 +294,7 @@ get_choices(int fd, size_t insert)
291294
}
292295
offset = start - buf;
293296
dchoices = choices.length - nchoices;
294-
if (dchoices == 0 || nchoices == 0)
297+
if (dchoices == 0 || nchoices == 0 || insert == -1)
295298
return n;
296299

297300
/* Move new choices after the given insert index. */
@@ -325,11 +328,12 @@ selected_choice(void)
325328
{
326329
struct pollfd fds[2];
327330
const char *buf;
331+
ssize_t insert;
328332
size_t cursor_position, i, j, length, nfds, xscroll;
329333
size_t choices_count = 0;
330334
size_t selection = 0;
331335
size_t yscroll = 0;
332-
int dokey, doread, timo;
336+
int dokey, doread, nready, timo;
333337
int choices_reset = 1;
334338
int dochoices = 0;
335339
int dofilter = 1;
@@ -346,7 +350,8 @@ selected_choice(void)
346350
for (;;) {
347351
dokey = doread = 0;
348352
toggle_sigwinch(1);
349-
if (poll(fds, nfds, timo) == -1 && errno != EINTR)
353+
nready = xpoll(fds, nfds, timo);
354+
if (nready == -1 && errno != EINTR)
350355
err(1, "poll");
351356
toggle_sigwinch(0);
352357
if (gotsigwinch) {
@@ -355,33 +360,33 @@ selected_choice(void)
355360
}
356361
timo = -1;
357362
for (i = 0; i < nfds; i++) {
358-
if (fds[i].revents & (POLLERR | POLLNVAL))
359-
errx(1, "%d: bad file descriptor", fds[i].fd);
363+
if (fds[i].revents & (POLLERR | POLLNVAL)) {
364+
errno = EBADF;
365+
err(1, "poll");
366+
}
360367
if ((fds[i].revents & (POLLIN | POLLHUP)) == 0)
361368
continue;
362369

363-
if (fds[i].fd == fds[0].fd)
364-
dokey = 1;
365-
else if (fds[i].fd == fds[1].fd)
370+
if (fds[i].fd == STDIN_FILENO)
366371
doread = 1;
372+
else if (fds[i].fd == fileno(tty_in))
373+
dokey = 1;
367374
}
368375

369376
length = choices.length;
370377
if (doread) {
371-
if (get_choices(STDIN_FILENO, query_length > 0 ?
372-
choices_count : 0)) {
378+
insert = query_length > 0 ? (ssize_t)choices_count : -1;
379+
if (get_choices(fds[1].fd, insert)) {
373380
if (query_length > 0) {
374381
dofilter = 1;
375382
choices_reset = 0;
376383
choices_count += choices.length -
377384
length;
378385
}
379386
} else {
380-
nfds = 1; /* EOF */
387+
nfds--; /* EOF */
381388
}
382389
}
383-
if (!dokey && query_length == 0 && length >= choices_lines)
384-
continue; /* prevent redundant rendering */
385390
if (!dokey)
386391
goto render;
387392

@@ -589,8 +594,8 @@ selected_choice(void)
589594
int
590595
filter_choices(size_t nchoices)
591596
{
592-
struct choice *c;
593597
struct pollfd pfd;
598+
struct choice *c;
594599
size_t i, match_length;
595600
int nready;
596601

@@ -610,7 +615,7 @@ filter_choices(size_t nchoices)
610615
if (i > 0 && i % 50 == 0) {
611616
pfd.fd = fileno(tty_in);
612617
pfd.events = POLLIN;
613-
if ((nready = poll(&pfd, 1, 0)) == -1)
618+
if ((nready = xpoll(&pfd, 1, 0)) == -1)
614619
err(1, "poll");
615620
if (nready == 1 && pfd.revents & (POLLIN | POLLHUP))
616621
return 0;
@@ -1116,6 +1121,12 @@ tty_getc(void)
11161121
ssize_t n;
11171122
unsigned char c;
11181123

1124+
if (tty_getc_peek != -1) {
1125+
c = tty_getc_peek;
1126+
tty_getc_peek = -1;
1127+
return c;
1128+
}
1129+
11191130
n = read(fileno(tty_in), &c, sizeof(c));
11201131
if (n == -1)
11211132
err(1, "read");

tests/pick-test.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <errno.h>
77
#include <fcntl.h>
88
#include <limits.h>
9+
#include <poll.h>
910
#include <signal.h>
1011
#include <stdio.h>
1112
#include <stdlib.h>

0 commit comments

Comments
 (0)