diff mbox series

posix: Add posix_spawn_file_actions_closefrom

Message ID 20190612205026.16964-1-adhemerval.zanella@linaro.org
State New
Headers show
Series posix: Add posix_spawn_file_actions_closefrom | expand

Commit Message

Adhemerval Zanella Netto June 12, 2019, 8:50 p.m. UTC
This patch adds a way to close a range of file descriptors on posix_spawn
as a new file action.  The API is similar to the one provided by Solaris
11 [1], where the file action causes the all open file descriptors greater
than or equal to input on to be closed when the new process is spawned.

There are some discussions on BZ#10353 [2], although the bug itself asks
for a generic solution (similar to the closeall provided by some BSD).
The posix_spawn is safe to be implemented by interacting over /proc/self/fd,
the Linux spawni.c does not use CLONE_FILES, so the helper process has its
own file descriptor table and any failure (in /proc operation) aborts the
process creation and returns an error to the caller.

I am aware that this file action might be redundant to the current approach
of POSIX in promoting O_CLOEXEC in more interfaces. However O_CLOEXEC is still
not the default and for some specific usages, the caller needs to close all
possible file descriptors to avoid them leaking.  Some examples are CPython
(discussed in BZ#10353) and OpenJDK jspawnhelper [3] (where OpenJDK spawns a
helper process to exactly closes all file descriptors).  Most likely any
environment which calls functions that might open file descriptor under the
hood and aim to use posix_spawn might face the same requirement.

Regarding implementation, to avoid relying on malloc call on child helper
I added an internal opendir that receives a previously allocated buffer.
The allocated buffer should be large enough to avoid calling getdents too
often and I think it would be hardly a hotspot.

Checked on x86_64-linux-gnu, i686-linux-gnu, powerpc64le-linux-gnu, and
aarch64-linux-gnu.

[1] https://docs.oracle.com/cd/E36784_01/html/E36874/posix-spawn-file-actions-addclosefrom-np-3c.html
[2] https://sourceware.org/bugzilla/show_bug.cgi?id=10353
[3] https://github.com/openjdk/jdk/blob/master/src/java.base/unix/native/libjava/childproc.c

	* include/dirent.h (__opendir_inplace, __alloc_dir): New prototypes.
	* posix/Makefile (routines): Add spawn_faction_addclosefrom.
	(tests): Add tst-spawn5.
	(tst-spawn5-ARGS): New rule.
	* posix/Versions [GLIBC_2.30] (libc): Add
	posix_spawn_file_actions_addclosefrom_np.
	* posix/spawn.h (posix_spawn_file_actions_addclosefrom_np): New
	prototype.
	* posix/spawn_faction_addclosefrom.c: New file.
	* posix/spawn_int_abi.h: Likewise.
	* sysdeps/unix/sysv/linux/spawn_int_abi.h: Likewise.
	* posix/tst-spawn5.c: Likewise.
	* posix/spawn_faction_destroy.c (__posix_spawn_file_actions_destroy):
	Add spawn_do_closefrom handling.
	* posix/spawn_int.h (__spawn_action): Add spawn_do_closefrom tag and
	closefrom_action action.
	* sysdeps/posix/closedir.c (__closedir): Check if buffer was
	allocated through malloc before call free.
	* sysdeps/posix/dirstream.h (__dirstream): Add allocated member.
	* sysdeps/posix/fdopendir.c (__fdopendir): Adapt to use __alloc_dir
	signature.
	* sysdeps/posix/opendir.c (opendir_tail_common,
	opendir_tail_inplace, __opendir_inplace): New symbols.
	(__alloc_dir): Add argument to use a pre-allocated buffer.
	* sysdeps/mach/hurd/spawni.c (__spawni): Handle spawn_do_closefrom.
	* sysdeps/posix/spawni.c (__spawni): Likewise.
	* sysdeps/mach/hurd/i386/libc.abilist (2.30): Add
	posix_spawn_file_actions_addclosefrom_np.
	* sysdeps/unix/sysv/linux/spawni.c (spawn_closefrom): New function.
	(__spawni_child): Handle spawn_do_closefrom.
	* sysdeps/unix/sysv/linux/aarch64/libc.abilist: Likewise.
	* sysdeps/unix/sysv/linux/alpha/libc.abilist: Likewise.
	* sysdeps/unix/sysv/linux/arm/libc.abilist: Likewise.
	* sysdeps/unix/sysv/linux/csky/libc.abilist: Likewise.
	* sysdeps/unix/sysv/linux/hppa/libc.abilist: Likewise.
	* sysdeps/unix/sysv/linux/i386/libc.abilist: Likewise.
	* sysdeps/unix/sysv/linux/ia64/libc.abilist: Likewise.
	* sysdeps/unix/sysv/linux/m68k/coldfire/libc.abilist: Likewise.
	* sysdeps/unix/sysv/linux/m68k/m680x0/libc.abilist: Likewise.
	* sysdeps/unix/sysv/linux/microblaze/libc.abilist: Likewise.
	* sysdeps/unix/sysv/linux/mips/mips32/fpu/libc.abilist: Likewise.
	* sysdeps/unix/sysv/linux/mips/mips32/nofpu/libc.abilist: Likewise.
	* sysdeps/unix/sysv/linux/mips/mips64/n32/libc.abilist: Likewise.
	* sysdeps/unix/sysv/linux/mips/mips64/n64/libc.abilist: Likewise.
	* sysdeps/unix/sysv/linux/nios2/libc.abilist: Likewise
	* sysdeps/unix/sysv/linux/powerpc/powerpc32/fpu/libc.abilist:
	Likewise.
	* sysdeps/unix/sysv/linux/powerpc/powerpc64/be/libc.abilist:
	Likewise.
	* sysdeps/unix/sysv/linux/powerpc/powerpc64/le/libc.abilist:
	Likewise.
	* sysdeps/unix/sysv/linux/riscv/rv64/libc.abilist: Likewise
	* sysdeps/unix/sysv/linux/s390/s390-32/libc.abilist: Likewise
	* sysdeps/unix/sysv/linux/s390/s390-64/libc.abilist: Likewise
	* sysdeps/unix/sysv/linux/sh/libc.abilist: Likewise.
	* sysdeps/unix/sysv/linux/sparc/sparc32/libc.abilist: Likewise.
	* sysdeps/unix/sysv/linux/sparc/sparc64/libc.abilist: Likewise.
	* sysdeps/unix/sysv/linux/x86_64/64/libc.abilist: Likewise.
	* sysdeps/unix/sysv/linux/x86_64/x32/libc.abilist: Likewise.
