diff options
Diffstat (limited to 'lib/dpkg/fields.c')
-rw-r--r-- | lib/dpkg/fields.c | 713 |
1 files changed, 713 insertions, 0 deletions
diff --git a/lib/dpkg/fields.c b/lib/dpkg/fields.c new file mode 100644 index 0000000..8294149 --- /dev/null +++ b/lib/dpkg/fields.c @@ -0,0 +1,713 @@ +/* + * libdpkg - Debian packaging suite library routines + * fields.c - parsing of all the different fields, when reading in + * + * Copyright © 1995 Ian Jackson <ijackson@chiark.greenend.org.uk> + * Copyright © 2001 Wichert Akkerman + * Copyright © 2006-2015 Guillem Jover <guillem@debian.org> + * Copyright © 2009 Canonical Ltd. + * Copyright © 2011 Linaro Limited + * Copyright © 2011 Raphaël Hertzog <hertzog@debian.org> + * + * 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 <https://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <compat.h> + +#include <string.h> +#include <stdio.h> + +#include <dpkg/i18n.h> +#include <dpkg/c-ctype.h> +#include <dpkg/dpkg.h> +#include <dpkg/dpkg-db.h> +#include <dpkg/arch.h> +#include <dpkg/string.h> +#include <dpkg/path.h> +#include <dpkg/parsedump.h> +#include <dpkg/pkg-spec.h> +#include <dpkg/triglib.h> + +/** + * Flags to parse a name associated to a value. + */ +enum parse_nv_flags { + /** Expect no more words (default). */ + PARSE_NV_LAST = 0, + /** Expect another word after the parsed name. */ + PARSE_NV_NEXT = DPKG_BIT(0), + /** Do not fail if there is no name with an associated value found. */ + PARSE_NV_FALLBACK = DPKG_BIT(1), +}; + +/** + * Parses a name and returns its associated value. + * + * Gets a pointer to the string to parse in @a strp, and modifies the pointer + * to the string to point to the end of the parsed text. If no value is found + * for the name and #PARSE_NV_FALLBACK is set in @a flags then @a strp is set + * to NULL and returns -1, otherwise a parse error is emitted. + */ +static int +parse_nv(struct parsedb_state *ps, enum parse_nv_flags flags, + const char **strp, const struct namevalue *nv_head) +{ + const char *str_start = *strp, *str_end; + const struct namevalue *nv; + int value; + + dpkg_error_destroy(&ps->err); + + if (str_start[0] == '\0') + return dpkg_put_error(&ps->err, _("is missing a value")); + + nv = namevalue_find_by_name(nv_head, str_start); + if (nv == NULL) { + /* We got no match, skip further string validation. */ + if (!(flags & PARSE_NV_FALLBACK)) + return dpkg_put_error(&ps->err, _("has invalid value '%.50s'"), str_start); + + str_end = NULL; + value = -1; + } else { + str_end = str_start + nv->length; + while (c_isspace(str_end[0])) + str_end++; + value = nv->value; + } + + if (!(flags & PARSE_NV_NEXT) && str_is_set(str_end)) + return dpkg_put_error(&ps->err, _("has trailing junk")); + + *strp = str_end; + + return value; +} + +void +f_name(struct pkginfo *pkg, struct pkgbin *pkgbin, + struct parsedb_state *ps, + const char *value, const struct fieldinfo *fip) +{ + const char *e; + + e = pkg_name_is_illegal(value); + if (e != NULL) + parse_error(ps, _("invalid package name in '%s' field: %s"), fip->name, e); + /* We use the new name, as pkg_hash_find_set() may have done a tolower for us. */ + pkg->set->name = pkg_hash_find_set(value)->name; +} + +void +f_archives(struct pkginfo *pkg, struct pkgbin *pkgbin, + struct parsedb_state *ps, + const char *value, const struct fieldinfo *fip) +{ + struct archivedetails *fdp, **fdpp; + char *cpos, *space; + int allowextend; + + if (!*value) + parse_error(ps, _("empty archive details '%s' field"), fip->name); + if (!(ps->flags & pdb_recordavailable)) + parse_error(ps, + _("archive details '%s' field not allowed in status file"), + fip->name); + allowextend = !pkg->archives; + fdpp = &pkg->archives; + cpos= nfstrsave(value); + while (*cpos) { + space = cpos; + while (*space && !c_isspace(*space)) + space++; + if (*space) + *space++ = '\0'; + fdp= *fdpp; + if (!fdp) { + if (!allowextend) + parse_error(ps, + _("too many values in archive details '%s' field " + "(compared to others)"), fip->name); + fdp = nfmalloc(sizeof(*fdp)); + fdp->next= NULL; + fdp->name= fdp->msdosname= fdp->size= fdp->md5sum= NULL; + *fdpp= fdp; + } + STRUCTFIELD(fdp, fip->integer, const char *) = cpos; + fdpp= &fdp->next; + while (*space && c_isspace(*space)) + space++; + cpos= space; + } + if (*fdpp) + parse_error(ps, + _("too few values in archive details '%s' field " + "(compared to others)"), fip->name); +} + +void +f_charfield(struct pkginfo *pkg, struct pkgbin *pkgbin, + struct parsedb_state *ps, + const char *value, const struct fieldinfo *fip) +{ + if (*value) + STRUCTFIELD(pkgbin, fip->integer, char *) = nfstrsave(value); +} + +void +f_boolean(struct pkginfo *pkg, struct pkgbin *pkgbin, + struct parsedb_state *ps, + const char *value, const struct fieldinfo *fip) +{ + bool boolean; + + if (!*value) + return; + + boolean = parse_nv(ps, PARSE_NV_LAST, &value, booleaninfos); + if (dpkg_has_error(&ps->err)) + parse_error(ps, _("boolean (yes/no) '%s' field: %s"), + fip->name, ps->err.str); + + STRUCTFIELD(pkgbin, fip->integer, bool) = boolean; +} + +void +f_multiarch(struct pkginfo *pkg, struct pkgbin *pkgbin, + struct parsedb_state *ps, + const char *value, const struct fieldinfo *fip) +{ + int multiarch; + + if (!*value) + return; + + multiarch = parse_nv(ps, PARSE_NV_LAST, &value, multiarchinfos); + if (dpkg_has_error(&ps->err)) + parse_error(ps, _("quadstate (foreign/allowed/same/no) '%s' field: %s"), + fip->name, ps->err.str); + STRUCTFIELD(pkgbin, fip->integer, int) = multiarch; +} + +void +f_architecture(struct pkginfo *pkg, struct pkgbin *pkgbin, + struct parsedb_state *ps, + const char *value, const struct fieldinfo *fip) +{ + pkgbin->arch = dpkg_arch_find(value); + if (pkgbin->arch->type == DPKG_ARCH_ILLEGAL) + parse_warn(ps, _("'%s' is not a valid architecture name in '%s' field: %s"), + value, fip->name, dpkg_arch_name_is_illegal(value)); +} + +void +f_section(struct pkginfo *pkg, struct pkgbin *pkgbin, + struct parsedb_state *ps, + const char *value, const struct fieldinfo *fip) +{ + if (!*value) return; + pkg->section = nfstrsave(value); +} + +void +f_priority(struct pkginfo *pkg, struct pkgbin *pkgbin, + struct parsedb_state *ps, + const char *value, const struct fieldinfo *fip) +{ + const char *str = value; + int priority; + + if (!*value) return; + + priority = parse_nv(ps, PARSE_NV_LAST | PARSE_NV_FALLBACK, &str, + priorityinfos); + if (dpkg_has_error(&ps->err)) + parse_error(ps, _("word in '%s' field: %s"), fip->name, ps->err.str); + + if (str == NULL) { + pkg->priority = PKG_PRIO_OTHER; + pkg->otherpriority = nfstrsave(value); + } else { + pkg->priority = priority; + } +} + +void +f_status(struct pkginfo *pkg, struct pkgbin *pkgbin, + struct parsedb_state *ps, + const char *value, const struct fieldinfo *fip) +{ + if (ps->flags & pdb_rejectstatus) + parse_error(ps, + _("value for '%s' field not allowed in this context"), + fip->name); + if (ps->flags & pdb_recordavailable) + return; + + pkg->want = parse_nv(ps, PARSE_NV_NEXT, &value, wantinfos); + if (dpkg_has_error(&ps->err)) + parse_error(ps, _("first (want) word in '%s' field: %s"), + fip->name, ps->err.str); + pkg->eflag = parse_nv(ps, PARSE_NV_NEXT, &value, eflaginfos); + if (dpkg_has_error(&ps->err)) + parse_error(ps, _("second (error) word in '%s' field: %s"), + fip->name, ps->err.str); + pkg->status = parse_nv(ps, PARSE_NV_LAST, &value, statusinfos); + if (dpkg_has_error(&ps->err)) + parse_error(ps, _("third (status) word in '%s' field: %s"), + fip->name, ps->err.str); +} + +void +f_version(struct pkginfo *pkg, struct pkgbin *pkgbin, + struct parsedb_state *ps, + const char *value, const struct fieldinfo *fip) +{ + if (parse_db_version(ps, &pkgbin->version, value) < 0) + parse_problem(ps, _("'%s' field value '%.250s'"), fip->name, value); +} + +void +f_revision(struct pkginfo *pkg, struct pkgbin *pkgbin, + struct parsedb_state *ps, + const char *value, const struct fieldinfo *fip) +{ + char *newversion; + + parse_warn(ps, _("obsolete '%s' field used"), fip->name); + if (!*value) return; + if (str_is_set(pkgbin->version.revision)) { + newversion = nfmalloc(strlen(pkgbin->version.version) + + strlen(pkgbin->version.revision) + 2); + sprintf(newversion, "%s-%s", pkgbin->version.version, + pkgbin->version.revision); + pkgbin->version.version = newversion; + } + pkgbin->version.revision = nfstrsave(value); +} + +void +f_configversion(struct pkginfo *pkg, struct pkgbin *pkgbin, + struct parsedb_state *ps, + const char *value, const struct fieldinfo *fip) +{ + if (ps->flags & pdb_rejectstatus) + parse_error(ps, + _("value for '%s' field not allowed in this context"), + fip->name); + if (ps->flags & pdb_recordavailable) + return; + + if (parse_db_version(ps, &pkg->configversion, value) < 0) + parse_problem(ps, _("'%s' field value '%.250s'"), fip->name, value); +} + +/* + * The code in f_conffiles ensures that value[-1] == ' ', which is helpful. + */ +static void conffvalue_lastword(const char *value, const char *from, + const char *endent, + const char **word_start_r, int *word_len_r, + const char **new_from_r, + struct parsedb_state *ps) +{ + const char *lastspc; + + if (from <= value+1) goto malformed; + for (lastspc= from-1; *lastspc != ' '; lastspc--); + if (lastspc <= value+1 || lastspc >= endent-1) goto malformed; + + *new_from_r= lastspc; + *word_start_r= lastspc + 1; + *word_len_r= (int)(from - *word_start_r); + return; + +malformed: + parse_error(ps, + _("value for '%s' field has malformed line '%.*s'"), + "Conffiles", (int)min(endent - value, 250), value); +} + +void +f_conffiles(struct pkginfo *pkg, struct pkgbin *pkgbin, + struct parsedb_state *ps, + const char *value, const struct fieldinfo *fip) +{ + static const char obsolete_str[]= "obsolete"; + struct conffile **lastp, *newlink; + const char *endent, *endfn, *hashstart; + int c, namelen, hashlen; + bool obsolete; + char *newptr; + + lastp = &pkgbin->conffiles; + while (*value) { + c= *value++; + if (c == '\n') continue; + if (c != ' ') + parse_error(ps, + _("value for '%s' field has line starting with non-space '%c'"), + fip->name, c); + for (endent = value; (c = *endent) != '\0' && c != '\n'; endent++) ; + conffvalue_lastword(value, endent, endent, + &hashstart, &hashlen, &endfn, + ps); + obsolete= (hashlen == sizeof(obsolete_str)-1 && + memcmp(hashstart, obsolete_str, hashlen) == 0); + if (obsolete) + conffvalue_lastword(value, endfn, endent, + &hashstart, &hashlen, &endfn, + ps); + newlink = nfmalloc(sizeof(*newlink)); + value = path_skip_slash_dotslash(value); + namelen= (int)(endfn-value); + if (namelen <= 0) + parse_error(ps, + _("root or empty directory listed as a conffile in '%s' field"), + fip->name); + newptr = nfmalloc(namelen+2); + newptr[0]= '/'; + memcpy(newptr+1,value,namelen); + newptr[namelen+1] = '\0'; + newlink->name= newptr; + newptr= nfmalloc(hashlen+1); + memcpy(newptr, hashstart, hashlen); + newptr[hashlen] = '\0'; + newlink->hash= newptr; + newlink->obsolete= obsolete; + newlink->next =NULL; + *lastp= newlink; + lastp= &newlink->next; + value= endent; + } +} + +void +f_dependency(struct pkginfo *pkg, struct pkgbin *pkgbin, + struct parsedb_state *ps, + const char *value, const struct fieldinfo *fip) +{ + char c1, c2; + const char *p, *emsg; + const char *depnamestart, *versionstart; + int depnamelength, versionlength; + static struct varbuf depname, version; + + struct dependency *dyp, **ldypp; + struct deppossi *dop, **ldopp; + + /* Empty fields are ignored. */ + if (!*value) + return; + p= value; + + ldypp = &pkgbin->depends; + while (*ldypp) + ldypp = &(*ldypp)->next; + + /* Loop creating new struct dependency's. */ + for (;;) { + dyp = nfmalloc(sizeof(*dyp)); + /* Set this to NULL for now, as we don't know what our real + * struct pkginfo address (in the database) is going to be yet. */ + dyp->up = NULL; + dyp->next= NULL; *ldypp= dyp; ldypp= &dyp->next; + dyp->list= NULL; ldopp= &dyp->list; + dyp->type= fip->integer; + + /* Loop creating new struct deppossi's. */ + for (;;) { + depnamestart= p; + /* Skip over package name characters. */ + while (*p && !c_isspace(*p) && *p != ':' && *p != '(' && *p != ',' && + *p != '|') + p++; + depnamelength= p - depnamestart ; + if (depnamelength == 0) + parse_error(ps, + _("'%s' field, missing package name, or garbage where " + "package name expected"), fip->name); + + varbuf_reset(&depname); + varbuf_add_buf(&depname, depnamestart, depnamelength); + varbuf_end_str(&depname); + + emsg = pkg_name_is_illegal(depname.buf); + if (emsg) + parse_error(ps, + _("'%s' field, invalid package name '%.255s': %s"), + fip->name, depname.buf, emsg); + dop = nfmalloc(sizeof(*dop)); + dop->up= dyp; + dop->ed = pkg_hash_find_set(depname.buf); + dop->next= NULL; *ldopp= dop; ldopp= &dop->next; + + /* Don't link this (which is after all only ‘new_pkg’ from + * the main parsing loop in parsedb) into the depended on + * packages' lists yet. This will be done later when we + * install this (in parse.c). For the moment we do the + * ‘forward’ links in deppossi (‘ed’) only, and the ‘backward’ + * links from the depended on packages to dop are left undone. */ + dop->rev_next = NULL; + dop->rev_prev = NULL; + + dop->cyclebreak = false; + + /* See if we have an architecture qualifier. */ + if (*p == ':') { + static struct varbuf arch; + const char *archstart; + int archlength; + + archstart = ++p; + while (*p && !c_isspace(*p) && *p != '(' && *p != ',' && *p != '|') + p++; + archlength = p - archstart; + if (archlength == 0) + parse_error(ps, _("'%s' field, missing architecture name, or garbage " + "where architecture name expected"), fip->name); + + varbuf_reset(&arch); + varbuf_add_buf(&arch, archstart, archlength); + varbuf_end_str(&arch); + + dop->arch_is_implicit = false; + dop->arch = dpkg_arch_find(arch.buf); + + if (dop->arch->type == DPKG_ARCH_ILLEGAL) + emsg = dpkg_arch_name_is_illegal(arch.buf); + if (emsg) + parse_error(ps, _("'%s' field, reference to '%.255s': " + "invalid architecture name '%.255s': %s"), + fip->name, depname.buf, arch.buf, emsg); + } else if (fip->integer == dep_conflicts || fip->integer == dep_breaks || + fip->integer == dep_replaces) { + /* Conflicts/Breaks/Replaces get an implicit "any" arch qualifier. */ + dop->arch_is_implicit = true; + dop->arch = dpkg_arch_get(DPKG_ARCH_WILDCARD); + } else { + /* Otherwise use the pkgbin architecture, which will be assigned to + * later on by parse.c, once we can guarantee we have parsed it from + * the control stanza. */ + dop->arch_is_implicit = true; + dop->arch = NULL; + } + + /* Skip whitespace after package name. */ + while (c_isspace(*p)) + p++; + + /* See if we have a versioned relation. */ + if (*p == '(') { + p++; + while (c_isspace(*p)) + p++; + c1= *p; + if (c1 == '<' || c1 == '>') { + c2= *++p; + dop->verrel = (c1 == '<') ? DPKG_RELATION_LT : DPKG_RELATION_GT; + if (c2 == '=') { + dop->verrel |= DPKG_RELATION_EQ; + p++; + } else if (c2 == c1) { + /* Either ‘<<’ or ‘>>’. */ + p++; + } else if (c2 == '<' || c2 == '>') { + parse_error(ps, + _("'%s' field, reference to '%.255s':\n" + " bad version relationship %c%c"), + fip->name, depname.buf, c1, c2); + dop->verrel = DPKG_RELATION_NONE; + } else { + parse_warn(ps, + _("'%s' field, reference to '%.255s':\n" + " '%c' is obsolete, use '%c=' or '%c%c' instead"), + fip->name, depname.buf, c1, c1, c1, c1); + dop->verrel |= DPKG_RELATION_EQ; + } + } else if (c1 == '=') { + dop->verrel = DPKG_RELATION_EQ; + p++; + } else { + parse_warn(ps, + _("'%s' field, reference to '%.255s':\n" + " implicit exact match on version number, " + "suggest using '=' instead"), + fip->name, depname.buf); + dop->verrel = DPKG_RELATION_EQ; + } + if ((dop->verrel != DPKG_RELATION_EQ) && (fip->integer == dep_provides)) + parse_warn(ps, + _("only exact versions may be used for '%s' field"), + fip->name); + + if (!c_isspace(*p) && !c_isalnum(*p)) { + parse_warn(ps, + _("'%s' field, reference to '%.255s':\n" + " version value starts with non-alphanumeric, " + "suggest adding a space"), + fip->name, depname.buf); + } + /* Skip spaces between the relation and the version. */ + while (c_isspace(*p)) + p++; + + versionstart= p; + while (*p && *p != ')' && *p != '(') { + if (c_isspace(*p)) + break; + p++; + } + versionlength= p - versionstart; + while (c_isspace(*p)) + p++; + if (*p == '(') + parse_error(ps, + _("'%s' field, reference to '%.255s': " + "version contains '%c'"), fip->name, depname.buf, ')'); + else if (*p != ')') + parse_error(ps, + _("'%s' field, reference to '%.255s': " + "version contains '%c'"), fip->name, depname.buf, ' '); + else if (*p == '\0') + parse_error(ps, + _("'%s' field, reference to '%.255s': " + "version unterminated"), fip->name, depname.buf); + varbuf_reset(&version); + varbuf_add_buf(&version, versionstart, versionlength); + varbuf_end_str(&version); + if (parse_db_version(ps, &dop->version, version.buf) < 0) + parse_problem(ps, + _("'%s' field, reference to '%.255s': version '%s'"), + fip->name, depname.buf, version.buf); + p++; + while (c_isspace(*p)) + p++; + } else { + dop->verrel = DPKG_RELATION_NONE; + dpkg_version_blank(&dop->version); + } + if (!*p || *p == ',') break; + if (*p != '|') + parse_error(ps, + _("'%s' field, syntax error after reference to package '%.255s'"), + fip->name, dop->ed->name); + if (fip->integer == dep_conflicts || + fip->integer == dep_breaks || + fip->integer == dep_provides || + fip->integer == dep_replaces) + parse_error(ps, _("alternatives ('|') not allowed in '%s' field"), + fip->name); + p++; + while (c_isspace(*p)) + p++; + } + if (!*p) break; + p++; + while (c_isspace(*p)) + p++; + } +} + +static const char * +scan_word(const char **valp) +{ + static struct varbuf word; + const char *p, *start, *end; + + p = *valp; + for (;;) { + if (!*p) { + *valp = p; + return NULL; + } + if (c_iswhite(*p)) { + p++; + continue; + } + start = p; + break; + } + for (;;) { + if (*p && !c_iswhite(*p)) { + p++; + continue; + } + end = p; + break; + } + + varbuf_reset(&word); + varbuf_add_buf(&word, start, end - start); + varbuf_end_str(&word); + + *valp = p; + + return word.buf; +} + +void +f_trigpend(struct pkginfo *pend, struct pkgbin *pkgbin, + struct parsedb_state *ps, + const char *value, const struct fieldinfo *fip) +{ + const char *word, *emsg; + + if (ps->flags & pdb_rejectstatus) + parse_error(ps, + _("value for '%s' field not allowed in this context"), + fip->name); + + while ((word = scan_word(&value))) { + emsg = trig_name_is_illegal(word); + if (emsg) + parse_error(ps, + _("illegal pending trigger name '%.255s': %s"), word, emsg); + + if (!trig_note_pend_core(pend, nfstrsave(word))) + parse_error(ps, + _("duplicate pending trigger '%.255s'"), word); + } +} + +void +f_trigaw(struct pkginfo *aw, struct pkgbin *pkgbin, + struct parsedb_state *ps, + const char *value, const struct fieldinfo *fip) +{ + const char *word; + struct pkginfo *pend; + + if (ps->flags & pdb_rejectstatus) + parse_error(ps, + _("value for '%s' field not allowed in this context"), + fip->name); + + while ((word = scan_word(&value))) { + struct dpkg_error err; + + pend = pkg_spec_parse_pkg(word, &err); + if (pend == NULL) + parse_error(ps, + _("illegal package name in awaited trigger '%.255s': %s"), + word, err.str); + + if (!trig_note_aw(pend, aw)) + parse_error(ps, + _("duplicate awaited trigger package '%.255s'"), word); + + trig_awaited_pend_enqueue(pend); + } +} |