/* This file is part of Mailfromd.
Copyright (C) 2005-2020 Sergey Poznyakoff
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see . */
#ifdef HAVE_CONFIG_H
# include
#endif
#include
#include
#include "mailfromd.h"
#include "prog.h"
extern int yy_flex_debug;
static mu_list_t include_path;
static mu_list_t std_include_path;
struct file_data {
const char *name;
size_t namelen;
char *buf;
size_t buflen;
int found;
};
static int
find_include_file(void *item, void *data)
{
char *dir = item;
struct file_data *dptr = data;
size_t size = strlen(dir) + 1 + dptr->namelen + 1;
if (size > dptr->buflen) {
dptr->buflen = size;
dptr->buf = mu_realloc(dptr->buf, dptr->buflen);
}
strcpy(dptr->buf, dir);
strcat(dptr->buf, "/");
strcat(dptr->buf, dptr->name);
return dptr->found = access(dptr->buf, F_OK) == 0;
}
void
include_path_setup()
{
mu_list_create(&include_path);
mu_list_create(&std_include_path);
mu_list_append(std_include_path, DEFAULT_VERSION_INCLUDE_DIR);
mu_list_append(std_include_path, DEFAULT_INCLUDE_DIR);
mu_list_append(std_include_path, "/usr/share/mailfromd/include");
mu_list_append(std_include_path, "/usr/local/share/mailfromd/include");
}
void
add_include_dir(const char *dir)
{
int rc;
if (!include_path) {
rc = mu_list_create(&include_path);
if (rc) {
mu_error(_("cannot create include path list: %s"),
mu_strerror(rc));
return;
}
}
rc = mu_list_append(include_path, mu_strdup(dir));
if (rc)
mu_error(_("cannot append to the include path list: %s"),
mu_strerror(rc));
}
#define ID_EQ(a,b) \
((a).i_node == (b).i_node && (a).device == (a).device)
static int
input_file_ident_cmp(const void *item, const void *data)
{
const struct input_file_ident *id1 = item;
const struct input_file_ident *id2 = data;
return !ID_EQ(*id1, *id2);
}
static void
input_file_ident_free(void *item)
{
free(item);
}
int
source_lookup(struct input_file_ident *idptr)
{
int rc;
if (!top_module->incl_sources) {
mu_list_create(&top_module->incl_sources);
mu_list_set_comparator(top_module->incl_sources,
input_file_ident_cmp);
mu_list_set_destroy_item(top_module->incl_sources,
input_file_ident_free);
}
rc = mu_list_locate (top_module->incl_sources, idptr, NULL);
if (rc == MU_ERR_NOENT) {
struct input_file_ident *new_id = mu_alloc(sizeof *new_id);
*new_id = *idptr;
mu_list_append(top_module->incl_sources, new_id);
}
return rc == 0;
}
static int
try_file(const char *name, int allow_cwd, int err_not_found, char **newp)
{
static char *cwd = ".";
struct file_data fd;
fd.name = name;
fd.namelen = strlen(name);
fd.buf = NULL;
fd.buflen = 0;
fd.found = 0;
if (allow_cwd) {
mu_list_prepend(include_path, cwd);
mu_list_foreach(include_path, find_include_file, &fd);
mu_list_remove(include_path, cwd);
} else
mu_list_foreach(include_path, find_include_file, &fd);
if (!fd.found) {
mu_list_foreach(std_include_path, find_include_file, &fd);
if (!fd.found && err_not_found) {
parse_error(_("%s: no such file or directory"), name);
*newp = NULL;
}
}
if (fd.found)
*newp = fd.buf;
return fd.found;
}
int
begin_module(const char *modname, const char *filename,
struct import_rule *import_rules)
{
int rc;
if (access(filename, R_OK)) {
parse_error(_("input file %s is not readable: %s"),
filename,
mu_strerror(errno));
return 1;
}
if (set_top_module(modname, filename, import_rules, get_locus()) == 0)
rc = lex_new_source(filename, LEX_MODULE);
else
rc = 0;
return rc;
}
int
parse_include(const char *text, int once)
{
struct mu_wordsplit ws;
char *tmp = NULL;
char *p = NULL;
int rc = 1;
if (mu_wordsplit(text, &ws, MU_WRDSF_DEFFLAGS)) {
parse_error(_("cannot parse include line: %s"),
mu_wordsplit_strerror (&ws));
return errno;
} else if (ws.ws_wordc != 2)
mu_wordsplit_free (&ws);
else {
size_t len;
int allow_cwd;
p = ws.ws_wordv[1];
len = strlen(p);
if (p[0] == '<' && p[len - 1] == '>') {
allow_cwd = 0;
p[len - 1] = 0;
p++;
} else
allow_cwd = 1;
if (p[0] != '/' && try_file(p, allow_cwd, 1, &tmp))
p = tmp;
}
if (p)
rc = lex_new_source(p, once ? LEX_ONCE : LEX_NONE);
free(tmp);
mu_wordsplit_free (&ws);
return rc;
}
#define DEFAULT_SUFFIX ".mf"
void
require_module(const char *modname, struct import_rule *import_rules)
{
size_t len;
char *fname, *pathname;
len = strlen(modname);
fname = mu_alloc(len + sizeof DEFAULT_SUFFIX);
strcpy(fname, modname);
strcat(fname, DEFAULT_SUFFIX);
if (try_file(fname, 0, 1, &pathname))
begin_module(modname, pathname, import_rules);
free(fname);
}
static int
assign_locus(struct mu_locus_point *ploc, const char *name,
const char *linestr)
{
char *p;
unsigned long linenum;
errno = 0;
linenum = strtoul(linestr, &p, 10);
if (errno)
return errno;
if (*p)
return MU_ERR_PARSE;
if (linenum > UINT_MAX)
return ERANGE;
mu_locus_point_init(ploc);
mu_locus_point_set_file(ploc, name);
ploc->mu_line = (unsigned) linenum;
return 0;
}
int
parse_line(char *text, struct mu_locus_point *ploc)
{
int rc;
struct mu_wordsplit ws;
while (*text && mu_isspace (*text))
text++;
text++;
if (mu_wordsplit(text, &ws, MU_WRDSF_DEFFLAGS)) {
parse_error(_("cannot parse #line line: %s"),
mu_wordsplit_strerror (&ws));
return 1;
} else {
if (ws.ws_wordc == 2)
rc = assign_locus(ploc, NULL, ws.ws_wordv[1]);
else if (ws.ws_wordc == 3)
rc = assign_locus(ploc, ws.ws_wordv[2],
ws.ws_wordv[1]);
else
rc = 1;
if (rc)
parse_error(_("malformed #line statement"));
}
mu_wordsplit_free(&ws);
return rc;
}
int
parse_line_cpp(char *text, struct mu_locus_point *ploc)
{
struct mu_wordsplit ws;
int rc;
if (mu_wordsplit(text, &ws, MU_WRDSF_DEFFLAGS)) {
parse_error(_("cannot parse #line line: %s"),
mu_wordsplit_strerror (&ws));
rc = 1;
} else if (ws.ws_wordc < 3) {
parse_error(_("invalid #line statement"));
rc = 1;
} else if (assign_locus(ploc, ws.ws_wordv[2], ws.ws_wordv[1])) {
parse_error(_("malformed #line statement"));
rc = 1;
} else
rc = 0;
mu_wordsplit_free(&ws);
return rc;
}
static void
stderr_redirector(char *ppcmd, char **argv)
{
if (!logger_flags(LOGF_STDERR)) {
int p[2];
char buf[1024];
FILE *fp;
pid_t pid;
signal(SIGCHLD, SIG_DFL);
if (pipe(p)) {
mu_diag_funcall(MU_DIAG_ERROR, "pipe", "p", errno);
exit(127);
}
switch (pid = fork()) {
/* Grandchild */
case 0:
if (p[1] != 2) {
close(2);
dup2(p[1], 2);
}
close(p[0]);
execvp(argv[0], argv);
exit(127);
case -1:
/* Fork failed */
mf_server_log_setup();
mu_error("Cannot run `%s': %s",
ppcmd, mu_strerror(errno));
exit(127);
default:
/* Sub-master */
close(p[1]);
fp = fdopen(p[0], "r");
mf_server_log_setup();
while (fgets(buf, sizeof(buf), fp))
mu_error("%s", buf);
exit(0);
}
} else {
execvp(argv[0], argv);
mf_server_log_setup();
mu_error("Cannot run `%s': %s",
ppcmd, mu_strerror(errno));
exit(127);
}
}
FILE *
pp_extrn_start(const char *name, pid_t *ppid)
{
int pout[2];
pid_t pid;
size_t size;
char *ppcmd;
struct mu_wordsplit ws;
FILE *fp = NULL;
size = strlen(ext_pp) + strlen(name) + 2;
ppcmd = mu_alloc(size);
strcpy(ppcmd, ext_pp);
strcat(ppcmd, " ");
strcat(ppcmd, name);
mu_debug(MF_SOURCE_PP, MU_DEBUG_TRACE1,
("Running preprocessor: `%s'", ppcmd));
if (mu_wordsplit(ppcmd, &ws, MU_WRDSF_DEFFLAGS)) {
parse_error(_("cannot parse line: %s"),
mu_wordsplit_strerror (&ws));
exit(EX_UNAVAILABLE);
}
if (pipe(pout)) {
mu_diag_funcall(MU_DIAG_ERROR, "pipe", "pout", errno);
exit(EX_UNAVAILABLE);
}
switch (pid = fork()) {
/* The child branch. */
case 0:
if (pout[1] != 1) {
close(1);
dup2(pout[1], 1);
}
/* Close unneded descripitors */
close_fds_above(1);
stderr_redirector(ppcmd, ws.ws_wordv);
break;
case -1:
/* Fork failed */
mu_error("Cannot run `%s': %s",
ppcmd, mu_strerror(errno));
break;
default:
close(pout[1]);
fp = fdopen(pout[0], "r");
break;
}
mu_wordsplit_free(&ws);
free(ppcmd);
*ppid = pid;
return fp;
}
void
pp_extrn_shutdown(FILE *file, pid_t pid)
{
int status;
fclose(file);
waitpid(pid, &status, 0);
if (WIFEXITED(status)) {
status = WEXITSTATUS(status);
if (status) {
mu_error(_("preprocessor exited with status %d"),
status);
}
} else if (WIFSIGNALED(status)) {
mu_error(_("preprocessor terminated on signal %d"),
WTERMSIG(status));
} else if (WIFSTOPPED(status)) {
mu_error("%s", _("preprocessor stopped"));
} else {
mu_error("%s", _("preprocessor terminated abnormally"));
}
}
static int
_count_include_size(void *item, void *data)
{
size_t *psize = data;
*psize += 3 + strlen((char*)item);
return 0;
}
static int
_append_includes(void *item, void *data)
{
char *str = data;
strcat(str, " -I");
strcat(str, (char*) item);
return 0;
}
static char *setup_file;
void
alloc_ext_pp()
{
size_t size;
if (ext_pp_options_given && !ext_pp) {
if (!DEF_EXT_PP) {
mu_error(_("default preprocessor is not set; "
"use --preprocessor option"));
exit(EX_USAGE);
}
ext_pp = DEF_EXT_PP;
}
if (!ext_pp)
return;
ext_pp = mu_strdup(ext_pp);
if (ext_pp_options) {
size_t len;
char *p;
len = strlen(ext_pp) + strlen(ext_pp_options);
p = mu_alloc(len + 1);
strcpy(p, ext_pp);
strcat(p, ext_pp_options);
ext_pp = p;
}
size = 0;
mu_list_foreach(include_path, _count_include_size, &size);
if (size) {
ext_pp = mu_realloc(ext_pp, strlen(ext_pp) + size + 1);
mu_list_foreach(include_path, _append_includes, ext_pp);
}
if (try_file("pp-setup", 1, 0, &setup_file)) {
ext_pp = mu_realloc(ext_pp, strlen(ext_pp) +
strlen(setup_file) + 2);
strcat(ext_pp, " ");
strcat(ext_pp, setup_file);
}
}
int
preprocess_input()
{
char buffer[512];
FILE *file;
pid_t pid;
ssize_t n;
file = pp_extrn_start(script_file, &pid);
if (!file)
return EX_NOINPUT;
while ((n = fread(buffer, 1, sizeof buffer, file)) > 0)
fwrite(buffer, 1, n, stdout);
pp_extrn_shutdown(file, pid);
return 0;
}