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/apache2/2.2/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 #include <stdio.h>
  66 #include <libintl.h>
  67 #include <stdlib.h>
  68 #include <libgen.h>
  69 #include <strings.h>
  70 #include <sys/types.h>
  71 #include <sys/param.h>
  72 #include <sys/stat.h>
  73 #include <dirent.h>
  74 #include "err.h"
  75 #include "fn.h"
  76 #include "glob.h"
  77 
  78 /* forward declarations for functions used internally by this module */
  79 static struct fn_list *glob_debrace(struct fn *fnp);
  80 static struct fn_list *glob_reglob_list(struct fn_list *fnlp);
  81 static boolean_t glob_magic(struct fn *fnp);
  82 
  83 /* expand curly braces (like file{one,two,three}name) */
  84 static struct fn_list *
  85 glob_debrace(struct fn *fnp)
  86 {
  87         struct fn_list *ret = fn_list_new(NULL);
  88         struct fn_list *newret;
  89         char *sp = fn_s(fnp);
  90         char *left;
  91         char *right;
  92         char *comma;
  93 
  94         /* start with an empty string in the list */
  95         fn_list_adds(ret, "");
  96 
  97         /* while braces remain... */
  98         while (sp != NULL && (left = strchr(sp, '{')) != NULL)
  99                 if ((right = strchr(left, '}')) == NULL) {
 100                         err(EF_FILE|EF_JMP, "Missing }");
 101                         fn_list_free(ret);
 102                         return (NULL);
 103                 } else {
 104                         /* stuff before "left" is finished */
 105                         fn_list_appendrange(ret, sp, left);
 106 
 107                         /* stuff after "right" still need processing */
 108                         sp = right + 1;
 109 
 110                         if (left + 1 == right)
 111                                 continue;       /* just an empty {} */
 112 
 113                         /* stuff between "left" and "right" is comma-sep list */
 114                         left++;
 115                         newret = fn_list_new(NULL);
 116                         while ((comma = strchr(left, ',')) != NULL) {
 117                                 struct fn_list *dup = fn_list_dup(ret);
 118 
 119                                 /* stuff from left to comma is one variant */
 120                                 fn_list_appendrange(dup, left, comma);
 121                                 fn_list_addfn_list(newret, dup);
 122                                 left = comma + 1;
 123                         }
 124                         /* what's left is the last item in the list */
 125                         fn_list_appendrange(ret, left, right);
 126                         fn_list_addfn_list(newret, ret);
 127                         ret = newret;
 128                 }
 129 
 130         /* anything remaining in "s" is finished */
 131         fn_list_appendrange(ret, sp, &sp[strlen(sp)]);
 132         return (ret);
 133 }
 134 
 135 /* return true if filename contains any "magic" characters (*,?,[) */
 136 static boolean_t
 137 glob_magic(struct fn *fnp)
 138 {
 139         char *s = fn_s(fnp);
 140 
 141         for (; s != NULL && *s; s++)
 142                 if (*s == '*' ||
 143                     *s == '?' ||
 144                     *s == '[')
 145                         return (B_TRUE);
 146 
 147         return (B_FALSE);
 148 }
 149 
 150 /*
 151  * glob_glob -- given a filename glob, return the list of matching filenames
 152  *
 153  * fn_setn() and fn_setstat() are called to set the "n" and stat information
 154  * for the resulting filenames.
 155  */
 156 struct fn_list *
 157 glob_glob(struct fn *fnp)
 158 {
 159         struct fn_list *tmplist = glob_debrace(fnp);
 160         struct fn_list *ret;
 161         struct fn *nextfnp;
 162         struct fn *newfnp;
 163         int magic = 0;
 164 
 165         /* debracing produced NULL list? */
 166         if (tmplist == NULL)
 167                 return (NULL);
 168 
 169         /* see if anything in list contains magic characters */
 170         fn_list_rewind(tmplist);
 171         while ((nextfnp = fn_list_next(tmplist)) != NULL)
 172                 if (glob_magic(nextfnp)) {
 173                         magic = 1;
 174                         break;
 175                 }
 176 
 177         if (!magic)
 178                 return (tmplist);       /* no globs to expand */
 179 
 180         /* foreach name in the list, call glob_glob() to expand it */
 181         fn_list_rewind(tmplist);
 182         ret = fn_list_new(NULL);
 183         while ((nextfnp = fn_list_next(tmplist)) != NULL) {
 184                 newfnp = glob_to_reglob(nextfnp);
 185                 fn_list_addfn(ret, newfnp);
 186         }
 187         fn_list_free(tmplist);
 188         tmplist = ret;
 189         ret = glob_reglob_list(tmplist);
 190         fn_list_free(tmplist);
 191 
 192         return (ret);
 193 }
 194 
 195 /*
 196  * glob_glob_list -- given a list of filename globs, return all matches
 197  */
 198 struct fn_list *
 199 glob_glob_list(struct fn_list *fnlp)
 200 {
 201         struct fn_list *ret = fn_list_new(NULL);
 202         struct fn *fnp;
 203 
 204         fn_list_rewind(fnlp);
 205         while ((fnp = fn_list_next(fnlp)) != NULL)
 206                 fn_list_addfn_list(ret, glob_glob(fnp));
 207         return (ret);
 208 }
 209 
 210 /*
 211  * glob_reglob -- given a filename reglob, return a list of matching filenames
 212  *
 213  * this routine does all the hard work in this module.
 214  */
 215 struct fn_list *
 216 glob_reglob(struct fn *fnp)
 217 {
 218         struct fn_list *ret = fn_list_new(NULL);
 219         struct fn_list *newret;
 220         struct fn *nextfnp;
 221         char *mys = STRDUP(fn_s(fnp));
 222         char *sp = mys;
 223         char *slash;
 224         int skipdotfiles;
 225         char *re;
 226         char ret0[MAXPATHLEN];
 227 
 228 
 229         /* start with the initial directory in the list */
 230         if (*sp == '/') {
 231                 fn_list_adds(ret, "/");
 232                 while (*sp == '/')
 233                         sp++;
 234         } else
 235                 fn_list_adds(ret, "./");
 236 
 237         /* while components remain... */
 238         do {
 239                 if ((slash = strchr(sp, '/')) != NULL) {
 240                         *slash++ = '\0';
 241                         /* skip superfluous slashes */
 242                         while (*slash == '/')
 243                                 slash++;
 244                 }
 245 
 246                 /* dot files are skipped unless a dot was specifically given */
 247                 if (sp[0] == '\\' && sp[1] == '.')
 248                         skipdotfiles = 0;
 249                 else
 250                         skipdotfiles = 1;
 251 
 252                 /* compile the regex */
 253                 if ((re = regcmp("^", sp, "$", (char *)0)) == NULL)
 254                         err(EF_FILE|EF_JMP, "regcmp failed on <%s>", sp);
 255 
 256                 /* apply regex to every filename we've matched so far */
 257                 newret = fn_list_new(NULL);
 258                 fn_list_rewind(ret);
 259                 while ((nextfnp = fn_list_next(ret)) != NULL) {
 260                         DIR *dirp;
 261                         struct dirent *dp;
 262 
 263                         /* go through directory looking for matches */
 264                         if ((dirp = opendir(fn_s(nextfnp))) == NULL)
 265                                 continue;
 266 
 267                         while ((dp = readdir(dirp)) != NULL) {
 268                                 if (skipdotfiles && dp->d_name[0] == '.')
 269                                         continue;
 270                                 *ret0 = '\0';
 271                                 if (regex(re, dp->d_name, ret0)) {
 272                                         struct fn *matchfnp = fn_dup(nextfnp);
 273                                         struct stat stbuf;
 274                                         int n;
 275 
 276                                         fn_puts(matchfnp, dp->d_name);
 277 
 278                                         if (stat(fn_s(matchfnp), &stbuf) < 0) {
 279                                                 fn_free(matchfnp);
 280                                                 continue;
 281                                         }
 282 
 283                                         /* skip non-dirs if more components */
 284                                         if (slash &&
 285                                             (stbuf.st_mode & S_IFMT) !=
 286                                             S_IFDIR) {
 287                                                 fn_free(matchfnp);
 288                                                 continue;
 289                                         }
 290 
 291                                         /*
 292                                          * component matched, fill in "n"
 293                                          * value, stat information, and
 294                                          * append component to directory
 295                                          * name just searched.
 296                                          */
 297 
 298                                         if (*ret0)
 299                                                 n = atoi(ret0);
 300                                         else
 301                                                 n = -1;
 302                                         fn_setn(matchfnp, n);
 303                                         fn_setstat(matchfnp, &stbuf);
 304 
 305                                         if (slash)
 306                                                 fn_putc(matchfnp, '/');
 307 
 308                                         fn_list_addfn(newret, matchfnp);
 309                                 }
 310                         }
 311                         (void) closedir(dirp);
 312                 }
 313                 fn_list_free(ret);
 314                 ret = newret;
 315                 sp = slash;
 316         } while (slash);
 317 
 318         FREE(mys);
 319 
 320         return (ret);
 321 }
 322 
 323 /* reglob a list of filenames */
 324 static struct fn_list *
 325 glob_reglob_list(struct fn_list *fnlp)
 326 {
 327         struct fn_list *ret = fn_list_new(NULL);
 328         struct fn *fnp;
 329 
 330         fn_list_rewind(fnlp);
 331         while ((fnp = fn_list_next(fnlp)) != NULL)
 332                 fn_list_addfn_list(ret, glob_reglob(fnp));
 333         return (ret);
 334 }
 335 
 336 /*
 337  * glob_to_reglob -- convert a glob (*, ?, etc) to a reglob (.*, ., etc.)
 338  */
 339 struct fn *
 340 glob_to_reglob(struct fn *fnp)
 341 {
 342         int c;
 343         struct fn *ret = fn_new(NULL);
 344 
 345         fn_rewind(fnp);
 346         while ((c = fn_getc(fnp)) != '\0')
 347                 switch (c) {
 348                 case '.':
 349                 case '(':
 350                 case ')':
 351                 case '^':
 352                 case '+':
 353                 case '{':
 354                 case '}':
 355                 case '$':
 356                         /* magic characters need backslash */
 357                         fn_putc(ret, '\\');
 358                         fn_putc(ret, c);
 359                         break;
 360                 case '?':
 361                         /* change '?' to a single dot */
 362                         fn_putc(ret, '.');
 363                         break;
 364                 case '*':
 365                         /* change '*' to ".*" */
 366                         fn_putc(ret, '.');
 367                         fn_putc(ret, '*');
 368                         break;
 369                 default:
 370                         fn_putc(ret, c);
 371                 }
 372 
 373         return (ret);
 374 }
 375 
 376 #ifdef  TESTMODULE
 377 
 378 /*
 379  * test main for glob module, usage: a.out [-r] [pattern...]
 380  *      -r means the patterns are reglobs instead of globs
 381  */
 382 int
 383 main(int argc, char *argv[])
 384 {
 385         int i;
 386         int reglobs = 0;
 387         struct fn *argfnp = fn_new(NULL);
 388         struct fn *fnp;
 389         struct fn_list *fnlp;
 390 
 391         err_init(argv[0]);
 392         setbuf(stdout, NULL);
 393 
 394         for (i = 1; i < argc; i++) {
 395                 if (strcmp(argv[i], "-r") == 0) {
 396                         reglobs = 1;
 397                         continue;
 398                 }
 399 
 400                 if (SETJMP) {
 401                         printf("    skipped due to errors\n");
 402                         continue;
 403                 } else {
 404                         printf("<%s>:\n", argv[i]);
 405                         fn_renew(argfnp, argv[i]);
 406                         if (reglobs)
 407                                 fnlp = glob_reglob(argfnp);
 408                         else
 409                                 fnlp = glob_glob(argfnp);
 410                 }
 411 
 412                 fn_list_rewind(fnlp);
 413                 while ((fnp = fn_list_next(fnlp)) != NULL)
 414                         printf("    <%s>\n", fn_s(fnp));
 415 
 416                 printf("total size: %lld\n", fn_list_totalsize(fnlp));
 417 
 418                 while ((fnp = fn_list_popoldest(fnlp)) != NULL) {
 419                         printf("    oldest <%s>\n", fn_s(fnp));
 420                         fn_free(fnp);
 421                 }
 422 
 423                 fn_list_free(fnlp);
 424         }
 425         fn_free(argfnp);
 426 
 427         err_done(0);
 428         /* NOTREACHED */
 429         return (0);
 430 }
 431 
 432 #endif  /* TESTMODULE */