1 /* $NetBSD: expand.c,v 1.14 2002/06/14 01:18:54 wiz Exp $ */ 2 3 /* 4 * Copyright (c) 1983, 1993 5 * The Regents of the University of California. All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. All advertising materials mentioning features or use of this software 16 * must display the following acknowledgement: 17 * This product includes software developed by the University of 18 * California, Berkeley and its contributors. 19 * 4. Neither the name of the University nor the names of its contributors 20 * may be used to endorse or promote products derived from this software 21 * without specific prior written permission. 22 * 23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 33 * SUCH DAMAGE. 34 */ 35 36 #include <sys/cdefs.h> 37 #ifndef lint 38 #if 0 39 static char sccsid[] = "@(#)expand.c 8.1 (Berkeley) 6/9/93"; 40 #else 41 __RCSID("$NetBSD: expand.c,v 1.14 2002/06/14 01:18:54 wiz Exp $"); 42 #endif 43 #endif /* not lint */ 44 45 #include <sys/types.h> 46 47 #include <errno.h> 48 #include <pwd.h> 49 50 #include "defs.h" 51 52 #define GAVSIZ NCARGS / 6 53 #define LC '{' 54 #define RC '}' 55 56 static char shchars[] = "${[*?"; 57 58 int which; /* bit mask of types to expand */ 59 int eargc; /* expanded arg count */ 60 char **eargv; /* expanded arg vectors */ 61 char *path; 62 char *pathp; 63 char *lastpathp; 64 char *tilde; /* "~user" if not expanding tilde, else "" */ 65 char *tpathp; 66 int nleft; 67 68 int expany; /* any expansions done? */ 69 char *entp; 70 char **sortbase; 71 72 #define sort() qsort((char *)sortbase, &eargv[eargc] - sortbase, \ 73 sizeof(*sortbase), argcmp), sortbase = &eargv[eargc] 74 75 static void Cat(char *, char *); 76 static void addpath(int); 77 static int amatch(char *, char *); 78 static int argcmp(const void *, const void *); 79 static int execbrc(char *, char *); 80 static void expsh(char *); 81 static void expstr(char *); 82 static int match(char *, char *); 83 static void matchdir(char *); 84 static int smatch(char *, char *); 85 86 /* 87 * Take a list of names and expand any macros, etc. 88 * wh = E_VARS if expanding variables. 89 * wh = E_SHELL if expanding shell characters. 90 * wh = E_TILDE if expanding `~'. 91 * or any of these or'ed together. 92 * 93 * Major portions of this were snarfed from csh/sh.glob.c. 94 */ 95 struct namelist * 96 expand(struct namelist *list, int wh) 97 { 98 struct namelist *nl, *prev; 99 int n; 100 char pathbuf[BUFSIZ]; 101 char *argvbuf[GAVSIZ]; 102 103 if (debug) { 104 printf("expand(%lx, %d)\nlist = ", (long)list, wh); 105 prnames(list); 106 } 107 108 if (wh == 0) { 109 char *cp; 110 111 for (nl = list; nl != NULL; nl = nl->n_next) 112 for (cp = nl->n_name; *cp; cp++) 113 *cp = *cp & TRIM; 114 return(list); 115 } 116 117 which = wh; 118 path = tpathp = pathp = pathbuf; 119 *pathp = '\0'; 120 lastpathp = &path[sizeof pathbuf - 2]; 121 tilde = ""; 122 eargc = 0; 123 eargv = sortbase = argvbuf; 124 *eargv = 0; 125 nleft = NCARGS - 4; 126 /* 127 * Walk the name list and expand names into eargv[]; 128 */ 129 for (nl = list; nl != NULL; nl = nl->n_next) 130 expstr(nl->n_name); 131 /* 132 * Take expanded list of names from eargv[] and build a new list. 133 */ 134 list = prev = NULL; 135 for (n = 0; n < eargc; n++) { 136 nl = makenl(NULL); 137 nl->n_name = eargv[n]; 138 if (prev == NULL) 139 list = prev = nl; 140 else { 141 prev->n_next = nl; 142 prev = nl; 143 } 144 } 145 if (debug) { 146 printf("expanded list = "); 147 prnames(list); 148 } 149 return(list); 150 } 151 152 static void 153 expstr(char *s) 154 { 155 char *cp, *cp1; 156 struct namelist *tp; 157 char *tail; 158 char buf[BUFSIZ]; 159 int savec, oeargc; 160 extern char homedir[]; 161 162 if (s == NULL || *s == '\0') 163 return; 164 165 if ((which & E_VARS) && (cp = strchr(s, '$')) != NULL) { 166 *cp++ = '\0'; 167 if (*cp == '\0') { 168 yyerror("no variable name after '$'"); 169 return; 170 } 171 if (*cp == LC) { 172 cp++; 173 if ((tail = strchr(cp, RC)) == NULL) { 174 yyerror("unmatched '{'"); 175 return; 176 } 177 *tail++ = savec = '\0'; 178 if (*cp == '\0') { 179 yyerror("no variable name after '$'"); 180 return; 181 } 182 } else { 183 tail = cp + 1; 184 savec = *tail; 185 *tail = '\0'; 186 } 187 tp = lookup(cp, 0, 0); 188 if (savec != '\0') 189 *tail = savec; 190 if (tp != NULL) { 191 for (; tp != NULL; tp = tp->n_next) { 192 snprintf(buf, sizeof(buf), "%s%s%s", s, 193 tp->n_name, tail); 194 expstr(buf); 195 } 196 return; 197 } 198 snprintf(buf, sizeof(buf), "%s%s", s, tail); 199 expstr(buf); 200 return; 201 } 202 if ((which & ~E_VARS) == 0 || !strcmp(s, "{") || !strcmp(s, "{}")) { 203 Cat(s, ""); 204 sort(); 205 return; 206 } 207 if (*s == '~') { 208 cp = ++s; 209 if (*cp == '\0' || *cp == '/') { 210 tilde = "~"; 211 cp1 = homedir; 212 } else { 213 tilde = cp1 = buf; 214 *cp1++ = '~'; 215 do 216 *cp1++ = *cp++; 217 while (*cp && *cp != '/'); 218 *cp1 = '\0'; 219 if (pw == NULL || strcmp(pw->pw_name, buf+1) != 0) { 220 if ((pw = getpwnam(buf+1)) == NULL) { 221 strcat(buf, ": unknown user name"); 222 yyerror(buf+1); 223 return; 224 } 225 } 226 cp1 = pw->pw_dir; 227 s = cp; 228 } 229 for (cp = path; (*cp++ = *cp1++) != 0; ) 230 ; 231 tpathp = pathp = cp - 1; 232 } else { 233 tpathp = pathp = path; 234 tilde = ""; 235 } 236 *pathp = '\0'; 237 if (!(which & E_SHELL)) { 238 if (which & E_TILDE) 239 Cat(path, s); 240 else 241 Cat(tilde, s); 242 sort(); 243 return; 244 } 245 oeargc = eargc; 246 expany = 0; 247 expsh(s); 248 if (eargc == oeargc) 249 Cat(s, ""); /* "nonomatch" is set */ 250 sort(); 251 } 252 253 static int 254 argcmp(const void *a1, const void *a2) 255 { 256 257 return (strcmp(*(char **)a1, *(char **)a2)); 258 } 259 260 /* 261 * If there are any Shell meta characters in the name, 262 * expand into a list, after searching directory 263 */ 264 static void 265 expsh(char *s) 266 { 267 char *cp; 268 char *spathp, *oldcp; 269 struct stat stb; 270 271 spathp = pathp; 272 cp = s; 273 while (!any(*cp, shchars)) { 274 if (*cp == '\0') { 275 if (!expany || stat(path, &stb) >= 0) { 276 if (which & E_TILDE) 277 Cat(path, ""); 278 else 279 Cat(tilde, tpathp); 280 } 281 goto endit; 282 } 283 addpath(*cp++); 284 } 285 oldcp = cp; 286 while (cp > s && *cp != '/') 287 cp--, pathp--; 288 if (*cp == '/') 289 cp++, pathp++; 290 *pathp = '\0'; 291 if (*oldcp == '{') { 292 execbrc(cp, NULL); 293 return; 294 } 295 matchdir(cp); 296 endit: 297 pathp = spathp; 298 *pathp = '\0'; 299 } 300 301 static void 302 matchdir(char *pattern) 303 { 304 struct stat stb; 305 struct dirent *dp; 306 DIR *dirp; 307 308 dirp = opendir(path); 309 if (dirp == NULL) { 310 if (expany) 311 return; 312 goto patherr2; 313 } 314 if (fstat(dirp->dd_fd, &stb) < 0) 315 goto patherr1; 316 if (!S_ISDIR(stb.st_mode)) { 317 errno = ENOTDIR; 318 goto patherr1; 319 } 320 while ((dp = readdir(dirp)) != NULL) 321 if (match(dp->d_name, pattern)) { 322 if (which & E_TILDE) 323 Cat(path, dp->d_name); 324 else { 325 strcpy(pathp, dp->d_name); 326 Cat(tilde, tpathp); 327 *pathp = '\0'; 328 } 329 } 330 closedir(dirp); 331 return; 332 333 patherr1: 334 closedir(dirp); 335 patherr2: 336 strcat(path, ": "); 337 strcat(path, strerror(errno)); 338 yyerror(path); 339 } 340 341 static int 342 execbrc(char *p, char *s) 343 { 344 char restbuf[BUFSIZ + 2]; 345 char *pe, *pm, *pl; 346 int brclev = 0; 347 char *lm, savec, *spathp; 348 349 for (lm = restbuf; *p != '{'; *lm++ = *p++) 350 continue; 351 for (pe = ++p; *pe; pe++) 352 switch (*pe) { 353 354 case '{': 355 brclev++; 356 continue; 357 358 case '}': 359 if (brclev == 0) 360 goto pend; 361 brclev--; 362 continue; 363 364 case '[': 365 for (pe++; *pe && *pe != ']'; pe++) 366 continue; 367 if (!*pe) 368 yyerror("Missing ']'"); 369 continue; 370 } 371 pend: 372 if (brclev || !*pe) { 373 yyerror("Missing '}'"); 374 return (0); 375 } 376 for (pl = pm = p; pm <= pe; pm++) 377 switch (*pm & (QUOTE|TRIM)) { 378 379 case '{': 380 brclev++; 381 continue; 382 383 case '}': 384 if (brclev) { 385 brclev--; 386 continue; 387 } 388 goto doit; 389 390 case ',': 391 if (brclev) 392 continue; 393 doit: 394 savec = *pm; 395 *pm = 0; 396 strcpy(lm, pl); 397 strcat(restbuf, pe + 1); 398 *pm = savec; 399 if (s == 0) { 400 spathp = pathp; 401 expsh(restbuf); 402 pathp = spathp; 403 *pathp = 0; 404 } else if (amatch(s, restbuf)) 405 return (1); 406 sort(); 407 pl = pm + 1; 408 continue; 409 410 case '[': 411 for (pm++; *pm && *pm != ']'; pm++) 412 continue; 413 if (!*pm) 414 yyerror("Missing ']'"); 415 continue; 416 } 417 return (0); 418 } 419 420 static int 421 match(char *s, char *p) 422 { 423 int c; 424 char *sentp; 425 char sexpany = expany; 426 427 if (*s == '.' && *p != '.') 428 return (0); 429 sentp = entp; 430 entp = s; 431 c = amatch(s, p); 432 entp = sentp; 433 expany = sexpany; 434 return (c); 435 } 436 437 static int 438 amatch(char *s, char *p) 439 { 440 int scc; 441 int ok, lc; 442 char *spathp; 443 struct stat stb; 444 int c, cc; 445 446 expany = 1; 447 for (;;) { 448 scc = *s++ & TRIM; 449 switch (c = *p++) { 450 451 case '{': 452 return (execbrc(p - 1, s - 1)); 453 454 case '[': 455 ok = 0; 456 lc = 077777; 457 while ((cc = *p++) != 0) { 458 if (cc == ']') { 459 if (ok) 460 break; 461 return (0); 462 } 463 if (cc == '-') { 464 if (lc <= scc && scc <= *p++) 465 ok++; 466 } else 467 if (scc == (lc = cc)) 468 ok++; 469 } 470 if (cc == 0) { 471 yyerror("Missing ']'"); 472 return (0); 473 } 474 continue; 475 476 case '*': 477 if (!*p) 478 return (1); 479 if (*p == '/') { 480 p++; 481 goto slash; 482 } 483 for (s--; *s; s++) 484 if (amatch(s, p)) 485 return (1); 486 return (0); 487 488 case '\0': 489 return (scc == '\0'); 490 491 default: 492 if ((c & TRIM) != scc) 493 return (0); 494 continue; 495 496 case '?': 497 if (scc == '\0') 498 return (0); 499 continue; 500 501 case '/': 502 if (scc) 503 return (0); 504 slash: 505 s = entp; 506 spathp = pathp; 507 while (*s) 508 addpath(*s++); 509 addpath('/'); 510 if (stat(path, &stb) == 0 && S_ISDIR(stb.st_mode)) { 511 if (*p == '\0') { 512 if (which & E_TILDE) 513 Cat(path, ""); 514 else 515 Cat(tilde, tpathp); 516 } else 517 expsh(p); 518 } 519 pathp = spathp; 520 *pathp = '\0'; 521 return (0); 522 } 523 } 524 } 525 526 static int 527 smatch(char *s, char *p) 528 { 529 int scc; 530 int ok, lc; 531 int c, cc; 532 533 for (;;) { 534 scc = *s++ & TRIM; 535 switch (c = *p++) { 536 537 case '[': 538 ok = 0; 539 lc = 077777; 540 while ((cc = *p++) != 0) { 541 if (cc == ']') { 542 if (ok) 543 break; 544 return (0); 545 } 546 if (cc == '-') { 547 if (lc <= scc && scc <= *p++) 548 ok++; 549 } else 550 if (scc == (lc = cc)) 551 ok++; 552 } 553 if (cc == 0) { 554 yyerror("Missing ']'"); 555 return (0); 556 } 557 continue; 558 559 case '*': 560 if (!*p) 561 return (1); 562 for (s--; *s; s++) 563 if (smatch(s, p)) 564 return (1); 565 return (0); 566 567 case '\0': 568 return (scc == '\0'); 569 570 default: 571 if ((c & TRIM) != scc) 572 return (0); 573 continue; 574 575 case '?': 576 if (scc == 0) 577 return (0); 578 continue; 579 580 } 581 } 582 } 583 584 static void 585 Cat(char *s1, char *s2) 586 { 587 int len = strlen(s1) + strlen(s2) + 1; 588 char *s; 589 590 nleft -= len; 591 if (nleft <= 0 || ++eargc >= GAVSIZ) 592 yyerror("Arguments too long"); 593 eargv[eargc] = 0; 594 eargv[eargc - 1] = s = malloc(len); 595 if (s == NULL) 596 fatal("ran out of memory\n"); 597 while ((*s++ = *s1++ & TRIM) != 0) 598 ; 599 s--; 600 while ((*s++ = *s2++ & TRIM) != 0) 601 ; 602 } 603 604 static void 605 addpath(int c) 606 { 607 608 if (pathp >= lastpathp) 609 yyerror("Pathname too long"); 610 else { 611 *pathp++ = c & TRIM; 612 *pathp = '\0'; 613 } 614 } 615 616 /* 617 * Expand file names beginning with `~' into the 618 * user's home directory path name. Return a pointer in buf to the 619 * part corresponding to `file'. 620 */ 621 char * 622 exptilde(char *buf, char *file) 623 { 624 char *s1, *s2, *s3; 625 extern char homedir[]; 626 627 if (*file != '~') { 628 strcpy(buf, file); 629 return(buf); 630 } 631 if (*++file == '\0') { 632 s2 = homedir; 633 s3 = NULL; 634 } else if (*file == '/') { 635 s2 = homedir; 636 s3 = file; 637 } else { 638 s3 = file; 639 while (*s3 && *s3 != '/') 640 s3++; 641 if (*s3 == '/') 642 *s3 = '\0'; 643 else 644 s3 = NULL; 645 if (pw == NULL || strcmp(pw->pw_name, file) != 0) { 646 if ((pw = getpwnam(file)) == NULL) { 647 error("%s: unknown user name\n", file); 648 if (s3 != NULL) 649 *s3 = '/'; 650 return(NULL); 651 } 652 } 653 if (s3 != NULL) 654 *s3 = '/'; 655 s2 = pw->pw_dir; 656 } 657 for (s1 = buf; (*s1++ = *s2++) != 0; ) 658 ; 659 s2 = --s1; 660 if (s3 != NULL) { 661 s2++; 662 while ((*s1++ = *s3++) != 0) 663 ; 664 } 665 return(s2); 666 } 667