/* * 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 . */ #include "config.h" #include #include #include #include #if defined (HAVE_EPOLL_CREATE) #include #elif defined (HAVE_KQUEUE) #include #include #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_CREATE) /* * 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); }