Skip to content

Commit 00f1a58

Browse files
committed
Tentative macOS poll work-around
1 parent 49cbb86 commit 00f1a58

File tree

6 files changed

+214
-43
lines changed

6 files changed

+214
-43
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-fdwait.c \
8+
compat-reallocarray.c \
9+
compat-strtonum.c \
10+
compat.h \
11+
pick.c
812
pick_CPPFLAGS=$(AM_CPPFLAGS) $(NCURSES_CFLAGS)
913
pick_LDADD=$(NCURSES_LIBS)
1014

compat-fdwait.c

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
#ifdef HAVE_CONFIG_H
2+
#include "config.h"
3+
#endif
4+
5+
#include <sys/types.h>
6+
7+
#include "compat.h"
8+
9+
#ifdef HAVE_BROKEN_POLL
10+
11+
#include <assert.h>
12+
#include <errno.h>
13+
#include <fcntl.h>
14+
#include <poll.h>
15+
#include <unistd.h>
16+
17+
extern int tty_getc_peek;
18+
19+
static int ttygetchar(int);
20+
21+
int
22+
fdwait(int ttyfd, int infd, int timeout)
23+
{
24+
static int state;
25+
struct pollfd pfd;
26+
int flags, nready;
27+
int res = 0;
28+
29+
/* XXX filter_choices() poll disabled */
30+
if (infd == -1 && timeout == 0)
31+
return 0;
32+
33+
if (state == 0) {
34+
flags = fcntl(ttyfd, F_GETFL, 0);
35+
if (flags == -1)
36+
return -1;
37+
flags |= O_NONBLOCK;
38+
if (fcntl(ttyfd, F_SETFL, flags) == -1)
39+
return -1;
40+
state++;
41+
}
42+
43+
if (state == 1) {
44+
if (timeout < 0)
45+
timeout = 100;
46+
pfd.fd = infd;
47+
pfd.events = POLLIN;
48+
while (res == 0) {
49+
if (infd == -1) {
50+
state++;
51+
break;
52+
}
53+
54+
nready = poll(&pfd, 1, timeout);
55+
if (nready == -1)
56+
return -1;
57+
if (nready > 0) {
58+
if (pfd.revents & (POLLERR | POLLNVAL)) {
59+
errno = EBADF;
60+
return -1;
61+
} else if (pfd.revents & (POLLIN | POLLHUP)) {
62+
res |= FD_STDIN_READY;
63+
}
64+
}
65+
66+
nready = ttygetchar(ttyfd);
67+
if (nready == -1) {
68+
if (errno == EAGAIN)
69+
continue;
70+
return -1;
71+
} else if (nready > 0) {
72+
res |= FD_TTY_READY;
73+
}
74+
}
75+
}
76+
77+
if (state == 2) {
78+
flags = fcntl(ttyfd, F_GETFL, 0);
79+
if (flags == -1)
80+
return -1;
81+
flags &= ~O_NONBLOCK;
82+
if (fcntl(ttyfd, F_SETFL, flags) == -1)
83+
return -1;
84+
state++;
85+
}
86+
87+
if (state == 3) {
88+
nready = ttygetchar(ttyfd);
89+
if (nready == -1)
90+
return -1;
91+
else if (nready > 0)
92+
res |= FD_TTY_READY;
93+
}
94+
95+
return res;
96+
}
97+
98+
static int
99+
ttygetchar(int fd)
100+
{
101+
ssize_t n;
102+
unsigned char c;
103+
104+
n = read(fd, &c, sizeof(c));
105+
if (n > 0) {
106+
assert(tty_getc_peek == -1);
107+
tty_getc_peek = c;
108+
}
109+
return n;
110+
}
111+
112+
#else
113+
114+
#include <errno.h>
115+
#include <poll.h>
116+
117+
int
118+
fdwait(int ttyfd, int infd, int timeout)
119+
{
120+
struct pollfd fds[2];
121+
int i, nready;
122+
int nfds = 0;
123+
int res = 0;
124+
125+
if (ttyfd != -1) {
126+
fds[nfds].fd = ttyfd;
127+
fds[nfds].events = POLLIN;
128+
nfds++;
129+
}
130+
if (infd != -1) {
131+
fds[nfds].fd = infd;
132+
fds[nfds].events = POLLIN;
133+
nfds++;
134+
}
135+
nready = poll(fds, nfds, timeout);
136+
if (nready <= 0)
137+
return nready;
138+
for (i = 0; i < nfds; i++) {
139+
if (fds[i].revents & (POLLERR | POLLNVAL)) {
140+
errno = EBADF;
141+
return -1;
142+
} else if ((fds[i].revents & (POLLIN | POLLHUP)) == 0) {
143+
continue;
144+
}
145+
146+
if (fds[i].fd == ttyfd)
147+
res |= FD_TTY_READY;
148+
else if (fds[i].fd == infd)
149+
res |= FD_STDIN_READY;
150+
}
151+
return res;
152+
}
153+
154+
#endif /* HAVE_BROKEN_POLL */

compat.h

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

3434
#endif /* COMPAT_H */
35+
36+
#define FD_STDIN_READY 0x1
37+
#define FD_TTY_READY 0x2
38+
39+
int fdwait(int, int, int);

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: 31 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
#include <errno.h>
1010
#include <limits.h>
1111
#include <locale.h>
12-
#include <poll.h>
1312
#include <signal.h>
1413
#include <stdio.h>
1514
#include <stdlib.h>
@@ -70,13 +69,15 @@ struct choice {
7069
double score;
7170
};
7271

