| /* |
| * libdpkg - Debian packaging suite library routines |
| * fields.c - parsing of all the different fields, when reading in |
| * |
| * Copyright © 1995 Ian Jackson <ian@chiark.greenend.org.uk> |
| * Copyright © 2001 Wichert Akkerman |
| * |
| * 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 <ctype.h> |
| #include <string.h> |
| #include <stdio.h> |
| |
| #include <dpkg/i18n.h> |
| #include <dpkg/dpkg.h> |
| #include <dpkg/dpkg-db.h> |
| #include <dpkg/path.h> |
| #include <dpkg/parsedump.h> |
| #include <dpkg/triglib.h> |
| |
| static int |
| parse_nv_next(struct parsedb_state *ps, |
| const char *what, const struct namevalue *nv_head, |
| const char **strp) |
| { |
| const char *str_start = *strp, *str_end; |
| const struct namevalue *nv; |
| |
| if (str_start[0] == '\0') |
| parse_error(ps, _("%s is missing"), what); |
| |
| nv = namevalue_find_by_name(nv_head, str_start); |
| if (nv == NULL) |
| parse_error(ps, _("'%.50s' is not allowed for %s"), str_start, what); |
| |
| /* We got the fallback value, skip further string validation. */ |
| if (nv->length == 0) { |
| str_end = NULL; |
| } else { |
| str_end = str_start + nv->length; |
| while (isspace(str_end[0])) |
| str_end++; |
| } |
| *strp = str_end; |
| |
| return nv->value; |
| } |
| |
| static int |
| parse_nv_last(struct parsedb_state *ps, |
| const char *what, const struct namevalue *nv_head, |
| const char *str) |
| { |
| int value; |
| |
| value = parse_nv_next(ps, what, nv_head, &str); |
| if (str != NULL && str[0] != '\0') |
| parse_error(ps, _("junk after %s"), what); |
| |
| return value; |
| } |
| |
| void |
| f_name(struct pkginfo *pigp, struct pkgbin *pifp, |
| struct parsedb_state *ps, |
| const char *value, const struct fieldinfo *fip) |
| { |
| const char *e; |
| |
| e = pkg_name_is_illegal(value, NULL); |
| if (e != NULL) |
| parse_error(ps, _("invalid package name (%.250s)"), e); |
| /* We use the new name, as pkg_db_find() may have done a tolower for us. */ |
| pigp->name = pkg_db_find(value)->name; |
| } |
| |
| void |
| f_filecharf(struct pkginfo *pigp, struct pkgbin *pifp, |
| struct parsedb_state *ps, |
| const char *value, const struct fieldinfo *fip) |
| { |
| struct filedetails *fdp, **fdpp; |
| char *cpos, *space; |
| int allowextend; |
| |
| if (!*value) |
| parse_error(ps, _("empty file details field `%s'"), fip->name); |
| if (!(ps->flags & pdb_recordavailable)) |
| parse_error(ps, |
| _("file details field `%s' not allowed in status file"), |
| fip->name); |
| allowextend= !pigp->files; |
| fdpp= &pigp->files; |
| cpos= nfstrsave(value); |
| while (*cpos) { |
| space= cpos; while (*space && !isspace(*space)) space++; |
| if (*space) |
| *space++ = '\0'; |
| fdp= *fdpp; |
| if (!fdp) { |
| if (!allowextend) |
| parse_error(ps, |
| _("too many values in file details field `%s' " |
| "(compared to others)"), fip->name); |
| fdp= nfmalloc(sizeof(struct filedetails)); |
| fdp->next= NULL; |
| fdp->name= fdp->msdosname= fdp->size= fdp->md5sum= NULL; |
| *fdpp= fdp; |
| } |
| FILEFFIELD(fdp,fip->integer,const char*)= cpos; |
| fdpp= &fdp->next; |
| while (*space && isspace(*space)) space++; |
| cpos= space; |
| } |
| if (*fdpp) |
| parse_error(ps, |
| _("too few values in file details field `%s' " |
| "(compared to others)"), fip->name); |
| } |
| |
| void |
| f_charfield(struct pkginfo *pigp, struct pkgbin *pifp, |
| struct parsedb_state *ps, |
| const char *value, const struct fieldinfo *fip) |
| { |
| if (*value) PKGPFIELD(pifp,fip->integer,char*)= nfstrsave(value); |
| } |
| |
| void |
| f_boolean(struct pkginfo *pigp, struct pkgbin *pifp, |
| struct parsedb_state *ps, |
| const char *value, const struct fieldinfo *fip) |
| { |
| bool boolean; |
| |
| if (!*value) |
| return; |
| |
| boolean = parse_nv_last(ps, _("yes/no in boolean field"), |
| booleaninfos, value); |
| PKGPFIELD(pifp, fip->integer, bool) = boolean; |
| } |
| |
| void |
| f_section(struct pkginfo *pigp, struct pkgbin *pifp, |
| struct parsedb_state *ps, |
| const char *value, const struct fieldinfo *fip) |
| { |
| if (!*value) return; |
| pigp->section= nfstrsave(value); |
| } |
| |
| void |
| f_priority(struct pkginfo *pigp, struct pkgbin *pifp, |
| struct parsedb_state *ps, |
| const char *value, const struct fieldinfo *fip) |
| { |
| if (!*value) return; |
| pigp->priority = parse_nv_last(ps, _("word in `priority' field"), |
| priorityinfos, value); |
| if (pigp->priority == pri_other) pigp->otherpriority= nfstrsave(value); |
| } |
| |
| void |
| f_status(struct pkginfo *pigp, struct pkgbin *pifp, |
| struct parsedb_state *ps, |
| const char *value, const struct fieldinfo *fip) |
| { |
| if (ps->flags & pdb_rejectstatus) |
| parse_error(ps, |
| _("value for `status' field not allowed in this context")); |
| if (ps->flags & pdb_recordavailable) |
| return; |
| |
| pigp->want = parse_nv_next(ps, |
| _("first (want) word in `status' field"), |
| wantinfos, &value); |
| pigp->eflag = parse_nv_next(ps, |
| _("second (error) word in `status' field"), |
| eflaginfos, &value); |
| pigp->status = parse_nv_last(ps, |
| _("third (status) word in `status' field"), |
| statusinfos, value); |
| } |
| |
| void |
| f_version(struct pkginfo *pigp, struct pkgbin *pifp, |
| struct parsedb_state *ps, |
| const char *value, const struct fieldinfo *fip) |
| { |
| parse_db_version(ps, &pifp->version, value, |
| _("error in Version string '%.250s'"), value); |
| } |
| |
| void |
| f_revision(struct pkginfo *pigp, struct pkgbin *pifp, |
| struct parsedb_state *ps, |
| const char *value, const struct fieldinfo *fip) |
| { |
| char *newversion; |
| |
| parse_warn(ps, |
| _("obsolete `Revision' or `Package-Revision' field used")); |
| if (!*value) return; |
| if (pifp->version.revision && *pifp->version.revision) { |
| newversion= nfmalloc(strlen(pifp->version.version)+strlen(pifp->version.revision)+2); |
| sprintf(newversion,"%s-%s",pifp->version.version,pifp->version.revision); |
| pifp->version.version= newversion; |
| } |
| pifp->version.revision= nfstrsave(value); |
| } |
| |
| void |
| f_configversion(struct pkginfo *pigp, struct pkgbin *pifp, |
| struct parsedb_state *ps, |
| const char *value, const struct fieldinfo *fip) |
| { |
| if (ps->flags & pdb_rejectstatus) |
| parse_error(ps, |
| _("value for `config-version' field not allowed in this context")); |
| if (ps->flags & pdb_recordavailable) |
| return; |
| |
| parse_db_version(ps, &pigp->configversion, value, |
| _("error in Config-Version string '%.250s'"), 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 `conffiles' has malformatted line `%.*s'"), |
| (int)min(endent - value, 250), value); |
| } |
| |
| void |
| f_conffiles(struct pkginfo *pigp, struct pkgbin *pifp, |
| 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, obsolete; |
| char *newptr; |
| |
| lastp= &pifp->conffiles; |
| while (*value) { |
| c= *value++; |
| if (c == '\n') continue; |
| if (c != ' ') |
| parse_error(ps, |
| _("value for `conffiles' has line starting with non-space `%c'"), |
| 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)); |
| if (obsolete) |
| conffvalue_lastword(value, endfn, endent, |
| &hashstart, &hashlen, &endfn, |
| ps); |
| newlink= nfmalloc(sizeof(struct conffile)); |
| value = path_skip_slash_dotslash(value); |
| namelen= (int)(endfn-value); |
| if (namelen <= 0) |
| parse_error(ps, |
| _("root or null directory is listed as a conffile")); |
| 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 *pigp, struct pkgbin *pifp, |
| 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= &pifp->depends; while (*ldypp) ldypp= &(*ldypp)->next; |
| |
| /* Loop creating new struct dependency's. */ |
| for (;;) { |
| dyp= nfmalloc(sizeof(struct dependency)); |
| /* 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 && !isspace(*p) && *p != '(' && *p != ',' && *p != '|') { |
| p++; |
| } |
| depnamelength= p - depnamestart ; |
| varbuf_reset(&depname); |
| varbuf_add_buf(&depname, depnamestart, depnamelength); |
| varbuf_end_str(&depname); |
| if (!depname.buf[0]) |
| parse_error(ps, |
| _("`%s' field, missing package name, or garbage where " |
| "package name expected"), fip->name); |
| emsg = pkg_name_is_illegal(depname.buf, NULL); |
| if (emsg) |
| parse_error(ps, |
| _("`%s' field, invalid package name `%.255s': %s"), |
| fip->name, depname.buf, emsg); |
| dop= nfmalloc(sizeof(struct deppossi)); |
| dop->up= dyp; |
| dop->ed = pkg_db_find(depname.buf); |
| dop->next= NULL; *ldopp= dop; ldopp= &dop->next; |
| |
| /* Don't link this (which is after all only ‘newpig’ 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; |
| |
| /* Skip whitespace after package name. */ |
| while (isspace(*p)) p++; |
| |
| /* See if we have a versioned relation. */ |
| if (*p == '(') { |
| p++; while (isspace(*p)) p++; |
| c1= *p; |
| if (c1 == '<' || c1 == '>') { |
| c2= *++p; |
| dop->verrel= (c1 == '<') ? dvrf_earlier : dvrf_later; |
| if (c2 == '=') { |
| dop->verrel |= (dvrf_orequal | dvrf_builtup); |
| p++; |
| } else if (c2 == c1) { |
| dop->verrel |= (dvrf_strict | dvrf_builtup); |
| 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= dvr_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 |= (dvrf_orequal | dvrf_builtup); |
| } |
| } else if (c1 == '=') { |
| dop->verrel= dvr_exact; |
| 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= dvr_exact; |
| } |
| if ((dop->verrel!=dvr_exact) && (fip->integer==dep_provides)) |
| parse_warn(ps, |
| _("Only exact versions may be used for Provides")); |
| |
| if (!isspace(*p) && !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 (isspace(*p)) p++; |
| |
| versionstart= p; |
| while (*p && *p != ')' && *p != '(') { |
| if (isspace(*p)) break; |
| p++; |
| } |
| versionlength= p - versionstart; |
| while (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); |
| parse_db_version(ps, &dop->version, version.buf, |
| _("'%s' field, reference to '%.255s': " |
| "error in version"), fip->name, depname.buf); |
| p++; while (isspace(*p)) p++; |
| } else { |
| dop->verrel= dvr_none; |
| blankversion(&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 (isspace(*p)) p++; |
| } |
| if (!*p) break; |
| p++; while (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 (cisspace(*p)) { |
| p++; |
| continue; |
| } |
| start = p; |
| break; |
| } |
| for (;;) { |
| if (*p && !cisspace(*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 *pifp, |
| 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 `triggers-pending' field not allowed in " |
| "this context")); |
| |
| 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 *pifp, |
| struct parsedb_state *ps, |
| const char *value, const struct fieldinfo *fip) |
| { |
| const char *word, *emsg; |
| struct pkginfo *pend; |
| |
| if (ps->flags & pdb_rejectstatus) |
| parse_error(ps, |
| _("value for `triggers-awaited' field not allowed in " |
| "this context")); |
| |
| while ((word = scan_word(&value))) { |
| emsg = pkg_name_is_illegal(word, NULL); |
| if (emsg) |
| parse_error(ps, |
| _("illegal package name in awaited trigger `%.255s': %s"), |
| word, emsg); |
| pend = pkg_db_find(word); |
| |
| if (!trig_note_aw(pend, aw)) |
| parse_error(ps, |
| _("duplicate awaited trigger package `%.255s'"), word); |
| |
| trig_awaited_pend_enqueue(pend); |
| } |
| } |