1 /* $NetBSD: expand.c,v 1.13 1998/12/19 20:32:17 christos 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.13 1998/12/19 20:32:17 christos 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 __P((char *, char *)); 76 static void addpath __P((int)); 77 static int amatch __P((char *, char *)); 78 static int argcmp __P((const void *, const void *)); 79 static int execbrc __P((char *, char *)); 80 static void expsh __P((char *)); 81 static void expstr __P((char *)); 82 static int match __P((char *, char *)); 83 static void matchdir __P((char *)); 84 static int smatch __P((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(list, wh) 97 struct namelist *list; 98 int wh; 99 { 100 struct namelist *nl, *prev; 101 int n; 102 char pathbuf[BUFSIZ]; 103 char *argvbuf[GAVSIZ]; 104 105 if (debug) { 106 printf("expand(%lx, %d)\nlist = ", (long)list, wh); 107 prnames(list); 108 } 109 110 if (wh == 0) { 111 char *cp; 112 113 for (nl = list; nl != NULL; nl = nl->n_next) 114 for (cp = nl->n_name; *cp; cp++) 115 *cp = *cp & TRIM; 116 return(list); 117 } 118 119 which = wh; 120 path = tpathp = pathp = pathbuf; 121 *pathp = '\0'; 122 lastpathp = &path[sizeof pathbuf - 2]; 123 tilde = ""; 124 eargc = 0; 125 eargv = sortbase = argvbuf; 126 *eargv = 0; 127 nleft = NCARGS - 4; 128 /* 129 * Walk the name list and expand names into eargv[]; 130 */ 131 for (nl = list; nl != NULL; nl = nl->n_next) 132 expstr(nl->n_name); 133 /* 134 * Take expanded list of names from eargv[] and build a new list. 135 */ 136 list = prev = NULL; 137 for (n = 0; n < eargc; n++) { 138 nl = makenl(NULL); 139 nl->n_name = eargv[n]; 140 if (prev == NULL) 141 list = prev = nl; 142 else { 143 prev->n_next = nl; 144 prev = nl; 145 } 146 } 147 if (debug) { 148 printf("expanded list = "); 149 prnames(list); 150 } 151 return(list); 152 } 153 154 static void 155 expstr(s) 156 char *s; 157 { 158 char *cp, *cp1; 159 struct namelist *tp; 160 char *tail; 161 char buf[BUFSIZ]; 162 int savec, oeargc; 163 extern char homedir[]; 164 165 if (s == NULL || *s == '\0') 166 return; 167 168 if ((which & E_VARS) && (cp = strchr(s, '$')) != NULL) { 169 *cp++ = '\0'; 170 if (*cp == '\0') { 171 yyerror("no variable name after '$'"); 172 return; 173 } 174 if (*cp == LC) { 175 cp++; 176 if ((tail = strchr(cp, RC)) == NULL) { 177 yyerror("unmatched '{'"); 178 return; 179 } 180 *tail++ = savec = '\0'; 181 if (*cp == '\0') { 182 yyerror("no variable name after '$'"); 183 return; 184 } 185 } else { 186 tail = cp + 1; 187 savec = *tail; 188 *tail = '\0'; 189 } 190 tp = lookup(cp, 0, 0); 191 if (savec != '\0') 192 *tail = savec; 193 if (tp != NULL) { 194 for (; tp != NULL; tp = tp->n_next) { 195 snprintf(buf, sizeof(buf), "%s%s%s", s, 196 tp->n_name, tail); 197 expstr(buf); 198 } 199 return; 200 } 201 snprintf(buf, sizeof(buf), "%s%s", s, tail); 202 expstr(buf); 203 return; 204 } 205 if ((which & ~E_VARS) == 0 || !strcmp(s, "{") || !strcmp(s, "{}")) { 206 Cat(s, ""); 207 sort(); 208 return; 209 } 210 if (*s == '~') { 211 cp = ++s; 212 if (*cp == '\0' || *cp == '/') { 213 tilde = "~"; 214 cp1 = homedir; 215 } else { 216 tilde = cp1 = buf; 217 *cp1++ = '~'; 218 do 219 *cp1++ = *cp++; 220 while (*cp && *cp != '/'); 221 *cp1 = '\0'; 222 if (pw == NULL || strcmp(pw->pw_name, buf+1) != 0) { 223 if ((pw = getpwnam(buf+1)) == NULL) { 224 strcat(buf, ": unknown user name"); 225 yyerror(buf+1); 226 return; 227 } 228 } 229 cp1 = pw->pw_dir; 230 s = cp; 231 } 232 for (cp = path; (*cp++ = *cp1++) != 0; ) 233 ; 234 tpathp = pathp = cp - 1; 235 } else { 236 tpathp = pathp = path; 237 tilde = ""; 238 } 239 *pathp = '\0'; 240 if (!(which & E_SHELL)) { 241 if (which & E_TILDE) 242 Cat(path, s); 243 else 244 Cat(tilde, s); 245 sort(); 246 return; 247 } 248 oeargc = eargc; 249 expany = 0; 250 expsh(s); 251 if (eargc == oeargc) 252 Cat(s, ""); /* "nonomatch" is set */ 253 sort(); 254 } 255 256 static int 257 argcmp(a1, a2) 258 const void *a1, *a2; 259 { 260 261 return (strcmp(*(char **)a1, *(char **)a2)); 262 } 263 264 /* 265 * If there are any Shell meta characters in the name, 266 * expand into a list, after searching directory 267 */ 268 static void 269 expsh(s) 270 char *s; 271 { 272 char *cp; 273 char *spathp, *oldcp; 274 struct stat stb; 275 276 spathp = pathp; 277 cp = s; 278 while (!any(*cp, shchars)) { 279 if (*cp == '\0') { 280 if (!expany || stat(path, &stb) >= 0) { 281 if (which & E_TILDE) 282 Cat(path, ""); 283 else 284 Cat(tilde, tpathp); 285 } 286 goto endit; 287 } 288 addpath(*cp++); 289 } 290 oldcp = cp; 291 while (cp > s && *cp != '/') 292 cp--, pathp--; 293 if (*cp == '/') 294 cp++, pathp++; 295 *pathp = '\0'; 296 if (*oldcp == '{') { 297 execbrc(cp, NULL); 298 return; 299 } 300 matchdir(cp); 301 endit: 302 pathp = spathp; 303 *pathp = '\0'; 304 } 305 306 static void 307 matchdir(pattern) 308 char *pattern; 309 { 310 struct stat stb; 311 struct dirent *dp; 312 DIR *dirp; 313 314 dirp = opendir(path); 315 if (dirp == NULL) { 316 if (expany) 317 return; 318 goto patherr2; 319 } 320 if (fstat(dirp->dd_fd, &stb) < 0) 321 goto patherr1; 322 if (!S_ISDIR(stb.st_mode)) { 323 errno = ENOTDIR; 324 goto patherr1; 325 } 326 while ((dp = readdir(dirp)) != NULL) 327 if (match(dp->d_name, pattern)) { 328 if (which & E_TILDE) 329 Cat(path, dp->d_name); 330 else { 331 strcpy(pathp, dp->d_name); 332 Cat(tilde, tpathp); 333 *pathp = '\0'; 334 } 335 } 336 closedir(dirp); 337 return; 338 339 patherr1: 340 closedir(dirp); 341 patherr2: 342 strcat(path, ": "); 343 strcat(path, strerror(errno)); 344 yyerror(path); 345 } 346 347 static int 348 execbrc(p, s) 349 char *p, *s; 350 { 351 char restbuf[BUFSIZ + 2]; 352 char *pe, *pm, *pl; 353 int brclev = 0; 354 char *lm, savec, *spathp; 355 356 for (lm = restbuf; *p != '{'; *lm++ = *p++) 357 continue; 358 for (pe = ++p; *pe; pe++) 359 switch (*pe) { 360 361 case '{': 362 brclev++; 363 continue; 364 365 case '}': 366 if (brclev == 0) 367 goto pend; 368 brclev--; 369 continue; 370 371 case '[': 372 for (pe++; *pe && *pe != ']'; pe++) 373 continue; 374 if (!*pe) 375 yyerror("Missing ']'"); 376 continue; 377 } 378 pend: 379 if (brclev || !*pe) { 380 yyerror("Missing '}'"); 381 return (0); 382 } 383 for (pl = pm = p; pm <= pe; pm++) 384 switch (*pm & (QUOTE|TRIM)) { 385 386 case '{': 387 brclev++; 388 continue; 389 390 case '}': 391 if (brclev) { 392 brclev--; 393 continue; 394 } 395 goto doit; 396 397 case ',': 398 if (brclev) 399 continue; 400 doit: 401 savec = *pm; 402 *pm = 0; 403 strcpy(lm, pl); 404 strcat(restbuf, pe + 1); 405 *pm = savec; 406 if (s == 0) { 407 spathp = pathp; 408 expsh(restbuf); 409 pathp = spathp; 410 *pathp = 0; 411 } else if (amatch(s, restbuf)) 412 return (1); 413 sort(); 414 pl = pm + 1; 415 continue; 416 417 case '[': 418 for (pm++; *pm && *pm != ']'; pm++) 419 continue; 420 if (!*pm) 421 yyerror("Missing ']'"); 422 continue; 423 } 424 return (0); 425 } 426 427 static int 428 match(s, p) 429 char *s, *p; 430 { 431 int c; 432 char *sentp; 433 char sexpany = expany; 434 435 if (*s == '.' && *p != '.') 436 return (0); 437 sentp = entp; 438 entp = s; 439 c = amatch(s, p); 440 entp = sentp; 441 expany = sexpany; 442 return (c); 443 } 444 445 static int 446 amatch(s, p) 447 char *s, *p; 448 { 449 int scc; 450 int ok, lc; 451 char *spathp; 452 struct stat stb; 453 int c, cc; 454 455 expany = 1; 456 for (;;) { 457 scc = *s++ & TRIM; 458 switch (c = *p++) { 459 460 case '{': 461 return (execbrc(p - 1, s - 1)); 462 463 case '[': 464 ok = 0; 465 lc = 077777; 466 while ((cc = *p++) != 0) { 467 if (cc == ']') { 468 if (ok) 469 break; 470 return (0); 471 } 472 if (cc == '-') { 473 if (lc <= scc && scc <= *p++) 474 ok++; 475 } else 476 if (scc == (lc = cc)) 477 ok++; 478 } 479 if (cc == 0) { 480 yyerror("Missing ']'"); 481 return (0); 482 } 483 continue; 484 485 case '*': 486 if (!*p) 487 return (1); 488 if (*p == '/') { 489 p++; 490 goto slash; 491 } 492 for (s--; *s; s++) 493 if (amatch(s, p)) 494 return (1); 495 return (0); 496 497 case '\0': 498 return (scc == '\0'); 499 500 default: 501 if ((c & TRIM) != scc) 502 return (0); 503 continue; 504 505 case '?': 506 if (scc == '\0') 507 return (0); 508 continue; 509 510 case '/': 511 if (scc) 512 return (0); 513 slash: 514 s = entp; 515 spathp = pathp; 516 while (*s) 517 addpath(*s++); 518 addpath('/'); 519 if (stat(path, &stb) == 0 && S_ISDIR(stb.st_mode)) { 520 if (*p == '\0') { 521 if (which & E_TILDE) 522 Cat(path, ""); 523 else 524 Cat(tilde, tpathp); 525 } else 526 expsh(p); 527 } 528 pathp = spathp; 529 *pathp = '\0'; 530 return (0); 531 } 532 } 533 } 534 535 static int 536 smatch(s, p) 537 char *s, *p; 538 { 539 int scc; 540 int ok, lc; 541 int c, cc; 542 543 for (;;) { 544 scc = *s++ & TRIM; 545 switch (c = *p++) { 546 547 case '[': 548 ok = 0; 549 lc = 077777; 550 while ((cc = *p++) != 0) { 551 if (cc == ']') { 552 if (ok) 553 break; 554 return (0); 555 } 556 if (cc == '-') { 557 if (lc <= scc && scc <= *p++) 558 ok++; 559 } else 560 if (scc == (lc = cc)) 561 ok++; 562 } 563 if (cc == 0) { 564 yyerror("Missing ']'"); 565 return (0); 566 } 567 continue; 568 569 case '*': 570 if (!*p) 571 return (1); 572 for (s--; *s; s++) 573 if (smatch(s, p)) 574 return (1); 575 return (0); 576 577 case '\0': 578 return (scc == '\0'); 579 580 default: 581 if ((c & TRIM) != scc) 582 return (0); 583 continue; 584 585 case '?': 586 if (scc == 0) 587 return (0); 588 continue; 589 590 } 591 } 592 } 593 594 static void 595 Cat(s1, s2) 596 char *s1, *s2; 597 { 598 int len = strlen(s1) + strlen(s2) + 1; 599 char *s; 600 601 nleft -= len; 602 if (nleft <= 0 || ++eargc >= GAVSIZ) 603 yyerror("Arguments too long"); 604 eargv[eargc] = 0; 605 eargv[eargc - 1] = s = malloc(len); 606 if (s == NULL) 607 fatal("ran out of memory\n"); 608 while ((*s++ = *s1++ & TRIM) != 0) 609 ; 610 s--; 611 while ((*s++ = *s2++ & TRIM) != 0) 612 ; 613 } 614 615 static void 616 addpath(c) 617 int c; 618 { 619 620 if (pathp >= lastpathp) 621 yyerror("Pathname too long"); 622 else { 623 *pathp++ = c & TRIM; 624 *pathp = '\0'; 625 } 626 } 627 628 /* 629 * Expand file names beginning with `~' into the 630 * user's home directory path name. Return a pointer in buf to the 631 * part corresponding to `file'. 632 */ 633 char * 634 exptilde(buf, file) 635 char buf[]; 636 char *file; 637 { 638 char *s1, *s2, *s3; 639 extern char homedir[]; 640 641 if (*file != '~') { 642 strcpy(buf, file); 643 return(buf); 644 } 645 if (*++file == '\0') { 646 s2 = homedir; 647 s3 = NULL; 648 } else if (*file == '/') { 649 s2 = homedir; 650 s3 = file; 651 } else { 652 s3 = file; 653 while (*s3 && *s3 != '/') 654 s3++; 655 if (*s3 == '/') 656 *s3 = '\0'; 657 else 658 s3 = NULL; 659 if (pw == NULL || strcmp(pw->pw_name, file) != 0) { 660 if ((pw = getpwnam(file)) == NULL) { 661 error("%s: unknown user name\n", file); 662 if (s3 != NULL) 663 *s3 = '/'; 664 return(NULL); 665 } 666 } 667 if (s3 != NULL) 668 *s3 = '/'; 669 s2 = pw->pw_dir; 670 } 671 for (s1 = buf; (*s1++ = *s2++) != 0; ) 672 ; 673 s2 = --s1; 674 if (s3 != NULL) { 675 s2++; 676 while ((*s1++ = *s3++) != 0) 677 ; 678 } 679 return(s2); 680 } 681