aboutsummaryrefslogtreecommitdiffstats
path: root/close_range.c
diff options
context:
space:
mode:
authorMattias Andrée <maandree@kth.se>2024-01-27 19:05:28 +0100
committerMattias Andrée <maandree@kth.se>2024-01-27 19:05:28 +0100
commitd055dfe7bbdbcbc7fbd521168b4dc728e004dcd1 (patch)
treeda559a50512c1b3446a1a91d0702b7713ecca97b /close_range.c
parentFix man pages (diff)
downloadlibsimple-d055dfe7bbdbcbc7fbd521168b4dc728e004dcd1.tar.gz
libsimple-d055dfe7bbdbcbc7fbd521168b4dc728e004dcd1.tar.bz2
libsimple-d055dfe7bbdbcbc7fbd521168b4dc728e004dcd1.tar.xz
Add libsimple_close_range
Signed-off-by: Mattias Andrée <maandree@kth.se>
Diffstat (limited to 'close_range.c')
-rw-r--r--close_range.c142
1 files changed, 142 insertions, 0 deletions
diff --git a/close_range.c b/close_range.c
new file mode 100644
index 0000000..540eba0
--- /dev/null
+++ b/close_range.c
@@ -0,0 +1,142 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+#ifdef __linux__
+# include <sys/syscall.h>
+#endif
+
+
+static int
+uintpcmp(const void *av, const void *bv)
+{
+ const unsigned int *ap = av;
+ const unsigned int *bp = bv;
+ const unsigned int a = *ap;
+ const unsigned int b = *bp;
+ return a < b ? -1 : a > b;
+}
+
+static unsigned int *
+list_fds(unsigned int min, unsigned int max, size_t *count)
+{
+ DIR *dir;
+ int ignore_fd;
+ unsigned int *ret = NULL, *new;
+ size_t size = 0;
+ struct dirent *f;
+ unsigned long int fd;
+ char *end;
+
+ *count = 0;
+
+ dir = opendir("/dev/fd");
+ if (!dir)
+ return NULL;
+ ignore_fd = dirfd(dir);
+
+ errno = 0;
+ while ((f = readdir(dir))) {
+ if (!isdigit(f->d_name[0]))
+ continue;
+ fd = strtoul(f->d_name, &end, 10);
+ if ((int)fd == ignore_fd || (unsigned int)fd < min || (unsigned int)fd > max)
+ continue;
+ if (*end || fd > INT_MAX || (!fd && errno))
+ continue;
+ if (*count == size) {
+ size += 16;
+ new = realloc(ret, size * sizeof(*ret));
+ if (!new)
+ goto fail;
+ ret = new;
+ }
+ ret[(*count)++] = (unsigned int)fd;
+ }
+
+ if (errno) {
+ fail:
+ closedir(dir);
+ fail_closed:
+ free(ret);
+ return NULL;
+ }
+
+ if (closedir(dir))
+ goto fail_closed;
+
+ return ret;
+}
+
+int
+libsimple_close_range(unsigned int first, unsigned int last, unsigned int *next) /* TODO test */
+{
+ int saved_errno;
+
+ *next = first;
+
+ if (first > last) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (first > INT_MAX)
+ return 0;
+ if (last > INT_MAX)
+ last = INT_MAX;
+
+ saved_errno = errno;
+
+#if defined(__linux__) && defined(__NR_close_range)
+ if (!syscall(__NR_close_range, first, last, 0))
+ return 0;
+ else if (errno != ENOSYS)
+ return -1;
+#endif
+
+ if (last - first > 100) { /* TODO best limit should be researched */
+ unsigned int *fds;
+ size_t n, i;
+ fds = list_fds(first, last, &n);
+ if (!fds)
+ goto fallback;
+ qsort(fds, n, sizeof(*fds), uintpcmp);
+ for (i = 0; i < n; i++) {
+ if (close((int)fds[i]) && errno != EBADF) {
+ if (i + 1 < n)
+ *next = fds[i + 1];
+ else
+ *next = fds[i] + (fds[i] < LIBSIMPLE_CLOSE_RANGE_MAX);
+ free(fds);
+ return -1;
+ }
+ }
+ free(fds);
+ goto out;
+ }
+
+fallback:
+ do {
+ if (close((int)first) && errno != EBADF) {
+ *next = first + (first < LIBSIMPLE_CLOSE_RANGE_MAX);
+ return -1;
+ }
+ } while (first++ < last);
+
+out:
+ *next = last + (last < LIBSIMPLE_CLOSE_RANGE_MAX);
+ errno = saved_errno;
+ return 0;
+}
+
+
+#else
+#include "test.h"
+
+int
+main(void)
+{
+ return 0;
+}
+
+#endif