/* 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 assing_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 = assing_locus(ploc, NULL, argv[1]); else if (argc == 3) rc = assing_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 (assing_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); }