#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <wordexp.h>
#include <pthread.h>
#include <regex.h>
#include <assert.h>
#include <unistd.h>
#include <paths.h>
#include <strings.h>
#include <spawn.h>
#include <sys/errno.h>
#include <crt_externs.h>
extern size_t malloc_good_size(size_t size);
extern int errno;
pthread_once_t re_init_c = PTHREAD_ONCE_INIT;
static regex_t re_cmd, re_goodchars, re_subcmd_syntax_err_kludge, re_quoted_string;
pid_t popen_oe(char *cmd, FILE **out, FILE **err) {
int out_pipe[2], err_pipe[2];
char *argv[4];
pid_t pid;
posix_spawn_file_actions_t file_actions;
int errrtn;
if ((errrtn = posix_spawn_file_actions_init(&file_actions)) != 0) {
errno = errrtn;
return 0;
}
if (pipe(out_pipe) < 0) {
posix_spawn_file_actions_destroy(&file_actions);
return 0;
}
if (pipe(err_pipe) < 0) {
posix_spawn_file_actions_destroy(&file_actions);
close(out_pipe[0]);
close(out_pipe[1]);
return 0;
}
if (out_pipe[1] != STDOUT_FILENO) {
posix_spawn_file_actions_adddup2(&file_actions, out_pipe[1], STDOUT_FILENO);
posix_spawn_file_actions_addclose(&file_actions, out_pipe[1]);
}
posix_spawn_file_actions_addclose(&file_actions, out_pipe[0]);
if (err_pipe[1] != STDERR_FILENO) {
posix_spawn_file_actions_adddup2(&file_actions, err_pipe[1], STDERR_FILENO);
posix_spawn_file_actions_addclose(&file_actions, err_pipe[1]);
}
posix_spawn_file_actions_addclose(&file_actions, err_pipe[0]);
argv[0] = "sh";
argv[1] = "-c";
argv[2] = cmd;
argv[3] = NULL;
errrtn = posix_spawn(&pid, _PATH_BSHELL, &file_actions, NULL, argv, *_NSGetEnviron());
posix_spawn_file_actions_destroy(&file_actions);
if (errrtn != 0) {
close(out_pipe[0]);
close(out_pipe[1]);
close(err_pipe[0]);
close(err_pipe[1]);
errno = errrtn;
return 0;
}
*out = fdopen(out_pipe[0], "r");
assert(*out);
close(out_pipe[1]);
*err = fdopen(err_pipe[0], "r");
assert(*err);
close(err_pipe[1]);
return pid;
}
void re_init(void) {
int rc = regcomp(&re_cmd, "(^|[^\\])(`|\\$\\([^(])", REG_EXTENDED|REG_NOSUB);
char *rx = "^([^\\\"'|&;<>(){}\n]"
"|\\\\."
"|'(\\\\\\\\|\\\\'|[^'])*'"
"|\"(\\\\\\\\|\\\\\"|[^\"])*\""
"|`(\\\\\\\\|\\\\`|[^`])*`"
"|\\$\\(\\(([^)]|\\\\)*\\)\\)"
"|\\$\\(([^)]|\\\\)*\\)"
"|\\$\\{[^}]*\\}"
")*$";
rc = regcomp(&re_goodchars, rx,
REG_EXTENDED|REG_NOSUB);
rc = regcomp(&re_subcmd_syntax_err_kludge,
"command substitution.*syntax error", REG_EXTENDED|REG_NOSUB);
rc = regcomp(&re_quoted_string,
"(^|[^\\])'(\\\\\\\\|\\\\'|[^'])*'", REG_EXTENDED|REG_NOSUB);
}
static int word_alloc(size_t want, wordexp_t *__restrict__ pwe, size_t *have) {
if (want < *have) {
return 1;
}
size_t bytes = malloc_good_size(sizeof(char *) * want * 2);
pwe->we_wordv = reallocf(pwe->we_wordv, bytes);
if (pwe->we_wordv) {
*have = bytes / sizeof(char *);
return 1;
}
return 0;
}
static int
cmd_search(const char *str) {
regoff_t first = 0;
regoff_t last = strlen(str);
regmatch_t m = {first, last};
int flags;
if (last == 0) return REG_NOMATCH;
flags = REG_STARTEND;
while(regexec(&re_quoted_string, str, 1, &m, flags) == 0) {
regmatch_t head = {first, m.rm_so};
if (regexec(&re_cmd, str, 1, &head, flags) == 0) {
return 0;
}
flags = REG_NOTBOL | REG_STARTEND;
m.rm_so = first = m.rm_eo;
m.rm_eo = last;
}
flags = REG_STARTEND;
if (m.rm_so > 0) flags |= REG_NOTBOL;
return regexec(&re_cmd, str, 1, &m, flags);
}
int wordexp(const char *__restrict__ words,
wordexp_t *__restrict__ pwe, int flags) {
size_t cbuf_l = 1024;
char *cbuf = NULL;
char *cmd = "/usr/bin/perl -e 'print join(chr(0), @ARGV), chr(0)' -- ";
size_t wordv_l = 0, wordv_i = 0;
int rc;
wordexp_t save;
save = *pwe;
pthread_once(&re_init_c, re_init);
if (flags & WRDE_NOCMD) {
rc = cmd_search(words);
if (rc != REG_NOMATCH) {
return WRDE_CMDSUB;
}
}
rc = regexec(&re_goodchars, words, 0, NULL, 0);
if (rc != 0) {
return WRDE_BADCHAR;
}
if (flags & WRDE_APPEND) {
wordv_i = wordv_l = pwe->we_wordc;
if (flags & WRDE_DOOFFS) {
wordv_l = wordv_i += pwe->we_offs;
}
} else {
if (flags & WRDE_REUSE) {
wordfree(pwe);
}
pwe->we_wordc = 0;
pwe->we_wordv = NULL;
if (flags & WRDE_DOOFFS) {
size_t wend = wordv_i + pwe->we_offs;
word_alloc(wend, pwe, &wordv_l);
if (!pwe->we_wordv) {
return WRDE_NOSPACE;
}
bzero(pwe->we_wordv + wordv_i, pwe->we_offs * sizeof(char *));
wordv_i = wend;
} else {
pwe->we_offs = 0;
}
}
size_t need = 0;
while(!cbuf || need > cbuf_l) {
if (need > cbuf_l) {
cbuf_l = malloc_good_size(need +1);
}
cbuf = reallocf(cbuf, cbuf_l);
if (cbuf == NULL) {
wordfree(pwe);
return WRDE_NOSPACE;
}
cbuf[0] = '\0';
if (flags & WRDE_UNDEF) {
strlcat(cbuf, "set -u; ", cbuf_l);
}
if (getenv("IFS")) {
setenv("_IFS", getenv("IFS"), 1);
strlcat(cbuf, "export IFS=${_IFS}; ", cbuf_l);
}
strlcat(cbuf, cmd, cbuf_l);
need = strlcat(cbuf, words, cbuf_l);
}
FILE *out, *err;
pid_t pid = popen_oe(cbuf, &out, &err);
if (pid == 0) {
wordfree(pwe);
return WRDE_NOSPACE;
}
char *word = NULL;
int word_l = 0;
int word_i = 0;
int ch;
while(EOF != (ch = fgetc(out))) {
if (word_l <= word_i) {
word_l = malloc_good_size(word_l * 2 + 1);
word = reallocf(word, word_l);
if (!word) {
fclose(err);
fclose(out);
wordfree(pwe);
return WRDE_NOSPACE;
}
}
word[word_i++] = ch;
if (ch == '\0') {
word_alloc(wordv_i + 1, pwe, &wordv_l);
char *tmp = strdup(word);
if (pwe->we_wordv == NULL || tmp == NULL) {
fclose(err);
fclose(out);
wordfree(pwe);
free(word);
free(tmp);
int status;
wait4(pid, &status, 0, NULL);
return WRDE_NOSPACE;
}
pwe->we_wordv[wordv_i++] = tmp;
pwe->we_wordc++;
word_i = 0;
}
}
assert(word_i == 0);
free(word);
char err_buf[1024];
size_t err_sz = fread(err_buf, 1, sizeof(err_buf) -1, err);
err_buf[(err_sz >= 0) ? err_sz : 0] = '\0';
if (flags & WRDE_SHOWERR) {
fputs(err_buf, stderr);
}
pid_t got_pid = 0;
int status;
do {
pid = wait4(pid, &status, 0, NULL);
} while(got_pid == -1 && errno == EINTR);
fclose(out);
fclose(err);
if (regexec(&re_subcmd_syntax_err_kludge, err_buf, 0, NULL, 0) == 0
|| got_pid == -1 || (WIFEXITED(status) && WEXITSTATUS(status))) {
if (!(flags & (WRDE_APPEND|WRDE_REUSE))) {
*pwe = save;
}
if (strstr(err_buf, " unbound variable")) {
return WRDE_BADVAL;
}
return WRDE_SYNTAX;
}
if (!word_alloc(wordv_i + 1, pwe, &wordv_l)) {
return WRDE_NOSPACE;
}
pwe->we_wordv[wordv_i] = NULL;
return 0;
}
void wordfree(wordexp_t *pwe) {
if (pwe == NULL || pwe->we_wordv == NULL) {
return;
}
int i = 0, e = pwe->we_wordc + pwe->we_offs;
for(i = pwe->we_offs; i < e; i++) {
free(pwe->we_wordv[i]);
}
free(pwe->we_wordv);
pwe->we_wordv = NULL;
}