File: [local] / src / usr.bin / rdist / expand.c (download)
Revision 1.18, Fri Jun 28 13:35:03 2019 UTC (4 years, 11 months ago) by deraadt
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5, OPENBSD_7_4_BASE, OPENBSD_7_4, OPENBSD_7_3_BASE, OPENBSD_7_3, OPENBSD_7_2_BASE, OPENBSD_7_2, OPENBSD_7_1_BASE, OPENBSD_7_1, OPENBSD_7_0_BASE, OPENBSD_7_0, OPENBSD_6_9_BASE, OPENBSD_6_9, OPENBSD_6_8_BASE, OPENBSD_6_8, OPENBSD_6_7_BASE, OPENBSD_6_7, OPENBSD_6_6_BASE, OPENBSD_6_6, HEAD Changes since 1.17: +2 -2 lines
When system calls indicate an error they return -1, not some arbitrary
value < 0. errno is only updated in this case. Change all (most?)
callers of syscalls to follow this better, and let's see if this strictness
helps us in the future.
|
/* $OpenBSD: expand.c,v 1.18 2019/06/28 13:35:03 deraadt Exp $ */
/*
* Copyright (c) 1983 Regents of the University of California.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "client.h"
#define MAXEARGS 2048
#define LC '{'
#define RC '}'
static char shchars[] = "${[*?";
int which; /* bit mask of types to expand */
int eargc; /* expanded arg count */
char **eargv; /* expanded arg vectors */
char *path;
char *pathp;
char *lastpathp;
char *tilde; /* "~user" if not expanding tilde, else "" */
char *tpathp;
char pathbuf[BUFSIZ];
int expany; /* any expansions done? */
char *entp;
char **sortbase;
char *argvbuf[MAXEARGS];
#define sort() qsort((char *)sortbase, &eargv[eargc] - sortbase, \
sizeof(*sortbase), argcmp), sortbase = &eargv[eargc]
static void Cat(u_char *, u_char *);
static void addpath(int);
static int argcmp(const void *, const void *);
static void
Cat(u_char *s1, u_char *s2) /* quote in s1 and s2 */
{
char *cp;
int len = strlen((char *)s1) + strlen((char *)s2) + 2;
if ((eargc + 1) >= MAXEARGS) {
yyerror("Too many names");
return;
}
eargv[++eargc] = NULL;
eargv[eargc - 1] = cp = xmalloc(len);
do {
if (*s1 == QUOTECHAR)
s1++;
} while ((*cp++ = *s1++) != '\0');
cp--;
do {
if (*s2 == QUOTECHAR)
s2++;
} while ((*cp++ = *s2++) != '\0');
}
static void
addpath(int c)
{
if (pathp >= lastpathp) {
yyerror("Pathname too long");
return;
} else {
*pathp++ = c;
*pathp = CNULL;
}
}
/*
* Take a list of names and expand any macros, etc.
* wh = E_VARS if expanding variables.
* wh = E_SHELL if expanding shell characters.
* wh = E_TILDE if expanding `~'.
* or any of these or'ed together.
*
* Major portions of this were snarfed from csh/sh.glob.c.
*/
struct namelist *
expand(struct namelist *list, int wh) /* quote in list->n_name */
{
struct namelist *nl, *prev;
int n;
if (debug)
debugmsg(DM_CALL, "expand(%p, %d) start, list = %s",
list, wh, getnlstr(list));
if (wh == 0)
fatalerr("expand() contains invalid 'wh' argument.");
which = wh;
path = tpathp = pathp = pathbuf;
*pathp = CNULL;
lastpathp = &pathbuf[sizeof pathbuf - 2];
tilde = "";
eargc = 0;
eargv = sortbase = argvbuf;
*eargv = NULL;
/*
* Walk the name list and expand names into eargv[];
*/
for (nl = list; nl != NULL; nl = nl->n_next)
expstr((u_char *)nl->n_name);
/*
* Take expanded list of names from eargv[] and build a new list.
*/
list = prev = NULL;
for (n = 0; n < eargc; n++) {
nl = makenl(NULL);
nl->n_name = eargv[n];
if (prev == NULL)
list = prev = nl;
else {
prev->n_next = nl;
prev = nl;
}
}
return(list);
}
/*
* xstrchr() is a version of strchr() that
* handles u_char buffers.
*/
u_char *
xstrchr(u_char *str, int ch)
{
u_char *cp;
for (cp = str; cp && *cp != CNULL; ++cp)
if (ch == *cp)
return(cp);
return(NULL);
}
void
expstr(u_char *s)
{
u_char *cp, *cp1;
struct namelist *tp;
u_char *tail;
u_char ebuf[BUFSIZ];
u_char varbuff[BUFSIZ];
int savec, oeargc;
if (s == NULL || *s == CNULL)
return;
/*
* Remove quoted characters
*/
if (IS_ON(which, E_VARS)) {
if (strlen((char *)s) > sizeof(varbuff)) {
yyerror("Variable is too large.");
return;
}
for (cp = s, cp1 = varbuff; cp && *cp; ++cp) {
/*
* remove quoted character if the next
* character is not $
*/
if (*cp == QUOTECHAR && *(cp+1) != '$')
++cp;
else
*cp1++ = *cp;
}
*cp1 = CNULL;
s = varbuff;
}
/*
* Consider string 's' a variable that should be expanded if
* there is a '$' in 's' that is not quoted.
*/
if (IS_ON(which, E_VARS) &&
((cp = xstrchr(s, '$')) && !(cp > s && *(cp-1) == QUOTECHAR))) {
*cp++ = CNULL;
if (*cp == CNULL) {
yyerror("no variable name after '$'");
return;
}
if (*cp == LC) {
cp++;
for (cp1 = cp; ; cp1 = tail + 1) {
if ((tail = xstrchr(cp1, RC)) == NULL) {
yyerror("unmatched '{'");
return;
}
if (tail[-1] != QUOTECHAR) break;
}
*tail++ = savec = CNULL;
if (*cp == CNULL) {
yyerror("no variable name after '$'");
return;
}
} else {
tail = cp + 1;
savec = *tail;
*tail = CNULL;
}
tp = lookup((char *)cp, LOOKUP, NULL);
if (savec != CNULL)
*tail = savec;
if (tp != NULL) {
for (; tp != NULL; tp = tp->n_next) {
(void) snprintf((char *)ebuf, sizeof(ebuf),
"%s%s%s", s, tp->n_name, tail);
expstr(ebuf);
}
return;
}
(void) snprintf((char *)ebuf, sizeof(ebuf), "%s%s", s, tail);
expstr(ebuf);
return;
}
if ((which & ~E_VARS) == 0 || !strcmp((char *)s, "{") ||
!strcmp((char *)s, "{}")) {
Cat(s, (u_char *)"");
sort();
return;
}
if (*s == '~') {
if ((cp = strchr(s, '/')) == NULL) {
tilde = "~";
s++;
} else {
tilde = memcpy(ebuf, s, (cp - s));
ebuf[cp - s] = '\0';
s = cp;
}
cp = exptilde(path, tilde, sizeof(pathbuf));
tpathp = pathp = (char *)cp;
} else {
tpathp = pathp = path;
tilde = "";
}
*pathp = CNULL;
if (!(which & E_SHELL)) {
if (which & E_TILDE)
Cat((u_char *)path, s);
else
Cat((u_char *)tilde, s);
sort();
return;
}
oeargc = eargc;
expany = 0;
expsh(s);
if (eargc == oeargc)
Cat(s, (u_char *)""); /* "nonomatch" is set */
sort();
}
static int
argcmp(const void *v1, const void *v2)
{
const char *const *a1 = v1, *const *a2 = v2;
return (strcmp(*a1, *a2));
}
/*
* If there are any Shell meta characters in the name,
* expand into a list, after searching directory
*/
void
expsh(u_char *s) /* quote in s */
{
u_char *cp, *oldcp;
char *spathp;
struct stat stb;
spathp = pathp;
cp = s;
while (!any(*cp, shchars)) {
if (*cp == CNULL) {
if (!expany || stat(path, &stb) >= 0) {
if (which & E_TILDE)
Cat((u_char *)path, (u_char *)"");
else
Cat((u_char *)tilde, (u_char *)tpathp);
}
goto endit;
}
if (*cp == QUOTECHAR) cp++;
addpath(*cp++);
}
oldcp = cp;
while (cp > s && *cp != '/')
cp--, pathp--;
if (*cp == '/')
cp++, pathp++;
*pathp = CNULL;
if (*oldcp == '{') {
(void) execbrc(cp, NULL);
return;
}
matchdir((char *)cp);
endit:
pathp = spathp;
*pathp = CNULL;
}
void
matchdir(char *pattern) /* quote in pattern */
{
struct stat stb;
struct dirent *dp;
DIR *dirp;
dirp = opendir(path);
if (dirp == NULL) {
if (expany)
return;
goto patherr2;
}
if (fstat(dirfd(dirp), &stb) == -1)
goto patherr1;
if (!S_ISDIR(stb.st_mode)) {
errno = ENOTDIR;
goto patherr1;
}
while ((dp = readdir(dirp)) != NULL)
if (match(dp->d_name, pattern)) {
if (which & E_TILDE)
Cat((u_char *)path, (u_char *)dp->d_name);
else {
(void) strlcpy(pathp, dp->d_name,
lastpathp - pathp + 2);
Cat((u_char *)tilde, (u_char *)tpathp);
*pathp = CNULL;
}
}
closedir(dirp);
return;
patherr1:
closedir(dirp);
patherr2:
(void) strlcat(path, ": ", lastpathp - path + 2);
(void) strlcat(path, SYSERR, lastpathp - path + 2);
yyerror(path);
}
int
execbrc(u_char *p, u_char *s) /* quote in p */
{
u_char restbuf[BUFSIZ + 2];
u_char *pe, *pm, *pl;
int brclev = 0;
u_char *lm, savec;
char *spathp;
for (lm = restbuf; *p != '{'; *lm++ = *p++)
if (*p == QUOTECHAR) *lm++ = *p++;
for (pe = ++p; *pe; pe++)
switch (*pe) {
case '{':
brclev++;
continue;
case '}':
if (brclev == 0)
goto pend;
brclev--;
continue;
case '[':
for (pe++; *pe && *pe != ']'; pe++)
if (*p == QUOTECHAR) pe++;
if (!*pe)
yyerror("Missing ']'");
continue;
case QUOTECHAR: /* skip this character */
pe++;
continue;
}
pend:
if (brclev || !*pe) {
yyerror("Missing '}'");
return (0);
}
for (pl = pm = p; pm <= pe; pm++)
/* the strip code was a noop */
switch (*pm) {
case '{':
brclev++;
continue;
case '}':
if (brclev) {
brclev--;
continue;
}
goto doit;
case ',':
if (brclev)
continue;
doit:
savec = *pm;
*pm = 0;
*lm = 0;
(void) strlcat((char *)restbuf, (char *)pl,
sizeof(restbuf));
(void) strlcat((char *)restbuf, (char *)pe + 1,
sizeof(restbuf));
*pm = savec;
if (s == 0) {
spathp = pathp;
expsh(restbuf);
pathp = spathp;
*pathp = 0;
} else if (amatch((char *)s, restbuf))
return (1);
sort();
pl = pm + 1;
continue;
case '[':
for (pm++; *pm && *pm != ']'; pm++)
if (*pm == QUOTECHAR) pm++;
if (!*pm)
yyerror("Missing ']'");
continue;
case QUOTECHAR: /* skip one character */
pm++;
continue;
}
return (0);
}
int
match(char *s, char *p) /* quote in p */
{
int c;
char *sentp;
char sexpany = expany;
if (*s == '.' && *p != '.')
return (0);
sentp = entp;
entp = s;
c = amatch(s, p);
entp = sentp;
expany = sexpany;
return (c);
}
int
amatch(char *s, u_char *p) /* quote in p */
{
int scc;
int ok, lc;
char *spathp;
struct stat stb;
int c, cc;
expany = 1;
for (;;) {
scc = *s++;
switch (c = *p++) {
case '{':
return (execbrc((u_char *)p - 1, (u_char *)s - 1));
case '[':
ok = 0;
lc = 077777;
while ((cc = *p++) != '\0') {
if (cc == ']') {
if (ok)
break;
return (0);
}
if (cc == QUOTECHAR) cc = *p++;
if (cc == '-') {
if (lc <= scc && scc <= (int)*p++)
ok++;
} else
if (scc == (lc = cc))
ok++;
}
if (cc == 0) {
yyerror("Missing ']'");
return (0);
}
continue;
case '*':
if (!*p)
return (1);
if (*p == '/') {
p++;
goto slash;
}
for (s--; *s; s++)
if (amatch(s, p))
return (1);
return (0);
case CNULL:
return (scc == CNULL);
default:
if (c != scc)
return (0);
continue;
case '?':
if (scc == CNULL)
return (0);
continue;
case '/':
if (scc)
return (0);
slash:
s = entp;
spathp = pathp;
while (*s)
addpath(*s++);
addpath('/');
if (stat(path, &stb) == 0 && S_ISDIR(stb.st_mode)) {
if (*p == CNULL) {
if (which & E_TILDE) {
Cat((u_char *)path,
(u_char *)"");
} else {
Cat((u_char *)tilde,
(u_char *)tpathp);
}
} else
expsh(p);
}
pathp = spathp;
*pathp = CNULL;
return (0);
}
}
}