---
 include/dirent.h                              |   6 +-
 posix/Makefile                                |   5 +-
 posix/Versions                                |   1 +
 posix/spawn.h                                 |   7 +
 posix/spawn_faction_addclosefrom.c            |  58 +++++
 posix/spawn_faction_destroy.c                 |   1 +
 posix/spawn_int.h                             |   6 +
 posix/spawn_int_abi.h                         |  27 +++
 posix/tst-spawn5.c                            | 210 ++++++++++++++++++
 sysdeps/mach/hurd/i386/libc.abilist           |   1 +
 sysdeps/mach/hurd/spawni.c                    |   4 +
 sysdeps/posix/closedir.c                      |   3 +-
 sysdeps/posix/dirstream.h                     |   1 +
 sysdeps/posix/fdopendir.c                     |   2 +-
 sysdeps/posix/opendir.c                       |  94 ++++++--
 sysdeps/posix/spawni.c                        |   4 +
 sysdeps/unix/sysv/linux/aarch64/libc.abilist  |   1 +
 sysdeps/unix/sysv/linux/alpha/libc.abilist    |   1 +
 sysdeps/unix/sysv/linux/arm/libc.abilist      |   1 +
 sysdeps/unix/sysv/linux/csky/libc.abilist     |   1 +
 sysdeps/unix/sysv/linux/hppa/libc.abilist     |   1 +
 sysdeps/unix/sysv/linux/i386/libc.abilist     |   1 +
 sysdeps/unix/sysv/linux/ia64/libc.abilist     |   1 +
 .../sysv/linux/m68k/coldfire/libc.abilist     |   1 +
 .../unix/sysv/linux/m68k/m680x0/libc.abilist  |   1 +
 .../unix/sysv/linux/microblaze/libc.abilist   |   1 +
 .../sysv/linux/mips/mips32/fpu/libc.abilist   |   1 +
 .../sysv/linux/mips/mips32/nofpu/libc.abilist |   1 +
 .../sysv/linux/mips/mips64/n32/libc.abilist   |   1 +
 .../sysv/linux/mips/mips64/n64/libc.abilist   |   1 +
 sysdeps/unix/sysv/linux/nios2/libc.abilist    |   1 +
 .../linux/powerpc/powerpc32/fpu/libc.abilist  |   1 +
 .../linux/powerpc/powerpc64/be/libc.abilist   |   1 +
 .../linux/powerpc/powerpc64/le/libc.abilist   |   1 +
 .../unix/sysv/linux/riscv/rv64/libc.abilist   |   1 +
 .../unix/sysv/linux/s390/s390-32/libc.abilist |   1 +
 .../unix/sysv/linux/s390/s390-64/libc.abilist |   1 +
 sysdeps/unix/sysv/linux/sh/libc.abilist       |   1 +
 .../sysv/linux/sparc/sparc32/libc.abilist     |   1 +
 .../sysv/linux/sparc/sparc64/libc.abilist     |   1 +
 sysdeps/unix/sysv/linux/spawn_int_abi.h       |  25 +++
 sysdeps/unix/sysv/linux/spawni.c              |  65 +++++-
 .../unix/sysv/linux/x86_64/64/libc.abilist    |   1 +
 .../unix/sysv/linux/x86_64/x32/libc.abilist   |   1 +
 44 files changed, 516 insertions(+), 30 deletions(-)
 create mode 100644 posix/spawn_faction_addclosefrom.c
 create mode 100644 posix/spawn_int_abi.h
 create mode 100644 posix/tst-spawn5.c
 create mode 100644 sysdeps/unix/sysv/linux/spawn_int_abi.h

-- 
2.17.1

Comments

Florian Weimer June 25, 2019, 2:57 p.m. UTC | #1
* Adhemerval Zanella:

> +int

> +__posix_spawn_file_actions_addclosefrom (posix_spawn_file_actions_t

> +					 *file_actions, int from)

