#include "config.h"
#include <sys/types.h>
#include <sys/queue.h>
#include <bitstring.h>
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "../common/common.h"
static int argv_alloc(SCR *, size_t);
static int argv_comp(const void *, const void *);
static int argv_fexp(SCR *, EXCMD *,
char *, size_t, char *, size_t *, char **, size_t *, int);
static int argv_lexp(SCR *, EXCMD *, char *);
static int argv_sexp(SCR *, char **, size_t *, size_t *);
int
argv_init(SCR *sp, EXCMD *excp)
{
EX_PRIVATE *exp;
exp = EXP(sp);
exp->argsoff = 0;
argv_alloc(sp, 1);
excp->argv = exp->args;
excp->argc = exp->argsoff;
return (0);
}
int
argv_exp0(SCR *sp, EXCMD *excp, char *cmd, size_t cmdlen)
{
EX_PRIVATE *exp;
exp = EXP(sp);
argv_alloc(sp, cmdlen);
memcpy(exp->args[exp->argsoff]->bp, cmd, cmdlen);
exp->args[exp->argsoff]->bp[cmdlen] = '\0';
exp->args[exp->argsoff]->len = cmdlen;
++exp->argsoff;
excp->argv = exp->args;
excp->argc = exp->argsoff;
return (0);
}
int
argv_exp1(SCR *sp, EXCMD *excp, char *cmd, size_t cmdlen, int is_bang)
{
size_t blen, len;
char *bp, *p, *t;
GET_SPACE_RET(sp, bp, blen, 512);
len = 0;
if (argv_fexp(sp, excp, cmd, cmdlen, bp, &len, &bp, &blen, is_bang)) {
FREE_SPACE(sp, bp, blen);
return (1);
}
if (len != 0) {
for (p = bp, t = bp + len; p < t; ++p)
if (!isblank(*p))
break;
if (p == t)
goto ret;
} else
goto ret;
(void)argv_exp0(sp, excp, bp, len);
ret: FREE_SPACE(sp, bp, blen);
return (0);
}
int
argv_exp2(SCR *sp, EXCMD *excp, char *cmd, size_t cmdlen)
{
size_t blen, len, n;
int rval;
char *bp, *mp, *p;
GET_SPACE_RET(sp, bp, blen, 512);
#define SHELLECHO "echo "
#define SHELLOFFSET (sizeof(SHELLECHO) - 1)
memcpy(bp, SHELLECHO, SHELLOFFSET);
p = bp + SHELLOFFSET;
len = SHELLOFFSET;
#if defined(DEBUG) && 0
TRACE(sp, "file_argv: {%.*s}\n", (int)cmdlen, cmd);
#endif
if (argv_fexp(sp, excp, cmd, cmdlen, p, &len, &bp, &blen, 0)) {
rval = 1;
goto err;
}
#if defined(DEBUG) && 0
TRACE(sp, "before shell: %d: {%s}\n", len, bp);
#endif
if (opts_empty(sp, O_SHELL, 1) || opts_empty(sp, O_SHELLMETA, 1))
n = 0;
else {
for (p = mp = O_STR(sp, O_SHELLMETA); *p != '\0'; ++p)
if (isblank(*p) || isalnum(*p))
break;
p = bp + SHELLOFFSET;
n = len - SHELLOFFSET;
if (*p != '\0') {
for (; n > 0; --n, ++p)
if (strchr(mp, *p) != NULL)
break;
} else
for (; n > 0; --n, ++p)
if (!isblank(*p) &&
!isalnum(*p) && strchr(mp, *p) != NULL)
break;
}
switch (n) {
case 0:
p = bp + SHELLOFFSET;
len -= SHELLOFFSET;
rval = argv_exp3(sp, excp, p, len);
break;
case 1:
if (*p == '*') {
*p = '\0';
rval = argv_lexp(sp, excp, bp + SHELLOFFSET);
break;
}
default:
if (argv_sexp(sp, &bp, &blen, &len)) {
rval = 1;
goto err;
}
p = bp;
rval = argv_exp3(sp, excp, p, len);
break;
}
err: FREE_SPACE(sp, bp, blen);
return (rval);
}
int
argv_exp3(SCR *sp, EXCMD *excp, char *cmd, size_t cmdlen)
{
EX_PRIVATE *exp;
size_t len;
int ch, off;
char *ap, *p;
for (exp = EXP(sp); cmdlen > 0; ++exp->argsoff) {
for (; cmdlen > 0; --cmdlen, ++cmd) {
ch = *cmd;
if (!isblank(ch))
break;
}
if (cmdlen == 0)
break;
for (ap = cmd, len = 0; cmdlen > 0; ++cmd, --cmdlen, ++len) {
ch = *cmd;
if (IS_ESCAPE(sp, excp, ch) && cmdlen > 1) {
++cmd;
--cmdlen;
} else if (isblank(ch))
break;
}
argv_alloc(sp, len);
off = exp->argsoff;
exp->args[off]->len = len;
for (p = exp->args[off]->bp; len > 0; --len, *p++ = *ap++)
if (IS_ESCAPE(sp, excp, *ap))
++ap;
*p = '\0';
}
excp->argv = exp->args;
excp->argc = exp->argsoff;
#if defined(DEBUG) && 0
for (cnt = 0; cnt < exp->argsoff; ++cnt)
TRACE(sp, "arg %d: {%s}\n", cnt, exp->argv[cnt]);
#endif
return (0);
}
static int
argv_fexp(SCR *sp, EXCMD *excp, char *cmd, size_t cmdlen, char *p,
size_t *lenp, char **bpp, size_t *blenp, int is_bang)
{
EX_PRIVATE *exp;
char *bp, *t;
size_t blen, len, off, tlen;
for (bp = *bpp, blen = *blenp, len = *lenp; cmdlen > 0; --cmdlen, ++cmd)
switch (*cmd) {
case '!':
if (!is_bang)
goto ins_ch;
exp = EXP(sp);
if (exp->lastbcomm == NULL) {
msgq(sp, M_ERR,
"No previous command to replace \"!\"");
return (1);
}
len += tlen = strlen(exp->lastbcomm);
off = p - bp;
ADD_SPACE_RET(sp, bp, blen, len);
p = bp + off;
memcpy(p, exp->lastbcomm, tlen);
p += tlen;
F_SET(excp, E_MODIFY);
break;
case '%':
if (sp->frp == NULL || (t = sp->frp->name) == NULL) {
msgq(sp, M_ERR,
"No filename to substitute for %%");
return (1);
}
tlen = strlen(t);
len += tlen;
off = p - bp;
ADD_SPACE_RET(sp, bp, blen, len);
p = bp + off;
memcpy(p, t, tlen);
p += tlen;
F_SET(excp, E_MODIFY);
break;
case '#':
if ((t = sp->alt_name) == NULL) {
msgq(sp, M_ERR,
"No filename to substitute for #");
return (1);
}
len += tlen = strlen(t);
off = p - bp;
ADD_SPACE_RET(sp, bp, blen, len);
p = bp + off;
memcpy(p, t, tlen);
p += tlen;
F_SET(excp, E_MODIFY);
break;
case '\\':
if (cmdlen > 1 &&
(cmd[1] == '%' || cmd[1] == '#' || cmd[1] == '!')) {
++cmd;
--cmdlen;
}
default:
ins_ch: ++len;
off = p - bp;
ADD_SPACE_RET(sp, bp, blen, len);
p = bp + off;
*p++ = *cmd;
}
++len;
off = p - bp;
ADD_SPACE_RET(sp, bp, blen, len);
p = bp + off;
*p = '\0';
*lenp = len - 1;
*bpp = bp;
*blenp = blen;
return (0);
}
static int
argv_alloc(SCR *sp, size_t len)
{
ARGS *ap;
EX_PRIVATE *exp;
int cnt, off;
#define INCREMENT 20
exp = EXP(sp);
off = exp->argsoff;
if (exp->argscnt == 0 || off + 2 >= exp->argscnt - 1) {
cnt = exp->argscnt + INCREMENT;
REALLOCARRAY(sp, exp->args, cnt, sizeof(ARGS *));
if (exp->args == NULL) {
(void)argv_free(sp);
goto mem;
}
memset(&exp->args[exp->argscnt], 0, INCREMENT * sizeof(ARGS *));
exp->argscnt = cnt;
}
if (exp->args[off] == NULL) {
CALLOC(sp, exp->args[off], 1, sizeof(ARGS));
if (exp->args[off] == NULL)
goto mem;
}
ap = exp->args[off];
ap->len = 0;
if (ap->blen < len + 1) {
ap->blen = len + 1;
REALLOCARRAY(sp, ap->bp, ap->blen, sizeof(CHAR_T));
if (ap->bp == NULL) {
ap->bp = NULL;
ap->blen = 0;
F_CLR(ap, A_ALLOCATED);
mem: msgq(sp, M_SYSERR, NULL);
return (1);
}
F_SET(ap, A_ALLOCATED);
}
if (exp->args[++off] == NULL) {
CALLOC(sp, exp->args[off], 1, sizeof(ARGS));
if (exp->args[off] == NULL)
goto mem;
}
exp->args[off]->len = 0;
return (0);
}
int
argv_free(SCR *sp)
{
EX_PRIVATE *exp;
int off;
exp = EXP(sp);
if (exp->args != NULL) {
for (off = 0; off < exp->argscnt; ++off) {
if (exp->args[off] == NULL)
continue;
if (F_ISSET(exp->args[off], A_ALLOCATED))
free(exp->args[off]->bp);
free(exp->args[off]);
}
free(exp->args);
}
exp->args = NULL;
exp->argscnt = 0;
exp->argsoff = 0;
return (0);
}
static int
argv_lexp(SCR *sp, EXCMD *excp, char *path)
{
struct dirent *dp;
DIR *dirp;
EX_PRIVATE *exp;
int off;
size_t dlen, nlen;
char *dname, *name, *p;
exp = EXP(sp);
if ((p = strrchr(path, '/')) == NULL) {
dname = ".";
dlen = 0;
name = path;
} else {
if (p == path) {
dname = "/";
dlen = 1;
} else {
*p = '\0';
dname = path;
dlen = strlen(path);
}
name = p + 1;
}
nlen = strlen(name);
if ((dirp = opendir(dname)) == NULL) {
msgq_str(sp, M_SYSERR, dname, "%s");
return (1);
}
for (off = exp->argsoff; (dp = readdir(dirp)) != NULL;) {
if (nlen == 0) {
if (dp->d_name[0] == '.')
continue;
} else {
if (dp->d_namlen < nlen ||
memcmp(dp->d_name, name, nlen))
continue;
}
argv_alloc(sp, dlen + dp->d_namlen + 2);
p = exp->args[exp->argsoff]->bp;
if (dlen != 0) {
memcpy(p, dname, dlen);
p += dlen;
if (dlen > 1 || dname[0] != '/')
*p++ = '/';
}
memcpy(p, dp->d_name, dp->d_namlen + 1);
exp->args[exp->argsoff]->len = dlen + dp->d_namlen + 1;
++exp->argsoff;
excp->argv = exp->args;
excp->argc = exp->argsoff;
}
closedir(dirp);
if (off == exp->argsoff) {
msgq(sp, M_ERR, "Shell expansion failed");
return (1);
}
qsort(exp->args + off, exp->argsoff - off, sizeof(ARGS *), argv_comp);
return (0);
}
static int
argv_comp(const void *a, const void *b)
{
return (strcmp((char *)(*(ARGS **)a)->bp, (char *)(*(ARGS **)b)->bp));
}
static int
argv_sexp(SCR *sp, char **bpp, size_t *blenp, size_t *lenp)
{
enum { SEXP_ERR, SEXP_EXPANSION_ERR, SEXP_OK } rval;
FILE *ifp;
pid_t pid;
size_t blen, len;
int ch, std_output[2];
char *bp, *p, *sh, *sh_path;
if (O_ISSET(sp, O_SECURE)) {
msgq(sp, M_ERR,
"Shell expansions not supported when the secure edit option is set");
return (1);
}
sh_path = O_STR(sp, O_SHELL);
if ((sh = strrchr(sh_path, '/')) == NULL)
sh = sh_path;
else
++sh;
bp = *bpp;
blen = *blenp;
ifp = NULL;
std_output[0] = std_output[1] = -1;
if (pipe(std_output) < 0) {
msgq(sp, M_SYSERR, "pipe");
return (1);
}
if ((ifp = fdopen(std_output[0], "r")) == NULL) {
msgq(sp, M_SYSERR, "fdopen");
goto err;
}
switch (pid = vfork()) {
case -1:
msgq(sp, M_SYSERR, "vfork");
err: if (ifp != NULL)
(void)fclose(ifp);
else if (std_output[0] != -1)
close(std_output[0]);
if (std_output[1] != -1)
close(std_output[0]);
return (1);
case 0:
(void)dup2(std_output[1], STDOUT_FILENO);
(void)close(std_output[0]);
(void)close(std_output[1]);
(void)close(STDERR_FILENO);
execl(sh_path, sh, "-c", bp, (char *)NULL);
msgq_str(sp, M_SYSERR, sh_path, "Error: execl: %s");
_exit(127);
default:
(void)close(std_output[1]);
break;
}
for (p = bp, len = 0, ch = EOF;
(ch = getc(ifp)) != EOF; *p++ = ch, --blen, ++len)
if (blen < 5) {
ADD_SPACE_GOTO(sp, bp, *blenp, *blenp * 2);
p = bp + len;
blen = *blenp - len;
}
if (p > bp && (p[-1] == '\n' || p[-1] == '\r')) {
--p;
--len;
}
*p = '\0';
*lenp = len;
*bpp = bp;
if (ferror(ifp))
goto ioerr;
if (fclose(ifp)) {
ioerr: msgq_str(sp, M_ERR, sh, "I/O error: %s");
alloc_err: rval = SEXP_ERR;
} else
rval = SEXP_OK;
if (proc_wait(sp, pid, sh, 1, 0))
rval = SEXP_EXPANSION_ERR;
for (p = bp; len; ++p, --len)
if (!isblank(*p))
break;
if (len == 0)
rval = SEXP_EXPANSION_ERR;
if (rval == SEXP_EXPANSION_ERR)
msgq(sp, M_ERR, "Shell expansion failed");
return (rval == SEXP_OK ? 0 : 1);
}