about summary refs log tree commit diff
path: root/sysdeps/unix/sysv/linux/closefrom_fallback.c
blob: f215fd2c092aa88a078d44dd6e8277450b3e4064 (plain) (blame)
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
/* Close a range of file descriptors.  Linux version.
   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 <arch-fd_to_filename.h>
#include <dirent.h>
#include <not-cancel.h>
#include <stdbool.h>

/* Fallback code: iterates over /proc/self/fd, closing each file descriptor
   that fall on the criteria.  If DIRFD_FALLBACK is set, a failure on
   /proc/self/fd open will trigger a fallback that tries to close a file
   descriptor before proceed.  */
_Bool
__closefrom_fallback (int from, _Bool dirfd_fallback)
{
  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.  */
      if (errno == ENOENT || !dirfd_fallback)
        goto err;

      for (int i = from; i < INT_MAX; i++)
        {
          int r = __close_nocancel (i);
          if (r == 0 || (r == -1 && errno != EBADF))
            break;
        }

      dirfd = __open_nocancel (FD_TO_FILENAME_PREFIX, O_RDONLY | O_DIRECTORY,
                               0);
      if (dirfd == -1)
        return false;
    }

  char buffer[1024];
  while (true)
    {
      ssize_t ret = __getdents64 (dirfd, buffer, sizeof (buffer));
      if (ret == -1)
        goto err;
      else if (ret == 0)
        break;

      /* If any file descriptor is closed it resets the /proc/self position
         read again from the start (to obtain any possible kernel update).  */
      bool closed = false;
      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;

          /* We ignore close errors because EBADF, EINTR, and EIO means the
             descriptor has been released.  */
          __close_nocancel (fd);
          closed = true;
        }

      if (closed && __lseek (dirfd, 0, SEEK_SET) < 0)
        goto err;
    }

  ret = true;
err:
  __close_nocancel (dirfd);
  return ret;
}