1/*	$OpenBSD: scandir.c,v 1.23 2024/04/15 15:47:58 florian Exp $ */
2/*
3 * Copyright (c) 1983, 1993
4 *	The Regents of the University of California.  All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 * 3. Neither the name of the University nor the names of its contributors
15 *    may be used to endorse or promote products derived from this software
16 *    without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 * SUCH DAMAGE.
29 */
30
31/*
32 * Scan the directory dirname calling select to make a list of selected
33 * directory entries then sort using qsort and compare routine dcomp.
34 * Returns the number of entries and a pointer to a list of pointers to
35 * struct dirent (through namelist). Returns -1 if there were any errors.
36 */
37
38#include <sys/types.h>
39#include <sys/stat.h>
40#include <dirent.h>
41#include <errno.h>
42#include <fcntl.h>
43#include <stdint.h>
44#include <stdlib.h>
45#include <string.h>
46#include <unistd.h>
47#include "telldir.h"
48
49#define MAXIMUM(a, b)	(((a) > (b)) ? (a) : (b))
50
51/*
52 * The DIRSIZ macro is the minimum record length which will hold the directory
53 * entry.  This requires the amount of space in struct dirent without the
54 * d_name field, plus enough space for the name and a terminating nul byte
55 * (dp->d_namlen + 1), rounded up to a 4 byte boundary.
56 */
57#undef DIRSIZ
58#define DIRSIZ(dp)							\
59	((sizeof(struct dirent) - sizeof(dp)->d_name) +			\
60	    (((dp)->d_namlen + 1 + 3) &~ 3))
61
62static int
63scandir_dirp(DIR *dirp, struct dirent ***namelist,
64    int (*select)(const struct dirent *),
65    int (*dcomp)(const struct dirent **, const struct dirent **))
66{
67	struct dirent *d, *p, **names = NULL;
68	size_t nitems = 0;
69	struct stat stb;
70	long arraysz;
71
72	if (fstat(dirp->dd_fd, &stb) == -1)
73		goto fail;
74
75	/*
76	 * estimate the array size by taking the size of the directory file
77	 * and dividing it by a multiple of the minimum size entry.
78	 */
79	arraysz = MAXIMUM(stb.st_size / 24, 16);
80	if (arraysz > SIZE_MAX / sizeof(struct dirent *)) {
81		errno = ENOMEM;
82		goto fail;
83	}
84	names = calloc(arraysz, sizeof(struct dirent *));
85	if (names == NULL)
86		goto fail;
87
88	while ((d = readdir(dirp)) != NULL) {
89		if (select != NULL && !(*select)(d))
90			continue;	/* just selected names */
91
92		/*
93		 * Check to make sure the array has space left and
94		 * realloc the maximum size.
95		 */
96		if (nitems >= arraysz) {
97			struct dirent **nnames;
98
99			if (fstat(dirp->dd_fd, &stb) == -1)
100				goto fail;
101
102			arraysz *= 2;
103			if (SIZE_MAX / sizeof(struct dirent *) < arraysz)
104				goto fail;
105			nnames = reallocarray(names,
106			    arraysz, sizeof(struct dirent *));
107			if (nnames == NULL)
108				goto fail;
109
110			names = nnames;
111		}
112
113		/*
114		 * Make a minimum size copy of the data
115		 */
116		p = malloc(DIRSIZ(d));
117		if (p == NULL)
118			goto fail;
119
120		p->d_ino = d->d_ino;
121		p->d_type = d->d_type;
122		p->d_reclen = d->d_reclen;
123		p->d_namlen = d->d_namlen;
124		bcopy(d->d_name, p->d_name, p->d_namlen + 1);
125		names[nitems++] = p;
126	}
127	closedir(dirp);
128	if (nitems && dcomp != NULL)
129		qsort(names, nitems, sizeof(struct dirent *),
130		    (int(*)(const void *, const void *))dcomp);
131	*namelist = names;
132	return (nitems);
133
134fail:
135	while (nitems > 0)
136		free(names[--nitems]);
137	free(names);
138	closedir(dirp);
139	return (-1);
140}
141
142int
143scandir(const char *dirname, struct dirent ***namelist,
144    int (*select)(const struct dirent *),
145    int (*dcomp)(const struct dirent **, const struct dirent **))
146{
147	DIR *dirp;
148
149	if ((dirp = opendir(dirname)) == NULL)
150		return (-1);
151
152	return (scandir_dirp(dirp, namelist, select, dcomp));
153}
154
155int
156scandirat(int dirfd, const char *dirname, struct dirent ***namelist,
157    int (*select)(const struct dirent *),
158    int (*dcomp)(const struct dirent **, const struct dirent **))
159{
160	DIR *dirp;
161	int fd;
162
163	fd = HIDDEN(openat)(dirfd, dirname, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
164	if (fd == -1)
165		return (-1);
166	dirp = __fdopendir(fd);
167	if (dirp == NULL) {
168		HIDDEN(close)(fd);
169		return (-1);
170	}
171	return (scandir_dirp(dirp, namelist, select, dcomp));
172}
173
174/*
175 * Alphabetic order comparison routine for those who want it.
176 */
177int
178alphasort(const struct dirent **d1, const struct dirent **d2)
179{
180	return(strcmp((*d1)->d_name, (*d2)->d_name));
181}
182