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 * 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 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 * 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 * 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 * 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 * 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 * 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 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 */