/** \ingroup rpmbuild * \file build/parseChangelog.c * Parse %changelog section from spec file. */ #include "system.h" #include "rpmbuild.h" #include "debug.h" void addChangelogEntry(Header h, time_t time, const char *name, const char *text) { int_32 mytime = time; /* XXX convert to header representation */ if (headerIsEntry(h, RPMTAG_CHANGELOGTIME)) { (void) headerAppendEntry(h, RPMTAG_CHANGELOGTIME, RPM_INT32_TYPE, &mytime, 1); (void) headerAppendEntry(h, RPMTAG_CHANGELOGNAME, RPM_STRING_ARRAY_TYPE, &name, 1); (void) headerAppendEntry(h, RPMTAG_CHANGELOGTEXT, RPM_STRING_ARRAY_TYPE, &text, 1); } else { (void) headerAddEntry(h, RPMTAG_CHANGELOGTIME, RPM_INT32_TYPE, &mytime, 1); (void) headerAddEntry(h, RPMTAG_CHANGELOGNAME, RPM_STRING_ARRAY_TYPE, &name, 1); (void) headerAddEntry(h, RPMTAG_CHANGELOGTEXT, RPM_STRING_ARRAY_TYPE, &text, 1); } } /** * Parse date string to seconds. * @param datestr date string (e.g. 'Wed Jan 1 1997') * @retval secs secs since the unix epoch * @return 0 on success, -1 on error */ static int dateToTimet(const char * datestr, /*@out@*/ time_t * secs) /*@modifies *secs @*/ { struct tm time; char * p, * pe, * q, ** idx; char * date = strcpy(alloca(strlen(datestr) + 1), datestr); /*@observer@*/ static char * days[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", NULL }; /*@observer@*/ static char * months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL }; /*@observer@*/ static char lengths[] = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; memset(&time, 0, sizeof(time)); pe = date; /* day of week */ p = pe; SKIPSPACE(p); if (*p == '\0') return -1; pe = p; SKIPNONSPACE(pe); if (*pe != '\0') *pe++ = '\0'; for (idx = days; *idx && strcmp(*idx, p); idx++) {}; if (*idx == NULL) return -1; /* month */ p = pe; SKIPSPACE(p); if (*p == '\0') return -1; pe = p; SKIPNONSPACE(pe); if (*pe != '\0') *pe++ = '\0'; for (idx = months; *idx && strcmp(*idx, p); idx++) {}; if (*idx == NULL) return -1; time.tm_mon = idx - months; /* day */ p = pe; SKIPSPACE(p); if (*p == '\0') return -1; pe = p; SKIPNONSPACE(pe); if (*pe != '\0') *pe++ = '\0'; /* make this noon so the day is always right (as we make this UTC) */ time.tm_hour = 12; time.tm_mday = strtol(p, &q, 10); if (!(q && *q == '\0')) return -1; if (time.tm_mday < 0 || time.tm_mday > lengths[time.tm_mon]) return -1; /* year */ p = pe; SKIPSPACE(p); if (*p == '\0') return -1; pe = p; SKIPNONSPACE(pe); if (*pe != '\0') *pe++ = '\0'; time.tm_year = strtol(p, &q, 10); if (!(q && *q == '\0')) return -1; if (time.tm_year < 1997 || time.tm_year >= 3000) return -1; time.tm_year -= 1900; *secs = mktime(&time); if (*secs == -1) return -1; /* adjust to GMT */ *secs += timezone; return 0; } /** * Add %changelog section to header. * @param h header * @param sb changelog strings * @return 0 on success */ static int addChangelog(Header h, StringBuf sb) /*@modifies h @*/ { char *s; int i; time_t time; time_t lastTime = 0; char *date, *name, *text, *next; s = getStringBuf(sb); /* skip space */ SKIPSPACE(s); while (*s != '\0') { if (*s != '*') { rpmError(RPMERR_BADSPEC, _("%%changelog entries must start with *\n")); return RPMERR_BADSPEC; } /* find end of line */ date = s; while(*s && *s != '\n') s++; if (! *s) { rpmError(RPMERR_BADSPEC, _("incomplete %%changelog entry\n")); return RPMERR_BADSPEC; } /*@-modobserver@*/ *s = '\0'; /*@=modobserver@*/ text = s + 1; /* 4 fields of date */ date++; s = date; for (i = 0; i < 4; i++) { SKIPSPACE(s); SKIPNONSPACE(s); } SKIPSPACE(date); if (dateToTimet(date, &time)) { rpmError(RPMERR_BADSPEC, _("bad date in %%changelog: %s\n"), date); return RPMERR_BADSPEC; } if (lastTime && lastTime < time) { rpmError(RPMERR_BADSPEC, _("%%changelog not in descending chronological order\n")); return RPMERR_BADSPEC; } lastTime = time; /* skip space to the name */ SKIPSPACE(s); if (! *s) { rpmError(RPMERR_BADSPEC, _("missing name in %%changelog\n")); return RPMERR_BADSPEC; } /* name */ name = s; while (*s != '\0') s++; while (s > name && xisspace(*s)) { *s-- = '\0'; } if (s == name) { rpmError(RPMERR_BADSPEC, _("missing name in %%changelog\n")); return RPMERR_BADSPEC; } /* text */ SKIPSPACE(text); if (! *text) { rpmError(RPMERR_BADSPEC, _("no description in %%changelog\n")); return RPMERR_BADSPEC; } /* find the next leading '*' (or eos) */ s = text; do { s++; } while (*s && (*(s-1) != '\n' || *s != '*')); next = s; s--; /* backup to end of description */ while ((s > text) && xisspace(*s)) { *s-- = '\0'; } addChangelogEntry(h, time, name, text); s = next; } return 0; } int parseChangelog(Spec spec) { int nextPart, res, rc; char *fmt_name = NULL, *fmt_text = NULL, *fmt_time = NULL; StringBuf sb = newStringBuf(); /* There are no options to %changelog */ if ((rc = readLine(spec, STRIP_COMMENTS)) == 1) { sb = freeStringBuf(sb); return PART_NONE; } if (rc) return rc; while (! (nextPart = isPart(spec->line))) { appendStringBuf(sb, spec->line); if ((rc = readLine(spec, STRIP_COMMENTS)) == 1) { nextPart = PART_NONE; break; } if (rc) return rc; } if ((fmt_name = getenv("RPM_ADD_CHANGELOG_NAME")) && (fmt_text = getenv("RPM_ADD_CHANGELOG_TEXT")) && (fmt_time = getenv("RPM_ADD_CHANGELOG_TIME"))) { const char *errstr = NULL; char *end = NULL; time_t time; long long ltime = strtoll(fmt_time, &end, 0); const long long tmax = (1ULL << (sizeof(size_t) * 8 - 1)) - 1; if (ltime > 0 && ltime < tmax && !*end) { time = ltime; } else { rpmlog(RPMLOG_WARNING, "RPM_ADD_CHANGELOG_TIME cannot be converted from %s.\n", fmt_time); time = 0; } char *name = headerSprintf(spec->packages->header, fmt_name, rpmTagTable, rpmHeaderFormats, &errstr); if (!name) rpmlog(RPMLOG_WARNING, "RPM_ADD_CHANGELOG_NAME: %s\n", errstr); char *text = headerSprintf(spec->packages->header, fmt_text, rpmTagTable, rpmHeaderFormats, &errstr); if (!text) rpmlog(RPMLOG_WARNING, "RPM_ADD_CHANGELOG_TEXT: %s\n", errstr); if (time != 0 && name && text) addChangelogEntry(spec->packages->header, time, name, text); free(name); free(text); } else if (fmt_name || fmt_text || fmt_time) { rpmlog(RPMLOG_WARNING, "Each of RPM_ADD_CHANGELOG_NAME, RPM_ADD_CHANGELOG_TEXT and RPM_ADD_CHANGELOG_TIME" " environment variables should be set to add a changelog entry.\n"); } res = addChangelog(spec->packages->header, sb); sb = freeStringBuf(sb); return (res) ? res : nextPart; }