> +{

> +#if __SUPPORT_SPAWN_CLOSEFROM

> +  struct __spawn_action *rec;

> +

> +  if (!__spawn_valid_fd (from))

> +    return EBADF;


I don't think this check (against _SC_OPEN_MAX) is valid.  >= 0 would be

> diff --git a/posix/spawn_int_abi.h b/posix/spawn_int_abi.h

> new file mode 100644

> index 0000000000..3008937e21


> +/* The closefrom file actions requires either a syscall or an arch-specific

> +   way to interact over all file descriptors and act uppon them (such

> +   /proc/self/fd on Linux).  */

> +#define __SUPPORT_SPAWN_CLOSEFROM 0


I think __SUPPORT should be reserved support/, so please move SPAWN to
the front.

> diff --git a/sysdeps/unix/sysv/linux/spawni.c b/sysdeps/unix/sysv/linux/spawni.c

> index c1abf3f960..4e35dc21e7 100644

> --- a/sysdeps/unix/sysv/linux/spawni.c

> +++ b/sysdeps/unix/sysv/linux/spawni.c


> +/* Close all file descriptor up to FROM by interacting /proc/self/fd.

> +   Any failure should */

> +static bool

> +spawn_closefrom (int from)


Truncated comment.

> +{

> +  /* Each name in /proc/self/fd will be an integer up to sizeof (int),

> +     so d_name will be most 'sizeof (int) * 3 + 1'.  This makes each

> +     'dirent struct' entry to hold up to

> +     'offsetof (struct libc_dirent_2, d_name)' plus the d_name size.  */

> +

> +  enum

> +  {

> +    dirent_base_size  = offsetof (struct dirent64, d_name),

> +    d_name_max_length = sizeof (int) * 3 + 1,

> +    dirent_max_size   = ALIGN_UP (dirent_base_size + d_name_max_length,

> +				  sizeof (long double))

> +  };

> +

> +  /* Increasing the buffer size incurs in less getdents syscalls from

> +     readdir, however it would require more stack size to be allocated

> +     on __spawnix.  */

> +  char buffer[sizeof (DIR) + 16 * dirent_max_size];

> +

> +  DIR *dp;

> +  if ((dp = __opendir_inplace ("/proc/self/fd", buffer, sizeof buffer))

> +      == NULL)

> +    return false;


Maybe you could have a look at my direntries patch and see if it would
be useful here?

> +  bool ret = true;

> +  struct dirent64 *dirp;

> +  while ((dirp = __readdir64 (dp)) != NULL)

> +    {

> +      if (dirp->d_name[0] == '.')

> +        continue;

> +

> +      char *endptr;

> +      long int fd = strtol (dirp->d_name, &endptr, 10);

> +      if (*endptr != '\0' || fd < 0 || fd > INT_MAX)

> +	{

> +	  ret = false;

> +	  break;

> +	}

> +

> +      if (fd == dirfd (dp) || fd < from)

> +        continue;

> +

> +      __close (fd);


I still think this should rewind (seek to zero) if any file descriptors
have been closed.

With the direntries iterator, you could process everything that has been
read by the last getdents64 call.  With readdir64, the buffer boundary
is implicit.

> +  /* The __spawni_child uses about 768 on x86_64 (including the stack for

> +     opendir_inplace), so add some extra space.  */

> +  const size_t stack_slack = 1024;

> +

>    /* Add a slack area for child's stack.  */

> -  size_t argv_size = (argc * sizeof (void *)) + 512;

> +  size_t argv_size = (argc * sizeof (void *)) + stack_slack;

>    /* We need at least a few pages in case the compiler's stack checking is

>       enabled.  In some configs, it is known to use at least 24KiB.  We use

>       32KiB to be "safe" from anything the compiler might do.  Besides, the


I think this should be aligned witht the computation above.
Alternatively, you could allocate the buffer on the original stack.

Thanks,
Florian
diff mbox series

Patch

diff --git a/include/dirent.h b/include/dirent.h
index 3736803bce..9ffb8892f4 100644
--- a/include/dirent.h
+++ b/include/dirent.h
@@ -16,6 +16,8 @@  struct scandir_cancel_struct
 
 /* Now define the internal interfaces.  */
 extern DIR *__opendir (const char *__name) attribute_hidden;
+extern DIR *__opendir_inplace (const char *__name, void *buffer, size_t size)
+  attribute_hidden;
 extern DIR *__opendirat (int dfd, const char *__name) attribute_hidden;
 extern DIR *__fdopendir (int __fd) attribute_hidden;
 extern int __closedir (DIR *__dirp) attribute_hidden;
@@ -48,8 +50,8 @@  extern int __alphasort64 (const struct dirent64 **a, const struct dirent64 **b)
 extern int __versionsort64 (const struct dirent64 **a,
 			    const struct dirent64 **b)
      __attribute_pure__;
-extern DIR *__alloc_dir (int fd, bool close_fd, int flags,
-			 const struct stat64 *statp) attribute_hidden;
+extern DIR *__alloc_dir (int fd, bool close_fd, const struct stat64 *statp,
+			 void *buffer, size_t size) attribute_hidden;
 extern __typeof (rewinddir) __rewinddir;
 extern __typeof (seekdir) __seekdir;
 extern __typeof (dirfd) __dirfd;
diff --git a/posix/Makefile b/posix/Makefile
index 8ac6743ad7..1ac41ad85a 100644
--- a/posix/Makefile
+++ b/posix/Makefile
@@ -57,6 +57,7 @@  routines :=								      \
 	spawn_faction_init spawn_faction_destroy spawn_faction_addclose	      \
 	spawn_faction_addopen spawn_faction_adddup2 spawn_valid_fd	      \
 	spawn_faction_addchdir spawn_faction_addfchdir			      \
+	spawn_faction_addclosefrom					      \
 	spawnattr_init spawnattr_destroy				      \
 	spawnattr_getdefault spawnattr_setdefault			      \
 	spawnattr_getflags spawnattr_setflags				      \
@@ -100,7 +101,8 @@  tests		:= test-errno tstgetopt testfnm runtests runptests \
 		   tst-posix_fadvise tst-posix_fadvise64 \
 		   tst-sysconf-empty-chroot tst-glob_symlinks tst-fexecve \
 		   tst-glob-tilde test-ssize-max tst-spawn4 bug-regex37 \
-		   bug-regex38 tst-regcomp-truncated tst-spawn-chdir
+		   bug-regex38 tst-regcomp-truncated tst-spawn-chdir \
+		   tst-spawn5
 tests-internal	:= bug-regex5 bug-regex20 bug-regex33 \
 		   tst-rfc3484 tst-rfc3484-2 tst-rfc3484-3 \
 		   tst-glob_lstat_compat tst-spawn4-compat
@@ -254,6 +256,7 @@  tst-exec-static-ARGS = $(tst-exec-ARGS)
 tst-execvpe5-ARGS = -- $(host-test-program-cmd)
 tst-spawn-ARGS = -- $(host-test-program-cmd)
 tst-spawn-static-ARGS = $(tst-spawn-ARGS)
+tst-spawn5-ARGS = -- $(host-test-program-cmd)
 tst-dir-ARGS = `pwd` `cd $(common-objdir)/$(subdir); pwd` `cd $(common-objdir); pwd` $(objpfx)tst-dir
 tst-chmod-ARGS = $(objdir)
 tst-vfork3-ARGS = --test-dir=$(objpfx)
diff --git a/posix/Versions b/posix/Versions
index 7d06a6d0c0..c8268e5996 100644
--- a/posix/Versions
+++ b/posix/Versions
@@ -146,6 +146,7 @@  libc {
     posix_spawn_file_actions_addfchdir_np;
   }
   GLIBC_2.30 {
+    posix_spawn_file_actions_addclosefrom_np;
   }
   GLIBC_PRIVATE {
     __libc_fork; __libc_pread; __libc_pwrite;
diff --git a/posix/spawn.h b/posix/spawn.h
index 471dbea022..773f416b2e 100644
--- a/posix/spawn.h
+++ b/posix/spawn.h
@@ -213,6 +213,13 @@  extern int posix_spawn_file_actions_addchdir_np (posix_spawn_file_actions_t *
 extern int posix_spawn_file_actions_addfchdir_np (posix_spawn_file_actions_t *,
 						  int __fd)
      __THROW __nonnull ((1));
+
+/* Add an action to close all file descriptor greater than FROM during
+   spawn.  This affects the subsequent file actions.  */
+extern int posix_spawn_file_actions_addclosefrom_np (posix_spawn_file_actions_t *,
+						     int __from)
+     __THROW __nonnull ((1));
+
 #endif
 
 __END_DECLS
diff --git a/posix/spawn_faction_addclosefrom.c b/posix/spawn_faction_addclosefrom.c
new file mode 100644
index 0000000000..d9be7deb3f
--- /dev/null
+++ b/posix/spawn_faction_addclosefrom.c
@@ -0,0 +1,58 @@ 
+/* Add a closefrom to a file action list for posix_spawn.
+   Copyright (C) 2019 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
+   <http://www.gnu.org/licenses/>.  */
+
+#include <errno.h>
+#include <spawn.h>
+#include <unistd.h>
+#include <spawn_int.h>
+
+int
+__posix_spawn_file_actions_addclosefrom (posix_spawn_file_actions_t
+					 *file_actions, int from)
+{
+#if __SUPPORT_SPAWN_CLOSEFROM
+  struct __spawn_action *rec;
+
+  if (!__spawn_valid_fd (from))
+    return EBADF;
+
+  /* Allocate more memory if needed.  */
+  if (file_actions->__used == file_actions->__allocated
+      && __posix_spawn_file_actions_realloc (file_actions) != 0)
+    /* This can only mean we ran out of memory.  */
+    return ENOMEM;
+
+  /* Add the new value.  */
+  rec = &file_actions->__actions[file_actions->__used];
+  rec->tag = spawn_do_closefrom;
+  rec->action.closefrom_action.from = from;
+
+  /* Account for the new entry.  */
+  ++file_actions->__used;
+
+  return 0;
+#else
+  __set_errno (EINVAL);
+  return -1;
+#endif
+}
+weak_alias (__posix_spawn_file_actions_addclosefrom,
+	    posix_spawn_file_actions_addclosefrom_np)
+#if !__SUPPORT_SPAWN_CLOSEFROM
+stub_warning (posix_spawn_file_actions_addclosefrom_np)
+#endif
diff --git a/posix/spawn_faction_destroy.c b/posix/spawn_faction_destroy.c
index 51fab13585..b45d1cd889 100644
--- a/posix/spawn_faction_destroy.c
+++ b/posix/spawn_faction_destroy.c
@@ -39,6 +39,7 @@  __posix_spawn_file_actions_destroy (posix_spawn_file_actions_t *file_actions)
 	case spawn_do_close:
 	case spawn_do_dup2:
 	case spawn_do_fchdir:
+	case spawn_do_closefrom:
 	  /* No cleanup required.  */
 	  break;
 	}
diff --git a/posix/spawn_int.h b/posix/spawn_int.h
index 93b7597f90..6f6d3ef366 100644
--- a/posix/spawn_int.h
+++ b/posix/spawn_int.h
@@ -20,6 +20,7 @@ 
 #define _SPAWN_INT_H
 
 #include <spawn.h>
+#include <spawn_int_abi.h>
 #include <stdbool.h>
 
 /* Data structure to contain the action information.  */
@@ -32,6 +33,7 @@  struct __spawn_action
     spawn_do_open,
     spawn_do_chdir,
     spawn_do_fchdir,
+    spawn_do_closefrom
   } tag;
 
   union
@@ -60,6 +62,10 @@  struct __spawn_action
     {
       int fd;
     } fchdir_action;
+    struct
+    {
+      int from;
+    } closefrom_action;
   } action;
 };
 
diff --git a/posix/spawn_int_abi.h b/posix/spawn_int_abi.h
new file mode 100644
index 0000000000..3008937e21
--- /dev/null
+++ b/posix/spawn_int_abi.h
@@ -0,0 +1,27 @@ 
+/* Internal ABI specific for posix_spawn functionality.  Generic version.
+   Copyright (C) 2019 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
+   <http://www.gnu.org/licenses/>.  */
+
+#ifndef _SPAWN_INT_ABI_H
+#define _SPAWN_INT_ABI_H
+
+/* The closefrom file actions requires either a syscall or an arch-specific
+   way to interact over all file descriptors and act uppon them (such
+   /proc/self/fd on Linux).  */
+#define __SUPPORT_SPAWN_CLOSEFROM 0
+
+#endif /* _SPAWN_INT_H */
diff --git a/posix/tst-spawn5.c b/posix/tst-spawn5.c
new file mode 100644
index 0000000000..7af33a4dbe
--- /dev/null
+++ b/posix/tst-spawn5.c
@@ -0,0 +1,210 @@ 
+/* Tests for posix_spawn signal handling.
+   Copyright (C) 2019 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
+   <http://www.gnu.org/licenses/>.  */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <spawn.h>
+#include <fcntl.h>
+#include <sys/wait.h>
+#include <dirent.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <limits.h>
+
+#include <support/check.h>
+#include <support/xunistd.h>
+#include <support/support.h>
+#include <array_length.h>
+
+/* Nonzero if the program gets called via `exec'.  */
+static int restart;
+#define CMDLINE_OPTIONS \
+  { "restart", no_argument, &restart, 1 },
+
+/* Called on process re-execution.  */
+static int
+handle_restart (int from)
+{
+  DIR *fds = opendir ("/proc/self/fd");
+  if (fds == NULL)
+    FAIL_EXIT1 ("opendir (\"/proc/self/fd\"): %m");
+
+  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;
+
+      struct stat64 st;
+      if (fstat64 (fd, &st) != 0)
+        FAIL_EXIT1 ("readdir: fstat64 (%ld) failed: %m", fd);
+
+      if (fd >= from)
+	FAIL_EXIT1 ("error: fd (%ld) greater than from (%d)", fd, from);
+    }
+
+  closedir (fds);
+
+  return 0;
+}
+
+/* Common argument used for process re-execution.  */
+static char *initial_spargv[5];
+static size_t initial_spargv_size;
+
+/* Re-execute the test process with both '--direct', '--restart', and the
+   TEST (as integer value) as arguments.  */
+static void
+reexecute (int fd, const posix_spawn_file_actions_t *fa)
+{
+  char *spargv[8];
+  int i;
+
+  for (i = 0; i < initial_spargv_size; i++)
+    spargv[i] = initial_spargv[i];
+  /* Three digits per byte plus null terminator.  */
+  char teststr[3 * sizeof (fd) + 1];
+  snprintf (teststr, array_length (teststr), "%d", fd);
+  spargv[i++] = teststr;
+  spargv[i] = NULL;
+  TEST_VERIFY (i < 8);
+
+  pid_t pid;
+  int status;
+
+  TEST_COMPARE (posix_spawn (&pid, spargv[0], fa, NULL, spargv, environ),
+		0);
+  TEST_COMPARE (xwaitpid (pid, &status, 0), pid);
+  TEST_VERIFY (WIFEXITED (status));
+  TEST_VERIFY (!WIFSIGNALED (status));
+  TEST_COMPARE (WEXITSTATUS (status), 0);
+}
+
+static void
+do_test_closefrom (int num_fd_to_open)
+{
+  int *fds = xmalloc (num_fd_to_open * sizeof (int));
+  for (int i = 0; i < num_fd_to_open; i++)
+    fds[i] = xopen ("/dev/null", O_WRONLY, 0);
+
+  posix_spawn_file_actions_t fa;
+  /* posix_spawn_file_actions_init does not fail.  */
+  posix_spawn_file_actions_init (&fa);
+
+  {
+    int ret = posix_spawn_file_actions_addclosefrom_np (&fa, fds[0]);
+    if (ret == -1)
+      {
+	if (errno == ENOSYS)
+	  /* Hurd currently does not support closefrom fileaction.  */
+	  FAIL_UNSUPPORTED ("posix_spawn_file_actions_addclosefrom_np unsupported");
+        else
+	  FAIL_EXIT1 ("posix_spawn_file_actions_addclosefrom_np failed");
+      }
+  }
+
+  /* Default check, all file descriptor from [fd[0], fd[1]) are opened.  */
+  reexecute (fds[0], &fa);
+
+  /* Add a gap in the range.  */
+  xclose (fds[num_fd_to_open/2]);
+  xclose (fds[num_fd_to_open/2 + 1]);
+  reexecute (fds[0], &fa);
+
+  /* Add another gap, at the beginning.  */
+  xclose (fds[0]);
+  xclose (fds[1]);
+  reexecute (fds[0], &fa);
+
+  /* Add another gap, now at the end.  */
+  xclose (fds[num_fd_to_open-1]);
+  xclose (fds[num_fd_to_open-2]);
+  reexecute (fds[0], &fa);
+
+  /* Open some more files, filling the gaps.  */
+  for (int i = 0; i < 6; i++)
+    xopen ("/dev/null", O_WRONLY, 0);
+  reexecute (fds[0], &fa);
+
+  /* Open some more, but with O_CLOEXEC.  */
+  for (int i = 0; i < num_fd_to_open/2; i++)
+    xopen ("/dev/null", O_WRONLY | O_CLOEXEC, 0);
+
+  free (fds);
+}
+
+
+static int
+do_test (int argc, char *argv[])
+{
+  /* We must have one or four parameters left if called initially:
+       + path for ld.so		optional
+       + "--library-path"	optional
+       + the library path	optional
+       + the application name
+
+     Plus one parameter to indicate which test to execute through
+     re-execution.
+
+     So for default usage without --enable-hardcoded-path-in-tests, it
+     will be called initially with 5 arguments and later with 2.  For
+     --enable-hardcoded-path-in-tests it will be called with 2 arguments
+     regardless.  */
+
+  if (argc != (restart ? 2 : 5) && argc != 2)
+    FAIL_EXIT1 ("wrong number of arguments (%d)", argc);
+
+  if (restart)
+    return handle_restart (atoi (argv[1]));
+
+  /* Respawn using the same arguments.  */
+  for (initial_spargv_size = 0;
+       initial_spargv_size < (argc == 5 ? 4 : 1);
+       initial_spargv_size++)
+    initial_spargv[initial_spargv_size] = argv[initial_spargv_size + 1];
+  initial_spargv[initial_spargv_size++] = (char *) "--direct";
+  initial_spargv[initial_spargv_size++] = (char *) "--restart";
+
+  do_test_closefrom (10);
+  do_test_closefrom (100);
+
+  return 0;
+}
+
+#define TEST_FUNCTION_ARGV do_test
+#include <support/test-driver.c>
diff --git a/sysdeps/mach/hurd/i386/libc.abilist b/sysdeps/mach/hurd/i386/libc.abilist
index 1fc7ab2433..fcf957cfc6 100644
--- a/sysdeps/mach/hurd/i386/libc.abilist
+++ b/sysdeps/mach/hurd/i386/libc.abilist
@@ -2175,6 +2175,7 @@  GLIBC_2.3.4 setipv4sourcefilter F
 GLIBC_2.3.4 setsourcefilter F
 GLIBC_2.3.4 xdr_quad_t F
 GLIBC_2.3.4 xdr_u_quad_t F
