[PATCH v2 3/4] io: Add closefrom [BZ #10353]

Florian Weimer fweimer@redhat.com
Wed Dec 23 13:28:31 GMT 2020


* Adhemerval Zanella:

> The symbol closes all open file descriptors greater than or equal to
> input argument.  Negative values are campled to 0, i.e, it will close
> all file descriptors.

“The function”?

Typo: “campled”.

> diff --git a/io/tst-closefrom.c b/io/tst-closefrom.c
> new file mode 100644
> index 0000000000..47b50b74b6
> --- /dev/null
> +++ b/io/tst-closefrom.c

> +#define NFDS 100
> +
> +static int
> +closefrom_test (void)
> +{
> +  struct support_descriptors *descrs = support_descriptors_list ();
> +
> +  int lowfd = xopen ("/dev/null", O_RDONLY | O_CLOEXEC, 0600);
> +  for (int i = 0; i < NFDS; i++)
> +    xopen ("/dev/null", O_RDONLY | O_CLOEXEC, 0600);

See the previous comments about checking for contiguous allocation and
the meaning of NFDS.

> +  const int maximum_fd = lowfd + NFDS;
> +  const int half_fd = maximum_fd / 2;
> +  const int gap = maximum_fd / 4;
> +
> +  /* Close half of the descriptors and check result.  */
> +  closefrom (half_fd);
> +
> +  for (int i = half_fd; i <= maximum_fd; i++)
> +    {
> +      TEST_COMPARE (fcntl (i, F_GETFL), -1);
> +      TEST_COMPARE (errno, EBADF);
> +    }
> +  for (int i = 0; i < half_fd; i++)
> +    TEST_VERIFY (fcntl (i, F_GETFL) > -1);
> +
> +  /* Create some gaps, close up to a threshold, and check result.  */
> +  xclose (35);
> +  xclose (38);
> +  xclose (42);
> +  xclose (46);

lowfd + 35 etc., please.

> +/* Check if closefrom works even when no new file descriptors can be
> +   created.  */
> +static int
> +closefrom_test_file_desc_limit (void)
> +{
> +  int max_fd = NFDS;
> +  {
> +    struct rlimit rl;
> +    if (getrlimit (RLIMIT_NOFILE, &rl) == -1)
> +      FAIL_EXIT1 ("getrlimit (RLIMIT_NOFILE): %m");
> +
> +    max_fd = (rl.rlim_cur < max_fd ? rl.rlim_cur : max_fd);
> +    rl.rlim_cur = max_fd;
> +
> +    if (setrlimit (RLIMIT_NOFILE, &rl) == 1)
> +      FAIL_EXIT1 ("setrlimit (RLIMIT_NOFILE): %m");
> +  }
> +
> +  /* Exhauste the file descriptor limit.  */
> +  int lowfd = xopen ("/dev/null", O_RDONLY | O_CLOEXEC, 0600);
> +  for (;;)
> +    {
> +      int fd = open ("/dev/null", O_RDONLY | O_CLOEXEC, 0600);
> +      if (fd == -1)
> +	{
> +	  if (errno != EMFILE)
> +	    FAIL_EXIT1 ("create_temp_file: %m");
> +	  break;
> +	}
> +    }
> +
> +  closefrom (lowfd);
> +  for (int i = lowfd; i < NFDS; i++)
> +    {
> +      TEST_COMPARE (fcntl (i, F_GETFL), -1);
> +      TEST_COMPARE (errno, EBADF);
> +    }
> +
> +  return 0;
> +}

Good test, thanks.

> diff --git a/manual/llio.texi b/manual/llio.texi
> index 018e7933de..d2cf7bbabd 100644
> --- a/manual/llio.texi
> +++ b/manual/llio.texi
> @@ -314,6 +314,20 @@ is used.
>  @end table
>  @end deftypefun
>  
> +@deftypefun void closefrom (int @var{lowfd})
> +@standards{GNU, unistd.h}
> +@safety{@prelim{}@mtsafe{}@assafe{}@acsafe{@acsfd{}}}
> +
> +The function @code{closefrom} closes all file descriptors large or equal
> +then @var{lowfd}.  This function is similar to call @code{close} in specified
> +file descriptor range.
> +
> +@table @code
> +@item EINVAL
> +The @var{lowfd} value is larger than @var{maxfd} or an unsupported @var{flag}
> +is used.
> +@end table
> +@end deftypefun

I think this function cannot fail.

>  @node I/O Primitives
>  @section Input and Output Primitives
> diff --git a/posix/unistd.h b/posix/unistd.h
> index 32b8161619..652fed4616 100644
> --- a/posix/unistd.h
> +++ b/posix/unistd.h
> @@ -352,6 +352,12 @@ extern __off64_t lseek64 (int __fd, __off64_t __offset, int __whence)
>     __THROW.  */
>  extern int close (int __fd);
>  
> +#ifdef __USE_GNU
> +/* Close all open file descriptors greater than or equal to LOWFD.
> +   Negative LOWFD is clamped to 0.  */
> +extern void closefrom (int __lowfd) __THROW;
> +#endif

__USE_MISC instead of GNU?  Given that this is so widely implemented.

> diff --git a/sysdeps/unix/sysv/linux/closefrom_fallback.c b/sysdeps/unix/sysv/linux/closefrom_fallback.c
> new file mode 100644
> index 0000000000..461582792d
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/closefrom_fallback.c

> +/* Fallback code: iterates over /proc/self/fd, closing each file descriptor
> +   that fall on the criteria.  */
> +_Bool
> +__closefrom_fallback (int from)
> +{
> +  bool ret = false;
> +
> +  int dirfd = __open_nocancel (FD_TO_FILENAME_PREFIX, O_RDONLY | O_DIRECTORY,
> +			       0);
> +  if (dirfd == -1)
> +    {
> +      /* The closefrom should work even when process can't open new files.
> +	 In this case it loops over RLIMIT_NOFILE / rlim_cur until it frees
> +	 a file descriptor to iterate over /proc.  */
> +      if (errno != EMFILE)
> +	goto err;

As explained, this should be errno == ENOENT.  There are a few more
calls that could take the shortcut, but I don't think they can occur in
practice.

> +  char buffer[1024];
> +  while (true)
> +    {
> +      ssize_t ret = __getdents64 (dirfd, buffer, sizeof (buffer));
> +      if (ret == -1)
> +        goto err;
> +      else if (ret == 0)
> +        break;
> +
> +      char *begin = buffer, *end = buffer + ret;
> +      while (begin != end)
> +	{
> +          unsigned short int d_reclen;
> +	  memcpy (&d_reclen, begin + offsetof (struct dirent64, d_reclen),
> +		  sizeof (d_reclen));
> +	  const char *dname = begin + offsetof (struct dirent64, d_name);
> +	  begin += d_reclen;
> +
> +	  if (dname[0] == '.')
> +	    continue;
> +
> +	  int fd = 0;
> +	  for (const char *s = dname; (unsigned int) (*s) - '0' < 10; s++)
> +	    fd = 10 * fd + (*s - '0');
> +
> +	  if (fd == dirfd || fd < from)
> +	    continue;
> +
> +	  __close_nocancel (fd);
> +	}
> +    }

Do you object to the idea of the rewind?  The implementation above
encodes very specific /proc/self/fd behavior.

With containers and seccomp filters, we can't assume that a future
behavioral change will not matter because we have close_range.

Thanks,
Florian
-- 
Red Hat GmbH, https://de.redhat.com/ , Registered seat: Grasbrunn,
Commercial register: Amtsgericht Muenchen, HRB 153243,
Managing Directors: Charles Cachera, Brian Klemm, Laurie Krebs, Michael O'Neill



More information about the Libc-alpha mailing list