blob: c31b1f66f2add09d573c8336d39552fdc34f62ce [file] [log] [blame]
/*
* libdpkg - Debian packaging suite library routines
* parse.c - database file parsing, main package/field loop
*
* Copyright © 1995 Ian Jackson <ian@chiark.greenend.org.uk>
*
* This 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 2 of the License, or
* (at your option) any later version.
*
* This 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <compat.h>
#include <sys/types.h>
#include <sys/stat.h>
#ifdef USE_MMAP
#include <sys/mman.h>
#endif
#include <assert.h>
#include <fcntl.h>
#include <ctype.h>
#include <string.h>
#include <unistd.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <dpkg/macros.h>
#include <dpkg/i18n.h>
#include <dpkg/dpkg.h>
#include <dpkg/dpkg-db.h>
#include <dpkg/parsedump.h>
#include <dpkg/fdio.h>
/**
* Fields information.
*/
const struct fieldinfo fieldinfos[]= {
/* Note: Capitalization of field name strings is important. */
{ "Package", f_name, w_name },
{ "Essential", f_boolean, w_booleandefno, PKGIFPOFF(essential) },
{ "Status", f_status, w_status },
{ "Priority", f_priority, w_priority },
{ "Section", f_section, w_section },
{ "Installed-Size", f_charfield, w_charfield, PKGIFPOFF(installedsize) },
{ "Origin", f_charfield, w_charfield, PKGIFPOFF(origin) },
{ "Maintainer", f_charfield, w_charfield, PKGIFPOFF(maintainer) },
{ "Bugs", f_charfield, w_charfield, PKGIFPOFF(bugs) },
{ "Architecture", f_charfield, w_charfield, PKGIFPOFF(arch) },
{ "Source", f_charfield, w_charfield, PKGIFPOFF(source) },
{ "Version", f_version, w_version, PKGIFPOFF(version) },
{ "Revision", f_revision, w_null },
{ "Config-Version", f_configversion, w_configversion },
{ "Replaces", f_dependency, w_dependency, dep_replaces },
{ "Provides", f_dependency, w_dependency, dep_provides },
{ "Depends", f_dependency, w_dependency, dep_depends },
{ "Pre-Depends", f_dependency, w_dependency, dep_predepends },
{ "Recommends", f_dependency, w_dependency, dep_recommends },
{ "Suggests", f_dependency, w_dependency, dep_suggests },
{ "Breaks", f_dependency, w_dependency, dep_breaks },
{ "Conflicts", f_dependency, w_dependency, dep_conflicts },
{ "Enhances", f_dependency, w_dependency, dep_enhances },
{ "Conffiles", f_conffiles, w_conffiles },
{ "Filename", f_filecharf, w_filecharf, FILEFOFF(name) },
{ "Size", f_filecharf, w_filecharf, FILEFOFF(size) },
{ "MD5sum", f_filecharf, w_filecharf, FILEFOFF(md5sum) },
{ "MSDOS-Filename", f_filecharf, w_filecharf, FILEFOFF(msdosname) },
{ "Description", f_charfield, w_charfield, PKGIFPOFF(description) },
{ "Triggers-Pending", f_trigpend, w_trigpend },
{ "Triggers-Awaited", f_trigaw, w_trigaw },
/* Note that aliases are added to the nicknames table. */
{ NULL }
};
static const struct nickname nicknames[] = {
/* Note: Capitalization of these strings is important. */
{ .nick = "Recommended", .canon = "Recommends" },
{ .nick = "Optional", .canon = "Suggests" },
{ .nick = "Class", .canon = "Priority" },
{ .nick = "Package-Revision", .canon = "Revision" },
{ .nick = "Package_Revision", .canon = "Revision" },
{ .nick = NULL }
};
/**
* Package object being parsed.
*
* Structure used to hold the parsed data for the package being constructed,
* before it gets properly inserted into the package database.
*/
struct pkg_parse_object {
struct pkginfo *pkg;
struct pkgbin *pkgbin;
};
/**
* Parse the field and value into the package being constructed.
*/
static void
pkg_parse_field(struct parsedb_state *ps, struct field_state *fs,
void *parse_obj)
{
struct pkg_parse_object *pkg_obj = parse_obj;
const struct nickname *nick;
const struct fieldinfo *fip;
int *ip;
for (nick = nicknames; nick->nick; nick++)
if (strncasecmp(nick->nick, fs->fieldstart, fs->fieldlen) == 0 &&
nick->nick[fs->fieldlen] == '\0')
break;
if (nick->nick) {
fs->fieldstart = nick->canon;
fs->fieldlen = strlen(fs->fieldstart);
}
for (fip = fieldinfos, ip = fs->fieldencountered; fip->name; fip++, ip++)
if (strncasecmp(fip->name, fs->fieldstart, fs->fieldlen) == 0)
break;
if (fip->name) {
if ((*ip)++)
parse_error(ps,
_("duplicate value for `%s' field"), fip->name);
varbuf_reset(&fs->value);
varbuf_add_buf(&fs->value, fs->valuestart, fs->valuelen);
varbuf_end_str(&fs->value);
fip->rcall(pkg_obj->pkg, pkg_obj->pkgbin, ps, fs->value.buf, fip);
} else {
struct arbitraryfield *arp, **larpp;
if (fs->fieldlen < 2)
parse_error(ps,
_("user-defined field name `%.*s' too short"),
fs->fieldlen, fs->fieldstart);
larpp = &pkg_obj->pkgbin->arbs;
while ((arp = *larpp) != NULL) {
if (!strncasecmp(arp->name, fs->fieldstart, fs->fieldlen))
parse_error(ps,
_("duplicate value for user-defined field `%.*s'"),
fs->fieldlen, fs->fieldstart);
larpp = &arp->next;
}
arp = nfmalloc(sizeof(struct arbitraryfield));
arp->name = nfstrnsave(fs->fieldstart, fs->fieldlen);
arp->value = nfstrnsave(fs->valuestart, fs->valuelen);
arp->next = NULL;
*larpp = arp;
}
}
/**
* Verify and fixup the package structure being constructed.
*/
static void
pkg_parse_verify(struct parsedb_state *ps,
struct pkginfo *pkg, struct pkgbin *pkgbin)
{
parse_must_have_field(ps, pkg->name, "package name");
/* XXX: We need to check for status != stat_halfinstalled as while
* unpacking an unselected package, it will not have yet all data in
* place. But we cannot check for > stat_halfinstalled as stat_configfiles
* always should have those fields. */
if ((ps->flags & pdb_recordavailable) ||
(pkg->status != stat_notinstalled &&
pkg->status != stat_halfinstalled)) {
parse_ensure_have_field(ps, &pkgbin->description, "description");
parse_ensure_have_field(ps, &pkgbin->maintainer, "maintainer");
parse_must_have_field(ps, pkgbin->version.version, "version");
}
/* XXX: Versions before dpkg 1.10.19 did not preserve the Architecture
* field in the status file. So there's still live systems with packages
* in stat_configfiles, ignore those too for now. */
if ((ps->flags & pdb_recordavailable) ||
pkg->status > stat_halfinstalled) {
/* We always want usable architecture information (as long as the package
* is in such a state that it make sense), so that it can be used safely
* on string comparisons and the like. */
parse_ensure_have_field(ps, &pkgbin->arch, "architecture");
} else if (pkgbin->arch == NULL) {
pkgbin->arch = "";
}
/* Check the Config-Version information:
* If there is a Config-Version it is definitely to be used, but
* there shouldn't be one if the package is ‘installed’ (in which case
* the Version and/or Revision will be copied) or if the package is
* ‘not-installed’ (in which case there is no Config-Version). */
if (!(ps->flags & pdb_recordavailable)) {
if (pkg->configversion.version) {
if (pkg->status == stat_installed || pkg->status == stat_notinstalled)
parse_error(ps,
_("Configured-Version for package with inappropriate Status"));
} else {
if (pkg->status == stat_installed)
pkg->configversion = pkgbin->version;
}
}
if (pkg->trigaw.head &&
(pkg->status <= stat_configfiles ||
pkg->status >= stat_triggerspending))
parse_error(ps,
_("package has status %s but triggers are awaited"),
statusinfos[pkg->status].name);
else if (pkg->status == stat_triggersawaited && !pkg->trigaw.head)
parse_error(ps,
_("package has status triggers-awaited but no triggers awaited"));
if (pkg->trigpend_head &&
!(pkg->status == stat_triggerspending ||
pkg->status == stat_triggersawaited))
parse_error(ps,
_("package has status %s but triggers are pending"),
statusinfos[pkg->status].name);
else if (pkg->status == stat_triggerspending && !pkg->trigpend_head)
parse_error(ps,
_("package has status triggers-pending but no triggers "
"pending"));
/* FIXME: There was a bug that could make a not-installed package have
* conffiles, so we check for them here and remove them (rather than
* calling it an error, which will do at some point). */
if (!(ps->flags & pdb_recordavailable) &&
pkg->status == stat_notinstalled &&
pkgbin->conffiles) {
parse_warn(ps,
_("Package which in state not-installed has conffiles, "
"forgetting them"));
pkgbin->conffiles = NULL;
}
/* XXX: Mark not-installed leftover packages for automatic removal on
* next database dump. This code can be removed after dpkg 1.16.x, when
* there's guarantee that no leftover is found on the status file on
* major distributions. */
if (!(ps->flags & pdb_recordavailable) &&
pkg->status == stat_notinstalled &&
pkg->eflag == eflag_ok &&
(pkg->want == want_purge ||
pkg->want == want_deinstall ||
pkg->want == want_hold)) {
pkg->want = want_unknown;
}
}
/**
* Copy into the in-core database the package being constructed.
*/
static void
pkg_parse_copy(struct parsedb_state *ps,
struct pkginfo *dst_pkg, struct pkgbin *dst_pkgbin,
struct pkginfo *src_pkg, struct pkgbin *src_pkgbin)
{
/* Copy the priority and section across, but don't overwrite existing
* values if the pdb_weakclassification flag is set. */
if (src_pkg->section && *src_pkg->section &&
!((ps->flags & pdb_weakclassification) &&
dst_pkg->section && *dst_pkg->section))
dst_pkg->section = src_pkg->section;
if (src_pkg->priority != pri_unknown &&
!((ps->flags & pdb_weakclassification) &&
dst_pkg->priority != pri_unknown)) {
dst_pkg->priority = src_pkg->priority;
if (src_pkg->priority == pri_other)
dst_pkg->otherpriority = src_pkg->otherpriority;
}
/* Sort out the dependency mess. */
copy_dependency_links(dst_pkg, &dst_pkgbin->depends, src_pkgbin->depends,
(ps->flags & pdb_recordavailable) ? true : false);
/* Leave the ‘depended’ pointer alone, we've just gone to such
* trouble to get it right :-). The ‘depends’ pointer in
* dst_pkgbin was indeed also updated by copy_dependency_links,
* but since the value was that from src_pkgbin anyway there's
* no need to copy it back. */
src_pkgbin->depended = dst_pkgbin->depended;
/* Copy across data. */
memcpy(dst_pkgbin, src_pkgbin, sizeof(struct pkgbin));
if (!(ps->flags & pdb_recordavailable)) {
struct trigaw *ta;
dst_pkg->want = src_pkg->want;
dst_pkg->eflag = src_pkg->eflag;
dst_pkg->status = src_pkg->status;
dst_pkg->configversion = src_pkg->configversion;
dst_pkg->files = NULL;
dst_pkg->trigpend_head = src_pkg->trigpend_head;
dst_pkg->trigaw = src_pkg->trigaw;
for (ta = dst_pkg->trigaw.head; ta; ta = ta->sameaw.next) {
assert(ta->aw == src_pkg);
ta->aw = dst_pkg;
/* ->othertrigaw_head is updated by trig_note_aw in *(pkg_db_find())
* rather than in dst_pkg. */
}
} else if (!(ps->flags & pdb_ignorefiles)) {
dst_pkg->files = src_pkg->files;
}
}
/**
* Open a file for RFC-822 parsing.
*/
void
parse_open(struct parsedb_state *ps, const char *filename,
enum parsedbflags flags)
{
static int fd;
struct stat st;
ps->filename = filename;
ps->flags = flags;
ps->lno = 0;
ps->pkg = NULL;
ps->pkgbin = NULL;
fd = open(filename, O_RDONLY);
if (fd == -1)
ohshite(_("failed to open package info file `%.255s' for reading"),
filename);
push_cleanup(cu_closefd, ~ehflag_normaltidy, NULL, 0, 1, &fd);
if (fstat(fd, &st) == -1)
ohshite(_("can't stat package info file `%.255s'"), filename);
if (st.st_size > 0) {
#ifdef USE_MMAP
ps->dataptr = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
if (ps->dataptr == MAP_FAILED)
ohshite(_("can't mmap package info file `%.255s'"), filename);
#else
ps->dataptr = m_malloc(st.st_size);
if (fd_read(fd, ps->dataptr, st.st_size) < 0)
ohshite(_("reading package info file '%.255s'"), filename);
#endif
ps->data = ps->dataptr;
ps->endptr = ps->dataptr + st.st_size;
} else {
ps->data = ps->dataptr = ps->endptr = NULL;
}
pop_cleanup(ehflag_normaltidy);
if (close(fd))
ohshite(_("failed to close after read: `%.255s'"), filename);
}
/**
* Parse an RFC-822 style stanza.
*/
bool
parse_stanza(struct parsedb_state *ps, struct field_state *fs,
parse_field_func *parse_field, void *parse_obj)
{
int c;
/* Skip adjacent new lines. */
while (!parse_EOF(ps)) {
c = parse_getc(ps);
if (c != '\n' && c != MSDOS_EOF_CHAR)
break;
ps->lno++;
}
/* Nothing relevant parsed, bail out. */
if (parse_EOF(ps))
return false;
/* Loop per field. */
for (;;) {
bool blank_line;
/* Scan field name. */
fs->fieldstart = ps->dataptr - 1;
while (!parse_EOF(ps) && !isspace(c) && c != ':' && c != MSDOS_EOF_CHAR)
c = parse_getc(ps);
fs->fieldlen = ps->dataptr - fs->fieldstart - 1;
/* Skip spaces before ‘:’. */
while (!parse_EOF(ps) && c != '\n' && isspace(c))
c = parse_getc(ps);
/* Validate ‘:’. */
if (parse_EOF(ps))
parse_error(ps,
_("EOF after field name `%.*s'"), fs->fieldlen, fs->fieldstart);
if (c == '\n')
parse_error(ps,
_("newline in field name `%.*s'"), fs->fieldlen, fs->fieldstart);
if (c == MSDOS_EOF_CHAR)
parse_error(ps,
_("MSDOS EOF (^Z) in field name `%.*s'"),
fs->fieldlen, fs->fieldstart);
if (c != ':')
parse_error(ps,
_("field name `%.*s' must be followed by colon"),
fs->fieldlen, fs->fieldstart);
/* Skip space after ‘:’ but before value and EOL. */
while (!parse_EOF(ps)) {
c = parse_getc(ps);
if (c == '\n' || !isspace(c))
break;
}
if (parse_EOF(ps))
parse_error(ps,
_("EOF before value of field `%.*s' (missing final newline)"),
fs->fieldlen, fs->fieldstart);
if (c == MSDOS_EOF_CHAR)
parse_error(ps,
_("MSDOS EOF char in value of field `%.*s' (missing newline?)"),
fs->fieldlen, fs->fieldstart);
blank_line = false;
/* Scan field value. */
fs->valuestart = ps->dataptr - 1;
for (;;) {
if (c == '\n' || c == MSDOS_EOF_CHAR) {
if (blank_line)
parse_error(ps,
_("blank line in value of field '%.*s'"),
fs->fieldlen, fs->fieldstart);
ps->lno++;
if (parse_EOF(ps))
break;
c = parse_getc(ps);
/* Found double EOL, or start of new field. */
if (parse_EOF(ps) || c == '\n' || !isspace(c))
break;
parse_ungetc(c, ps);
c = '\n';
blank_line = true;
} else if (blank_line && !isspace(c)) {
blank_line = false;
}
if (parse_EOF(ps))
parse_error(ps,
_("EOF during value of field `%.*s' (missing final newline)"),
fs->fieldlen, fs->fieldstart);
c = parse_getc(ps);
}
fs->valuelen = ps->dataptr - fs->valuestart - 1;
/* Trim ending space on value. */
while (fs->valuelen && isspace(*(fs->valuestart + fs->valuelen - 1)))
fs->valuelen--;
parse_field(ps, fs, parse_obj);
if (parse_EOF(ps) || c == '\n' || c == MSDOS_EOF_CHAR)
break;
} /* Loop per field. */
if (c == '\n')
ps->lno++;
return true;
}
/**
* Close an RFC-822 parser context.
*/
void
parse_close(struct parsedb_state *ps)
{
if (ps->data != NULL) {
#ifdef USE_MMAP
munmap(ps->data, ps->endptr - ps->data);
#else
free(ps->data);
#endif
}
}
/**
* Parse an RFC-822 style file.
*
* donep may be NULL.
* If donep is not NULL only one package's information is expected.
*/
int parsedb(const char *filename, enum parsedbflags flags,
struct pkginfo **donep)
{
struct pkginfo tmp_pkg;
struct pkginfo *new_pkg, *db_pkg;
struct pkgbin *new_pkgbin, *db_pkgbin;
struct pkg_parse_object pkg_obj;
int fieldencountered[array_count(fieldinfos)];
int pdone;
struct parsedb_state ps;
struct field_state fs;
memset(&fs, 0, sizeof(fs));
fs.fieldencountered = fieldencountered;
parse_open(&ps, filename, flags);
new_pkg = &tmp_pkg;
if (flags & pdb_recordavailable)
new_pkgbin = &new_pkg->available;
else
new_pkgbin = &new_pkg->installed;
ps.pkg = new_pkg;
ps.pkgbin = new_pkgbin;
pkg_obj.pkg = new_pkg;
pkg_obj.pkgbin = new_pkgbin;
pdone= 0;
/* Loop per package. */
for (;;) {
memset(fieldencountered, 0, sizeof(fieldencountered));
pkg_blank(new_pkg);
if (!parse_stanza(&ps, &fs, pkg_parse_field, &pkg_obj))
break;
if (pdone && donep)
parse_error(&ps,
_("several package info entries found, only one allowed"));
pkg_parse_verify(&ps, new_pkg, new_pkgbin);
db_pkg = pkg_db_find(new_pkg->name);
if (flags & pdb_recordavailable)
db_pkgbin = &db_pkg->available;
else
db_pkgbin = &db_pkg->installed;
if ((flags & pdb_ignoreolder) &&
versioncompare(&new_pkgbin->version, &db_pkgbin->version) < 0)
continue;
pkg_parse_copy(&ps, db_pkg, db_pkgbin, new_pkg, new_pkgbin);
if (donep)
*donep = db_pkg;
pdone++;
if (parse_EOF(&ps))
break;
}
parse_close(&ps);
varbuf_destroy(&fs.value);
if (donep && !pdone) ohshit(_("no package information in `%.255s'"),filename);
return pdone;
}
/**
* Copy dependency links structures.
*
* This routine is used to update the ‘reverse’ dependency pointers when
* new ‘forwards’ information has been constructed. It first removes all
* the links based on the old information. The old information starts in
* *updateme; after much brou-ha-ha the reverse structures are created
* and *updateme is set to the value from newdepends.
*
* @param pkg The package we're doing this for. This is used to construct
* correct uplinks.
* @param updateme The forwards dependency pointer that we are to update.
* This starts out containing the old forwards info, which we use to
* unthread the old reverse links. After we're done it is updated.
* @param newdepends The value that we ultimately want to have in updateme.
* @param available The pkgbin to modify, available or installed.
*
* It is likely that the backward pointer for the package in question
* (‘depended’) will be updated by this routine, but this will happen by
* the routine traversing the dependency data structures. It doesn't need
* to be told where to update that; I just mention it as something that
* one should be cautious about.
*/
void copy_dependency_links(struct pkginfo *pkg,
struct dependency **updateme,
struct dependency *newdepends,
bool available)
{
struct dependency *dyp;
struct deppossi *dop;
struct pkgbin *addtopifp;
/* Delete ‘backward’ (‘depended’) links from other packages to
* dependencies listed in old version of this one. We do this by
* going through all the dependencies in the old version of this
* one and following them down to find which deppossi nodes to
* remove. */
for (dyp= *updateme; dyp; dyp= dyp->next) {
for (dop= dyp->list; dop; dop= dop->next) {
if (dop->rev_prev)
dop->rev_prev->rev_next = dop->rev_next;
else
if (available)
dop->ed->available.depended = dop->rev_next;
else
dop->ed->installed.depended = dop->rev_next;
if (dop->rev_next)
dop->rev_next->rev_prev = dop->rev_prev;
}
}
/* Now fill in new ‘ed’ links from other packages to dependencies
* listed in new version of this one, and set our uplinks correctly. */
for (dyp= newdepends; dyp; dyp= dyp->next) {
dyp->up= pkg;
for (dop= dyp->list; dop; dop= dop->next) {
addtopifp= available ? &dop->ed->available : &dop->ed->installed;
dop->rev_next = addtopifp->depended;
dop->rev_prev = NULL;
if (addtopifp->depended)
addtopifp->depended->rev_prev = dop;
addtopifp->depended= dop;
}
}
/* Finally, we fill in the new value. */
*updateme= newdepends;
}