From e814f4b04ee413a7bb3dfa43e74c8fb4abf58359 Mon Sep 17 00:00:00 2001
From: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Date: Tue, 24 Aug 2021 16:12:24 -0300
Subject: [PATCH] support: Add support_open_dev_null_range

It returns a range of file descriptor referring to the '/dev/null'
pathname.  The function takes care of restarting the open range
if a file descriptor is found within the specified range and
also increases RLIMIT_NOFILE if required.

Checked on x86_64-linux-gnu.
---
 support/Makefile                          |   2 +
 support/support-open-dev-null-range.c     | 134 ++++++++++++++++++++++++++
 support/support.h                         |   8 ++
 support/tst-support-open-dev-null-range.c | 155 ++++++++++++++++++++++++++++++
 4 files changed, 299 insertions(+)
 create mode 100644 support/support-open-dev-null-range.c
 create mode 100644 support/tst-support-open-dev-null-range.c

diff --git a/support/Makefile b/support/Makefile
index a462781..6332e7b 100644
--- a/support/Makefile
+++ b/support/Makefile
@@ -66,6 +66,7 @@ libsupport-routines = \
   support_path_support_time64 \
   support_process_state \
   support_ptrace \
+  support-open-dev-null-range \
   support_openpty \
   support_paths \
   support_quote_blob \
@@ -264,6 +265,7 @@ tests = \
   tst-support_capture_subprocess \
   tst-support_descriptors \
   tst-support_format_dns_packet \
+  tst-support-open-dev-null-range \
   tst-support-process_state \
   tst-support_quote_blob \
   tst-support_quote_string \
diff --git a/support/support-open-dev-null-range.c b/support/support-open-dev-null-range.c
new file mode 100644
index 0000000..80d9dba
--- /dev/null
+++ b/support/support-open-dev-null-range.c
@@ -0,0 +1,134 @@
+/* Return a range of open file descriptors.
+   Copyright (C) 2021 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C 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.
+
+   The GNU C 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 the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <support/support.h>
+#include <support/check.h>
+#include <support/xunistd.h>
+#include <stdlib.h>
+#include <sys/resource.h>
+
+static void
+increase_nofile (void)
+{
+  struct rlimit rl;
+  if (getrlimit (RLIMIT_NOFILE, &rl) == -1)
+    FAIL_EXIT1 ("getrlimit (RLIMIT_NOFILE): %m");
+
+  rl.rlim_cur += 128;
+
+  if (setrlimit (RLIMIT_NOFILE, &rl) == 1)
+    FAIL_EXIT1 ("setrlimit (RLIMIT_NOFILE): %m");
+}
+
+static int
+open_dev_null (int flags, mode_t mode)
+{
+ int fd = open64 ("/dev/null", flags, mode);
+ if (fd > 0)
+   return fd;
+
+ if (fd < 0 && errno != EMFILE)
+   FAIL_EXIT1 ("open64 (\"/dev/null\", 0x%x, 0%o): %m", flags, mode);
+
+ increase_nofile ();
+
+ return xopen ("/dev/null", flags, mode);
+}
+
+struct range
+{
+  int lowfd;
+  size_t len;
+};
+
+struct range_list
+{
+  size_t total;
+  size_t used;
+  struct range *ranges;
+};
+
+static void
+range_init (struct range_list *r)
+{
+  r->total = 8;
+  r->used = 0;
+  r->ranges = xmalloc (r->total * sizeof (struct range));
+}
+
+static void
+range_add (struct range_list *r, int lowfd, size_t len)
+{
+  if (r->used == r->total)
+    {
+      r->total *= 2;
+      r->ranges = xrealloc (r->ranges, r->total * sizeof (struct range));
+    }
+  r->ranges[r->used].lowfd = lowfd;
+  r->ranges[r->used].len = len;
+  r->used++;
+}
+
+static void
+range_close (struct range_list *r)
+{
+  for (size_t i = 0; i < r->used; i++)
+    {
+      int minfd = r->ranges[i].lowfd;
+      int maxfd = r->ranges[i].lowfd + r->ranges[i].len;
+      for (int fd = minfd; fd < maxfd; fd++)
+	xclose (fd);
+    }
+  free (r->ranges);
+}
+
+int
+support_open_dev_null_range (int num, int flags, mode_t mode)
+{
+  /* We keep track of the ranges that hit an already opened descriptor, so
+     we close them after we get a working range.  */
+  struct range_list rl;
+  range_init (&rl);
+
+  int lowfd = open_dev_null (flags, mode);
+  int prevfd = lowfd;
+  while (true)
+    {
+      int i = 1;
+      for (; i < num; i++)
+	{
+	  int fd = open_dev_null (flags, mode);
+	  if (fd != lowfd + i)
+	    {
+	      range_add (&rl, lowfd, prevfd - lowfd + 1);
+
+	      prevfd = lowfd = fd;
+	      break;
+	    }
+	  prevfd = fd;
+	}
+      if (i == num)
+	break;
+    }
+
+  range_close (&rl);
+
+  return lowfd;
+}
diff --git a/support/support.h b/support/support.h
index 834dba9..e6911e1 100644
--- a/support/support.h
+++ b/support/support.h
@@ -193,6 +193,14 @@ struct support_stack support_stack_alloc (size_t size);
 /* Deallocate the STACK.  */
 void support_stack_free (struct support_stack *stack);
 
