csomh / source-git / rpm

Forked from source-git/rpm 4 years ago
Clone
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
}