1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21 /*
22 * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
24 *
25 * logadm/glob.c -- globbing routines
26 *
27 * these routines support two kinds of globs. first, the
28 * usual kind of filename globbing, like:
29 *
30 * *.c
31 * /var/log/syslog.?
32 * log[0-9]*file
33 * /var/apache/logs/x*{access,error}_log
34 *
35 * this is basically the same syntax that csh supports for globs and
36 * is provided by the routine glob_glob() which takes a filename and
37 * returns a list of filenames that match the glob.
38 *
39 * the second type is something called a "reglob" which is a pathname
40 * where the components are regular expressions as described in regex(3c).
41 * some examples:
42 *
43 * .*\.c
44 * /var/log/syslog\..
45 * log[0-9].*file
46 * /var/log/syslog\.([0-9]+)$0
47 *
48 * the last example uses the ()$n form to assign a numeric extension
49 * on a filename to the "n" value kept by the fn routines with each
50 * filename (see fn_setn() in fn.c). logadm uses this mechanism to
51 * correctly sort lognames when templates containing $n are used.
52 *
53 * the routine glob_reglob() is used to expand reglobs. glob_glob()
54 * is implemented by expanding the curly braces, converting the globs
55 * to reglobs, and then passing the work to glob_reglob().
56 *
57 * finally, since expanding globs and reglobs requires doing a stat(2)
58 * on the files, we store the resulting stat information in the filename
59 * struct (see fn_setstat() in fn.c).
60 *
61 * the glob(3c) routines are not used here since they don't support
62 * braces, and don't support the more powerful reglobs required by logadm.
63 */
64
65 #pragma ident "%Z%%M% %I% %E% SMI"
66
67 #include <stdio.h>
68 #include <libintl.h>
69 #include <stdlib.h>
70 #include <libgen.h>
71 #include <strings.h>
72 #include <sys/types.h>
73 #include <sys/param.h>
74 #include <sys/stat.h>
75 #include <dirent.h>
76 #include "err.h"
77 #include "fn.h"
78 #include "glob.h"
79
80 /* forward declarations for functions used internally by this module */
81 static struct fn_list *glob_debrace(struct fn *fnp);
82 static struct fn_list *glob_reglob_list(struct fn_list *fnlp);
83 static boolean_t glob_magic(struct fn *fnp);
84
85 /* expand curly braces (like file{one,two,three}name) */
86 static struct fn_list *
glob_debrace(struct fn * fnp)87 glob_debrace(struct fn *fnp)
88 {
89 struct fn_list *ret = fn_list_new(NULL);
90 struct fn_list *newret;
91 char *sp = fn_s(fnp);
92 char *left;
93 char *right;
94 char *comma;
95
96 /* start with an empty string in the list */
97 fn_list_adds(ret, "");
98
99 /* while braces remain... */
100 while (sp != NULL && (left = strchr(sp, '{')) != NULL)
101 if ((right = strchr(left, '}')) == NULL) {
102 err(EF_FILE|EF_JMP, "Missing }");
103 fn_list_free(ret);
104 return (NULL);
105 } else {
106 /* stuff before "left" is finished */
107 fn_list_appendrange(ret, sp, left);
108
109 /* stuff after "right" still need processing */
110 sp = right + 1;
111
112 if (left + 1 == right)
113 continue; /* just an empty {} */
114
115 /* stuff between "left" and "right" is comma-sep list */
116 left++;
117 newret = fn_list_new(NULL);
118 while ((comma = strchr(left, ',')) != NULL) {
119 struct fn_list *dup = fn_list_dup(ret);
120
121 /* stuff from left to comma is one variant */
122 fn_list_appendrange(dup, left, comma);
123 fn_list_addfn_list(newret, dup);
124 left = comma + 1;
125 }
126 /* what's left is the last item in the list */
127 fn_list_appendrange(ret, left, right);
128 fn_list_addfn_list(newret, ret);
129 ret = newret;
130 }
131
132 /* anything remaining in "s" is finished */
133 fn_list_appendrange(ret, sp, &sp[strlen(sp)]);
134 return (ret);
135 }
136
137 /* return true if filename contains any "magic" characters (*,?,[) */
138 static boolean_t
glob_magic(struct fn * fnp)139 glob_magic(struct fn *fnp)
140 {
141 char *s = fn_s(fnp);
142
143 for (; s != NULL && *s; s++)
144 if (*s == '*' ||
145 *s == '?' ||
146 *s == '[')
147 return (B_TRUE);
148
149 return (B_FALSE);
150 }
151
152 /*
153 * glob_glob -- given a filename glob, return the list of matching filenames
154 *
155 * fn_setn() and fn_setstat() are called to set the "n" and stat information
156 * for the resulting filenames.
157 */
158 struct fn_list *
glob_glob(struct fn * fnp)159 glob_glob(struct fn *fnp)
160 {
161 struct fn_list *tmplist = glob_debrace(fnp);
162 struct fn_list *ret;
163 struct fn *nextfnp;
164 struct fn *newfnp;
165 int magic = 0;
166
167 /* debracing produced NULL list? */
168 if (tmplist == NULL)
169 return (NULL);
170
171 /* see if anything in list contains magic characters */
172 fn_list_rewind(tmplist);
173 while ((nextfnp = fn_list_next(tmplist)) != NULL)
174 if (glob_magic(nextfnp)) {
175 magic = 1;
176 break;
177 }
178
179 if (!magic)
180 return (tmplist); /* no globs to expand */
181
182 /* foreach name in the list, call glob_glob() to expand it */
183 fn_list_rewind(tmplist);
184 ret = fn_list_new(NULL);
185 while ((nextfnp = fn_list_next(tmplist)) != NULL) {
186 newfnp = glob_to_reglob(nextfnp);
187 fn_list_addfn(ret, newfnp);
188 }
189 fn_list_free(tmplist);
190 tmplist = ret;
191 ret = glob_reglob_list(tmplist);
192 fn_list_free(tmplist);
193
194 return (ret);
195 }
196
197 /*
198 * glob_glob_list -- given a list of filename globs, return all matches
199 */
200 struct fn_list *
glob_glob_list(struct fn_list * fnlp)201 glob_glob_list(struct fn_list *fnlp)
202 {
203 struct fn_list *ret = fn_list_new(NULL);
204 struct fn *fnp;
205
206 fn_list_rewind(fnlp);
207 while ((fnp = fn_list_next(fnlp)) != NULL)
208 fn_list_addfn_list(ret, glob_glob(fnp));
209 return (ret);
210 }
211
212 /*
213 * glob_reglob -- given a filename reglob, return a list of matching filenames
214 *
215 * this routine does all the hard work in this module.
216 */
217 struct fn_list *
glob_reglob(struct fn * fnp)218 glob_reglob(struct fn *fnp)
219 {
220 struct fn_list *ret = fn_list_new(NULL);
221 struct fn_list *newret;
222 struct fn *nextfnp;
223 char *mys = STRDUP(fn_s(fnp));
224 char *sp = mys;
225 char *slash;
226 int skipdotfiles;
227 char *re;
228 char ret0[MAXPATHLEN];
229
230
231 /* start with the initial directory in the list */
232 if (*sp == '/') {
233 fn_list_adds(ret, "/");
234 while (*sp == '/')
235 sp++;
236 } else
237 fn_list_adds(ret, "./");
238
239 /* while components remain... */
240 do {
241 if ((slash = strchr(sp, '/')) != NULL) {
242 *slash++ = '\0';
243 /* skip superfluous slashes */
244 while (*slash == '/')
245 slash++;
246 }
247
248 /* dot files are skipped unless a dot was specifically given */
249 if (sp[0] == '\\' && sp[1] == '.')
250 skipdotfiles = 0;
251 else
252 skipdotfiles = 1;
253
254 /* compile the regex */
255 if ((re = regcmp("^", sp, "$", (char *)0)) == NULL)
256 err(EF_FILE|EF_JMP, "regcmp failed on <%s>", sp);
257
258 /* apply regex to every filename we've matched so far */
259 newret = fn_list_new(NULL);
260 fn_list_rewind(ret);
261 while ((nextfnp = fn_list_next(ret)) != NULL) {
262 DIR *dirp;
263 struct dirent *dp;
264
265 /* go through directory looking for matches */
266 if ((dirp = opendir(fn_s(nextfnp))) == NULL)
267 continue;
268
269 while ((dp = readdir(dirp)) != NULL) {
270 if (skipdotfiles && dp->d_name[0] == '.')
271 continue;
272 *ret0 = '\0';
273 if (regex(re, dp->d_name, ret0)) {
274 struct fn *matchfnp = fn_dup(nextfnp);
275 struct stat stbuf;
276 int n;
277
278 fn_puts(matchfnp, dp->d_name);
279
280 if (stat(fn_s(matchfnp), &stbuf) < 0) {
281 fn_free(matchfnp);
282 continue;
283 }
284
285 /* skip non-dirs if more components */
286 if (slash &&
287 (stbuf.st_mode & S_IFMT) !=
288 S_IFDIR) {
289 fn_free(matchfnp);
290 continue;
291 }
292
293 /*
294 * component matched, fill in "n"
295 * value, stat information, and
296 * append component to directory
297 * name just searched.
298 */
299
300 if (*ret0)
301 n = atoi(ret0);
302 else
303 n = -1;
304 fn_setn(matchfnp, n);
305 fn_setstat(matchfnp, &stbuf);
306
307 if (slash)
308 fn_putc(matchfnp, '/');
309
310 fn_list_addfn(newret, matchfnp);
311 }
312 }
313 (void) closedir(dirp);
314 }
315 fn_list_free(ret);
316 ret = newret;
317 sp = slash;
318 } while (slash);
319
320 FREE(mys);
321
322 return (ret);
323 }
324
325 /* reglob a list of filenames */
326 static struct fn_list *
glob_reglob_list(struct fn_list * fnlp)327 glob_reglob_list(struct fn_list *fnlp)
328 {
329 struct fn_list *ret = fn_list_new(NULL);
330 struct fn *fnp;
331
332 fn_list_rewind(fnlp);
333 while ((fnp = fn_list_next(fnlp)) != NULL)
334 fn_list_addfn_list(ret, glob_reglob(fnp));
335 return (ret);
336 }
337
338 /*
339 * glob_to_reglob -- convert a glob (*, ?, etc) to a reglob (.*, ., etc.)
340 */
341 struct fn *
glob_to_reglob(struct fn * fnp)342 glob_to_reglob(struct fn *fnp)
343 {
344 int c;
345 struct fn *ret = fn_new(NULL);
346
347 fn_rewind(fnp);
348 while ((c = fn_getc(fnp)) != '\0')
349 switch (c) {
350 case '.':
351 case '(':
352 case ')':
353 case '^':
354 case '+':
355 case '{':
356 case '}':
357 case '$':
358 /* magic characters need backslash */
359 fn_putc(ret, '\\');
360 fn_putc(ret, c);
361 break;
362 case '?':
363 /* change '?' to a single dot */
364 fn_putc(ret, '.');
365 break;
366 case '*':
367 /* change '*' to ".*" */
368 fn_putc(ret, '.');
369 fn_putc(ret, '*');
370 break;
371 default:
372 fn_putc(ret, c);
373 }
374
375 return (ret);
376 }
377
378 #ifdef TESTMODULE
379
380 /*
381 * test main for glob module, usage: a.out [-r] [pattern...]
382 * -r means the patterns are reglobs instead of globs
383 */
384 int
main(int argc,char * argv[])385 main(int argc, char *argv[])
386 {
387 int i;
388 int reglobs = 0;
389 struct fn *argfnp = fn_new(NULL);
390 struct fn *fnp;
391 struct fn_list *fnlp;
392
393 err_init(argv[0]);
394 setbuf(stdout, NULL);
395
396 for (i = 1; i < argc; i++) {
397 if (strcmp(argv[i], "-r") == 0) {
398 reglobs = 1;
399 continue;
400 }
401
402 if (SETJMP) {
403 printf(" skipped due to errors\n");
404 continue;
405 } else {
406 printf("<%s>:\n", argv[i]);
407 fn_renew(argfnp, argv[i]);
408 if (reglobs)
409 fnlp = glob_reglob(argfnp);
410 else
411 fnlp = glob_glob(argfnp);
412 }
413
414 fn_list_rewind(fnlp);
415 while ((fnp = fn_list_next(fnlp)) != NULL)
416 printf(" <%s>\n", fn_s(fnp));
417
418 printf("total size: %lld\n", fn_list_totalsize(fnlp));
419
420 while ((fnp = fn_list_popoldest(fnlp)) != NULL) {
421 printf(" oldest <%s>\n", fn_s(fnp));
422 fn_free(fnp);
423 }
424
425 fn_list_free(fnlp);
426 }
427 fn_free(argfnp);
428
429 err_done(0);
430 /* NOTREACHED */
431 return (0);
432 }
433
434 #endif /* TESTMODULE */
435