+
+/* Create a range of NUM opened '/dev/null' file descriptors using FLAGS and
+   MODE.  The function takes care of restarting the open range if a file
+   descriptor is found within the specified range and also increases
+   RLIMIT_NOFILE if required.
+   The returned value is the lowest file descriptor number.  */
+int support_open_dev_null_range (int num, int flags, mode_t mode);
+
 __END_DECLS
 
 #endif /* SUPPORT_H */
diff --git a/support/tst-support-open-dev-null-range.c b/support/tst-support-open-dev-null-range.c
new file mode 100644
index 0000000..8e29def
--- /dev/null
+++ b/support/tst-support-open-dev-null-range.c
@@ -0,0 +1,155 @@
+/* Tests for support_open_dev_null_range.
+   Copyright (C) 2021 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C 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.
+
+   The GNU C 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 the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <errno.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <support/check.h>
+#include <support/support.h>
+#include <support/xunistd.h>
+#include <sys/resource.h>
+#include <stdlib.h>
+
+#ifndef PATH_MAX
+# define PATH_MAX 1024
+#endif
+
+#include <stdio.h>
+
+static void
+check_path (int fd)
+{
+  char *proc_fd_path = xasprintf ("/proc/self/fd/%d", fd);
+  char file_path[PATH_MAX];
+  ssize_t file_path_length
+    = readlink (proc_fd_path, file_path, sizeof (file_path));
+  free (proc_fd_path);
+  if (file_path_length < 0)
+    FAIL_EXIT1 ("readlink (%s, %p, %zu)", proc_fd_path, file_path,
+		sizeof (file_path));
+  file_path[file_path_length] = '\0';
+  TEST_COMPARE_STRING (file_path, "/dev/null");
+}
+
+static int
+number_of_opened_files (void)
+{
+  DIR *fds = opendir ("/proc/self/fd");
+  if (fds == NULL)
+    FAIL_EXIT1 ("opendir (\"/proc/self/fd\"): %m");
+
+  int r = 0;
+  while (true)
+    {
+      errno = 0;
+      struct dirent64 *e = readdir64 (fds);
+      if (e == NULL)
+        {
+          if (errno != 0)
+            FAIL_EXIT1 ("readdir: %m");
+          break;
+        }
+
+      if (e->d_name[0] == '.')
+        continue;
+
+      char *endptr;
+      long int fd = strtol (e->d_name, &endptr, 10);
+      if (*endptr != '\0' || fd < 0 || fd > INT_MAX)
+        FAIL_EXIT1 ("readdir: invalid file descriptor name: /proc/self/fd/%s",
+                    e->d_name);
+
+      /* Skip the descriptor which is used to enumerate the
+         descriptors.  */
+      if (fd == dirfd (fds))
+        continue;
+
+      r = r + 1;
+    }
+
+  closedir (fds);
+
+  return r;
+}
+
+static int
+do_test (void)
+{
+  const int nfds1 = 8;
+  int lowfd = support_open_dev_null_range (nfds1, O_RDONLY, 0600);
+  for (int i = 0; i < nfds1; i++)
+    {
+      TEST_VERIFY (fcntl (lowfd + i, F_GETFL) > -1);
+      check_path (lowfd + i);
+    }
+
+  /* create some gaps.  */
+  xclose (lowfd + 1);
+  xclose (lowfd + 5);
+  xclose (lowfd + 6);
+
+  const int nfds2 = 16;
+  int lowfd2 = support_open_dev_null_range (nfds2, O_RDONLY, 0600);
+  for (int i = 0; i < nfds2; i++)
+    {
+      TEST_VERIFY (fcntl (lowfd2 + i, F_GETFL) > -1);
+      check_path (lowfd2 + i);
+    }
+
+  /* Decrease the maximum number of files.  */
+  {
+    struct rlimit rl;
+    if (getrlimit (RLIMIT_NOFILE, &rl) == -1)
+      FAIL_EXIT1 ("getrlimit (RLIMIT_NOFILE): %m");
+    
+    rl.rlim_cur = number_of_opened_files ();
+
+    if (setrlimit (RLIMIT_NOFILE, &rl) == 1)
+      FAIL_EXIT1 ("setrlimit (RLIMIT_NOFILE): %m");
+  }
+
+  const int nfds3 = 16;
+  int lowfd3 = support_open_dev_null_range (nfds3, O_RDONLY, 0600);
+  for (int i = 0; i < nfds3; i++)
+    {
+      TEST_VERIFY (fcntl (lowfd3 + i, F_GETFL) > -1);
+      check_path (lowfd3 + i);
+    }
+
+  /* create a lot of gaps to trigger the range extension.  */
+  xclose (lowfd3 + 1);
+  xclose (lowfd3 + 3);
+  xclose (lowfd3 + 5);
+  xclose (lowfd3 + 7);
+  xclose (lowfd3 + 9);
+  xclose (lowfd3 + 11);
+  xclose (lowfd3 + 13);
+
+  const int nfds4 = 16;
+  int lowfd4 = support_open_dev_null_range (nfds4, O_RDONLY, 0600);
+  for (int i = 0; i < nfds4; i++)
+    {
+      TEST_VERIFY (fcntl (lowfd4 + i, F_GETFL) > -1);
+      check_path (lowfd4 + i);
+    }
+
+  return 0;
+}
+
+#include <support/test-driver.c>
-- 
1.8.3.1