+GLIBC_2.30 posix_spawn_file_actions_addclosefrom_np F
 GLIBC_2.30 twalk_r F
 GLIBC_2.4 __confstr_chk F
 GLIBC_2.4 __fgets_chk F
diff --git a/sysdeps/mach/hurd/spawni.c b/sysdeps/mach/hurd/spawni.c
index e8024a2679..720e56ffb3 100644
--- a/sysdeps/mach/hurd/spawni.c
+++ b/sysdeps/mach/hurd/spawni.c
@@ -597,6 +597,10 @@  __spawni (pid_t *pid, const char *file,
 	  case spawn_do_fchdir:
 	    err = child_fchdir (action->action.fchdir_action.fd);
 	    break;
+
+	  case spawn_do_closefrom:
+	    err = EINVAL;
+	    break;
 	  }
 
 	if (err)
diff --git a/sysdeps/posix/closedir.c b/sysdeps/posix/closedir.c
index fb7da86218..0e55659485 100644
--- a/sysdeps/posix/closedir.c
+++ b/sysdeps/posix/closedir.c
@@ -47,7 +47,8 @@  __closedir (DIR *dirp)
   __libc_lock_fini (dirp->lock);
 #endif
 
-  free ((void *) dirp);
+  if (dirp->allocated)
+    free ((void *) dirp);
 
   return __close_nocancel (fd);
 }
diff --git a/sysdeps/posix/dirstream.h b/sysdeps/posix/dirstream.h
index 109fcc1609..861932abd6 100644
--- a/sysdeps/posix/dirstream.h
+++ b/sysdeps/posix/dirstream.h
@@ -34,6 +34,7 @@  struct __dirstream
     __libc_lock_define (, lock) /* Mutex lock for this structure.  */
 
     size_t allocation;		/* Space allocated for the block.  */
+    bool allocated;		/* Space was allocated by opendir.  */
     size_t size;		/* Total valid data in the block.  */
     size_t offset;		/* Current offset into the block.  */
 
diff --git a/sysdeps/posix/fdopendir.c b/sysdeps/posix/fdopendir.c
index 4b4e218115..13fe3f14b1 100644
--- a/sysdeps/posix/fdopendir.c
+++ b/sysdeps/posix/fdopendir.c
@@ -47,6 +47,6 @@  __fdopendir (int fd)
       return NULL;
     }
 
