t_glob.c revision 1.9 1 /* $NetBSD: t_glob.c,v 1.9 2020/03/13 22:58:31 rillig Exp $ */
2 /*-
3 * Copyright (c) 2010 The NetBSD Foundation, Inc.
4 * All rights reserved.
5 *
6 * This code is derived from software contributed to The NetBSD Foundation
7 * by Christos Zoulas
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 *
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in
17 * the documentation and/or other materials provided with the
18 * distribution.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
24 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
25 * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
26 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
28 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
30 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33
34 #include <sys/cdefs.h>
35 __RCSID("$NetBSD: t_glob.c,v 1.9 2020/03/13 22:58:31 rillig Exp $");
36
37 #include <atf-c.h>
38
39 #include <sys/param.h>
40 #include <sys/stat.h>
41
42 #include <dirent.h>
43 #include <glob.h>
44 #include <stdarg.h>
45 #include <stdio.h>
46 #include <stdlib.h>
47 #include <string.h>
48 #include <errno.h>
49
50 #include "h_macros.h"
51
52
53 #ifdef DEBUG
54 #define DPRINTF(a) printf a
55 #else
56 #define DPRINTF(a)
57 #endif
58
59 struct vfs_file {
60 const char *name;
61 int dir;
62 };
63
64 static struct vfs_file a[] = {
65 { "1", 0 },
66 { "b", 1 },
67 { "3", 0 },
68 { "4", 0 },
69 };
70
71 static struct vfs_file b[] = {
72 { "x", 0 },
73 { "y", 0 },
74 { "z", 0 },
75 { "w", 0 },
76 };
77
78 static struct vfs_file hidden_dir[] = {
79 { "visible-file", 0 },
80 { ".hidden-file", 0 },
81 };
82
83 static struct vfs_file dot[] = {
84 { "a", 1 },
85 { ".hidden-dir", 1 },
86 };
87
88 struct vfs_dir {
89 const char *name; /* directory name */
90 const struct vfs_file *dir;
91 size_t len, pos;
92 };
93
94 #define VFS_DIR_INIT(name, entries) \
95 { name, entries, __arraycount(entries), 0 }
96
97 static struct vfs_dir d[] = {
98 VFS_DIR_INIT("a", a),
99 VFS_DIR_INIT("a/b", b),
100 VFS_DIR_INIT(".", dot),
101 VFS_DIR_INIT(".hidden-dir", hidden_dir),
102 };
103
104 static void
105 trim(char *buf, size_t len, const char *name)
106 {
107 char *path = buf, *epath = buf + len;
108 while (path < epath && (*path++ = *name++) != '\0')
109 continue;
110 path--;
111 while (path > buf && *--path == '/')
112 *path = '\0';
113 }
114
115 static void *
116 vfs_opendir(const char *dir)
117 {
118 size_t i;
119 char buf[MAXPATHLEN];
120 trim(buf, sizeof(buf), dir);
121
122 for (i = 0; i < __arraycount(d); i++)
123 if (strcmp(buf, d[i].name) == 0) {
124 DPRINTF(("opendir %s %p\n", buf, &d[i]));
125 return &d[i];
126 }
127 DPRINTF(("opendir %s ENOENT\n", buf));
128 errno = ENOENT;
129 return NULL;
130 }
131
132 static struct dirent *
133 vfs_readdir(void *v)
134 {
135 static struct dirent dir;
136 struct vfs_dir *dd = v;
137 if (dd->pos < dd->len) {
138 const struct vfs_file *f = &dd->dir[dd->pos++];
139 strcpy(dir.d_name, f->name);
140 dir.d_namlen = strlen(f->name);
141 dir.d_ino = dd->pos;
142 dir.d_type = f->dir ? DT_DIR : DT_REG;
143 DPRINTF(("readdir %s %d\n", dir.d_name, dir.d_type));
144 dir.d_reclen = _DIRENT_RECLEN(&dir, dir.d_namlen);
145 return &dir;
146 }
147 return NULL;
148 }
149
150 static int
151 vfs_stat(const char *name , __gl_stat_t *st)
152 {
153 char buf[MAXPATHLEN];
154 trim(buf, sizeof(buf), name);
155 memset(st, 0, sizeof(*st));
156
157 for (size_t i = 0; i < __arraycount(d); i++)
158 if (strcmp(buf, d[i].name) == 0) {
159 st->st_mode = S_IFDIR | 0755;
160 goto out;
161 }
162
163 for (size_t i = 0; i < __arraycount(d); i++) {
164 size_t dir_len = strlen(d[i].name);
165 if (strncmp(buf, d[i].name, dir_len) != 0)
166 continue;
167 if (buf[dir_len] != '/')
168 continue;
169 const char *base = buf + dir_len + 1;
170 if (strcspn(base, "/") != strlen(base))
171 continue;
172
173 for (size_t j = 0; j < d[i].len; j++)
174 if (strcmp(d[i].dir[j].name, base) == 0) {
175 st->st_mode = d[i].dir[j].dir
176 ? S_IFDIR | 0755
177 : S_IFREG | 0644;
178 goto out;
179 }
180 }
181 DPRINTF(("stat %s ENOENT\n", buf));
182 errno = ENOENT;
183 return -1;
184 out:
185 DPRINTF(("stat %s %06o\n", buf, st->st_mode));
186 return 0;
187 }
188
189 static int
190 vfs_lstat(const char *name , __gl_stat_t *st)
191 {
192 return vfs_stat(name, st);
193 }
194
195 static void
196 vfs_closedir(void *v)
197 {
198 struct vfs_dir *dd = v;
199 dd->pos = 0;
200 DPRINTF(("closedir %p\n", dd));
201 }
202
203 static void
204 run(const char *p, int flags, /* const char *res */ ...)
205 {
206 glob_t gl;
207 size_t i;
208 int e;
209
210 DPRINTF(("pattern %s\n", p));
211 memset(&gl, 0, sizeof(gl));
212 gl.gl_opendir = vfs_opendir;
213 gl.gl_readdir = vfs_readdir;
214 gl.gl_closedir = vfs_closedir;
215 gl.gl_stat = vfs_stat;
216 gl.gl_lstat = vfs_lstat;
217
218 switch ((e = glob(p, GLOB_ALTDIRFUNC | flags, NULL, &gl))) {
219 case 0:
220 break;
221 case GLOB_NOSPACE:
222 fprintf(stderr, "Malloc call failed.\n");
223 goto bad;
224 case GLOB_ABORTED:
225 fprintf(stderr, "Unignored error.\n");
226 goto bad;
227 case GLOB_NOMATCH:
228 fprintf(stderr, "No match, and GLOB_NOCHECK was not set.\n");
229 goto bad;
230 case GLOB_NOSYS:
231 fprintf(stderr, "Implementation does not support function.\n");
232 goto bad;
233 default:
234 fprintf(stderr, "Unknown error %d.\n", e);
235 goto bad;
236 }
237
238 for (i = 0; i < gl.gl_pathc; i++)
239 DPRINTF(("glob result %zu: %s\n", i, gl.gl_pathv[i]));
240
241 va_list res;
242 va_start(res, flags);
243 i = 0;
244 const char *name;
245 while ((name = va_arg(res, const char *)) != NULL && i < gl.gl_pathc) {
246 ATF_CHECK_STREQ(gl.gl_pathv[i], name);
247 i++;
248 }
249 va_end(res);
250 ATF_CHECK_EQ_MSG(i, gl.gl_pathc,
251 "expected %zu results, got %zu", i, gl.gl_pathc);
252 ATF_CHECK_EQ_MSG(name, NULL,
253 "\"%s\" should have been found, but wasn't", name);
254
255 globfree(&gl);
256 return;
257 bad:
258 ATF_REQUIRE_MSG(e == 0, "No match for `%s'", p);
259 }
260
261 #define run(p, flags, ...) (run)(p, flags, __VA_ARGS__, (const char *) 0)
262
263 ATF_TC(glob_range);
264 ATF_TC_HEAD(glob_range, tc)
265 {
266 atf_tc_set_md_var(tc, "descr",
267 "Test glob(3) range");
268 }
269
270 ATF_TC_BODY(glob_range, tc)
271 {
272 run("a/b/[x-z]", 0,
273 "a/b/x", "a/b/y", "a/b/z");
274 }
275
276 ATF_TC(glob_range_not);
277 ATF_TC_HEAD(glob_range_not, tc)
278 {
279 atf_tc_set_md_var(tc, "descr",
280 "Test glob(3) ! range");
281 }
282
283 ATF_TC_BODY(glob_range_not, tc)
284 {
285 run("a/b/[!x-z]", 0,
286 "a/b/w");
287 }
288
289 ATF_TC(glob_star);
290 ATF_TC_HEAD(glob_star, tc)
291 {
292 atf_tc_set_md_var(tc, "descr",
293 "Test glob(3) ** with GLOB_STAR");
294 }
295
296 ATF_TC_BODY(glob_star, tc)
297 {
298 run("a/**", GLOB_STAR,
299 "a/1", "a/3", "a/4", "a/b", "a/b/w", "a/b/x", "a/b/y", "a/b/z");
300 }
301
302 ATF_TC(glob_star_not);
303 ATF_TC_HEAD(glob_star_not, tc)
304 {
305 atf_tc_set_md_var(tc, "descr",
306 "Test glob(3) ** without GLOB_STAR");
307 }
308
309 ATF_TC_BODY(glob_star_not, tc)
310 {
311 run("a/**", 0,
312 "a/1", "a/3", "a/4", "a/b");
313 }
314
315 ATF_TC(glob_star_star);
316 ATF_TC_HEAD(glob_star_star, tc)
317 {
318 atf_tc_set_md_var(tc, "descr",
319 "Test glob(3) with star-star");
320 }
321
322 ATF_TC_BODY(glob_star_star, tc)
323 {
324 run("**", GLOB_STAR,
325 "a",
326 "a/1", "a/3", "a/4", "a/b",
327 "a/b/w", "a/b/x", "a/b/y", "a/b/z");
328 }
329
330 ATF_TC(glob_hidden);
331 ATF_TC_HEAD(glob_hidden, tc)
332 {
333 atf_tc_set_md_var(tc, "descr",
334 "Test glob(3) with hidden directory");
335 }
336
337 ATF_TC_BODY(glob_hidden, tc)
338 {
339 run(".**", GLOB_STAR,
340 ".hidden-dir",
341 ".hidden-dir/visible-file");
342 }
343
344 #if 0
345 ATF_TC(glob_nocheck);
346 ATF_TC_HEAD(glob_nocheck, tc)
347 {
348 atf_tc_set_md_var(tc, "descr",
349 "Test glob(3) pattern with backslash and GLOB_NOCHECK");
350 }
351
352
353 ATF_TC_BODY(glob_nocheck, tc)
354 {
355 static const char pattern[] = { 'f', 'o', 'o', '\\', ';', 'b', 'a',
356 'r', '\0' };
357 static const char *glob_nocheck[] = {
358 pattern
359 };
360 run(pattern, GLOB_NOCHECK, glob_nocheck, __arraycount(glob_nocheck));
361 }
362 #endif
363
364 ATF_TP_ADD_TCS(tp)
365 {
366 ATF_TP_ADD_TC(tp, glob_star);
367 ATF_TP_ADD_TC(tp, glob_star_not);
368 ATF_TP_ADD_TC(tp, glob_range);
369 ATF_TP_ADD_TC(tp, glob_range_not);
370 ATF_TP_ADD_TC(tp, glob_star_star);
371 ATF_TP_ADD_TC(tp, glob_hidden);
372 /*
373 * Remove this test for now - the GLOB_NOCHECK return value has been
374 * re-defined to return a modified pattern in revision 1.33 of glob.c
375 *
376 * ATF_TP_ADD_TC(tp, glob_nocheck);
377 */
378
379 return atf_no_error();
380 }
381