/* This file is part of mailfromd.
Copyright (C) 2005, 2006, 2007 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 . */
#define MF_SOURCE_NAME MF_SOURCE_PP
#ifdef HAVE_CONFIG_H
# include
#endif
#include
#include
#define obstack_chunk_alloc malloc
#define obstack_chunk_free free
#include
#include "mailfromd.h"
#include "prog.h"
struct input_file_ident {
ino_t i_node;
dev_t device;
};
struct buffer_ctx {
struct buffer_ctx *prev;
struct locus locus;
size_t namelen;
struct input_file_ident id;
FILE *infile;
};
extern int yy_flex_debug;
static struct buffer_ctx *context_stack;
#define INFILE context_stack->infile
#define LOCUS context_stack->locus
static char *linebuf;
static size_t bufsize;
static char *putback_buffer;
static size_t putback_size;
static size_t putback_max;
static int push_source(const char *name, int once);
static int pop_source(void);
static int parse_include(const char *text, int once);
static int parse_require(const char *text);
static void
putback(const char *str)
{
size_t len;
if (!*str)
return;
len = strlen(str) + 1;
if (len > putback_max) {
putback_max = len;
putback_buffer = xrealloc(putback_buffer, putback_max);
}
strcpy(putback_buffer, str);
putback_size = len - 1;
}
/* Compute the size of the line
#line NNN "FILENAME"
*/
static size_t
pp_line_stmt_size()
{
char nbuf[NUMERIC_BUFSIZE_BOUND];
size_t n = snprintf(nbuf, sizeof nbuf, "%lu",
(unsigned long) LOCUS.line);
if (context_stack->namelen == 0)
context_stack->namelen = strlen(LOCUS.file);
/* "#line " is 6 chars, another space, two quotes and a linefeed
make another 4, summa facit 10 */
return 10 + n + context_stack->namelen;
}
static void
pp_line_stmt()
{
char *p;
size_t ls_size = pp_line_stmt_size();
size_t pb_size = putback_size + ls_size + 1;
if (pb_size > putback_max) {
putback_max = pb_size;
putback_buffer = xrealloc(putback_buffer, putback_max);
}
p = putback_buffer + putback_size;
snprintf(p, putback_max - putback_size,
"#line %lu \"%s\"\n",
(unsigned long) LOCUS.line,
LOCUS.file);
putback_size += ls_size;
}
#define STRMATCH(p, len, s) (len >= sizeof(s) \
&& memcmp (p, s, sizeof(s) - 1) == 0 \
&& isspace(p[sizeof(s) - 1]))
static int
next_line()
{
ssize_t rc;
do {
if (putback_size) {
if (putback_size + 1 > bufsize) {
bufsize = putback_size + 1;
linebuf = xrealloc(linebuf, bufsize);
}
strcpy(linebuf, putback_buffer);
rc = putback_size;
putback_size = 0;
} else if (!context_stack)
return 0;
else
rc = getline(&linebuf, &bufsize, INFILE);
} while (rc == -1 && pop_source() == 0);
return rc;
}
size_t
pp_fill_buffer(char *buf, size_t size)
{
size_t bufsize = size;
while (next_line() > 0) {
char *p;
size_t len;
int is_line = 0;
for (p = linebuf; *p && isspace(*p); p++)
;
if (*p == '#') {
size_t l;
for (p++; *p && isspace(*p); p++)
;
l = strlen(p);
if (STRMATCH(p, l, "include_once")) {
if (parse_include(linebuf, 1))
putback("/*include_once*/\n");
continue;
} else if (STRMATCH(p, l, "include")) {
if (parse_include(linebuf, 0))
putback("/*include*/\n");
continue;
} else if (STRMATCH(p, l, "require")) {
if (parse_require(linebuf))
putback("/*require*/\n");
continue;
} else if (STRMATCH(p, l, "line"))
is_line = 1;
}
len = strlen (linebuf);
if (len > size)
len = size;
memcpy(buf, linebuf, len);
buf += len;
size -= len;
if (size == 0) {
putback(linebuf + len);
break;
}
if (!is_line && len > 0 && linebuf[len-1] == '\n')
LOCUS.line++;
}
return bufsize - size;
}
#define STAT_ID_EQ(st,id) ((id).i_node == (st).st_ino \
&& (id).device == (st).st_dev)
static struct buffer_ctx *
ctx_lookup(struct stat *st)
{
struct buffer_ctx *ctx;
if (!context_stack)
return NULL;
for (ctx = context_stack->prev; ctx; ctx = ctx->prev)
if (STAT_ID_EQ(*st, ctx->id))
break;
return ctx;
}
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 = xrealloc(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(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, dir);
if (rc)
mu_error(_("Cannot append to the include path list: %s"),
mu_strerror(rc));
}
static mu_list_t /* of struct input_file_ident */ incl_sources;
static int
input_file_ident_cmp(const void *item, const void *data)
{
const struct input_file_ident *id = item;
const struct stat *st = data;
return !STAT_ID_EQ(*st, *id);
}
static void
input_file_ident_free(void *item)
{
free(item);
}
static int
source_lookup(struct stat *st)
{
int rc;
if (!incl_sources) {
mu_list_create(&incl_sources);
mu_list_set_comparator(incl_sources, input_file_ident_cmp);
mu_list_set_destroy_item(incl_sources, input_file_ident_free);
}
rc = mu_list_locate (incl_sources, st, NULL);
if (rc == MU_ERR_NOENT) {
struct input_file_ident *id = xmalloc(sizeof *id);
id->i_node = st->st_ino;
id->device = st->st_dev;
mu_list_append(incl_sources, id);
}
return rc == 0;
}
static int
push_source(const char *name, int once)
{
FILE *fp;
struct buffer_ctx *ctx;
struct stat st;
struct literal *lit;
if (stat(name, &st)) {
parse_error_locus(&LOCUS,
_("Cannot stat `%s': %s"),
name, mu_strerror(errno));
return 1;
}
if (context_stack) {
if (LOCUS.file && STAT_ID_EQ(st, context_stack->id)) {
parse_error_locus(&LOCUS, _("Recursive inclusion"));
return 1;
}
if ((ctx = ctx_lookup(&st))) {
parse_error_locus(&LOCUS, _("Recursive inclusion"));
if (ctx->prev)
parse_error_locus(&ctx->prev->locus,
_("`%s' already included here"),
name);
else
parse_error_locus(&LOCUS,
_("`%s' already included at top level"),
name);
return 1;
}
}
if (once && source_lookup(&st))
return -1;
fp = fopen(name, "r");
if (!fp) {
parse_error_locus(&LOCUS, _("Cannot open `%s': %s"),
name, mu_strerror(errno));
return 1;
}
/* Push current context */
ctx = xmalloc (sizeof (*ctx));
lit = string_alloc(name, strlen(name));
lit->flags |= VAR_REFERENCED;
ctx->locus.file = lit->text;
ctx->locus.line = 1;
ctx->namelen = strlen(ctx->locus.file);
ctx->id.i_node = st.st_ino;
ctx->id.device = st.st_dev;
ctx->infile = fp;
ctx->prev = context_stack;
context_stack = ctx;
if (yy_flex_debug)
fprintf(stderr, "Processing file `%s'\n", name);
pp_line_stmt();
return 0;
}
static int
pop_source()
{
struct buffer_ctx *ctx;
if (!context_stack)
return 1;
fclose(INFILE);
/* Restore previous context */
ctx = context_stack->prev;
free(context_stack);
context_stack = ctx;
if (!context_stack) {
if (yy_flex_debug)
fprintf(stderr, "End of input\n");
return 1;
}
LOCUS.line++;
if (yy_flex_debug)
fprintf(stderr, "Resuming file `%s' at line %lu\n",
LOCUS.file, (unsigned long) LOCUS.line);
pp_line_stmt();
return 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_do(include_path, find_include_file, &fd);
mu_list_remove(std_include_path, cwd);
} else
mu_list_do(include_path, find_include_file, &fd);
if (!fd.found) {
mu_list_do(std_include_path, find_include_file, &fd);
if (!fd.found && err_not_found) {
parse_error_locus(&LOCUS,
_("%s: No such file or directory"), name);
*newp = NULL;
}
}
if (fd.found)
*newp = fd.buf;
return fd.found;
}
static int
parse_include(const char *text, int once)
{
int argc;
char **argv;
char *tmp = NULL;
char *p = NULL;
int rc = 1;
if (mu_argcv_get(text, "", NULL, &argc, &argv))
parse_error_locus(&LOCUS, _("Cannot parse include line"));
else if (argc != 2)
parse_error_locus(&LOCUS, _("invalid include statement"));
else {
size_t len;
int allow_cwd;
p = argv[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 = push_source(p, once);
free(tmp);
mu_argcv_free(argc, argv);
return rc;
}
#define DEFAULT_SUFFIX ".mf"
static int
parse_require(const char *text)
{
int argc;
char **argv;
char *tmp = NULL;
char *p = NULL;
int rc = 1;
if (mu_argcv_get(text, "", NULL, &argc, &argv))
parse_error_locus(&LOCUS, _("cannot parse require line"));
else if (argc != 2)
parse_error_locus(&LOCUS, _("invalid require statement"));
else {
size_t len;
char *fname;
p = argv[1];
len = strlen(p);
/* For compatibility with #include */
if (p[0] == '<' && p[len - 1] == '>') {
p[--len] = 0;
p++;
}
if (len < sizeof DEFAULT_SUFFIX
|| memcmp(p + len - sizeof DEFAULT_SUFFIX - 1,
DEFAULT_SUFFIX,
sizeof DEFAULT_SUFFIX - 1)) {
fname = xmalloc(len + sizeof DEFAULT_SUFFIX);
strcpy(fname, p);
strcat(fname, DEFAULT_SUFFIX);
p = fname;
} else
fname = NULL;
if (p[0] != '/') {
if (try_file(p, 0, 1, &tmp))
p = tmp;
else
p = NULL;
free(fname);
} else if (fname) {
tmp = fname;
fname = NULL;
}
}
if (p)
rc = push_source(p, 1);
free(tmp);
mu_argcv_free(argc, argv);
return rc;
}
static int
assign_locus(struct locus *ploc, char *name, char *line)
{
char *p;
if (name) {
struct literal *lit = string_alloc(name, strlen(name));
lit->flags |= VAR_REFERENCED;
ploc->file = lit->text;
}
ploc->line = strtoul(line, &p, 10);
return *p != 0;
}
int
parse_line(char *text, struct locus *ploc)
{
int rc = 1;
int argc;
char **argv;
while (*text && isspace (*text))
text++;
text++;
if (mu_argcv_get(text, "", NULL, &argc, &argv))
parse_error(_("cannot parse #line line"));
else {
if (argc == 2)
rc = assign_locus(ploc, NULL, argv[1]);
else if (argc == 3)
rc = assign_locus(ploc, argv[2], argv[1]);
else
parse_error(_("invalid #line statement"));
if (rc)
parse_error(_("malformed #line statement"));
}
mu_argcv_free(argc, argv);
return rc;
}
void
parse_line_cpp(char *text, struct locus *ploc)
{
int argc;
char **argv;
if (mu_argcv_get(text, "", NULL, &argc, &argv))
parse_error(_("cannot parse #line line"));
else if (argc < 3)
parse_error(_("invalid #line statement"));
else {
if (assign_locus(ploc, argv[2], argv[1]))
parse_error(_("malformed #line statement"));
}
mu_argcv_free(argc, argv);
}
int
pp_init(char *name)
{
return push_source(name, 0);
}
void
pp_done()
{
free(linebuf);
free(putback_buffer);
}
static void
copy(FILE *infile, FILE *outfile)
{
char *buf = NULL;
size_t bufsize;
fseek(infile, 0, SEEK_END);
bufsize = ftell(infile);
fseek(infile, 0, SEEK_SET);
if (bufsize == 0)
bufsize = BUFSIZ;
do {
buf = malloc(bufsize);
} while (buf == NULL && (bufsize /= 2) != 0);
if (!buf) {
mu_error("%s", mu_strerror(errno));
return;
}
while (1) {
size_t rsize = fread(buf, 1, bufsize, infile);
if (rsize == 0)
break;
while (rsize) {
size_t wsize = fwrite(buf, 1, rsize, outfile);
if (wsize == 0)
break;
rsize -= wsize;
}
}
free(buf);
}
int
preprocess_input(char *extpp)
{
size_t i;
char buffer[512];
if (pp_init(script_file))
return EX_NOINPUT;
if (extpp) {
FILE *outfile;
char *setup_file;
debug1(1, "Running preprocessor: `%s'", extpp);
outfile = popen(extpp, "w");
if (!outfile) {
mu_error(_("Unable to start external preprocessor `%s': %s"),
extpp,
mu_strerror(errno));
return EX_OSFILE;
}
if (try_file("pp-setup", 1, 0, &setup_file)) {
FILE *infile = fopen(setup_file, "r");
if (!infile)
mu_error(_("Cannot open preprocessor setup file `%s': %s"),
setup_file, mu_strerror(errno));
else {
debug1(1, "Using preprocessor setup file `%s'",
setup_file);
copy(infile, outfile);
fclose(infile);
}
free(setup_file);
}
while (i = pp_fill_buffer(buffer, sizeof buffer))
fwrite(buffer, 1, i, outfile);
pclose(outfile);
} else {
while (i = pp_fill_buffer(buffer, sizeof buffer))
fwrite(buffer, 1, i, stdout);
}
pp_done();
return 0;
}
void
pp_make_argcv(int *pargc, char ***pargv)
{
size_t n = 0;
int argc;
char **argv;
mu_iterator_t itr;
extern char *program_invocation_name;
mu_list_count(include_path, &n);
argc = 6 + 2 * n;
argv = xcalloc(argc + 1, sizeof argv[0]);
argc = 0;
argv[argc++] = program_invocation_name;
argv[argc++] = "-E";
argv[argc++] = "--preprocessor";
argv[argc++] = ext_pp;
argv[argc++] = log_to_stderr ? "--stderr" : "--syslog";
if (mu_list_get_iterator(include_path, &itr) == 0) {
for (mu_iterator_first(itr);
!mu_iterator_is_done(itr);
mu_iterator_next(itr)) {
argv[argc++] = "-I";
mu_iterator_current(itr,
(void **)&argv[argc++]);
}
mu_iterator_destroy(&itr);
}
argv[argc++] = script_file;
argv[argc] = NULL;
*pargc = argc;
*pargv = argv;
}
FILE *
pp_extrn_start(int argc, char **argv, pid_t *ppid)
{
int pout[2];
pid_t pid;
int i;
char *ppcmd = "unknown";
FILE *fp = NULL;
mu_argcv_string(argc, argv, &ppcmd);
debug1(1, "Running preprocessor: `%s'", ppcmd);
pipe(pout);
switch (pid = fork()) {
/* The child branch. */
case 0:
if (pout[1] != 1) {
close(1);
dup2(pout[1], 1);
}
/* Close unneded descripitors */
for (i = getmaxfd(); i > 2; i--)
close(i);
if (!log_to_stderr) {
int p[2];
char *buf = NULL;
size_t size = 0;
FILE *fp;
signal(SIGCHLD, SIG_DFL);
pipe(p);
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 */
log_setup(log_to_stderr);
mu_error("Cannot run `%s': %s",
ppcmd, mu_strerror(errno));
exit(127);
default:
/* Sub-master */
close(p[1]);
fp = fdopen(p[0], "r");
log_setup(log_to_stderr);
while (getline(&buf, &size, fp) > 0)
mu_error("%s", buf);
exit(0);
}
} else {
execvp(argv[0], argv);
log_setup(log_to_stderr);
mu_error("Cannot run `%s': %s",
ppcmd, mu_strerror(errno));
exit(127);
}
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;
}
*ppid = pid;
return fp;
}
void
pp_extrn_shutdown(pid_t pid)
{
int status;
waitpid(pid, &status, 0);
}