-  return __alloc_dir (fd, false, flags, &statbuf);
+  return __alloc_dir (fd, false, &statbuf, NULL, 0);
 }
 weak_alias (__fdopendir, fdopendir)
diff --git a/sysdeps/posix/opendir.c b/sysdeps/posix/opendir.c
index 8e3ba480b7..97f95a2ae0 100644
--- a/sysdeps/posix/opendir.c
+++ b/sysdeps/posix/opendir.c
@@ -21,6 +21,8 @@ 
 #include <stdio.h>	/* For BUFSIZ.  */
 #include <sys/param.h>	/* For MIN and MAX.  */
 
+#include <dirstream.h>
+
 #include <not-cancel.h>
 
 /* The st_blksize value of the directory is used as a hint for the
@@ -46,27 +48,47 @@  invalid_name (const char *name)
   return false;
 }
 
-static DIR *
-opendir_tail (int fd)
+static bool
+opendir_tail_common (int fd, struct stat64 *statbuf)
 {
   if (__glibc_unlikely (fd < 0))
-    return NULL;
+    return false;
 
   /* Now make sure this really is a directory and nothing changed since the
      `stat' call.  The S_ISDIR check is superfluous if O_DIRECTORY works,
      but it's cheap and we need the stat call for st_blksize anyway.  */
-  struct stat64 statbuf;
-  if (__glibc_unlikely (__fxstat64 (_STAT_VER, fd, &statbuf) < 0))
-    goto lose;
-  if (__glibc_unlikely (! S_ISDIR (statbuf.st_mode)))
+  if (__glibc_unlikely (__fxstat64 (_STAT_VER, fd, statbuf) < 0))
+    {
+      __close_nocancel_nostatus (fd);
+      return false;
+    }
+  if (__glibc_unlikely (! S_ISDIR (statbuf->st_mode)))
     {
       __set_errno (ENOTDIR);
-    lose:
       __close_nocancel_nostatus (fd);
-      return NULL;
+      return false;
     }
+  return true;
+}
+
+static DIR *
+opendir_tail (int fd)
+{
+  struct stat64 statbuf;
+  if (!opendir_tail_common (fd, &statbuf))
+    return NULL;
+
+  return __alloc_dir (fd, true, &statbuf, NULL, 0);
+}
 
-  return __alloc_dir (fd, true, 0, &statbuf);
+static DIR *
+opendir_tail_inplace (int fd, void *buffer, size_t size)
+{
+  struct stat64 statbuf;
+  if (!opendir_tail_common (fd, &statbuf))
+    return NULL;
+
+  return __alloc_dir (fd, true, &statbuf, buffer, size);
 }
 
 
@@ -94,7 +116,18 @@  __opendir (const char *name)
 weak_alias (__opendir, opendir)
 
 DIR *
-__alloc_dir (int fd, bool close_fd, int flags, const struct stat64 *statp)
+__opendir_inplace (const char *name, void *buffer, size_t size)
+{
+  if (__glibc_unlikely (invalid_name (name)))
+    return NULL;
+
+  return opendir_tail_inplace (__open_nocancel (name, opendir_oflags),
+			       buffer, size);
+}
+
+DIR *
+__alloc_dir (int fd, bool close_fd, const struct stat64 *statp,
+	     void *buffer, size_t size)
 {
   /* We have to set the close-on-exit flag if the user provided the
      file descriptor.  */
@@ -114,31 +147,44 @@  __alloc_dir (int fd, bool close_fd, int flags, const struct stat64 *statp)
     allocation = MIN (MAX ((size_t) statp->st_blksize, default_allocation),
 		      MAX_DIR_BUFFER_SIZE);
 #endif
-
-  DIR *dirp = (DIR *) malloc (sizeof (DIR) + allocation);
-  if (dirp == NULL)
+  DIR *dirp;
+  if (!buffer)
     {
-      allocation = small_allocation;
-      dirp = (DIR *) malloc (sizeof (DIR) + allocation);
-
+      dirp = malloc (sizeof (DIR) + allocation);
       if (dirp == NULL)
-      lose:
 	{
-	  if (close_fd)
+	  allocation = small_allocation;
+	  dirp = (DIR *) malloc (sizeof (DIR) + allocation);
+
+	  if (dirp == NULL)
 	    {
-	      int save_errno = errno;
-	      __close_nocancel_nostatus (fd);
-	      __set_errno (save_errno);
+	      if (close_fd)
+	      lose:
+		{
+		  int save_errno = errno;
+		  __close_nocancel_nostatus (fd);
+		  __set_errno (save_errno);
+		}
+	      return NULL;
 	    }
-	  return NULL;
 	}
+      dirp->allocated = true;
+      dirp->allocation = allocation;
+    }
+  else
+    {
+      dirp = buffer;
+      if (allocation < sizeof (struct __dirstream)
+		       + sizeof (struct dirent))
+	goto lose;
+      dirp->allocated = false;
+      dirp->allocation = size - sizeof (DIR);
     }
 
   dirp->fd = fd;
 #if IS_IN (libc)
   __libc_lock_init (dirp->lock);
 #endif
-  dirp->allocation = allocation;
   dirp->size = 0;
   dirp->offset = 0;
   dirp->filepos = 0;
diff --git a/sysdeps/posix/spawni.c b/sysdeps/posix/spawni.c
index a5913feb14..3beaba91db 100644
--- a/sysdeps/posix/spawni.c
+++ b/sysdeps/posix/spawni.c
@@ -231,6 +231,10 @@  __spawni_child (void *arguments)
 	      if (__fchdir (action->action.fchdir_action.fd) != 0)
 		goto fail;
 	      break;
+
+	    case spawn_do_closefrom:
+	      __set_errno (EINVAL);
+	      goto fail;
 	    }
 	}
     }
diff --git a/sysdeps/unix/sysv/linux/aarch64/libc.abilist b/sysdeps/unix/sysv/linux/aarch64/libc.abilist
index a4c31932cb..e1e793b348 100644
--- a/sysdeps/unix/sysv/linux/aarch64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/aarch64/libc.abilist
@@ -2143,5 +2143,6 @@  GLIBC_2.29 posix_spawn_file_actions_addchdir_np F
 GLIBC_2.29 posix_spawn_file_actions_addfchdir_np F
 GLIBC_2.30 getdents64 F
 GLIBC_2.30 gettid F
+GLIBC_2.30 posix_spawn_file_actions_addclosefrom_np F
 GLIBC_2.30 tgkill F
 GLIBC_2.30 twalk_r F
diff --git a/sysdeps/unix/sysv/linux/alpha/libc.abilist b/sysdeps/unix/sysv/linux/alpha/libc.abilist
index fe85a35620..735e54f433 100644
--- a/sysdeps/unix/sysv/linux/alpha/libc.abilist
+++ b/sysdeps/unix/sysv/linux/alpha/libc.abilist
@@ -2218,6 +2218,7 @@  GLIBC_2.30 __nldbl_warn F
 GLIBC_2.30 __nldbl_warnx F
 GLIBC_2.30 getdents64 F
 GLIBC_2.30 gettid F
+GLIBC_2.30 posix_spawn_file_actions_addclosefrom_np F
 GLIBC_2.30 tgkill F
 GLIBC_2.30 twalk_r F
 GLIBC_2.4 _IO_fprintf F
diff --git a/sysdeps/unix/sysv/linux/arm/libc.abilist b/sysdeps/unix/sysv/linux/arm/libc.abilist
index bc3df8dcea..a3b9db6efa 100644
--- a/sysdeps/unix/sysv/linux/arm/libc.abilist
+++ b/sysdeps/unix/sysv/linux/arm/libc.abilist
@@ -128,6 +128,7 @@  GLIBC_2.29 posix_spawn_file_actions_addchdir_np F
 GLIBC_2.29 posix_spawn_file_actions_addfchdir_np F
 GLIBC_2.30 getdents64 F
 GLIBC_2.30 gettid F