72+
int tty_getc_peek = -1;
73+
7374
static int choice_cmp(const void *, const void *);
7475
static const char *choice_description(const struct choice *);
7576
static const char *choice_string(const struct choice *);
7677
static void delete_between(char *, size_t, size_t, size_t);
7778
static char *eager_strpbrk(const char *, const char *);
7879
static int filter_choices(size_t);
79-
static size_t get_choices(int, size_t);
80+
static size_t get_choices(int, ssize_t);
8081
static enum key get_key(const char **);
8182
static void handle_sigwinch(int);
8283
static int isu8cont(unsigned char);
@@ -224,7 +225,7 @@ usage(int status)
224225
}
225226

226227
size_t
227-
get_choices(int fd, size_t insert)
228+
get_choices(int fd, ssize_t insert)
228229
{
229230
static const char *ifs;
230231
static char *buf;
@@ -291,7 +292,7 @@ get_choices(int fd, size_t insert)
291292
}
292293
offset = start - buf;
293294
dchoices = choices.length - nchoices;
294-
if (dchoices == 0 || nchoices == 0)
295+
if (dchoices == 0 || nchoices == 0 || insert == -1)
295296
return n;
296297

297298
/* Move new choices after the given insert index. */
@@ -323,66 +324,53 @@ eager_strpbrk(const char *string, const char *separators)
323324
const struct choice *
324325
selected_choice(void)
325326
{
326-
struct pollfd fds[2];
327327
const char *buf;
328-
size_t cursor_position, i, j, length, nfds, xscroll;
328+
ssize_t insert;
329+
size_t cursor_position, i, j, length, xscroll;
329330
size_t choices_count = 0;
330331
size_t selection = 0;
331332
size_t yscroll = 0;
332-
int dokey, doread, timo;
333+
int fd, ready, timo;
333334
int choices_reset = 1;
334335
int dochoices = 0;
335336
int dofilter = 1;
336337

337338
cursor_position = query_length;
338339

339-
fds[0].fd = fileno(tty_in);
340-
fds[0].events = POLLIN;
341-
fds[1].fd = STDIN_FILENO;
342-
fds[1].events = POLLIN;
343-
nfds = 2;
344-
/* No timeout on first call to poll in order to render the UI fast. */
340+
/* No timeout on first call to fdwait in order to render the UI fast. */
345341
timo = 0;
342+
fd = STDIN_FILENO;
346343
for (;;) {
347-
dokey = doread = 0;
348344
toggle_sigwinch(1);
349-
if (poll(fds, nfds, timo) == -1 && errno != EINTR)
350-
err(1, "poll");
345+
ready = fdwait(fileno(tty_in), fd, timo);
346+
if (ready == -1) {
347+
if (errno == EINTR)
348+
ready = 0;
349+
else
350+
err(1, "fdwait");
351+
}
351352
toggle_sigwinch(0);
352353
if (gotsigwinch) {
353354
gotsigwinch = 0;
354355
goto resize;
355356
}
356357
timo = -1;
357-
for (i = 0; i < nfds; i++) {
358-
if (fds[i].revents & (POLLERR | POLLNVAL))
359-
errx(1, "%d: bad file descriptor", fds[i].fd);
360-
if ((fds[i].revents & (POLLIN | POLLHUP)) == 0)
361-
continue;
362-
363-
if (fds[i].fd == fds[0].fd)
364-
dokey = 1;
365-
else if (fds[i].fd == fds[1].fd)
366-
doread = 1;
367-
}
368358

369359
length = choices.length;
370-
if (doread) {
371-
if (get_choices(STDIN_FILENO, query_length > 0 ?
372-
choices_count : 0)) {
360+
if (ready & FD_STDIN_READY) {
361+
insert = query_length > 0 ? (ssize_t)choices_count : -1;
362+
if (get_choices(STDIN_FILENO, insert)) {
373363
if (query_length > 0) {
374364
dofilter = 1;
375365
choices_reset = 0;
376366
choices_count += choices.length -
377367
length;
378368
}
379369
} else {
380-
nfds = 1; /* EOF */
370+
fd = -1; /* EOF */
381371
}
382372
}
383-
if (!dokey && query_length == 0 && length >= choices_lines)
384-
continue; /* prevent redundant rendering */
385-
if (!dokey)
373+
if ((ready & FD_TTY_READY) == 0)
386374
goto render;
387375

388376
switch (get_key(&buf)) {
@@ -590,9 +578,8 @@ int
590578
filter_choices(size_t nchoices)
591579
{
592580
struct choice *c;
593-
struct pollfd pfd;
594581
size_t i, match_length;
595-
int nready;
582+
int ready;
596583

597584
for (i = 0; i < nchoices; i++) {
598585
c = &choices.v[i];
@@ -608,11 +595,8 @@ filter_choices(size_t nchoices)
608595
}
609596

610597
if (i > 0 && i % 50 == 0) {
611-
pfd.fd = fileno(tty_in);
612-
pfd.events = POLLIN;
613-
if ((nready = poll(&pfd, 1, 0)) == -1)
614-
err(1, "poll");
615-
if (nready == 1 && pfd.revents & (POLLIN | POLLHUP))
598+
ready = fdwait(fileno(tty_in), -1, 0);
599+
if (ready != -1 && (ready & FD_TTY_READY) != 0)
616600
return 0;
617601
}
618602
}
@@ -1116,6 +1100,12 @@ tty_getc(void)
11161100
ssize_t n;
11171101
unsigned char c;
11181102

1103+
if (tty_getc_peek != -1) {
1104+
c = tty_getc_peek;
1105+
tty_getc_peek = -1;
1106+
return c;
1107+
}
1108+
11191109
n = read(fileno(tty_in), &c, sizeof(c));
11201110
if (n == -1)
11211111
err(1, "read");

0 commit comments

Comments
 (0)