1 /* $NetBSD: t_glob.c,v 1.10 2020/03/13 23:27:54 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.10 2020/03/13 23:27:54 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 char type; /* 'd' or '-', like in ls(1) */
61 const char *name;
62 };
63
64 static struct vfs_file a[] = {
65 { '-', "1" },
66 { 'd', "b" },
67 { '-', "3" },
68 { '-', "4" },
69 };
70
71 static struct vfs_file b[] = {
72 { '-', "x" },
73 { '-', "y" },
74 { '-', "z" },
75 { '-', "w" },
76 };
77
78 static struct vfs_file hidden_dir[] = {
79 { '-', "visible-file" },
80 { '-', ".hidden-file" },
81 };
82
83 static struct vfs_file dot[] = {
84 { 'd', "a" },
85 { 'd', ".hidden-dir" },
86 };
87
88 struct vfs_dir {
89 const char *name; /* full directory name */
90 const struct vfs_file *entries;
91 size_t entries_len;
92 size_t pos; /* only between opendir/closedir */
93 };
94
95 #define VFS_DIR_INIT(name, entries) \
96 { name, entries, __arraycount(entries), 0 }
97
98 static struct vfs_dir d[] = {
99 VFS_DIR_INIT("a", a),
100 VFS_DIR_INIT("a/b", b),
101 VFS_DIR_INIT(".", dot),
102 VFS_DIR_INIT(".hidden-dir", hidden_dir),
103 };
104
105 static void
trim(char * buf,size_t len,const char * name)106 trim(char *buf, size_t len, const char *name)
107 {
108 char *path = buf, *epath = buf + len;
109 while (path < epath && (*path++ = *name++) != '\0')
110 continue;
111 path--;
112 while (path > buf && *--path == '/')
113 *path = '\0';
114 }
115
116 static void *
vfs_opendir(const char * dir)117 vfs_opendir(const char *dir)
118 {
119 size_t i;
120 char buf[MAXPATHLEN];
121 trim(buf, sizeof(buf), dir);
122
123 for (i = 0; i < __arraycount(d); i++)
124 if (strcmp(buf, d[i].name) == 0) {
125 DPRINTF(("opendir %s %p\n", buf, &d[i]));
126 return &d[i];
127 }
128 DPRINTF(("opendir %s ENOENT\n", buf));
129 errno = ENOENT;
130 return NULL;
131 }
132
133 static struct dirent *
vfs_readdir(void * v)134 vfs_readdir(void *v)
135 {
136 static struct dirent dir;
137 struct vfs_dir *dd = v;
138 if (dd->pos < dd->entries_len) {
139 const struct vfs_file *f = &dd->entries[dd->pos++];
140 strcpy(dir.d_name, f->name);
141 dir.d_namlen = strlen(f->name);
142 dir.d_ino = dd->pos;
143 dir.d_type = f->type == 'd' ? DT_DIR : DT_REG;
144 DPRINTF(("readdir %s %d\n", dir.d_name, dir.d_type));
145 dir.d_reclen = _DIRENT_RECLEN(&dir, dir.d_namlen);
146 return &dir;
147 }
148 return NULL;
149 }
150
151 static int
vfs_stat(const char * name,__gl_stat_t * st)152 vfs_stat(const char *name , __gl_stat_t *st)
153 {
154 char buf[MAXPATHLEN];
155 trim(buf, sizeof(buf), name);
156 memset(st, 0, sizeof(*st));
157
158 for (size_t i = 0; i < __arraycount(d); i++)
159 if (strcmp(buf, d[i].name) == 0) {
160 st->st_mode = S_IFDIR | 0755;
161 goto out;
162 }
163
164 for (size_t i = 0; i < __arraycount(d); i++) {
165 size_t dir_len = strlen(d[i].name);
166 if (strncmp(buf, d[i].name, dir_len) != 0)
167 continue;
168 if (buf[dir_len] != '/')
169 continue;
170 const char *base = buf + dir_len + 1;
171
172 for (size_t j = 0; j < d[i].entries_len; j++) {
173 const struct vfs_file *f = &d[i].entries[j];
174 if (strcmp(f->name, base) != 0)
175 continue;
176 ATF_CHECK(f->type != 'd'); // handled above
177 st->st_mode = 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
vfs_lstat(const char * name,__gl_stat_t * st)190 vfs_lstat(const char *name , __gl_stat_t *st)
191 {
192 return vfs_stat(name, st);
193 }
194
195 static void
vfs_closedir(void * v)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
run(const char * p,int flags,...)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);
ATF_TC_HEAD(glob_range,tc)264 ATF_TC_HEAD(glob_range, tc)
265 {
266 atf_tc_set_md_var(tc, "descr",
267 "Test glob(3) range");
268 }
269
ATF_TC_BODY(glob_range,tc)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);
ATF_TC_HEAD(glob_range_not,tc)277 ATF_TC_HEAD(glob_range_not, tc)
278 {
279 atf_tc_set_md_var(tc, "descr",
280 "Test glob(3) ! range");
281 }
282
ATF_TC_BODY(glob_range_not,tc)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);
ATF_TC_HEAD(glob_star,tc)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
ATF_TC_BODY(glob_star,tc)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);
ATF_TC_HEAD(glob_star_not,tc)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
ATF_TC_BODY(glob_star_not,tc)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);
ATF_TC_HEAD(glob_star_star,tc)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
ATF_TC_BODY(glob_star_star,tc)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);
ATF_TC_HEAD(glob_hidden,tc)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
ATF_TC_BODY(glob_hidden,tc)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
ATF_TP_ADD_TCS(tp)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