/* wydawca - automatic release submission daemon Copyright (C) 2007-2008, 2010-2012 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 file_triplet *trp) { size_t dcount, i, j; char *p; if (debug_level > 2) logmsg (LOG_DEBUG, _("%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) { logmsg (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 file_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 file_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 file_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 file_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 file_triplet *trp, unsigned from, unsigned to) { const char *val; unsigned version; if (directive_get_value (trp, "version", &val)) { logmsg (LOG_ERR, _("%s: missing `version' directive"), trp->file[file_directive].name); return 0; } if (directive_pack_version (val, &version)) { logmsg (LOG_ERR, _("%s: unparsable version: %s"), trp->file[file_directive].name, val); return 0; } logmsg (LOG_NOTICE, _("%s: VERSION: %s"), trp->file[file_directive].name, val); trp->version = version; if (from <= version && version <= to) return 1; logmsg (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 file_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")) { logmsg (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")) { logmsg (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) { logmsg (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)) { logmsg (LOG_ERR, _("%s: missing `filename' directive"), trp->file[file_directive].name); return 1; } if (strcmp (filename, trp->file[file_dist].name)) { logmsg (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) { logmsg (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) { logmsg (LOG_CRIT, _("error writing to temporary script file %s: %s"), file_name, strerror (errno)); break; } if (wrb == 0) { logmsg (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)) { logmsg (LOG_CRIT, "redirector pipe: %s", strerror (errno)); return -1; } pid = fork (); if (pid == -1) { logmsg (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); logmsg (LOG_NOTICE, "%s: %s", tag, buf); } _exit (0); } close (p[0]); return p[1]; } static int run_check_script (const char *script, struct file_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; if (debug_level > 1) logmsg (LOG_DEBUG, _("prep script: %20.20s%s"), script, strlen (script) > 20 ? "..." : ""); script_file = save_script (script); if (!script_file) return 1; if (debug_level > 1) logmsg (LOG_DEBUG, _("script file: %s"), script_file); if (pipe (p)) { logmsg (LOG_CRIT, "pipe: %s", strerror (errno)); return 1; } oldsig = signal (SIGCHLD, SIG_DFL); pid = fork (); if (pid == -1) { logmsg (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) { logmsg (LOG_CRIT, "cannot duplicate script's stdout: %s", strerror (errno)); _exit (127); } if (efd != 2 && dup2 (efd, 2) != 2) { logmsg (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", spool->dest_dir, 1); setenv ("WYDAWCA_URL", spool->url, 1); setenv ("WYDAWCA_TRIPLET_BASE", trp->name, 1); setenv ("WYDAWCA_DIST_FILE", trp->file[file_dist].name, 1); chdir (temp_homedir); 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; if (debug_level > 2) logmsg (LOG_DEBUG, _("reading script output...")); while (grecs_getline (&buf, &size, fp) > 0) { size_t len = strlen (buf); if (debug_level > 2) logmsg (LOG_DEBUG, _("read: %s"), buf); txtacc_grow (trp->acc, buf, len); total += size; } txtacc_1grow (trp->acc, 0); if (debug_level > 2) logmsg (LOG_DEBUG, _("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) { logmsg (LOG_ERR, "%s for %s@%s returned %d", descr, trp->name, trp->spool->tag, status); return 1; } else if (debug_level > 2) logmsg (LOG_DEBUG, "%s for %s@%s returned %d", descr, trp->name, trp->spool->tag, status); } else if (WIFSIGNALED (status)) { int sig = WTERMSIG (status); logmsg (LOG_NOTICE, "%s for %s@%s terminated on signal %d", descr, trp->name, trp->spool->tag, sig); return 1; } else { logmsg (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 file_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, ev_check_fail); } return rc; } /* Process the directives from TRP */ int process_directives (struct file_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: logmsg (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 { logmsg (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: { int rc = 0; struct wordsplit ws; if (wordsplit (val, &ws, WRDSF_DEFFLAGS)) { logmsg (LOG_ERR, _("cannot parse symlink value `%s'"), val); return 1; } if (ws.ws_wordc != 2) { rc = 1; logmsg (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); if (rc) return 1; } break; case rmsymlink_dir: if (rmsymlink_file (trp, val)) return 1; case replace_dir: if (trp->version < 102) { rc = 1; logmsg (LOG_ERR, _("\"replace\" directive is invalid for version %d"), trp->version); } } } if (!dry_run_mode && unlink (trp->file[file_directive].name)) { logmsg (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, ev_success); return 0; }