+GLIBC_2.30 posix_spawn_file_actions_addclosefrom_np F
 GLIBC_2.30 tgkill F
 GLIBC_2.30 twalk_r F
 GLIBC_2.4 _Exit F
diff --git a/sysdeps/unix/sysv/linux/csky/libc.abilist b/sysdeps/unix/sysv/linux/csky/libc.abilist
index 9b3cee65bb..88d112b9b1 100644
--- a/sysdeps/unix/sysv/linux/csky/libc.abilist
+++ b/sysdeps/unix/sysv/linux/csky/libc.abilist
@@ -2087,5 +2087,6 @@  GLIBC_2.29 xprt_register F
 GLIBC_2.29 xprt_unregister F
 GLIBC_2.30 getdents64 F
 GLIBC_2.30 gettid F
+GLIBC_2.30 posix_spawn_file_actions_addclosefrom_np F
 GLIBC_2.30 tgkill F
 GLIBC_2.30 twalk_r F
diff --git a/sysdeps/unix/sysv/linux/hppa/libc.abilist b/sysdeps/unix/sysv/linux/hppa/libc.abilist
index 75edece94a..2925c96183 100644
--- a/sysdeps/unix/sysv/linux/hppa/libc.abilist
+++ b/sysdeps/unix/sysv/linux/hppa/libc.abilist
@@ -2039,6 +2039,7 @@  GLIBC_2.3.4 xdr_quad_t F
 GLIBC_2.3.4 xdr_u_quad_t F
 GLIBC_2.30 getdents64 F
 GLIBC_2.30 gettid F
+GLIBC_2.30 posix_spawn_file_actions_addclosefrom_np F
 GLIBC_2.30 tgkill F
 GLIBC_2.30 twalk_r F
 GLIBC_2.4 __confstr_chk F
diff --git a/sysdeps/unix/sysv/linux/i386/libc.abilist b/sysdeps/unix/sysv/linux/i386/libc.abilist
index edeaf8e722..9fd8ceb639 100644
--- a/sysdeps/unix/sysv/linux/i386/libc.abilist
+++ b/sysdeps/unix/sysv/linux/i386/libc.abilist
@@ -2205,6 +2205,7 @@  GLIBC_2.3.4 xdr_quad_t F
 GLIBC_2.3.4 xdr_u_quad_t F
 GLIBC_2.30 getdents64 F
 GLIBC_2.30 gettid F
+GLIBC_2.30 posix_spawn_file_actions_addclosefrom_np F
 GLIBC_2.30 tgkill F
 GLIBC_2.30 twalk_r F
 GLIBC_2.4 __confstr_chk F
diff --git a/sysdeps/unix/sysv/linux/ia64/libc.abilist b/sysdeps/unix/sysv/linux/ia64/libc.abilist
index b5d460eeb2..37d817eeb3 100644
--- a/sysdeps/unix/sysv/linux/ia64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/ia64/libc.abilist
@@ -2071,6 +2071,7 @@  GLIBC_2.3.4 xdr_quad_t F
 GLIBC_2.3.4 xdr_u_quad_t F
 GLIBC_2.30 getdents64 F
 GLIBC_2.30 gettid F
+GLIBC_2.30 posix_spawn_file_actions_addclosefrom_np F
 GLIBC_2.30 tgkill F
 GLIBC_2.30 twalk_r F
 GLIBC_2.4 __confstr_chk F
diff --git a/sysdeps/unix/sysv/linux/m68k/coldfire/libc.abilist b/sysdeps/unix/sysv/linux/m68k/coldfire/libc.abilist
index 05633b3cb8..e81ab1f0bf 100644
--- a/sysdeps/unix/sysv/linux/m68k/coldfire/libc.abilist
+++ b/sysdeps/unix/sysv/linux/m68k/coldfire/libc.abilist
@@ -129,6 +129,7 @@  GLIBC_2.29 posix_spawn_file_actions_addchdir_np F
 GLIBC_2.29 posix_spawn_file_actions_addfchdir_np F
 GLIBC_2.30 getdents64 F
 GLIBC_2.30 gettid F
+GLIBC_2.30 posix_spawn_file_actions_addclosefrom_np F
 GLIBC_2.30 tgkill F
 GLIBC_2.30 twalk_r F
 GLIBC_2.4 _Exit F
diff --git a/sysdeps/unix/sysv/linux/m68k/m680x0/libc.abilist b/sysdeps/unix/sysv/linux/m68k/m680x0/libc.abilist
index 47eb7b4608..cd5742bf63 100644
--- a/sysdeps/unix/sysv/linux/m68k/m680x0/libc.abilist
+++ b/sysdeps/unix/sysv/linux/m68k/m680x0/libc.abilist
@@ -2148,6 +2148,7 @@  GLIBC_2.3.4 xdr_quad_t F
 GLIBC_2.3.4 xdr_u_quad_t F
 GLIBC_2.30 getdents64 F
 GLIBC_2.30 gettid F
+GLIBC_2.30 posix_spawn_file_actions_addclosefrom_np F
 GLIBC_2.30 tgkill F
 GLIBC_2.30 twalk_r F
 GLIBC_2.4 __confstr_chk F
diff --git a/sysdeps/unix/sysv/linux/microblaze/libc.abilist b/sysdeps/unix/sysv/linux/microblaze/libc.abilist
index f7ced487f7..957b14d992 100644
--- a/sysdeps/unix/sysv/linux/microblaze/libc.abilist
+++ b/sysdeps/unix/sysv/linux/microblaze/libc.abilist
@@ -2135,5 +2135,6 @@  GLIBC_2.29 posix_spawn_file_actions_addchdir_np F
 GLIBC_2.29 posix_spawn_file_actions_addfchdir_np F
 GLIBC_2.30 getdents64 F
 GLIBC_2.30 gettid F
+GLIBC_2.30 posix_spawn_file_actions_addclosefrom_np F
 GLIBC_2.30 tgkill F
 GLIBC_2.30 twalk_r F
diff --git a/sysdeps/unix/sysv/linux/mips/mips32/fpu/libc.abilist b/sysdeps/unix/sysv/linux/mips/mips32/fpu/libc.abilist
index e49dc4272e..b8ffdea448 100644
--- a/sysdeps/unix/sysv/linux/mips/mips32/fpu/libc.abilist
+++ b/sysdeps/unix/sysv/linux/mips/mips32/fpu/libc.abilist
@@ -2122,6 +2122,7 @@  GLIBC_2.3.4 xdr_quad_t F
 GLIBC_2.3.4 xdr_u_quad_t F
 GLIBC_2.30 getdents64 F
 GLIBC_2.30 gettid F
+GLIBC_2.30 posix_spawn_file_actions_addclosefrom_np F
 GLIBC_2.30 tgkill F
 GLIBC_2.30 twalk_r F
 GLIBC_2.4 __confstr_chk F
diff --git a/sysdeps/unix/sysv/linux/mips/mips32/nofpu/libc.abilist b/sysdeps/unix/sysv/linux/mips/mips32/nofpu/libc.abilist
index daa3b60c5b..e1c861720a 100644
--- a/sysdeps/unix/sysv/linux/mips/mips32/nofpu/libc.abilist
+++ b/sysdeps/unix/sysv/linux/mips/mips32/nofpu/libc.abilist
@@ -2120,6 +2120,7 @@  GLIBC_2.3.4 xdr_quad_t F
 GLIBC_2.3.4 xdr_u_quad_t F
 GLIBC_2.30 getdents64 F
 GLIBC_2.30 gettid F
+GLIBC_2.30 posix_spawn_file_actions_addclosefrom_np F
 GLIBC_2.30 tgkill F
 GLIBC_2.30 twalk_r F
 GLIBC_2.4 __confstr_chk F
