1 /* Copyright (C) 1991-2021 Free Software Foundation, Inc.
2    This file is part of the GNU C Library.
3 
4    The GNU C Library is free software; you can redistribute it and/or
5    modify it under the terms of the GNU Lesser General Public
6    License as published by the Free Software Foundation; either
7    version 2.1 of the License, or (at your option) any later version.
8 
9    The GNU C Library is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12    Lesser General Public License for more details.
13 
14    You should have received a copy of the GNU Lesser General Public
15    License along with the GNU C Library; if not, see
16    <https://www.gnu.org/licenses/>.  */
17 
18 #include <dirent.h>
19 #include <fcntl.h>
20 #include <errno.h>
21 #include <stdio.h>	/* For BUFSIZ.  */
22 #include <sys/param.h>	/* For MIN and MAX.  */
23 
24 #include <not-cancel.h>
25 
26 enum {
27   opendir_oflags = O_RDONLY|O_NDELAY|O_DIRECTORY|O_LARGEFILE|O_CLOEXEC
28 };
29 
30 static bool
invalid_name(const char * name)31 invalid_name (const char *name)
32 {
33   if (__glibc_unlikely (name[0] == '\0'))
34     {
35       /* POSIX.1-1990 says an empty name gets ENOENT;
36 	 but `open' might like it fine.  */
37       __set_errno (ENOENT);
38       return true;
39     }
40   return false;
41 }
42 
43 static DIR *
opendir_tail(int fd)44 opendir_tail (int fd)
45 {
46   if (__glibc_unlikely (fd < 0))
47     return NULL;
48 
49   /* Now make sure this really is a directory and nothing changed since the
50      `stat' call.  The S_ISDIR check is superfluous if O_DIRECTORY works,
51      but it's cheap and we need the stat call for st_blksize anyway.  */
52   struct __stat64_t64 statbuf;
53   if (__glibc_unlikely (__fstat64_time64 (fd, &statbuf) < 0))
54     goto lose;
55   if (__glibc_unlikely (! S_ISDIR (statbuf.st_mode)))
56     {
57       __set_errno (ENOTDIR);
58     lose:
59       __close_nocancel_nostatus (fd);
60       return NULL;
61     }
62 
63   return __alloc_dir (fd, true, 0, &statbuf);
64 }
65 
66 
67 #if IS_IN (libc)
68 DIR *
__opendirat(int dfd,const char * name)69 __opendirat (int dfd, const char *name)
70 {
71   if (__glibc_unlikely (invalid_name (name)))
72     return NULL;
73 
74   return opendir_tail (__openat_nocancel (dfd, name, opendir_oflags));
75 }
76 #endif
77 
78 
79 /* Open a directory stream on NAME.  */
80 DIR *
__opendir(const char * name)81 __opendir (const char *name)
82 {
83   if (__glibc_unlikely (invalid_name (name)))
84     return NULL;
85 
86   return opendir_tail (__open_nocancel (name, opendir_oflags));
87 }
weak_alias(__opendir,opendir)88 weak_alias (__opendir, opendir)
89 
90 DIR *
91 __alloc_dir (int fd, bool close_fd, int flags,
92 	     const struct __stat64_t64 *statp)
93 {
94   /* We have to set the close-on-exit flag if the user provided the
95      file descriptor.  */
96   if (!close_fd
97       && __glibc_unlikely (__fcntl64_nocancel (fd, F_SETFD, FD_CLOEXEC) < 0))
98     return NULL;
99 
100   /* The st_blksize value of the directory is used as a hint for the
101      size of the buffer which receives struct dirent values from the
102      kernel.  st_blksize is limited to max_buffer_size, in case the
103      file system provides a bogus value.  */
104   enum { max_buffer_size = 1048576 };
105 
106   enum { allocation_size = 32768 };
107   _Static_assert (allocation_size >= sizeof (struct dirent64),
108 		  "allocation_size < sizeof (struct dirent64)");
109 
110   /* Increase allocation if requested, but not if the value appears to
111      be bogus.  It will be between 32Kb and 1Mb.  */
112   size_t allocation = MIN (MAX ((size_t) statp->st_blksize, (size_t)
113                                 allocation_size), (size_t) max_buffer_size);
114 
115   DIR *dirp = (DIR *) malloc (sizeof (DIR) + allocation);
116   if (dirp == NULL)
117     {
118       if (close_fd)
119 	__close_nocancel_nostatus (fd);
120       return NULL;
121     }
122 
123   dirp->fd = fd;
124 #if IS_IN (libc)
125   __libc_lock_init (dirp->lock);
126 #endif
127   dirp->allocation = allocation;
128   dirp->size = 0;
129   dirp->offset = 0;
130   dirp->filepos = 0;
131   dirp->errcode = 0;
132 
133   return dirp;
134 }
135