File: giounix-private.c

package info (click to toggle)
glib2.0 2.85.1-2
  • links: PTS, VCS
  • area: main
  • in suites: experimental
  • size: 66,344 kB
  • sloc: ansic: 541,342; python: 9,624; sh: 1,572; xml: 1,482; perl: 1,222; cpp: 535; makefile: 316; javascript: 11
file content (147 lines) | stat: -rw-r--r-- 4,364 bytes parent folder | download | duplicates (4)
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);
}