diff --git a/sysdeps/unix/sysv/linux/mips/mips64/n32/libc.abilist b/sysdeps/unix/sysv/linux/mips/mips64/n32/libc.abilist
index 457ce0b6f2..88fe3f4d26 100644
--- a/sysdeps/unix/sysv/linux/mips/mips64/n32/libc.abilist
+++ b/sysdeps/unix/sysv/linux/mips/mips64/n32/libc.abilist
@@ -2128,6 +2128,7 @@  GLIBC_2.3.4 xdr_quad_t F
 GLIBC_2.3.4 xdr_u_quad_t F
 GLIBC_2.30 getdents64 F
 GLIBC_2.30 gettid F
+GLIBC_2.30 posix_spawn_file_actions_addclosefrom_np F
 GLIBC_2.30 tgkill F
 GLIBC_2.30 twalk_r F
 GLIBC_2.4 __confstr_chk F
diff --git a/sysdeps/unix/sysv/linux/mips/mips64/n64/libc.abilist b/sysdeps/unix/sysv/linux/mips/mips64/n64/libc.abilist
index 63d5c03bfb..7c6dafa818 100644
--- a/sysdeps/unix/sysv/linux/mips/mips64/n64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/mips/mips64/n64/libc.abilist
@@ -2122,6 +2122,7 @@  GLIBC_2.3.4 xdr_quad_t F
 GLIBC_2.3.4 xdr_u_quad_t F
 GLIBC_2.30 getdents64 F
 GLIBC_2.30 gettid F
+GLIBC_2.30 posix_spawn_file_actions_addclosefrom_np F
 GLIBC_2.30 tgkill F
 GLIBC_2.30 twalk_r F
 GLIBC_2.4 __confstr_chk F
diff --git a/sysdeps/unix/sysv/linux/nios2/libc.abilist b/sysdeps/unix/sysv/linux/nios2/libc.abilist
index 7fec0c9670..487b005070 100644
--- a/sysdeps/unix/sysv/linux/nios2/libc.abilist
+++ b/sysdeps/unix/sysv/linux/nios2/libc.abilist
@@ -2176,5 +2176,6 @@  GLIBC_2.29 posix_spawn_file_actions_addchdir_np F
 GLIBC_2.29 posix_spawn_file_actions_addfchdir_np F
 GLIBC_2.30 getdents64 F
 GLIBC_2.30 gettid F
+GLIBC_2.30 posix_spawn_file_actions_addclosefrom_np F
 GLIBC_2.30 tgkill F
 GLIBC_2.30 twalk_r F
diff --git a/sysdeps/unix/sysv/linux/powerpc/powerpc32/fpu/libc.abilist b/sysdeps/unix/sysv/linux/powerpc/powerpc32/fpu/libc.abilist
index 9200a54309..db81db978a 100644
--- a/sysdeps/unix/sysv/linux/powerpc/powerpc32/fpu/libc.abilist
+++ b/sysdeps/unix/sysv/linux/powerpc/powerpc32/fpu/libc.abilist
@@ -2178,6 +2178,7 @@  GLIBC_2.30 __nldbl_warn F
 GLIBC_2.30 __nldbl_warnx F
 GLIBC_2.30 getdents64 F
 GLIBC_2.30 gettid F
+GLIBC_2.30 posix_spawn_file_actions_addclosefrom_np F
 GLIBC_2.30 tgkill F
 GLIBC_2.30 twalk_r F
 GLIBC_2.4 _IO_fprintf F
diff --git a/sysdeps/unix/sysv/linux/powerpc/powerpc64/be/libc.abilist b/sysdeps/unix/sysv/linux/powerpc/powerpc64/be/libc.abilist
index 2860df8ebc..06dfdf1fed 100644
--- a/sysdeps/unix/sysv/linux/powerpc/powerpc64/be/libc.abilist
+++ b/sysdeps/unix/sysv/linux/powerpc/powerpc64/be/libc.abilist
@@ -2041,6 +2041,7 @@  GLIBC_2.30 __nldbl_warn F
 GLIBC_2.30 __nldbl_warnx F
 GLIBC_2.30 getdents64 F
 GLIBC_2.30 gettid F
+GLIBC_2.30 posix_spawn_file_actions_addclosefrom_np F
 GLIBC_2.30 tgkill F
 GLIBC_2.30 twalk_r F
 GLIBC_2.4 _IO_fprintf F
diff --git a/sysdeps/unix/sysv/linux/powerpc/powerpc64/le/libc.abilist b/sysdeps/unix/sysv/linux/powerpc/powerpc64/le/libc.abilist
index 2229a1dcc0..eb0532937e 100644
--- a/sysdeps/unix/sysv/linux/powerpc/powerpc64/le/libc.abilist
+++ b/sysdeps/unix/sysv/linux/powerpc/powerpc64/le/libc.abilist
@@ -2245,5 +2245,6 @@  GLIBC_2.30 __nldbl_warn F
 GLIBC_2.30 __nldbl_warnx F
 GLIBC_2.30 getdents64 F
 GLIBC_2.30 gettid F
+GLIBC_2.30 posix_spawn_file_actions_addclosefrom_np F
 GLIBC_2.30 tgkill F
 GLIBC_2.30 twalk_r F
diff --git a/sysdeps/unix/sysv/linux/riscv/rv64/libc.abilist b/sysdeps/unix/sysv/linux/riscv/rv64/libc.abilist
index 31010e6cf7..4985fa93e3 100644
--- a/sysdeps/unix/sysv/linux/riscv/rv64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/riscv/rv64/libc.abilist
@@ -2105,5 +2105,6 @@  GLIBC_2.29 posix_spawn_file_actions_addchdir_np F
 GLIBC_2.29 posix_spawn_file_actions_addfchdir_np F
 GLIBC_2.30 getdents64 F
 GLIBC_2.30 gettid F
+GLIBC_2.30 posix_spawn_file_actions_addclosefrom_np F
 GLIBC_2.30 tgkill F
 GLIBC_2.30 twalk_r F
diff --git a/sysdeps/unix/sysv/linux/s390/s390-32/libc.abilist b/sysdeps/unix/sysv/linux/s390/s390-32/libc.abilist
index 576295deff..1ef1b9d4cc 100644
--- a/sysdeps/unix/sysv/linux/s390/s390-32/libc.abilist
+++ b/sysdeps/unix/sysv/linux/s390/s390-32/libc.abilist
@@ -2173,6 +2173,7 @@  GLIBC_2.30 __nldbl_warn F
 GLIBC_2.30 __nldbl_warnx F
 GLIBC_2.30 getdents64 F
 GLIBC_2.30 gettid F
+GLIBC_2.30 posix_spawn_file_actions_addclosefrom_np F
 GLIBC_2.30 tgkill F
 GLIBC_2.30 twalk_r F
 GLIBC_2.4 _IO_fprintf F
diff --git a/sysdeps/unix/sysv/linux/s390/s390-64/libc.abilist b/sysdeps/unix/sysv/linux/s390/s390-64/libc.abilist
index abf0473683..f9d9fe68ca 100644
--- a/sysdeps/unix/sysv/linux/s390/s390-64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/s390/s390-64/libc.abilist
@@ -2077,6 +2077,7 @@  GLIBC_2.30 __nldbl_warn F
 GLIBC_2.30 __nldbl_warnx F
 GLIBC_2.30 getdents64 F
 GLIBC_2.30 gettid F
+GLIBC_2.30 posix_spawn_file_actions_addclosefrom_np F
 GLIBC_2.30 tgkill F
 GLIBC_2.30 twalk_r F
 GLIBC_2.4 _IO_fprintf F
diff --git a/sysdeps/unix/sysv/linux/sh/libc.abilist b/sysdeps/unix/sysv/linux/sh/libc.abilist
index 41977f6e9c..1b12384dd1 100644
--- a/sysdeps/unix/sysv/linux/sh/libc.abilist
+++ b/sysdeps/unix/sysv/linux/sh/libc.abilist
@@ -2043,6 +2043,7 @@  GLIBC_2.3.4 xdr_quad_t F
 GLIBC_2.3.4 xdr_u_quad_t F
 GLIBC_2.30 getdents64 F
 GLIBC_2.30 gettid F
