/* wydawca - automatic release submission daemon Copyright (C) 2007-2008, 2010-2013 Sergey Poznyakoff Wydawca 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 of the License, or (at your option) any later version. Wydawca 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 wydawca. If not, see . */ #include "wydawca.h" /* Directive file support */ /* Parse directives from TRP->blurb. Fill TRP->directive */ int directive_parse(struct wy_triplet *trp) { size_t dcount, i, j; char *p; wy_debug(3, (_("%s: parsing directive blurb: %s"), trp->file[file_directive].name, trp->blurb)); dcount = 0; for (p = trp->blurb; *p; p++) if (*p == '\n') dcount++; trp->directive = grecs_calloc(dcount + 1, sizeof trp->directive[0]); p = trp->blurb; for (i = j = 0; i < dcount; i++) { trp->directive[j] = p; p = strchr(p, '\n'); if (p) *p++ = 0; if (trim(trp->directive[j]) == 0) /* ignore empty lines */ continue; if (strchr(trp->directive[j], ':') == NULL) { wy_log(LOG_ERR, _("%s: invalid line: %s"), trp->file[file_directive].name, trp->directive[j]); free(trp->directive); trp->directive = NULL; return 1; } j++; if (!p) break; } trp->directive[j] = NULL; return 0; } /* If a directive KEY exists in the triplet TRP, return 0 and point PVAL (unless it is NULL) to its value. Othervise, return 1. */ int directive_get_value(struct wy_triplet *trp, const char *key, const char **pval) { int keylen = strlen(key); int i; for (i = 0; trp->directive[i]; i++) { char *str = trp->directive[i]; int len = strlen(str); if (len > keylen && memcmp(str, key, keylen) == 0 && str[keylen] == ':') { str += keylen + 1; while (*str && isspace(*str)) str++; if (pval) *pval = str; return 0; } } return 1; } /* Auxiliary function for sequential access to directories from TRP. Arguments: N - Index of the current directive, TRP - Triplet, PKEY, PVAL - Return addresses. The function points PKEY and PVAL to the keyword and value of the Nth directive, and returns N + 1. If N points past all the directive, the function returns 0. */ static int _directive_seq_get(int n, struct wy_triplet *trp, const char **pkey, const char **pval) { char *p; size_t len; if (trp->directive[n] == NULL) return 0; p = strchr(trp->directive[n], ':'); len = p - trp->directive[n]; if (len + 1 > trp->tmpsize) { trp->tmpsize = len + 1; trp->tmp = grecs_realloc(trp->tmp, trp->tmpsize); } memcpy(trp->tmp, trp->directive[n], len); trp->tmp[len] = 0; *pkey = trp->tmp; for (p++; *p && isspace(*p); p++) ; if (pval) *pval = p; return ++n; } /* Get the first directive from TRP. Point *PKEY to its keyword and *PVAL to its value. Return 1 on success, 0 on failure. */ int directive_first(struct wy_triplet *trp, const char **pkey, const char **pval) { int n = 0; return _directive_seq_get(n, trp, pkey, pval); } /* Get the first directive from TRP. Point *PKEY to its keyword and *PVAL to its value. Return 1 on success, 0 on failure. Return non-0 on success, 0 on failure */ int directive_next(struct wy_triplet *trp, int n, const char **pkey, const char **pval) { return _directive_seq_get(n, trp, pkey, pval); } /* Pack a directive string VAL into an unsigned number */ int directive_pack_version(const char *val, unsigned *pversion) { char *p; unsigned v; v = strtoul(val, &p, 10); if (*p != '.') return 1; p++; v *= 100; v += strtoul(p, &p, 10); if (*p && *p != '.') return 1; *pversion = v; return 0; } int directive_unpack_version(unsigned version, char **pbuf, size_t * psize) { return grecs_asprintf(pbuf, psize, "%u.%u", version / 100, version % 100); } /* Return true if the directory file version of the triplet TRP is within the inclusive range FROM and TO (packed) */ int directive_version_in_range_p(struct wy_triplet *trp, unsigned from, unsigned to) { const char *val; unsigned version; if (directive_get_value(trp, "version", &val)) { wy_log(LOG_ERR, _("%s: missing `version' directive"), trp->file[file_directive].name); return 0; } if (directive_pack_version(val, &version)) { wy_log(LOG_ERR, _("%s: unparsable version: %s"), trp->file[file_directive].name, val); return 0; } wy_log(LOG_NOTICE, _("%s: VERSION: %s"), trp->file[file_directive].name, val); trp->version = version; if (from <= version && version <= to) return 1; wy_log(LOG_ERR, _("%s: version %s is not in the allowed range"), trp->file[file_directive].name, val); return 0; } enum directive { unknown_dir, comment_dir, directory_dir, version_dir, filename_dir, rmsymlink_dir, archive_dir, symlink_dir, replace_dir }; struct directive_table { const char *name; enum directive dir; }; static struct directive_table directive_table[] = { { "comment", comment_dir }, { "directory", directory_dir }, { "version", version_dir }, { "filename", filename_dir }, { "symlink", symlink_dir }, { "rmsymlink", rmsymlink_dir }, { "archive", archive_dir }, { "replace", replace_dir }, { NULL } }; static enum directive find_directive(const char *key) { int i; for (i = 0; directive_table[i].name; i++) if (strcmp(directive_table[i].name, key) == 0) return directive_table[i].dir; return unknown_dir; } /* Return 0 if the directory file format of the triplet TRP is OK. */ int verify_directive_format(struct wy_triplet *trp) { int n, dnum; const char *key; if (!directive_version_in_range_p(trp, min_directive_version, max_directive_version)) return 1; dnum = 0; for (n = directive_first(trp, &key, NULL); n; n = directive_next(trp, n, &key, NULL)) { if (strcmp(key, "comment") == 0) continue; dnum++; switch (dnum) { case 1: if (strcmp(key, "version")) { wy_log(LOG_ERR, _("%s:%d: expected `%s' but found `%s'"), trp->file[file_directive].name, n, "version", key); return 1; } break; case 2: if (strcmp(key, "directory")) { wy_log(LOG_ERR, _("%s:%d: expected `%s' but found `%s'"), trp->file[file_directive].name, n, "directory", key); return 1; } break; default: if (find_directive(key) == unknown_dir) { wy_log(LOG_ERR, _("%s:%d: unknown directive `%s'"), trp->file[file_directive].name, n, key); return 1; } } } if (trp->file[file_dist].name && trp->file[file_signature].name) { const char *filename; if (directive_get_value(trp, "filename", &filename)) { wy_log(LOG_ERR, _("%s: missing `filename' directive"), trp->file[file_directive].name); return 1; } if (strcmp(filename, trp->file[file_dist].name)) { wy_log(LOG_ERR, _("%s: filename %s does not match actual name"), trp->file[file_dist].name, filename); return 1; } } return 0; } static char * save_script(const char *script) { char *file_name; mode_t old_mask; int fd; size_t length; file_name = concat_dir(temp_homedir, "chkXXXXXX", NULL); old_mask = umask(0077); fd = mkstemp(file_name); umask(old_mask); if (fd == -1) { wy_log(LOG_CRIT, _("cannot create temporary script file %s: %s"), file_name, strerror(errno)); free(file_name); return NULL; } length = strlen(script); while (length) { ssize_t wrb = write(fd, script, length); if (wrb == -1) { wy_log(LOG_CRIT, _("error writing to temporary script " "file %s: %s"), file_name, strerror(errno)); break; } if (wrb == 0) { wy_log(LOG_CRIT, _("short write to temporary script file %s"), file_name); break; } length -= wrb; script += wrb; } close(fd); if (length) { free(file_name); return NULL; } return file_name; } static int stderr_redirector(const char *tag) { int p[2]; pid_t pid; if (pipe(p)) { wy_log(LOG_CRIT, "redirector pipe: %s", strerror(errno)); return -1; } pid = fork(); if (pid == -1) { wy_log(LOG_CRIT, "redirector fork: %s", strerror(errno)); return -1; } if (pid == 0) { FILE *fp; size_t size = 0; char *buf = NULL; close(p[1]); fp = fdopen(p[0], "r"); if (!fp) _exit(127); while (grecs_getline(&buf, &size, fp) >= 0) { trim_crlf(buf); wy_log(LOG_NOTICE, "%s: %s", tag, buf); } _exit(0); } close(p[0]); return p[1]; } static int run_check_script(const char *script, struct wy_triplet *trp, const char *descr) { static char *script_file; pid_t pid; int status; int p[2]; RETSIGTYPE(*oldsig) (); FILE *fp; char *buf; size_t size, total; wy_debug(2, (_("prep script: %20.20s%s"), script, strlen(script) > 20 ? "..." : "")); script_file = save_script(script); if (!script_file) return 1; wy_debug(2, (_("script file: %s"), script_file)); if (pipe(p)) { wy_log(LOG_CRIT, "pipe: %s", strerror(errno)); return 1; } oldsig = signal(SIGCHLD, SIG_DFL); pid = fork(); if (pid == -1) { wy_log(LOG_CRIT, "fork: %s", strerror(errno)); free(script_file); close(p[0]); close(p[1]); signal(SIGCHLD, oldsig); return 1; } if (pid == 0) { int i; int efd; char *argv[4]; const struct spool *spool = trp->spool; signal(SIGHUP, SIG_DFL); signal(SIGTERM, SIG_DFL); signal(SIGQUIT, SIG_DFL); signal(SIGINT, SIG_DFL); signal(SIGCHLD, SIG_DFL); signal(SIGALRM, SIG_DFL); efd = stderr_redirector(script_file); if (efd == -1) _exit(127); for (i = getdtablesize(); i >= 0; i--) { if (i != p[1] && i != efd) close(i); } if (p[1] != 1 && dup2(p[1], 1) != 1) { wy_log(LOG_CRIT, "cannot duplicate script's stdout: %s", strerror(errno)); _exit(127); } if (efd != 2 && dup2(efd, 2) != 2) { wy_log(LOG_CRIT, "cannot duplicate script's stderr: %s", strerror(errno)); _exit(127); } setenv("WYDAWCA_SPOOL", spool->tag, 1); setenv("WYDAWCA_SOURCE", spool->source_dir, 1); setenv("WYDAWCA_DEST", wy_url_path(spool->dest_url), 1); setenv("WYDAWCA_URL", spool->url, 1); setenv("WYDAWCA_TRIPLET_BASE", trp->name, 1); setenv("WYDAWCA_DIST_FILE", trp->file[file_dist].name, 1); if (chdir(temp_homedir)) { wy_log(LOG_CRIT, "cannot change to %s: %s", temp_homedir, strerror(errno)); _exit(127); } argv[0] = "sh"; argv[1] = script_file; argv[2] = NULL; execv("/bin/sh", argv); _exit(127); } /* Master */ free(script_file); close(p[1]); fp = fdopen(p[0], "r"); buf = NULL; size = total = 0; wy_debug(3, (_("reading script output..."))); while (grecs_getline(&buf, &size, fp) > 0) { size_t len = strlen(buf); wy_debug(3, (_("read: %s"), buf)); txtacc_grow(trp->acc, buf, len); total += size; } txtacc_1grow(trp->acc, 0); wy_debug(3, (_("bytes read: %lu"), (unsigned long)total)); fclose(fp); waitpid(pid, &status, 0); signal(SIGCHLD, oldsig); if (total) trp->check_diag = txtacc_finish(trp->acc, 0); trp->check_result = status; if (WIFEXITED(status)) { status = WEXITSTATUS(status); if (status) { wy_log(LOG_ERR, "%s for %s@%s returned %d", descr, trp->name, trp->spool->tag, status); return 1; } else wy_debug(3, ("%s for %s@%s returned %d", descr, trp->name, trp->spool->tag, status)); } else if (WIFSIGNALED(status)) { int sig = WTERMSIG(status); wy_log(LOG_NOTICE, "%s for %s@%s terminated on signal %d", descr, trp->name, trp->spool->tag, sig); return 1; } else { wy_log(LOG_NOTICE, "%s for %s@%s terminated with unhandled status", descr, trp->name, trp->spool->tag); return 1; } return 0; } static int external_check(struct wy_triplet *trp) { int rc; const struct spool *spool = trp->spool; char *file; if (!trp->file[file_dist].name) return 0; if (!spool->check_script && !default_check_script) return 0; file = concat_dir(temp_homedir, trp->file[file_dist].name, NULL); if (copy_file(trp->file[file_dist].name, file)) { free(file); return 1; } rc = 0; if (spool->check_script) rc |= run_check_script(spool->check_script, trp, _("spool check script")); if (rc == 0 && default_check_script) rc |= run_check_script(default_check_script, trp, _("default check script")); free(file); if (rc) { UPDATE_STATS(STAT_CHECK_FAIL); notify(spool->notification, trp, wy_ev_check_fail); } return rc; } static int symlink_filelist(struct wy_triplet *trp, const char *key, const char *val) { int rc = 0; struct wordsplit ws; if (wordsplit(val, &ws, WRDSF_DEFFLAGS)) { wy_log(LOG_ERR, _("cannot parse symlink value `%s'"), val); return 1; } if (ws.ws_wordc != 2) { rc = 1; wy_log(LOG_ERR, _("wrong number of arguments to %s directive: `%s'"), key, val); } else rc = symlink_file(trp, ws.ws_wordv[0], ws.ws_wordv[1]); wordsplit_free(&ws); return rc; } /* Process the directives from TRP */ int process_directives(struct wy_triplet *trp) { int rc, n; const char *key, *val; const struct spool *spool; ASGN_SPOOL(spool, trp, return 1); UPDATE_STATS(STAT_COMPLETE_TRIPLETS); timer_start("triplet"); report_init(); for (n = directive_first(trp, &key, &val); n; n = directive_next(trp, n, &key, &val)) { enum directive d = find_directive(key); switch (d) { case unknown_dir: /* should not happen */ abort(); case comment_dir: wy_log(LOG_NOTICE, _("%s: COMMENT: %s"), trp->file[file_directive].name, val); break; case directory_dir: /* already processed (see fill_project_name in verify.c */ break; case filename_dir: rc = verify_detached_signature(trp); if (rc == 0) { if (external_check(trp)) return 1; if (move_file(trp, file_dist) || move_file(trp, file_signature)) return 1; } else { wy_log(LOG_ERR, _("invalid detached signature for %s"), trp->name); return 1; } break; case version_dir: /* Already processed. See directive_version_in_range_p, called by verify_directive_format */ break; case archive_dir: if (archive_file(trp, val)) return 1; break; case symlink_dir: if (symlink_filelist(trp, key, val)) return 1; break; case rmsymlink_dir: if (rmsymlink_file(trp, val)) return 1; case replace_dir: if (trp->version < 102) { rc = 1; wy_log(LOG_ERR, _("\"replace\" directive is invalid " "for version %d"), trp->version); } } } if (!wy_dry_run && unlink(trp->file[file_directive].name)) { wy_log(LOG_CRIT, _("%s: cannot unlink directive file: %s"), trp->file[file_directive].name, strerror(errno)); } UPDATE_STATS(STAT_TRIPLET_SUCCESS); report_finish(); timer_stop("triplet"); notify(spool->notification, trp, wy_ev_success); return 0; }