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.
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.
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]
22 * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
25 * logadm/glob.c -- globbing routines
27 * these routines support two kinds of globs. first, the
28 * usual kind of filename globbing, like:
33 * /var/apache/logs/x*{access,error}_log
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.
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).
46 * /var/log/syslog\.([0-9]+)$0
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.
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().
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).
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.
65 #pragma ident "%Z%%M% %I% %E% SMI"
72 #include <sys/types.h>
73 #include <sys/param.h>
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
);
85 /* expand curly braces (like file{one,two,three}name) */
86 static struct fn_list
*
87 glob_debrace(struct fn
*fnp
)
89 struct fn_list
*ret
= fn_list_new(NULL
);
90 struct fn_list
*newret
;
96 /* start with an empty string in the list */
97 fn_list_adds(ret
, "");
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 }");
106 /* stuff before "left" is finished */
107 fn_list_appendrange(ret
, sp
, left
);
109 /* stuff after "right" still need processing */
112 if (left
+ 1 == right
)
113 continue; /* just an empty {} */
115 /* stuff between "left" and "right" is comma-sep list */
117 newret
= fn_list_new(NULL
);
118 while ((comma
= strchr(left
, ',')) != NULL
) {
119 struct fn_list
*dup
= fn_list_dup(ret
);
121 /* stuff from left to comma is one variant */
122 fn_list_appendrange(dup
, left
, comma
);
123 fn_list_addfn_list(newret
, dup
);
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
);
132 /* anything remaining in "s" is finished */
133 fn_list_appendrange(ret
, sp
, &sp
[strlen(sp
)]);
137 /* return true if filename contains any "magic" characters (*,?,[) */
139 glob_magic(struct fn
*fnp
)
143 for (; s
!= NULL
&& *s
; s
++)
153 * glob_glob -- given a filename glob, return the list of matching filenames
155 * fn_setn() and fn_setstat() are called to set the "n" and stat information
156 * for the resulting filenames.
159 glob_glob(struct fn
*fnp
)
161 struct fn_list
*tmplist
= glob_debrace(fnp
);
167 /* debracing produced NULL list? */
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
)) {
180 return (tmplist
); /* no globs to expand */
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
);
189 fn_list_free(tmplist
);
191 ret
= glob_reglob_list(tmplist
);
192 fn_list_free(tmplist
);
198 * glob_glob_list -- given a list of filename globs, return all matches
201 glob_glob_list(struct fn_list
*fnlp
)
203 struct fn_list
*ret
= fn_list_new(NULL
);
206 fn_list_rewind(fnlp
);
207 while ((fnp
= fn_list_next(fnlp
)) != NULL
)
208 fn_list_addfn_list(ret
, glob_glob(fnp
));
213 * glob_reglob -- given a filename reglob, return a list of matching filenames
215 * this routine does all the hard work in this module.
218 glob_reglob(struct fn
*fnp
)
220 struct fn_list
*ret
= fn_list_new(NULL
);
221 struct fn_list
*newret
;
223 char *mys
= STRDUP(fn_s(fnp
));
228 char ret0
[MAXPATHLEN
];
231 /* start with the initial directory in the list */
233 fn_list_adds(ret
, "/");
237 fn_list_adds(ret
, "./");
239 /* while components remain... */
241 if ((slash
= strchr(sp
, '/')) != NULL
) {
243 /* skip superfluous slashes */
244 while (*slash
== '/')
248 /* dot files are skipped unless a dot was specifically given */
249 if (sp
[0] == '\\' && sp
[1] == '.')
254 /* compile the regex */
255 if ((re
= regcmp("^", sp
, "$", (char *)0)) == NULL
)
256 err(EF_FILE
|EF_JMP
, "regcmp failed on <%s>", sp
);
258 /* apply regex to every filename we've matched so far */
259 newret
= fn_list_new(NULL
);
261 while ((nextfnp
= fn_list_next(ret
)) != NULL
) {
265 /* go through directory looking for matches */
266 if ((dirp
= opendir(fn_s(nextfnp
))) == NULL
)
269 while ((dp
= readdir(dirp
)) != NULL
) {
270 if (skipdotfiles
&& dp
->d_name
[0] == '.')
273 if (regex(re
, dp
->d_name
, ret0
)) {
274 struct fn
*matchfnp
= fn_dup(nextfnp
);
278 fn_puts(matchfnp
, dp
->d_name
);
280 if (stat(fn_s(matchfnp
), &stbuf
) < 0) {
285 /* skip non-dirs if more components */
287 (stbuf
.st_mode
& S_IFMT
) !=
294 * component matched, fill in "n"
295 * value, stat information, and
296 * append component to directory
297 * name just searched.
304 fn_setn(matchfnp
, n
);
305 fn_setstat(matchfnp
, &stbuf
);
308 fn_putc(matchfnp
, '/');
310 fn_list_addfn(newret
, matchfnp
);
313 (void) closedir(dirp
);
325 /* reglob a list of filenames */
326 static struct fn_list
*
327 glob_reglob_list(struct fn_list
*fnlp
)
329 struct fn_list
*ret
= fn_list_new(NULL
);
332 fn_list_rewind(fnlp
);
333 while ((fnp
= fn_list_next(fnlp
)) != NULL
)
334 fn_list_addfn_list(ret
, glob_reglob(fnp
));
339 * glob_to_reglob -- convert a glob (*, ?, etc) to a reglob (.*, ., etc.)
342 glob_to_reglob(struct fn
*fnp
)
345 struct fn
*ret
= fn_new(NULL
);
348 while ((c
= fn_getc(fnp
)) != '\0')
358 /* magic characters need backslash */
363 /* change '?' to a single dot */
367 /* change '*' to ".*" */
381 * test main for glob module, usage: a.out [-r] [pattern...]
382 * -r means the patterns are reglobs instead of globs
385 main(int argc
, char *argv
[])
389 struct fn
*argfnp
= fn_new(NULL
);
391 struct fn_list
*fnlp
;
394 setbuf(stdout
, NULL
);
396 for (i
= 1; i
< argc
; i
++) {
397 if (strcmp(argv
[i
], "-r") == 0) {
403 printf(" skipped due to errors\n");
406 printf("<%s>:\n", argv
[i
]);
407 fn_renew(argfnp
, argv
[i
]);
409 fnlp
= glob_reglob(argfnp
);
411 fnlp
= glob_glob(argfnp
);
414 fn_list_rewind(fnlp
);
415 while ((fnp
= fn_list_next(fnlp
)) != NULL
)
416 printf(" <%s>\n", fn_s(fnp
));
418 printf("total size: %lld\n", fn_list_totalsize(fnlp
));
420 while ((fnp
= fn_list_popoldest(fnlp
)) != NULL
) {
421 printf(" oldest <%s>\n", fn_s(fnp
));
434 #endif /* TESTMODULE */