+GLIBC_2.30 posix_spawn_file_actions_addclosefrom_np F
 GLIBC_2.30 tgkill F
 GLIBC_2.30 twalk_r F
 GLIBC_2.4 __confstr_chk F
diff --git a/sysdeps/unix/sysv/linux/sparc/sparc32/libc.abilist b/sysdeps/unix/sysv/linux/sparc/sparc32/libc.abilist
index 3d2f00ca52..a7c244cb56 100644
--- a/sysdeps/unix/sysv/linux/sparc/sparc32/libc.abilist
+++ b/sysdeps/unix/sysv/linux/sparc/sparc32/libc.abilist
@@ -2167,6 +2167,7 @@  GLIBC_2.30 __nldbl_warn F
 GLIBC_2.30 __nldbl_warnx F
 GLIBC_2.30 getdents64 F
 GLIBC_2.30 gettid F
+GLIBC_2.30 posix_spawn_file_actions_addclosefrom_np F
 GLIBC_2.30 tgkill F
 GLIBC_2.30 twalk_r F
 GLIBC_2.4 _IO_fprintf F
diff --git a/sysdeps/unix/sysv/linux/sparc/sparc64/libc.abilist b/sysdeps/unix/sysv/linux/sparc/sparc64/libc.abilist
index 2f20643e8e..a71facfb43 100644
--- a/sysdeps/unix/sysv/linux/sparc/sparc64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/sparc/sparc64/libc.abilist
@@ -2094,6 +2094,7 @@  GLIBC_2.3.4 xdr_quad_t F
 GLIBC_2.3.4 xdr_u_quad_t F
 GLIBC_2.30 getdents64 F
 GLIBC_2.30 gettid F
+GLIBC_2.30 posix_spawn_file_actions_addclosefrom_np F
 GLIBC_2.30 tgkill F
 GLIBC_2.30 twalk_r F
 GLIBC_2.4 __confstr_chk F
diff --git a/sysdeps/unix/sysv/linux/spawn_int_abi.h b/sysdeps/unix/sysv/linux/spawn_int_abi.h
new file mode 100644
index 0000000000..d272c0eab1
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/spawn_int_abi.h
@@ -0,0 +1,25 @@ 
+/* Internal ABI specific for posix_spawn functionality.  Linux version.
+   Copyright (C) 2019 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
+   <http://www.gnu.org/licenses/>.  */
+
+#ifndef _SPAWN_INT_ABI_H
+#define _SPAWN_INT_ABI_H
+
+/* spawni.c implements closefrom by interacting over /proc/self/fd.  */
+#define __SUPPORT_SPAWN_CLOSEFROM 1
+
+#endif /* _SPAWN_INT_H */
diff --git a/sysdeps/unix/sysv/linux/spawni.c b/sysdeps/unix/sysv/linux/spawni.c
index c1abf3f960..4e35dc21e7 100644
--- a/sysdeps/unix/sysv/linux/spawni.c
+++ b/sysdeps/unix/sysv/linux/spawni.c
@@ -31,6 +31,7 @@ 
 #include <dl-sysdep.h>
 #include <libc-pointer-arith.h>
 #include <ldsodefs.h>
+#include <dirent.h>
 #include "spawn_int.h"
 
 /* The Linux implementation of posix_spawn{p} uses the clone syscall directly
@@ -114,6 +115,59 @@  maybe_script_execute (struct posix_spawn_args *args)
     }
 }
 
+/* Close all file descriptor up to FROM by interacting /proc/self/fd.
+   Any failure should */
+static bool
+spawn_closefrom (int from)
+{
+  /* Each name in /proc/self/fd will be an integer up to sizeof (int),
+     so d_name will be most 'sizeof (int) * 3 + 1'.  This makes each
+     'dirent struct' entry to hold up to
+     'offsetof (struct libc_dirent_2, d_name)' plus the d_name size.  */
+
+  enum
+  {
+    dirent_base_size  = offsetof (struct dirent64, d_name),
+    d_name_max_length = sizeof (int) * 3 + 1,
+    dirent_max_size   = ALIGN_UP (dirent_base_size + d_name_max_length,
+				  sizeof (long double))
+  };
+
+  /* Increasing the buffer size incurs in less getdents syscalls from
+     readdir, however it would require more stack size to be allocated
+     on __spawnix.  */
+  char buffer[sizeof (DIR) + 16 * dirent_max_size];
+
+  DIR *dp;
+  if ((dp = __opendir_inplace ("/proc/self/fd", buffer, sizeof buffer))
+      == NULL)
+    return false;
+
+  bool ret = true;
+  struct dirent64 *dirp;
+  while ((dirp = __readdir64 (dp)) != NULL)
+    {
+      if (dirp->d_name[0] == '.')
+        continue;
+
+      char *endptr;
+      long int fd = strtol (dirp->d_name, &endptr, 10);
+      if (*endptr != '\0' || fd < 0 || fd > INT_MAX)
+	{
+	  ret = false;
+	  break;
+	}
+
+      if (fd == dirfd (dp) || fd < from)
+        continue;
+
+      __close (fd);
+    }
+  __closedir (dp);
+
+  return ret;
+}
+
 /* Function used in the clone call to setup the signals mask, posix_spawn
    attributes, and file actions.  It run on its own stack (provided by the
    posix_spawn call).  */
@@ -280,6 +334,11 @@  __spawni_child (void *arguments)
 	      if (__fchdir (action->action.fchdir_action.fd) != 0)
 		goto fail;
 	      break;
+
+	    case spawn_do_closefrom:
+	      if (!spawn_closefrom (action->action.closefrom_action.from))
+		goto fail;
+	      break;
 	    }
 	}
     }
@@ -339,8 +398,12 @@  __spawnix (pid_t * pid, const char *file,
   int prot = (PROT_READ | PROT_WRITE
 	     | ((GL (dl_stack_flags) & PF_X) ? PROT_EXEC : 0));
 
+  /* The __spawni_child uses about 768 on x86_64 (including the stack for
+     opendir_inplace), so add some extra space.  */
+  const size_t stack_slack = 1024;
+
   /* Add a slack area for child's stack.  */
-  size_t argv_size = (argc * sizeof (void *)) + 512;
+  size_t argv_size = (argc * sizeof (void *)) + stack_slack;
   /* We need at least a few pages in case the compiler's stack checking is
      enabled.  In some configs, it is known to use at least 24KiB.  We use
      32KiB to be "safe" from anything the compiler might do.  Besides, the
diff --git a/sysdeps/unix/sysv/linux/x86_64/64/libc.abilist b/sysdeps/unix/sysv/linux/x86_64/64/libc.abilist
index 59f85d9373..78a43f5851 100644
--- a/sysdeps/unix/sysv/linux/x86_64/64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/x86_64/64/libc.abilist
@@ -2052,6 +2052,7 @@  GLIBC_2.3.4 xdr_quad_t F
 GLIBC_2.3.4 xdr_u_quad_t F
 GLIBC_2.30 getdents64 F
 GLIBC_2.30 gettid F
+GLIBC_2.30 posix_spawn_file_actions_addclosefrom_np F
 GLIBC_2.30 tgkill F
 GLIBC_2.30 twalk_r F
 GLIBC_2.4 __confstr_chk F
diff --git a/sysdeps/unix/sysv/linux/x86_64/x32/libc.abilist b/sysdeps/unix/sysv/linux/x86_64/x32/libc.abilist
index 67a4e238d6..b83897ddbf 100644
--- a/sysdeps/unix/sysv/linux/x86_64/x32/libc.abilist
+++ b/sysdeps/unix/sysv/linux/x86_64/x32/libc.abilist
@@ -2151,5 +2151,6 @@  GLIBC_2.29 posix_spawn_file_actions_addchdir_np F
 GLIBC_2.29 posix_spawn_file_actions_addfchdir_np F
 GLIBC_2.30 getdents64 F
 GLIBC_2.30 gettid F
+GLIBC_2.30 posix_spawn_file_actions_addclosefrom_np F
 GLIBC_2.30 tgkill F
 GLIBC_2.30 twalk_r F