xref: /minix3/lib/libc/gen/initdir.c (revision 84d9c625bfea59e274550651111ae9edfdc40fbd)
1 /*	$NetBSD: initdir.c,v 1.3 2012/03/13 21:13:36 christos Exp $	*/
2 
3 /*
4  * Copyright (c) 1983, 1993
5  *	The Regents of the University of California.  All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. Neither the name of the University nor the names of its contributors
16  *    may be used to endorse or promote products derived from this software
17  *    without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31 
32 #include <sys/cdefs.h>
33 #if defined(LIBC_SCCS) && !defined(lint)
34 __RCSID("$NetBSD: initdir.c,v 1.3 2012/03/13 21:13:36 christos Exp $");
35 #endif /* LIBC_SCCS and not lint */
36 
37 #include "namespace.h"
38 #include "reentrant.h"
39 #include "extern.h"
40 
41 #include <sys/param.h>
42 
43 #include <assert.h>
44 #include <dirent.h>
45 #include <errno.h>
46 #include <fcntl.h>
47 #include <stdlib.h>
48 #include <string.h>
49 #include <unistd.h>
50 
51 #include "dirent_private.h"
52 
53 #define	MAXITERATIONS	100
54 
55 int
_initdir(DIR * dirp,int fd,const char * name)56 _initdir(DIR *dirp, int fd, const char *name)
57 {
58 	int flags = dirp->dd_flags;
59 	int pagesz;
60 	int incr;
61 
62 	/*
63 	 * If the machine's page size is an exact multiple of DIRBLKSIZ,
64 	 * use a buffer that is cluster boundary aligned.
65 	 * Hopefully this can be a big win someday by allowing page trades
66 	 * to user space to be done by getdents()
67 	 */
68 	if (((pagesz = getpagesize()) % DIRBLKSIZ) == 0)
69 		incr = pagesz;
70 	else
71 		incr = DIRBLKSIZ;
72 
73 	if ((flags & DTF_REWIND) && name == NULL) {
74 		return EINVAL;
75 	}
76 	if ((flags & __DTF_READALL) != 0) {
77 		size_t len;
78 		size_t space;
79 		char *buf, *nbuf;
80 		char *ddptr;
81 		char *ddeptr;
82 		int n;
83 		struct dirent **dpv;
84 		int i;
85 
86 		/*
87 		 * The strategy here for directories on top of a union stack
88 		 * is to read all the directory entries into a buffer, sort
89 		 * the buffer, and remove duplicate entries by setting the
90 		 * inode number to zero.
91 		 *
92 		 * For directories on an NFS mounted filesystem, we try
93 	 	 * to get a consistent snapshot by trying until we have
94 		 * successfully read all of the directory without errors
95 		 * (i.e. 'bad cookie' errors from the server because
96 		 * the directory was modified). These errors should not
97 		 * happen often, but need to be dealt with.
98 		 */
99 		i = 0;
100 retry:
101 		len = 0;
102 		space = 0;
103 		buf = 0;
104 		ddptr = 0;
105 
106 		do {
107 			/*
108 			 * Always make at least DIRBLKSIZ bytes
109 			 * available to getdents
110 			 */
111 			if (space < DIRBLKSIZ) {
112 				space += incr;
113 				len += incr;
114 				nbuf = realloc(buf, len);
115 				if (nbuf == NULL) {
116 					dirp->dd_buf = buf;
117 					return errno;
118 				}
119 				buf = nbuf;
120 				ddptr = buf + (len - space);
121 			}
122 
123 			dirp->dd_seek = lseek(fd, (off_t)0, SEEK_CUR);
124 			n = getdents(fd, ddptr, space);
125 			/*
126 			 * For NFS: EINVAL means a bad cookie error
127 			 * from the server. Keep trying to get a
128 			 * consistent view, in this case this means
129 			 * starting all over again.
130 			 */
131 			if (n == -1 && errno == EINVAL &&
132 			    (flags & __DTF_RETRY_ON_BADCOOKIE) != 0) {
133 				free(buf);
134 				lseek(fd, (off_t)0, SEEK_SET);
135 				if (++i > MAXITERATIONS)
136 					return EINVAL;
137 				goto retry;
138 			}
139 			if (n > 0) {
140 				ddptr += n;
141 				space -= n;
142 			}
143 		} while (n > 0);
144 
145 		ddeptr = ddptr;
146 
147 		/*
148 		 * Re-open the directory.
149 		 * This has the effect of rewinding back to the
150 		 * top of the union stack and is needed by
151 		 * programs which plan to fchdir to a descriptor
152 		 * which has also been read -- see fts.c.
153 		 */
154 		if (flags & DTF_REWIND) {
155 			(void) close(fd);
156 			if ((fd = open(name, O_RDONLY | O_CLOEXEC)) == -1) {
157 				dirp->dd_buf = buf;
158 				return errno;
159 			}
160 		}
161 
162 		/*
163 		 * There is now a buffer full of (possibly) duplicate
164 		 * names.
165 		 */
166 		dirp->dd_buf = buf;
167 
168 		/*
169 		 * Go round this loop twice...
170 		 *
171 		 * Scan through the buffer, counting entries.
172 		 * On the second pass, save pointers to each one.
173 		 * Then sort the pointers and remove duplicate names.
174 		 */
175 		if ((flags & DTF_NODUP) != 0) {
176 			for (dpv = 0;;) {
177 				for (n = 0, ddptr = buf; ddptr < ddeptr;) {
178 					struct dirent *dp;
179 
180 					dp = (struct dirent *)(void *)ddptr;
181 					if ((long)dp & _DIRENT_ALIGN(dp))
182 						break;
183 					/*
184 					 * d_reclen is unsigned,
185 					 * so no need to compare <= 0
186 					 */
187 					if (dp->d_reclen > (ddeptr + 1 - ddptr))
188 						break;
189 					ddptr += dp->d_reclen;
190 					if (dp->d_fileno) {
191 						if (dpv)
192 							dpv[n] = dp;
193 						n++;
194 					}
195 				}
196 
197 				if (dpv) {
198 					struct dirent *xp;
199 
200 					/*
201 					 * This sort must be stable.
202 					 */
203 					mergesort(dpv, (size_t)n, sizeof(*dpv),
204 					    alphasort);
205 
206 					dpv[n] = NULL;
207 					xp = NULL;
208 
209 					/*
210 					 * Scan through the buffer in sort
211 					 * order, zapping the inode number
212 					 * of any duplicate names.
213 					 */
214 					for (n = 0; dpv[n]; n++) {
215 						struct dirent *dp = dpv[n];
216 
217 						if ((xp == NULL) ||
218 						    strcmp(dp->d_name,
219 						      xp->d_name))
220 							xp = dp;
221 						else
222 							dp->d_fileno = 0;
223 #if !defined(__minix)
224 						if (dp->d_type == DT_WHT &&
225 						    (flags & DTF_HIDEW))
226 							dp->d_fileno = 0;
227 #endif /* !defined(__minix) */
228 					}
229 
230 					free(dpv);
231 					break;
232 				} else {
233 					dpv = malloc((n + 1) *
234 					    sizeof(struct dirent *));
235 					if (dpv == NULL)
236 						break;
237 				}
238 			}
239 		}
240 
241 		_DIAGASSERT(__type_fit(int, len));
242 		dirp->dd_len = (int)len;
243 		dirp->dd_size = ddptr - dirp->dd_buf;
244 	} else {
245 		dirp->dd_len = incr;
246 		dirp->dd_buf = malloc((size_t)dirp->dd_len);
247 		if (dirp->dd_buf == NULL)
248 			return errno;
249 		dirp->dd_seek = 0;
250 		flags &= ~DTF_REWIND;
251 	}
252 	dirp->dd_loc = 0;
253 	dirp->dd_fd = fd;
254 	dirp->dd_flags = flags;
255 	/*
256 	 * Set up seek point for rewinddir.
257 	 */
258 	(void)_telldir_unlocked(dirp);
259 	return 0;
260 }
261 
262 void
_finidir(DIR * dirp)263 _finidir(DIR *dirp)
264 {
265 	struct dirpos *poslist;
266 
267 	free(dirp->dd_buf);
268 
269 	/* free seekdir/telldir storage */
270 	for (poslist = dirp->dd_internal; poslist; ) {
271 		struct dirpos *nextpos = poslist->dp_next;
272 		free(poslist);
273 		poslist = nextpos;
274 	}
275 	dirp->dd_internal = NULL;
276 }
277