1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
|
/*
* Copyright © 2021 Ole André Vadla Ravnås
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <errno.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#if defined (HAVE_EPOLL_CREATE1)
#include <sys/epoll.h>
#elif defined (HAVE_KQUEUE)
#include <sys/event.h>
#include <sys/time.h>
#endif
#include "giounix-private.h"
#define G_TEMP_FAILURE_RETRY(expression) \
({ \
gssize __result; \
\
do \
__result = (gssize) (expression); \
while (__result == -1 && errno == EINTR); \
\
__result; \
})
static gboolean g_fd_is_regular_file (int fd) G_GNUC_UNUSED;
gboolean
_g_fd_is_pollable (int fd)
{
/*
* Determining whether a file-descriptor (FD) is pollable turns out to be
* quite hard.
*
* We used to detect this by attempting to lseek() and check if it failed with
* ESPIPE, and if so we'd consider the FD pollable. But this turned out to not
* work on e.g. PTYs and other devices that are pollable.
*
* Another approach that was considered was to call fstat() and if it failed
* we'd assume that the FD is pollable, and if it succeeded we'd consider it
* pollable as long as it's not a regular file. This seemed to work alright
* except for FDs backed by simple devices, such as /dev/null.
*
* There are however OS-specific methods that allow us to figure this out with
* absolute certainty:
*/
#if defined (HAVE_EPOLL_CREATE1)
/*
* Linux
*
* The answer we seek is provided by the kernel's file_can_poll():
* https://github.com/torvalds/linux/blob/2ab38c17aac10bf55ab3efde4c4db3893d8691d2/include/linux/poll.h#L81-L84
* But we cannot probe that by using poll() as the returned events for
* non-pollable FDs are always IN | OUT.
*
* The best option then seems to be using epoll, as it will refuse to add FDs
* where file_can_poll() returns FALSE.
*/
int efd;
struct epoll_event ev = { 0, };
gboolean add_succeeded;
efd = epoll_create1 (EPOLL_CLOEXEC);
if (efd == -1)
g_error ("epoll_create1 () failed: %s", g_strerror (errno));
ev.events = EPOLLIN;
add_succeeded = epoll_ctl (efd, EPOLL_CTL_ADD, fd, &ev) == 0;
close (efd);
return add_succeeded;
#elif defined (HAVE_KQUEUE)
/*
* Apple OSes and BSDs
*
* Like on Linux, we cannot use poll() to do the probing, but kqueue does
* the trick as it will refuse to add non-pollable FDs. (Except for regular
* files, which we need to special-case. Even though kqueue does support them,
* poll() does not.)
*/
int kfd;
struct kevent ev;
gboolean add_succeeded;
if (g_fd_is_regular_file (fd))
return FALSE;
kfd = kqueue ();
if (kfd == -1)
g_error ("kqueue () failed: %s", g_strerror (errno));
EV_SET (&ev, fd, EVFILT_READ, EV_ADD, 0, 0, NULL);
add_succeeded =
G_TEMP_FAILURE_RETRY (kevent (kfd, &ev, 1, NULL, 0, NULL)) == 0;
close (kfd);
return add_succeeded;
#else
/*
* Other UNIXes (AIX, QNX, Solaris, etc.)
*
* We can rule out regular files, but devices such as /dev/null will be
* reported as pollable even though they're not. This is hopefully good
* enough for most use-cases, but easy to expand on later if needed.
*/
return !g_fd_is_regular_file (fd);
#endif
}
static gboolean
g_fd_is_regular_file (int fd)
{
struct stat st;
if (G_TEMP_FAILURE_RETRY (fstat (fd, &st)) == -1)
return FALSE;
return S_ISREG (st.st_mode);
}
|