|
|
2ff057 |
/** \ingroup rpmbuild
|
|
|
2ff057 |
* \file build/parseChangelog.c
|
|
|
2ff057 |
* Parse %changelog section from spec file.
|
|
|
2ff057 |
*/
|
|
|
2ff057 |
|
|
|
2ff057 |
#include "system.h"
|
|
|
2ff057 |
|
|
|
2ff057 |
#include <rpm/header.h>
|
|
|
2ff057 |
#include <rpm/rpmlog.h>
|
|
|
2ff057 |
#include "build/rpmbuild_internal.h"
|
|
|
2ff057 |
#include "debug.h"
|
|
|
2ff057 |
|
|
|
2ff057 |
#define SKIPSPACE(s) { while (*(s) && risspace(*(s))) (s)++; }
|
|
|
2ff057 |
#define SKIPNONSPACE(s) { while (*(s) && !risspace(*(s))) (s)++; }
|
|
|
2ff057 |
|
|
|
2ff057 |
static void addChangelogEntry(Header h, time_t time, const char *name, const char *text)
|
|
|
2ff057 |
{
|
|
|
2ff057 |
rpm_time_t mytime = time; /* XXX convert to header representation */
|
|
|
2ff057 |
|
|
|
2ff057 |
headerPutUint32(h, RPMTAG_CHANGELOGTIME, &mytime, 1);
|
|
|
2ff057 |
headerPutString(h, RPMTAG_CHANGELOGNAME, name);
|
|
|
2ff057 |
headerPutString(h, RPMTAG_CHANGELOGTEXT, text);
|
|
|
2ff057 |
}
|
|
|
2ff057 |
|
|
|
2ff057 |
static int sameDate(const struct tm *ot, const struct tm *nt)
|
|
|
2ff057 |
{
|
|
|
2ff057 |
return (ot->tm_year == nt->tm_year &&
|
|
|
2ff057 |
ot->tm_mon == nt->tm_mon &&
|
|
|
2ff057 |
ot->tm_mday == nt->tm_mday &&
|
|
|
2ff057 |
ot->tm_wday == nt->tm_wday);
|
|
|
2ff057 |
}
|
|
|
2ff057 |
|
|
|
2ff057 |
/**
|
|
|
2ff057 |
* Parse date string to seconds.
|
|
|
2ff057 |
* accepted date formats are "Mon Jun 6 2016" (original one)
|
|
|
2ff057 |
* and "Thu Oct 6 06:48:39 CEST 2016" (extended one)
|
|
|
2ff057 |
* @param datestr date string (e.g. 'Wed Jan 1 1997')
|
|
|
2ff057 |
* @retval secs secs since the unix epoch
|
|
|
2ff057 |
* @return 0 on success, -1 on error
|
|
|
2ff057 |
*/
|
|
|
2ff057 |
static int dateToTimet(const char * datestr, time_t * secs, int * date_words)
|
|
|
2ff057 |
{
|
|
|
2ff057 |
int rc = -1; /* assume failure */
|
|
|
2ff057 |
struct tm time, ntime;
|
|
|
2ff057 |
const char * const * idx;
|
|
|
2ff057 |
char *p, *pe, *q, *date, *tz;
|
|
|
2ff057 |
char tz_name[10]; /* name of timezone (if extended format is used) */
|
|
|
2ff057 |
|
|
|
2ff057 |
static const char * const days[] =
|
|
|
2ff057 |
{ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", NULL };
|
|
|
2ff057 |
static const char * const months[] =
|
|
|
2ff057 |
{ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
|
|
2ff057 |
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL };
|
|
|
2ff057 |
static const char lengths[] =
|
|
|
2ff057 |
{ 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
|
|
|
2ff057 |
|
|
|
2ff057 |
memset(&time, 0, sizeof(time));
|
|
|
2ff057 |
memset(&ntime, 0, sizeof(ntime));
|
|
|
2ff057 |
|
|
|
2ff057 |
date = xstrdup(datestr);
|
|
|
2ff057 |
pe = date;
|
|
|
2ff057 |
|
|
|
2ff057 |
/* day of week */
|
|
|
2ff057 |
p = pe; SKIPSPACE(p);
|
|
|
2ff057 |
if (*p == '\0') goto exit;
|
|
|
2ff057 |
pe = p; SKIPNONSPACE(pe); if (*pe != '\0') *pe++ = '\0';
|
|
|
2ff057 |
for (idx = days; *idx && !rstreq(*idx, p); idx++)
|
|
|
2ff057 |
{};
|
|
|
2ff057 |
if (*idx == NULL) goto exit;
|
|
|
2ff057 |
time.tm_wday = idx - days;
|
|
|
2ff057 |
|
|
|
2ff057 |
/* month */
|
|
|
2ff057 |
p = pe; SKIPSPACE(p);
|
|
|
2ff057 |
if (*p == '\0') goto exit;
|
|
|
2ff057 |
pe = p; SKIPNONSPACE(pe); if (*pe != '\0') *pe++ = '\0';
|
|
|
2ff057 |
for (idx = months; *idx && !rstreq(*idx, p); idx++)
|
|
|
2ff057 |
{};
|
|
|
2ff057 |
if (*idx == NULL) goto exit;
|
|
|
2ff057 |
time.tm_mon = idx - months;
|
|
|
2ff057 |
|
|
|
2ff057 |
/* day */
|
|
|
2ff057 |
p = pe; SKIPSPACE(p);
|
|
|
2ff057 |
if (*p == '\0') goto exit;
|
|
|
2ff057 |
pe = p; SKIPNONSPACE(pe); if (*pe != '\0') *pe++ = '\0';
|
|
|
2ff057 |
|
|
|
2ff057 |
time.tm_mday = strtol(p, &q, 10);
|
|
|
2ff057 |
if (!(q && *q == '\0')) goto exit;
|
|
|
2ff057 |
if (time.tm_mday < 0 || time.tm_mday > lengths[time.tm_mon]) goto exit;
|
|
|
2ff057 |
|
|
|
2ff057 |
/* first part of year entry (original format) / time entry (extended format)*/
|
|
|
2ff057 |
p = pe;
|
|
|
2ff057 |
SKIPSPACE(p);
|
|
|
2ff057 |
if (*p == '\0')
|
|
|
2ff057 |
goto exit;
|
|
|
2ff057 |
|
|
|
2ff057 |
/* in the original format here is year record (e.g. 1999),
|
|
|
2ff057 |
* in the extended one here is time stamp (e.g. 10:22:30).
|
|
|
2ff057 |
* Choose the format
|
|
|
2ff057 |
*/
|
|
|
2ff057 |
if ((p[1]==':') || ((p[1]!='\0') && ((p[2]==':')))) {
|
|
|
2ff057 |
/* it can be extended format */
|
|
|
2ff057 |
*date_words = 6;
|
|
|
2ff057 |
|
|
|
2ff057 |
/* second part of time entry */
|
|
|
2ff057 |
/* hours */
|
|
|
2ff057 |
time.tm_hour = strtol(p, &q, 10);
|
|
|
2ff057 |
if ( (time.tm_hour < 0) || (time.tm_hour > 23) )
|
|
|
2ff057 |
goto exit;
|
|
|
2ff057 |
if (*q!=':')
|
|
|
2ff057 |
goto exit;
|
|
|
2ff057 |
p = ++q;
|
|
|
2ff057 |
/* minutes */
|
|
|
2ff057 |
time.tm_min = strtol(p, &q, 10);
|
|
|
2ff057 |
if ( (time.tm_min < 0) || (time.tm_min > 59) )
|
|
|
2ff057 |
goto exit;
|
|
|
2ff057 |
if (*q != ':')
|
|
|
2ff057 |
goto exit;
|
|
|
2ff057 |
p = ++q;
|
|
|
2ff057 |
/* time - seconds */
|
|
|
2ff057 |
time.tm_sec = strtol(p, &q, 10);
|
|
|
2ff057 |
if ( (time.tm_sec < 0) || (time.tm_sec > 59) )
|
|
|
2ff057 |
goto exit;
|
|
|
2ff057 |
p = q;
|
|
|
2ff057 |
|
|
|
2ff057 |
/* time zone name */
|
|
|
2ff057 |
SKIPSPACE(p);
|
|
|
2ff057 |
if (*p == '\0')
|
|
|
2ff057 |
goto exit;
|
|
|
2ff057 |
pe = p;
|
|
|
2ff057 |
SKIPNONSPACE(pe);
|
|
|
2ff057 |
if (*pe != '\0')
|
|
|
2ff057 |
*pe++ = '\0';
|
|
|
2ff057 |
if (((int)(pe-p) + 1) > 9 )
|
|
|
2ff057 |
goto exit;
|
|
|
2ff057 |
strncpy(tz_name, p, (int)(pe-p));
|
|
|
2ff057 |
tz_name[(int)(pe-p)] = '\0';
|
|
|
2ff057 |
|
|
|
2ff057 |
/* first part of year entry */
|
|
|
2ff057 |
p = pe;
|
|
|
2ff057 |
SKIPSPACE(p);
|
|
|
2ff057 |
if (*p == '\0')
|
|
|
2ff057 |
goto exit;
|
|
|
2ff057 |
} else {
|
|
|
2ff057 |
*date_words = 4;
|
|
|
2ff057 |
/* the original format */
|
|
|
2ff057 |
/* make this noon so the day is always right (as we make this UTC) */
|
|
|
2ff057 |
time.tm_hour = 12;
|
|
|
2ff057 |
}
|
|
|
2ff057 |
|
|
|
2ff057 |
/* year - second part */
|
|
|
2ff057 |
pe = p;
|
|
|
2ff057 |
SKIPNONSPACE(pe);
|
|
|
2ff057 |
if (*pe != '\0')
|
|
|
2ff057 |
*pe = '\0';
|
|
|
2ff057 |
time.tm_year = strtol(p, &q, 10);
|
|
|
2ff057 |
if (!(q && *q == '\0')) goto exit;
|
|
|
2ff057 |
if (time.tm_year < 1990 || time.tm_year >= 3000) goto exit;
|
|
|
2ff057 |
time.tm_year -= 1900;
|
|
|
2ff057 |
|
|
|
2ff057 |
/* change time zone and compute calendar time representation */
|
|
|
2ff057 |
tz = getenv("TZ");
|
|
|
2ff057 |
if (tz)
|
|
|
2ff057 |
tz = xstrdup(tz);
|
|
|
2ff057 |
if (*date_words == 6) {
|
|
|
2ff057 |
/* changelog date is in read time zone */
|
|
|
2ff057 |
setenv("TZ", tz_name, 1);
|
|
|
2ff057 |
} else {
|
|
|
2ff057 |
/* changelog date is always in UTC */
|
|
|
2ff057 |
setenv("TZ", "UTC", 1);
|
|
|
2ff057 |
}
|
|
|
2ff057 |
ntime = time; /* struct assignment */
|
|
|
2ff057 |
*secs = mktime(&ntime);
|
|
|
2ff057 |
unsetenv("TZ");
|
|
|
2ff057 |
if (tz) {
|
|
|
2ff057 |
setenv("TZ", tz, 1);
|
|
|
2ff057 |
free(tz);
|
|
|
2ff057 |
}
|
|
|
2ff057 |
|
|
|
2ff057 |
if (*secs == -1) goto exit;
|
|
|
2ff057 |
|
|
|
2ff057 |
/* XXX Turn this into a hard error in a release or two */
|
|
|
2ff057 |
if (!sameDate(&time, &ntime))
|
|
|
2ff057 |
rpmlog(RPMLOG_WARNING, _("bogus date in %%changelog: %s\n"), datestr);
|
|
|
2ff057 |
|
|
|
2ff057 |
rc = 0;
|
|
|
2ff057 |
|
|
|
2ff057 |
exit:
|
|
|
2ff057 |
free(date);
|
|
|
2ff057 |
return rc;
|
|
|
2ff057 |
}
|
|
|
2ff057 |
|
|
|
2ff057 |
/**
|
|
|
2ff057 |
* Add %changelog section to header.
|
|
|
2ff057 |
* @param h header
|
|
|
2ff057 |
* @param sb changelog strings
|
|
|
2ff057 |
* @return RPMRC_OK on success
|
|
|
2ff057 |
*/
|
|
|
2ff057 |
static rpmRC addChangelog(Header h, ARGV_const_t sb)
|
|
|
2ff057 |
{
|
|
|
2ff057 |
rpmRC rc = RPMRC_FAIL; /* assume failure */
|
|
|
2ff057 |
char *s, *sp;
|
|
|
2ff057 |
int i;
|
|
|
2ff057 |
time_t time;
|
|
|
2ff057 |
time_t lastTime = 0;
|
|
|
2ff057 |
time_t trimtime = rpmExpandNumeric("%{?_changelog_trimtime}");
|
|
|
2ff057 |
char *date, *name, *text, *next;
|
|
|
2ff057 |
int date_words; /* number of words in date string */
|
|
|
2ff057 |
|
|
|
2ff057 |
s = sp = argvJoin(sb, "");
|
|
|
2ff057 |
|
|
|
2ff057 |
/* skip space */
|
|
|
2ff057 |
SKIPSPACE(s);
|
|
|
2ff057 |
|
|
|
2ff057 |
while (*s != '\0') {
|
|
|
2ff057 |
if (*s != '*') {
|
|
|
2ff057 |
rpmlog(RPMLOG_ERR, _("%%changelog entries must start with *\n"));
|
|
|
2ff057 |
goto exit;
|
|
|
2ff057 |
}
|
|
|
2ff057 |
|
|
|
2ff057 |
/* find end of line */
|
|
|
2ff057 |
date = s;
|
|
|
2ff057 |
while (*s && *s != '\n') s++;
|
|
|
2ff057 |
if (! *s) {
|
|
|
2ff057 |
rpmlog(RPMLOG_ERR, _("incomplete %%changelog entry\n"));
|
|
|
2ff057 |
goto exit;
|
|
|
2ff057 |
}
|
|
|
2ff057 |
*s = '\0';
|
|
|
2ff057 |
text = s + 1;
|
|
|
2ff057 |
|
|
|
2ff057 |
/* 4 fields of date */
|
|
|
2ff057 |
date++;
|
|
|
2ff057 |
s = date;
|
|
|
2ff057 |
SKIPSPACE(date);
|
|
|
2ff057 |
if (dateToTimet(date, &time, &date_words)) {
|
|
|
2ff057 |
rpmlog(RPMLOG_ERR, _("bad date in %%changelog: %s\n"), date);
|
|
|
2ff057 |
goto exit;
|
|
|
2ff057 |
}
|
|
|
2ff057 |
if (lastTime && lastTime < time) {
|
|
|
2ff057 |
rpmlog(RPMLOG_ERR,
|
|
|
2ff057 |
_("%%changelog not in descending chronological order\n"));
|
|
|
2ff057 |
goto exit;
|
|
|
2ff057 |
}
|
|
|
2ff057 |
for (i = 0; i < date_words; i++) {
|
|
|
2ff057 |
SKIPSPACE(s);
|
|
|
2ff057 |
SKIPNONSPACE(s);
|
|
|
2ff057 |
}
|
|
|
2ff057 |
lastTime = time;
|
|
|
2ff057 |
|
|
|
2ff057 |
/* skip space to the name */
|
|
|
2ff057 |
SKIPSPACE(s);
|
|
|
2ff057 |
if (! *s) {
|
|
|
2ff057 |
rpmlog(RPMLOG_ERR, _("missing name in %%changelog\n"));
|
|
|
2ff057 |
goto exit;
|
|
|
2ff057 |
}
|
|
|
2ff057 |
|
|
|
2ff057 |
/* name */
|
|
|
2ff057 |
name = s;
|
|
|
2ff057 |
while (*s != '\0') s++;
|
|
|
2ff057 |
while (s > name && risspace(*s)) {
|
|
|
2ff057 |
*s-- = '\0';
|
|
|
2ff057 |
}
|
|
|
2ff057 |
if (s == name) {
|
|
|
2ff057 |
rpmlog(RPMLOG_ERR, _("missing name in %%changelog\n"));
|
|
|
2ff057 |
goto exit;
|
|
|
2ff057 |
}
|
|
|
2ff057 |
|
|
|
2ff057 |
/* text */
|
|
|
2ff057 |
SKIPSPACE(text);
|
|
|
2ff057 |
if (! *text) {
|
|
|
2ff057 |
rpmlog(RPMLOG_ERR, _("no description in %%changelog\n"));
|
|
|
2ff057 |
goto exit;
|
|
|
2ff057 |
}
|
|
|
2ff057 |
|
|
|
2ff057 |
/* find the next leading '*' (or eos) */
|
|
|
2ff057 |
s = text;
|
|
|
2ff057 |
do {
|
|
|
2ff057 |
s++;
|
|
|
2ff057 |
} while (*s && (*(s-1) != '\n' || *s != '*'));
|
|
|
2ff057 |
next = s;
|
|
|
2ff057 |
s--;
|
|
|
2ff057 |
|
|
|
2ff057 |
/* backup to end of description */
|
|
|
2ff057 |
while ((s > text) && risspace(*s)) {
|
|
|
2ff057 |
*s-- = '\0';
|
|
|
2ff057 |
}
|
|
|
2ff057 |
|
|
|
2ff057 |
if ( !trimtime || time >= trimtime ) {
|
|
|
2ff057 |
addChangelogEntry(h, time, name, text);
|
|
|
2ff057 |
} else break;
|
|
|
2ff057 |
|
|
|
2ff057 |
s = next;
|
|
|
2ff057 |
}
|
|
|
2ff057 |
rc = RPMRC_OK;
|
|
|
2ff057 |
|
|
|
2ff057 |
exit:
|
|
|
2ff057 |
free(sp);
|
|
|
2ff057 |
|
|
|
2ff057 |
return rc;
|
|
|
2ff057 |
}
|
|
|
2ff057 |
|
|
|
2ff057 |
int parseChangelog(rpmSpec spec)
|
|
|
2ff057 |
{
|
|
|
2ff057 |
int nextPart, rc, res = PART_ERROR;
|
|
|
2ff057 |
ARGV_t sb = NULL;
|
|
|
2ff057 |
|
|
|
2ff057 |
if (headerIsEntry(spec->packages->header, RPMTAG_CHANGELOGTIME)) {
|
|
|
2ff057 |
rpmlog(RPMLOG_ERR, _("line %d: second %%changelog\n"), spec->lineNum);
|
|
|
2ff057 |
goto exit;
|
|
|
2ff057 |
}
|
|
|
2ff057 |
|
|
|
2ff057 |
/* There are no options to %changelog */
|
|
|
2ff057 |
if ((rc = readLine(spec, STRIP_COMMENTS)) > 0) {
|
|
|
2ff057 |
res = PART_NONE;
|
|
|
2ff057 |
goto exit;
|
|
|
2ff057 |
} else if (rc < 0) {
|
|
|
2ff057 |
goto exit;
|
|
|
2ff057 |
}
|
|
|
2ff057 |
|
|
|
2ff057 |
while (! (nextPart = isPart(spec->line))) {
|
|
|
2ff057 |
argvAdd(&sb, spec->line);
|
|
|
2ff057 |
if ((rc = readLine(spec, STRIP_COMMENTS)) > 0) {
|
|
|
2ff057 |
nextPart = PART_NONE;
|
|
|
2ff057 |
break;
|
|
|
2ff057 |
} else if (rc < 0) {
|
|
|
2ff057 |
goto exit;
|
|
|
2ff057 |
}
|
|
|
2ff057 |
}
|
|
|
2ff057 |
|
|
|
2ff057 |
if (sb && addChangelog(spec->packages->header, sb)) {
|
|
|
2ff057 |
goto exit;
|
|
|
2ff057 |
}
|
|
|
2ff057 |
res = nextPart;
|
|
|
2ff057 |
|
|
|
2ff057 |
exit:
|
|
|
2ff057 |
argvFree(sb);
|
|
|
2ff057 |
|
|
|
2ff057 |
return res;
|
|
|
2ff057 |
}
|