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