
(********************************************************************)
(*                                                                  *)
(*  rpm.s7i       Rpm archive library                               *)
(*  Copyright (C) 2020  Thomas Mertes                               *)
(*                                                                  *)
(*  This file is part of the Seed7 Runtime Library.                 *)
(*                                                                  *)
(*  The Seed7 Runtime Library is free software; you can             *)
(*  redistribute it and/or modify it under the terms of the GNU     *)
(*  Lesser General Public License as published by the Free Software *)
(*  Foundation; either version 2.1 of the License, or (at your      *)
(*  option) any later version.                                      *)
(*                                                                  *)
(*  The Seed7 Runtime Library 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 Lesser General Public License for more    *)
(*  details.                                                        *)
(*                                                                  *)
(*  You should have received a copy of the GNU Lesser General       *)
(*  Public License along with this program; if not, write to the    *)
(*  Free Software Foundation, Inc., 51 Franklin Street,             *)
(*  Fifth Floor, Boston, MA  02110-1301, USA.                       *)
(*                                                                  *)
(********************************************************************)


include "stdio.s7i";
include "time.s7i";
include "filesys.s7i";
include "filebits.s7i";
include "bytedata.s7i";
include "msgdigest.s7i";
include "strifile.s7i";
include "gzip.s7i";
include "lzma.s7i";
include "xz.s7i";
include "zstd.s7i";
include "cpio.s7i";


const string: RPM_LEAD_MAGIC is "\16#ed;\16#ab;\16#ee;\16#db;";
const string: RPM_HEADER_MAGIC is "\16#8e;\16#ad;\16#e8;";


const type: rpmLead is new struct
    var string: magic is "";
    var integer: majorVersion is 0;
    var integer: minorVersion is 0;
    var integer: fileType is 0;
    var integer: arch is 0;
    var string: name is "";
    var integer: os is 0;
    var integer: sig is 0;
  end struct;

const type: rpmHeader is new struct
    var string: magic is "";
    var integer: version is 0;
    var integer: indexCount is 0;
    var integer: storeSize is 0;
  end struct;

const type: rpmIndexEntry is new struct
    var integer: tag is 0;
    var integer: dataType is 0;
    var integer: offset is 0;
    var integer: count is 0;
    var string: striValue is "";
    var array string: arrayValue is 0 times "";
  end struct;

const type: rpmTagMap is hash [integer] rpmIndexEntry;

const integer: RPM_NULL_TYPE         is 0;  # No size
const integer: RPM_CHAR_TYPE         is 1;  # Size 1
const integer: RPM_INT8_TYPE         is 2;  # Size 1
const integer: RPM_INT16_TYPE        is 3;  # Size 2
const integer: RPM_INT32_TYPE        is 4;  # Size 4
const integer: RPM_INT64_TYPE        is 5;  # Size 8
const integer: RPM_STRING_TYPE       is 6;  # Variable number of bytes, terminated by a NULL
const integer: RPM_BIN_TYPE          is 7;  # Size 1
const integer: RPM_STRING_ARRAY_TYPE is 8;  # Variable, vector of NULL-terminated strings
const integer: RPM_I18NSTRING_TYPE   is 9;  # Variable, vector of NULL-terminated strings

const integer: RPMTAG_HEADERIMMUTABLE    is   63;  # BIN          Optional
const integer: RPMTAG_HEADERI18NTABLE    is  100;  # STRING_ARRAY Required  Contains a list of locales for which strings are provided
const integer: RPMTAG_NAME               is 1000;  # STRING       Required  Name of package
const integer: RPMTAG_VERSION            is 1001;  # STRING       Required  Version of package
const integer: RPMTAG_RELEASE            is 1002;  # STRING       Required  Release of package
const integer: RPMTAG_EPOCH              is 1003;  #                        Epoch of package
const integer: RPMTAG_SUMMARY            is 1004;  # I18NSTRING   Required  Summary description of the package
const integer: RPMTAG_DESCRIPTION        is 1005;  # I18NSTRING   Required  Full description of the package
const integer: RPMTAG_BUILDTIME          is 1006;  # INT32        Optional  Seconds since epoch when pkg built
const integer: RPMTAG_BUILDHOST          is 1007;  # STRING       Optional  Host package was built on
const integer: RPMTAG_INSTALLTIME        is 1008;
const integer: RPMTAG_SIZE               is 1009;  # INT32        Required  Sum of sizes of regular files in archive
const integer: RPMTAG_DISTRIBUTION       is 1010;  # STRING
const integer: RPMTAG_VENDOR             is 1011;
const integer: RPMTAG_GIF                is 1012;
const integer: RPMTAG_XPM                is 1013;
const integer: RPMTAG_LICENSE            is 1014;  # STRING       Required  Package license
const integer: RPMTAG_PACKAGER           is 1015;
const integer: RPMTAG_GROUP              is 1016;  # I18NSTRING   Required  Package group
const integer: RPMTAG_CHANGELOG          is 1017;
const integer: RPMTAG_SOURCE             is 1018;
const integer: RPMTAG_PATCH              is 1019;
const integer: RPMTAG_URL                is 1020;
const integer: RPMTAG_OS                 is 1021;  # STRING       Required
const integer: RPMTAG_ARCH               is 1022;  # STRING       Required  Architecture of package
const integer: RPMTAG_PREIN              is 1023;  # STRING
const integer: RPMTAG_POSTIN             is 1024;  # STRING
const integer: RPMTAG_PREUN              is 1025;  # STRING
const integer: RPMTAG_POSTUN             is 1026;  # STRING
const integer: RPMTAG_OLDFILENAMES       is 1027;  # STRING_ARRAY Optional  Filenames when not in "compressed format"
const integer: RPMTAG_FILESIZES          is 1028;  # INT32      Size of each file [indexed]
const integer: RPMTAG_FILESTATES         is 1029;  # CHAR       (only after installed)
const integer: RPMTAG_FILEMODES          is 1030;  # INT16      Mode of each file [indexed]
const integer: RPMTAG_FILEUIDS           is 1031;
const integer: RPMTAG_FILEGIDS           is 1032;
const integer: RPMTAG_FILERDEVS          is 1033;  # INT16      Device number from which the file was copied [indexed]
const integer: RPMTAG_FILEMTIMES         is 1034;  # INT32      Modification time seconds since epoch [indexed]
const integer: RPMTAG_FILEMD5S           is 1035;  # STRING_ARRAY  MD5 sum of each file [indexed] (blank if not regular file)
const integer: RPMTAG_FILELINKTOS        is 1036;  # STRING_ARRAY  Target for symlink [indexed]
const integer: RPMTAG_FILEFLAGS          is 1037;  # INT32 (bits)  Bits that classify and control how files installed [indexed]
const integer: RPMTAG_ROOT               is 1038;
const integer: RPMTAG_FILEUSERNAME       is 1039;  # STRING_ARRAY  Owner name of file [indexed]
const integer: RPMTAG_FILEGROUPNAME      is 1040;  # STRING_ARRAY  Group name of file [indexed]
const integer: RPMTAG_EXCLUDE            is 1041;
const integer: RPMTAG_EXCLUSIVE          is 1042;
const integer: RPMTAG_ICON               is 1043;
const integer: RPMTAG_SOURCERPM          is 1044;  # STRING       Optional  Name of associated source rpm
const integer: RPMTAG_FILEVERIFYFLAGS    is 1045;  # INT32 (bits) Optional  Bits that control how files are to be verified after install [indexed]
const integer: RPMTAG_ARCHIVESIZE        is 1046;  # INT32        Optional  Uncompressed size of the payload archive
const integer: RPMTAG_PROVIDENAME        is 1047;  # STRING_ARRAY  List of dependency names [index]
const integer: RPMTAG_REQUIREFLAGS       is 1048;  # INT32 (bits)  Bits to specify the dependency range and context [indexed]
const integer: RPMTAG_REQUIRENAME        is 1049;  # STRING_ARRAY  List of requirement names [index]
const integer: RPMTAG_REQUIREVERSION     is 1050;  # STRING_ARRAY  Version associated with Requirename [indexed]
const integer: RPMTAG_NOSOURCE           is 1051;
const integer: RPMTAG_NOPATCH            is 1052;
const integer: RPMTAG_CONFLICTFLAGS      is 1053;  # INT32 (bits)  Bits to specify the conflict range and context [indexed]
const integer: RPMTAG_CONFLICTNAME       is 1054;  # STRING_ARRAY  List of conflict names [index]
const integer: RPMTAG_CONFLICTVERSION    is 1055;  # STRING_ARRAY  Version associated with Versioname [indexed]
const integer: RPMTAG_DEFAULTPREFIX      is 1056;
const integer: RPMTAG_BUILDROOT          is 1057;
const integer: RPMTAG_INSTALLPREFIX      is 1058;
const integer: RPMTAG_EXCLUDEARCH        is 1059;
const integer: RPMTAG_EXCLUDEOS          is 1060;
const integer: RPMTAG_EXCLUSIVEARCH      is 1061;
const integer: RPMTAG_EXCLUSIVEOS        is 1062;
const integer: RPMTAG_AUTOREQPROV        is 1063;
const integer: RPMTAG_RPMVERSION         is 1064;  # STRING       Optional  Version of the RPM tool used to build package
const integer: RPMTAG_TRIGGERSCRIPTS     is 1065;
const integer: RPMTAG_TRIGGERNAME        is 1066;
const integer: RPMTAG_TRIGGERVERSION     is 1067;
const integer: RPMTAG_TRIGGERFLAGS       is 1068;
const integer: RPMTAG_TRIGGERINDEX       is 1069;
const integer: RPMTAG_VERIFYSCRIPT       is 1079;
const integer: RPMTAG_CHANGELOGTIME      is 1080;  # INT32        Optional  Seconds since epoch for changelog entry
const integer: RPMTAG_CHANGELOGNAME      is 1081;  # STRING_ARRAY Optional  Name line of changelog entry
const integer: RPMTAG_CHANGELOGTEXT      is 1082;  # STRING_ARRAY Optional  Text of changelog entry
const integer: RPMTAG_BROKENMD5          is 1083;
const integer: RPMTAG_PREREQ             is 1084;
const integer: RPMTAG_PREINPROG          is 1085;  # STRING  Interpreter for prein scripts
const integer: RPMTAG_POSTINPROG         is 1086;  # STRING  Interpreter for postin scripts
const integer: RPMTAG_PREUNPROG          is 1087;  # STRING  Interpreter for preun scripts
const integer: RPMTAG_POSTUNPROG         is 1088;  # STRING  Interpreter for postun scripts
const integer: RPMTAG_BUILDARCHS         is 1089;
const integer: RPMTAG_OBSOLETENAME       is 1090;  # STRING_ARRAY  List of obsoleted dependency names [index]
const integer: RPMTAG_VERIFYSCRIPTPROG   is 1091;
const integer: RPMTAG_TRIGGERSCRIPTPROG  is 1092;
const integer: RPMTAG_DOCDIR             is 1093;
const integer: RPMTAG_COOKIE             is 1094;  # STRING       Optional
const integer: RPMTAG_FILEDEVICES        is 1095;  # INT32  16-bit device number from which the file was copied [indexed]
const integer: RPMTAG_FILEINODES         is 1096;  # INT32  Inode value from the original file on build host [indexed]
const integer: RPMTAG_FILELANGS          is 1097;  # STRING_ARRAY  Per-file locale marker [indexed]
const integer: RPMTAG_PREFIXES           is 1098;  # STRING_ARRAY  Relocation prefixes
const integer: RPMTAG_INSTPREFIXES       is 1099;
const integer: RPMTAG_TRIGGERIN          is 1100;
const integer: RPMTAG_TRIGGERUN          is 1101;
const integer: RPMTAG_TRIGGERPOSTUN      is 1102;
const integer: RPMTAG_AUTOREQ            is 1103;
const integer: RPMTAG_AUTOPROV           is 1104;
const integer: RPMTAG_CAPABILITY         is 1105;
const integer: RPMTAG_SOURCEPACKAGE      is 1106;
const integer: RPMTAG_OLDORIGFILENAMES   is 1107;
const integer: RPMTAG_BUILDPREREQ        is 1108;
const integer: RPMTAG_BUILDREQUIRES      is 1109;
const integer: RPMTAG_BUILDCONFLICTS     is 1110;
const integer: RPMTAG_BUILDMACROS        is 1111;
const integer: RPMTAG_PROVIDEFLAGS       is 1112;  # INT32 (bits)  Bits to specify the provide range and context [indexed]
const integer: RPMTAG_PROVIDEVERSION     is 1113;  # STRING_ARRAY  Version associated with provide names [indexed]
const integer: RPMTAG_OBSOLETEFLAGS      is 1114;  # INT32 (bits)  Bits to specify the obsolete range and context [indexed]
const integer: RPMTAG_OBSOLETEVERSION    is 1115;  # STRING_ARRAY  Version associated with the obsolete names [indexed]
const integer: RPMTAG_DIRINDEXES         is 1116;  # INT32        Optional  Specifies the index for the file into the dirnames array [indexed]
const integer: RPMTAG_BASENAMES          is 1117;  # STRING_ARRAY Optional  Specified the base portion of the filename [index]
const integer: RPMTAG_DIRNAMES           is 1118;  # STRING_ARRAY Optional  The directory portion of the filename [indexed]
const integer: RPMTAG_ORIGDIRINDEXES     is 1119;
const integer: RPMTAG_ORIGBASENAMES      is 1120;
const integer: RPMTAG_ORIGDIRNAMES       is 1121;
const integer: RPMTAG_OPTFLAGS           is 1122;  # STRING       Optional  Additional flags passed when compiling the package
const integer: RPMTAG_DISTURL            is 1123;
const integer: RPMTAG_PAYLOADFORMAT      is 1124;  # STRING       Required  Format of the archive section. E.g.: "cpio"
const integer: RPMTAG_PAYLOADCOMPRESSOR  is 1125;  # STRING       Required  Format of the compression of the archive. E.g.: "gzip"
const integer: RPMTAG_PAYLOADFLAGS       is 1126;  # STRING       Required  Specifies the compression level of package "9"
const integer: RPMTAG_INSTALLCOLOR       is 1127;
const integer: RPMTAG_INSTALLTID         is 1128;
const integer: RPMTAG_REMOVETID          is 1129;
const integer: RPMTAG_SHA1RHN            is 1130;
const integer: RPMTAG_RHNPLATFORM        is 1131;  # STRING       Deprecated
const integer: RPMTAG_PLATFORM           is 1132;  # STRING       Optional
const integer: RPMTAG_PATCHESNAME        is 1133;
const integer: RPMTAG_PATCHESFLAGS       is 1134;
const integer: RPMTAG_PATCHESVERSION     is 1135;
const integer: RPMTAG_CACHECTIME         is 1136;
const integer: RPMTAG_CACHEPKGPATH       is 1137;
const integer: RPMTAG_CACHEPKGSIZE       is 1138;
const integer: RPMTAG_CACHEPKGMTIME      is 1139;
const integer: RPMTAG_FILECOLORS         is 1140;  # INT32 (bits)  "color" (type) of the file [indexed]
const integer: RPMTAG_FILECLASS          is 1141;  # INT  Index into class dictionary [indexed]
const integer: RPMTAG_CLASSDICT          is 1142;  # STRING_ARRAY  List of file classes (output of the "file" command)
const integer: RPMTAG_FILEDEPENDSX       is 1143;  # INT  Size of the entries in DependsDict [indexed]
const integer: RPMTAG_FILEDEPENDSN       is 1144;  # INT  Index into the entries in DependsDict [indexed]
const integer: RPMTAG_DEPENDSDICT        is 1145;  # INT32  Type/Index into Requires/Provides/Conflicts/Obsoletes
const integer: RPMTAG_SOURCEPKGID        is 1146;
const integer: RPMTAG_FILECONTEXTS       is 1147;
const integer: RPMTAG_FSCONTEXTS         is 1148;
const integer: RPMTAG_RECONTEXTS         is 1149;
const integer: RPMTAG_POLICIES           is 1150;
const integer: RPMTAG_PRETRANS           is 1151;
const integer: RPMTAG_POSTTRANS          is 1152;
const integer: RPMTAG_PRETRANSPROG       is 1153;
const integer: RPMTAG_POSTTRANSPROG      is 1154;
const integer: RPMTAG_DISTTAG            is 1155;
const integer: RPMTAG_OLDSUGGESTSNAME    is 1156;
const integer: RPMTAG_OLDSUGGESTSVERSION is 1157;
const integer: RPMTAG_OLDSUGGESTSFLAGS   is 1158;
const integer: RPMTAG_OLDENHANCESNAME    is 1159;
const integer: RPMTAG_OLDENHANCESVERSION is 1160;
const integer: RPMTAG_OLDENHANCESFLAGS   is 1161;
const integer: RPMTAG_PRIORITY           is 1162;
const integer: RPMTAG_CVSID              is 1163;
const integer: RPMTAG_BLINKPKGID         is 1164;
const integer: RPMTAG_BLINKHDRID         is 1165;
const integer: RPMTAG_BLINKNEVRA         is 1166;
const integer: RPMTAG_FLINKPKGID         is 1167;
const integer: RPMTAG_FLINKHDRID         is 1168;
const integer: RPMTAG_FLINKNEVRA         is 1169;
const integer: RPMTAG_PACKAGEORIGIN      is 1170;
const integer: RPMTAG_TRIGGERPREIN       is 1171;
const integer: RPMTAG_BUILDSUGGESTS      is 1172;
const integer: RPMTAG_BUILDENHANCES      is 1173;
const integer: RPMTAG_SCRIPTSTATES       is 1174;
const integer: RPMTAG_SCRIPTMETRICS      is 1175;
const integer: RPMTAG_BUILDCPUCLOCK      is 1176;
const integer: RPMTAG_FILEDIGESTALGOS    is 1177;
const integer: RPMTAG_VARIANTS           is 1178;
const integer: RPMTAG_XMAJOR             is 1179;
const integer: RPMTAG_XMINOR             is 1180;
const integer: RPMTAG_REPOTAG            is 1181;
const integer: RPMTAG_KEYWORDS           is 1182;
const integer: RPMTAG_BUILDPLATFORMS     is 1183;
const integer: RPMTAG_PACKAGECOLOR       is 1184;
const integer: RPMTAG_PACKAGEPREFCOLOR   is 1185;
const integer: RPMTAG_XATTRSDICT         is 1186;
const integer: RPMTAG_FILEXATTRSX        is 1187;
const integer: RPMTAG_DEPATTRSDICT       is 1188;
const integer: RPMTAG_CONFLICTATTRSX     is 1189;
const integer: RPMTAG_OBSOLETEATTRSX     is 1190;
const integer: RPMTAG_PROVIDEATTRSX      is 1191;
const integer: RPMTAG_REQUIREATTRSX      is 1192;
const integer: RPMTAG_BUILDPROVIDES      is 1193;
const integer: RPMTAG_BUILDOBSOLETES     is 1194;
const integer: RPMTAG_DBINSTANCE         is 1195;
const integer: RPMTAG_NVRA               is 1196;
const integer: RPMTAG_FILENAMES          is 5000;
const integer: RPMTAG_FILEPROVIDE        is 5001;
const integer: RPMTAG_FILEREQUIRE        is 5002;
const integer: RPMTAG_FSNAMES            is 5003;
const integer: RPMTAG_FSSIZES            is 5004;
const integer: RPMTAG_TRIGGERCONDS       is 5005;
const integer: RPMTAG_TRIGGERTYPE        is 5006;
const integer: RPMTAG_ORIGFILENAMES      is 5007;
const integer: RPMTAG_LONGFILESIZES      is 5008;
const integer: RPMTAG_LONGSIZE           is 5009;
const integer: RPMTAG_FILECAPS           is 5010;
const integer: RPMTAG_FILEDIGESTALGO     is 5011;
const integer: RPMTAG_BUGURL             is 5012;
const integer: RPMTAG_EVR                is 5013;
const integer: RPMTAG_NVR                is 5014;
const integer: RPMTAG_NEVR               is 5015;
const integer: RPMTAG_NEVRA              is 5016;
const integer: RPMTAG_HEADERCOLOR        is 5017;
const integer: RPMTAG_VERBOSE            is 5018;
const integer: RPMTAG_EPOCHNUM           is 5019;
const integer: RPMTAG_PREINFLAGS         is 5020;
const integer: RPMTAG_POSTINFLAGS        is 5021;
const integer: RPMTAG_PREUNFLAGS         is 5022;
const integer: RPMTAG_POSTUNFLAGS        is 5023;
const integer: RPMTAG_PRETRANSFLAGS      is 5024;
const integer: RPMTAG_POSTTRANSFLAGS     is 5025;
const integer: RPMTAG_VERIFYSCRIPTFLAGS  is 5026;
const integer: RPMTAG_TRIGGERSCRIPTFLAGS is 5027;
const integer: RPMTAG_COLLECTIONS        is 5029;
const integer: RPMTAG_POLICYNAMES        is 5030;
const integer: RPMTAG_POLICYTYPES        is 5031;
const integer: RPMTAG_POLICYTYPESINDEXES is 5032;
const integer: RPMTAG_POLICYFLAGS        is 5033;
const integer: RPMTAG_VCS                is 5034;
const integer: RPMTAG_ORDERNAME          is 5035;
const integer: RPMTAG_ORDERVERSION       is 5036;
const integer: RPMTAG_ORDERFLAGS         is 5037;
const integer: RPMTAG_MSSFMANIFEST       is 5038;
const integer: RPMTAG_MSSFDOMAIN         is 5039;
const integer: RPMTAG_INSTFILENAMES      is 5040;
const integer: RPMTAG_REQUIRENEVRS       is 5041;
const integer: RPMTAG_PROVIDENEVRS       is 5042;
const integer: RPMTAG_OBSOLETENEVRS      is 5043;
const integer: RPMTAG_CONFLICTNEVRS      is 5044;
const integer: RPMTAG_FILENLINKS         is 5045;
const integer: RPMTAG_RECOMMENDNAME      is 5046;
const integer: RPMTAG_RECOMMENDVERSION   is 5047;
const integer: RPMTAG_RECOMMENDFLAGS     is 5048;
const integer: RPMTAG_SUGGESTNAME        is 5049;
const integer: RPMTAG_SUGGESTVERSION     is 5050;
const integer: RPMTAG_SUGGESTFLAGS       is 5051;
const integer: RPMTAG_SUPPLEMENTNAME     is 5052;
const integer: RPMTAG_SUPPLEMENTVERSION  is 5053;
const integer: RPMTAG_SUPPLEMENTFLAGS    is 5054;
const integer: RPMTAG_ENHANCENAME        is 5055;
const integer: RPMTAG_ENHANCEVERSION     is 5056;
const integer: RPMTAG_ENHANCEFLAGS       is 5057;
const integer: RPMTAG_RECOMMENDNEVRS     is 5058;
const integer: RPMTAG_SUGGESTNEVRS       is 5059;
const integer: RPMTAG_SUPPLEMENTNEVRS    is 5060;
const integer: RPMTAG_ENHANCENEVRS       is 5061;
const integer: RPMTAG_ENCODING           is 5062;
const integer: RPMTAG_FILETRIGGERIN               is 5063;  # internal
const integer: RPMTAG_FILETRIGGERUN               is 5064;  # internal
const integer: RPMTAG_FILETRIGGERPOSTUN           is 5065;  # internal
const integer: RPMTAG_FILETRIGGERSCRIPTS          is 5066;  # s[]
const integer: RPMTAG_FILETRIGGERSCRIPTPROG       is 5067;  # s[]
const integer: RPMTAG_FILETRIGGERSCRIPTFLAGS      is 5068;  # i[]
const integer: RPMTAG_FILETRIGGERNAME             is 5069;  # s[]
const integer: RPMTAG_FILETRIGGERINDEX            is 5070;  # i[]
const integer: RPMTAG_FILETRIGGERVERSION          is 5071;  # s[]
const integer: RPMTAG_FILETRIGGERFLAGS            is 5072;  # i[]
const integer: RPMTAG_TRANSFILETRIGGERIN          is 5073;  # internal
const integer: RPMTAG_TRANSFILETRIGGERUN          is 5074;  # internal
const integer: RPMTAG_TRANSFILETRIGGERPOSTUN      is 5075;  # internal
const integer: RPMTAG_TRANSFILETRIGGERSCRIPTS     is 5076;  # s[]
const integer: RPMTAG_TRANSFILETRIGGERSCRIPTPROG  is 5077;  # s[]
const integer: RPMTAG_TRANSFILETRIGGERSCRIPTFLAGS is 5078;  # i[]
const integer: RPMTAG_TRANSFILETRIGGERNAME        is 5079;  # s[]
const integer: RPMTAG_TRANSFILETRIGGERINDEX       is 5080;  # i[]
const integer: RPMTAG_TRANSFILETRIGGERVERSION     is 5081;  # s[]
const integer: RPMTAG_TRANSFILETRIGGERFLAGS       is 5082;  # i[]
const integer: RPMTAG_REMOVEPATHPOSTFIXES         is 5083;  # s internal
const integer: RPMTAG_FILETRIGGERPRIORITIES       is 5084;  # i[]
const integer: RPMTAG_TRANSFILETRIGGERPRIORITIES  is 5085;  # i[]
const integer: RPMTAG_FILETRIGGERCONDS            is 5086;  # s[] extension
const integer: RPMTAG_FILETRIGGERTYPE             is 5087;  # s[] extension
const integer: RPMTAG_TRANSFILETRIGGERCONDS       is 5088;  # s[] extension
const integer: RPMTAG_TRANSFILETRIGGERTYPE        is 5089;  # s[] extension
const integer: RPMTAG_FILESIGNATURES              is 5090;  # s[]
const integer: RPMTAG_FILESIGNATURELENGTH         is 5091;  # i
const integer: RPMTAG_PAYLOADDIGEST               is 5092;  # s[]
const integer: RPMTAG_PAYLOADDIGESTALGO           is 5093;  # i
const integer: RPMTAG_AUTOINSTALLED               is 5094;  # i reservation (unimplemented)
const integer: RPMTAG_IDENTITY                    is 5095;  # s reservation (unimplemented)
const integer: RPMTAG_MODULARITYLABEL             is 5096;  # s
const integer: RPMTAG_PAYLOADDIGESTALT            is 5097;  # s[]

const integer: RPMSIGTAG_HEADERSIGNATURES is   62;  # BIN    Optional
const integer: RPMSIGTAG_SIZE             is 1000;  # INT32  Required  Byte count for the header section plus the compressed payload.
const integer: RPMSIGTAG_PGP              is 1002;  # BIN    Optional
const integer: RPMSIGTAG_MD5              is 1004;  # BIN    Required  MD5 checksum over the header section plus the payload.
const integer: RPMSIGTAG_GPG              is 1005;  # BIN    Optional
const integer: RPMSIGTAG_PAYLOADSIZE      is 1007;  # INT32  Optional  Byte count for the uncompressed payload.
const integer: RPMSIGTAG_RESERVEDSPACE    is 1008;  # BIN
const integer: RPMSIGTAG_SHA1HEADER       is 1010;  # STRING Optional
const integer: RPMSIGTAG_DSAHEADER        is 1011;  # BIN    Optional
const integer: RPMSIGTAG_RSAHEADER        is 1012;  # BIN    Optional
const integer: RPMSIGTAG_SHA1             is  269;  # STRING
const integer: RPMSIGTAG_SHA256HEADER     is  273;  # STRING

const integer: RPM_DIGESTALGO_MD5         is  1;
const integer: RPM_DIGESTALGO_SHA1        is  2;
const integer: RPM_DIGESTALGO_RIPEMD160   is  3;
const integer: RPM_DIGESTALGO_MD2         is  5;
const integer: RPM_DIGESTALGO_TIGER192    is  6;
const integer: RPM_DIGESTALGO_HAVAL_5_160 is  7;
const integer: RPM_DIGESTALGO_SHA256      is  8;
const integer: RPM_DIGESTALGO_SHA384      is  9;
const integer: RPM_DIGESTALGO_SHA512      is 10;
const integer: RPM_DIGESTALGO_SHA224      is 11;


const func string: sigtagName (in integer: tag) is func
  result
    var string: name is "";
  begin
    case tag of
      when {  62}: name := "HEADERSIGNATURES";
      when {1000}: name := "SIZE";
      when {1001}: name := "LEMD5_1";
      when {1002}: name := "PGP";
      when {1003}: name := "LEMD5_2";
      when {1004}: name := "MD5";
      when {1005}: name := "GPG";
      when {1006}: name := "PGP5";
      when {1007}: name := "PAYLOADSIZE";
      when {1008}: name := "RESERVEDSPACE";
      when { 257}: name := "SIGSIZE";
      when { 258}: name := "SIGLEMD5_1";
      when { 259}: name := "SIGPGP";
      when { 260}: name := "SIGLEMD5_2";
      when { 261}: name := "SIGMD5";
      when { 262}: name := "SIGGPG";
      when { 263}: name := "SIGPGP5";
      when { 264}: name := "BADSHA1_1";
      when { 265}: name := "BADSHA1_2";
      when { 267}: name := "DSA";
      when { 268}: name := "RSA";
      when { 269}: name := "SHA1";
      when { 270}: name := "LONGSIZE";
      when { 271}: name := "LONGARCHIVESIZE";
      when { 273}: name := "SHA256HEADER";
      otherwise: name := "tag " <& tag;
    end case;
  end func;


const func string: rpmtagName (in integer: tag) is func
  result
    var string: name is "";
  begin
    case tag of
      when {RPMTAG_HEADERIMMUTABLE}:             name := "HEADERIMMUTABLE";
      when {RPMTAG_HEADERI18NTABLE}:             name := "HEADERI18NTABLE";
      when {RPMTAG_NAME}:                        name := "NAME";
      when {RPMTAG_VERSION}:                     name := "VERSION";
      when {RPMTAG_RELEASE}:                     name := "RELEASE";
      when {RPMTAG_EPOCH}:                       name := "EPOCH";
      when {RPMTAG_SUMMARY}:                     name := "SUMMARY";
      when {RPMTAG_DESCRIPTION}:                 name := "DESCRIPTION";
      when {RPMTAG_BUILDTIME}:                   name := "BUILDTIME";
      when {RPMTAG_BUILDHOST}:                   name := "BUILDHOST";
      when {RPMTAG_INSTALLTIME}:                 name := "INSTALLTIME";
      when {RPMTAG_SIZE}:                        name := "SIZE";
      when {RPMTAG_DISTRIBUTION}:                name := "DISTRIBUTION";
      when {RPMTAG_VENDOR}:                      name := "VENDOR";
      when {RPMTAG_GIF}:                         name := "GIF";
      when {RPMTAG_XPM}:                         name := "XPM";
      when {RPMTAG_LICENSE}:                     name := "LICENSE";
      when {RPMTAG_PACKAGER}:                    name := "PACKAGER";
      when {RPMTAG_GROUP}:                       name := "GROUP";
      when {RPMTAG_CHANGELOG}:                   name := "CHANGELOG";
      when {RPMTAG_SOURCE}:                      name := "SOURCE";
      when {RPMTAG_PATCH}:                       name := "PATCH";
      when {RPMTAG_URL}:                         name := "URL";
      when {RPMTAG_OS}:                          name := "OS";
      when {RPMTAG_ARCH}:                        name := "ARCH";
      when {RPMTAG_PREIN}:                       name := "PREIN";
      when {RPMTAG_POSTIN}:                      name := "POSTIN";
      when {RPMTAG_PREUN}:                       name := "PREUN";
      when {RPMTAG_POSTUN}:                      name := "POSTUN";
      when {RPMTAG_OLDFILENAMES}:                name := "OLDFILENAMES";
      when {RPMTAG_FILESIZES}:                   name := "FILESIZES";
      when {RPMTAG_FILESTATES}:                  name := "FILESTATES";
      when {RPMTAG_FILEMODES}:                   name := "FILEMODES";
      when {RPMTAG_FILEUIDS}:                    name := "FILEUIDS";
      when {RPMTAG_FILEGIDS}:                    name := "FILEGIDS";
      when {RPMTAG_FILERDEVS}:                   name := "FILERDEVS";
      when {RPMTAG_FILEMTIMES}:                  name := "FILEMTIMES";
      when {RPMTAG_FILEMD5S}:                    name := "FILEMD5S";
      when {RPMTAG_FILELINKTOS}:                 name := "FILELINKTOS";
      when {RPMTAG_FILEFLAGS}:                   name := "FILEFLAGS";
      when {RPMTAG_ROOT}:                        name := "ROOT";
      when {RPMTAG_FILEUSERNAME}:                name := "FILEUSERNAME";
      when {RPMTAG_FILEGROUPNAME}:               name := "FILEGROUPNAME";
      when {RPMTAG_EXCLUDE}:                     name := "EXCLUDE";
      when {RPMTAG_EXCLUSIVE}:                   name := "EXCLUSIVE";
      when {RPMTAG_ICON}:                        name := "ICON";
      when {RPMTAG_SOURCERPM}:                   name := "SOURCERPM";
      when {RPMTAG_FILEVERIFYFLAGS}:             name := "FILEVERIFYFLAGS";
      when {RPMTAG_ARCHIVESIZE}:                 name := "ARCHIVESIZE";
      when {RPMTAG_PROVIDENAME}:                 name := "PROVIDENAME";
      when {RPMTAG_REQUIREFLAGS}:                name := "REQUIREFLAGS";
      when {RPMTAG_REQUIRENAME}:                 name := "REQUIRENAME";
      when {RPMTAG_REQUIREVERSION}:              name := "REQUIREVERSION";
      when {RPMTAG_NOSOURCE}:                    name := "NOSOURCE";
      when {RPMTAG_NOPATCH}:                     name := "NOPATCH";
      when {RPMTAG_CONFLICTFLAGS}:               name := "CONFLICTFLAGS";
      when {RPMTAG_CONFLICTNAME}:                name := "CONFLICTNAME";
      when {RPMTAG_CONFLICTVERSION}:             name := "CONFLICTVERSION";
      when {RPMTAG_DEFAULTPREFIX}:               name := "DEFAULTPREFIX";
      when {RPMTAG_BUILDROOT}:                   name := "BUILDROOT";
      when {RPMTAG_INSTALLPREFIX}:               name := "INSTALLPREFIX";
      when {RPMTAG_EXCLUDEARCH}:                 name := "EXCLUDEARCH";
      when {RPMTAG_EXCLUDEOS}:                   name := "EXCLUDEOS";
      when {RPMTAG_EXCLUSIVEARCH}:               name := "EXCLUSIVEARCH";
      when {RPMTAG_EXCLUSIVEOS}:                 name := "EXCLUSIVEOS";
      when {RPMTAG_AUTOREQPROV}:                 name := "AUTOREQPROV";
      when {RPMTAG_RPMVERSION}:                  name := "RPMVERSION";
      when {RPMTAG_TRIGGERSCRIPTS}:              name := "TRIGGERSCRIPTS";
      when {RPMTAG_TRIGGERNAME}:                 name := "TRIGGERNAME";
      when {RPMTAG_TRIGGERVERSION}:              name := "TRIGGERVERSION";
      when {RPMTAG_TRIGGERFLAGS}:                name := "TRIGGERFLAGS";
      when {RPMTAG_TRIGGERINDEX}:                name := "TRIGGERINDEX";
      when {RPMTAG_VERIFYSCRIPT}:                name := "VERIFYSCRIPT";
      when {RPMTAG_CHANGELOGTIME}:               name := "CHANGELOGTIME";
      when {RPMTAG_CHANGELOGNAME}:               name := "CHANGELOGNAME";
      when {RPMTAG_CHANGELOGTEXT}:               name := "CHANGELOGTEXT";
      when {RPMTAG_BROKENMD5}:                   name := "BROKENMD5";
      when {RPMTAG_PREREQ}:                      name := "PREREQ";
      when {RPMTAG_PREINPROG}:                   name := "PREINPROG";
      when {RPMTAG_POSTINPROG}:                  name := "POSTINPROG";
      when {RPMTAG_PREUNPROG}:                   name := "PREUNPROG";
      when {RPMTAG_POSTUNPROG}:                  name := "POSTUNPROG";
      when {RPMTAG_BUILDARCHS}:                  name := "BUILDARCHS";
      when {RPMTAG_OBSOLETENAME}:                name := "OBSOLETENAME";
      when {RPMTAG_VERIFYSCRIPTPROG}:            name := "VERIFYSCRIPTPROG";
      when {RPMTAG_TRIGGERSCRIPTPROG}:           name := "TRIGGERSCRIPTPROG";
      when {RPMTAG_DOCDIR}:                      name := "DOCDIR";
      when {RPMTAG_COOKIE}:                      name := "COOKIE";
      when {RPMTAG_FILEDEVICES}:                 name := "FILEDEVICES";
      when {RPMTAG_FILEINODES}:                  name := "FILEINODES";
      when {RPMTAG_FILELANGS}:                   name := "FILELANGS";
      when {RPMTAG_PREFIXES}:                    name := "PREFIXES";
      when {RPMTAG_INSTPREFIXES}:                name := "INSTPREFIXES";
      when {RPMTAG_TRIGGERIN}:                   name := "TRIGGERIN";
      when {RPMTAG_TRIGGERUN}:                   name := "TRIGGERUN";
      when {RPMTAG_TRIGGERPOSTUN}:               name := "TRIGGERPOSTUN";
      when {RPMTAG_AUTOREQ}:                     name := "AUTOREQ";
      when {RPMTAG_AUTOPROV}:                    name := "AUTOPROV";
      when {RPMTAG_CAPABILITY}:                  name := "CAPABILITY";
      when {RPMTAG_SOURCEPACKAGE}:               name := "SOURCEPACKAGE";
      when {RPMTAG_OLDORIGFILENAMES}:            name := "OLDORIGFILENAMES";
      when {RPMTAG_BUILDPREREQ}:                 name := "BUILDPREREQ";
      when {RPMTAG_BUILDREQUIRES}:               name := "BUILDREQUIRES";
      when {RPMTAG_BUILDCONFLICTS}:              name := "BUILDCONFLICTS";
      when {RPMTAG_BUILDMACROS}:                 name := "BUILDMACROS";
      when {RPMTAG_PROVIDEFLAGS}:                name := "PROVIDEFLAGS";
      when {RPMTAG_PROVIDEVERSION}:              name := "PROVIDEVERSION";
      when {RPMTAG_OBSOLETEFLAGS}:               name := "OBSOLETEFLAGS";
      when {RPMTAG_OBSOLETEVERSION}:             name := "OBSOLETEVERSION";
      when {RPMTAG_DIRINDEXES}:                  name := "DIRINDEXES";
      when {RPMTAG_BASENAMES}:                   name := "BASENAMES";
      when {RPMTAG_DIRNAMES}:                    name := "DIRNAMES";
      when {RPMTAG_ORIGDIRINDEXES}:              name := "ORIGDIRINDEXES";
      when {RPMTAG_ORIGBASENAMES}:               name := "ORIGBASENAMES";
      when {RPMTAG_ORIGDIRNAMES}:                name := "ORIGDIRNAMES";
      when {RPMTAG_OPTFLAGS}:                    name := "OPTFLAGS";
      when {RPMTAG_DISTURL}:                     name := "DISTURL";
      when {RPMTAG_PAYLOADFORMAT}:               name := "PAYLOADFORMAT";
      when {RPMTAG_PAYLOADCOMPRESSOR}:           name := "PAYLOADCOMPRESSOR";
      when {RPMTAG_PAYLOADFLAGS}:                name := "PAYLOADFLAGS";
      when {RPMTAG_INSTALLCOLOR}:                name := "INSTALLCOLOR";
      when {RPMTAG_INSTALLTID}:                  name := "INSTALLTID";
      when {RPMTAG_REMOVETID}:                   name := "REMOVETID";
      when {RPMTAG_SHA1RHN}:                     name := "SHA1RHN";
      when {RPMTAG_RHNPLATFORM}:                 name := "RHNPLATFORM";
      when {RPMTAG_PLATFORM}:                    name := "PLATFORM";
      when {RPMTAG_PATCHESNAME}:                 name := "PATCHESNAME";
      when {RPMTAG_PATCHESFLAGS}:                name := "PATCHESFLAGS";
      when {RPMTAG_PATCHESVERSION}:              name := "PATCHESVERSION";
      when {RPMTAG_CACHECTIME}:                  name := "CACHECTIME";
      when {RPMTAG_CACHEPKGPATH}:                name := "CACHEPKGPATH";
      when {RPMTAG_CACHEPKGSIZE}:                name := "CACHEPKGSIZE";
      when {RPMTAG_CACHEPKGMTIME}:               name := "CACHEPKGMTIME";
      when {RPMTAG_FILECOLORS}:                  name := "FILECOLORS";
      when {RPMTAG_FILECLASS}:                   name := "FILECLASS";
      when {RPMTAG_CLASSDICT}:                   name := "CLASSDICT";
      when {RPMTAG_FILEDEPENDSX}:                name := "FILEDEPENDSX";
      when {RPMTAG_FILEDEPENDSN}:                name := "FILEDEPENDSN";
      when {RPMTAG_DEPENDSDICT}:                 name := "DEPENDSDICT";
      when {RPMTAG_SOURCEPKGID}:                 name := "SOURCEPKGID";
      when {RPMTAG_FILECONTEXTS}:                name := "FILECONTEXTS";
      when {RPMTAG_FSCONTEXTS}:                  name := "FSCONTEXTS";
      when {RPMTAG_RECONTEXTS}:                  name := "RECONTEXTS";
      when {RPMTAG_POLICIES}:                    name := "POLICIES";
      when {RPMTAG_PRETRANS}:                    name := "PRETRANS";
      when {RPMTAG_POSTTRANS}:                   name := "POSTTRANS";
      when {RPMTAG_PRETRANSPROG}:                name := "PRETRANSPROG";
      when {RPMTAG_POSTTRANSPROG}:               name := "POSTTRANSPROG";
      when {RPMTAG_DISTTAG}:                     name := "DISTTAG";
      when {RPMTAG_OLDSUGGESTSNAME}:             name := "OLDSUGGESTSNAME";
      when {RPMTAG_OLDSUGGESTSVERSION}:          name := "OLDSUGGESTSVERSION";
      when {RPMTAG_OLDSUGGESTSFLAGS}:            name := "OLDSUGGESTSFLAGS";
      when {RPMTAG_OLDENHANCESNAME}:             name := "OLDENHANCESNAME";
      when {RPMTAG_OLDENHANCESVERSION}:          name := "OLDENHANCESVERSION";
      when {RPMTAG_OLDENHANCESFLAGS}:            name := "OLDENHANCESFLAGS";
      when {RPMTAG_PRIORITY}:                    name := "PRIORITY";
      when {RPMTAG_CVSID}:                       name := "CVSID";
      when {RPMTAG_BLINKPKGID}:                  name := "BLINKPKGID";
      when {RPMTAG_BLINKHDRID}:                  name := "BLINKHDRID";
      when {RPMTAG_BLINKNEVRA}:                  name := "BLINKNEVRA";
      when {RPMTAG_FLINKPKGID}:                  name := "FLINKPKGID";
      when {RPMTAG_FLINKHDRID}:                  name := "FLINKHDRID";
      when {RPMTAG_FLINKNEVRA}:                  name := "FLINKNEVRA";
      when {RPMTAG_PACKAGEORIGIN}:               name := "PACKAGEORIGIN";
      when {RPMTAG_TRIGGERPREIN}:                name := "TRIGGERPREIN";
      when {RPMTAG_BUILDSUGGESTS}:               name := "BUILDSUGGESTS";
      when {RPMTAG_BUILDENHANCES}:               name := "BUILDENHANCES";
      when {RPMTAG_SCRIPTSTATES}:                name := "SCRIPTSTATES";
      when {RPMTAG_SCRIPTMETRICS}:               name := "SCRIPTMETRICS";
      when {RPMTAG_BUILDCPUCLOCK}:               name := "BUILDCPUCLOCK";
      when {RPMTAG_FILEDIGESTALGOS}:             name := "FILEDIGESTALGOS";
      when {RPMTAG_VARIANTS}:                    name := "VARIANTS";
      when {RPMTAG_XMAJOR}:                      name := "XMAJOR";
      when {RPMTAG_XMINOR}:                      name := "XMINOR";
      when {RPMTAG_REPOTAG}:                     name := "REPOTAG";
      when {RPMTAG_KEYWORDS}:                    name := "KEYWORDS";
      when {RPMTAG_BUILDPLATFORMS}:              name := "BUILDPLATFORMS";
      when {RPMTAG_PACKAGECOLOR}:                name := "PACKAGECOLOR";
      when {RPMTAG_PACKAGEPREFCOLOR}:            name := "PACKAGEPREFCOLOR";
      when {RPMTAG_XATTRSDICT}:                  name := "XATTRSDICT";
      when {RPMTAG_FILEXATTRSX}:                 name := "FILEXATTRSX";
      when {RPMTAG_DEPATTRSDICT}:                name := "DEPATTRSDICT";
      when {RPMTAG_CONFLICTATTRSX}:              name := "CONFLICTATTRSX";
      when {RPMTAG_OBSOLETEATTRSX}:              name := "OBSOLETEATTRSX";
      when {RPMTAG_PROVIDEATTRSX}:               name := "PROVIDEATTRSX";
      when {RPMTAG_REQUIREATTRSX}:               name := "REQUIREATTRSX";
      when {RPMTAG_BUILDPROVIDES}:               name := "BUILDPROVIDES";
      when {RPMTAG_BUILDOBSOLETES}:              name := "BUILDOBSOLETES";
      when {RPMTAG_DBINSTANCE}:                  name := "DBINSTANCE";
      when {RPMTAG_NVRA}:                        name := "NVRA";
      when {RPMTAG_FILENAMES}:                   name := "FILENAMES";
      when {RPMTAG_FILEPROVIDE}:                 name := "FILEPROVIDE";
      when {RPMTAG_FILEREQUIRE}:                 name := "FILEREQUIRE";
      when {RPMTAG_FSNAMES}:                     name := "FSNAMES";
      when {RPMTAG_FSSIZES}:                     name := "FSSIZES";
      when {RPMTAG_TRIGGERCONDS}:                name := "TRIGGERCONDS";
      when {RPMTAG_TRIGGERTYPE}:                 name := "TRIGGERTYPE";
      when {RPMTAG_ORIGFILENAMES}:               name := "ORIGFILENAMES";
      when {RPMTAG_LONGFILESIZES}:               name := "LONGFILESIZES";
      when {RPMTAG_LONGSIZE}:                    name := "LONGSIZE";
      when {RPMTAG_FILECAPS}:                    name := "FILECAPS";
      when {RPMTAG_FILEDIGESTALGO}:              name := "FILEDIGESTALGO";
      when {RPMTAG_BUGURL}:                      name := "BUGURL";
      when {RPMTAG_EVR}:                         name := "EVR";
      when {RPMTAG_NVR}:                         name := "NVR";
      when {RPMTAG_NEVR}:                        name := "NEVR";
      when {RPMTAG_NEVRA}:                       name := "NEVRA";
      when {RPMTAG_HEADERCOLOR}:                 name := "HEADERCOLOR";
      when {RPMTAG_VERBOSE}:                     name := "VERBOSE";
      when {RPMTAG_EPOCHNUM}:                    name := "EPOCHNUM";
      when {RPMTAG_PREINFLAGS}:                  name := "PREINFLAGS";
      when {RPMTAG_POSTINFLAGS}:                 name := "POSTINFLAGS";
      when {RPMTAG_PREUNFLAGS}:                  name := "PREUNFLAGS";
      when {RPMTAG_POSTUNFLAGS}:                 name := "POSTUNFLAGS";
      when {RPMTAG_PRETRANSFLAGS}:               name := "PRETRANSFLAGS";
      when {RPMTAG_POSTTRANSFLAGS}:              name := "POSTTRANSFLAGS";
      when {RPMTAG_VERIFYSCRIPTFLAGS}:           name := "VERIFYSCRIPTFLAGS";
      when {RPMTAG_TRIGGERSCRIPTFLAGS}:          name := "TRIGGERSCRIPTFLAGS";
      when {RPMTAG_COLLECTIONS}:                 name := "COLLECTIONS";
      when {RPMTAG_POLICYNAMES}:                 name := "POLICYNAMES";
      when {RPMTAG_POLICYTYPES}:                 name := "POLICYTYPES";
      when {RPMTAG_POLICYTYPESINDEXES}:          name := "POLICYTYPESINDEXES";
      when {RPMTAG_POLICYFLAGS}:                 name := "POLICYFLAGS";
      when {RPMTAG_VCS}:                         name := "VCS";
      when {RPMTAG_ORDERNAME}:                   name := "ORDERNAME";
      when {RPMTAG_ORDERVERSION}:                name := "ORDERVERSION";
      when {RPMTAG_ORDERFLAGS}:                  name := "ORDERFLAGS";
      when {RPMTAG_MSSFMANIFEST}:                name := "MSSFMANIFEST";
      when {RPMTAG_MSSFDOMAIN}:                  name := "MSSFDOMAIN";
      when {RPMTAG_INSTFILENAMES}:               name := "INSTFILENAMES";
      when {RPMTAG_REQUIRENEVRS}:                name := "REQUIRENEVRS";
      when {RPMTAG_PROVIDENEVRS}:                name := "PROVIDENEVRS";
      when {RPMTAG_OBSOLETENEVRS}:               name := "OBSOLETENEVRS";
      when {RPMTAG_CONFLICTNEVRS}:               name := "CONFLICTNEVRS";
      when {RPMTAG_FILENLINKS}:                  name := "FILENLINKS";
      when {RPMTAG_RECOMMENDNAME}:               name := "RECOMMENDNAME";
      when {RPMTAG_RECOMMENDVERSION}:            name := "RECOMMENDVERSION";
      when {RPMTAG_RECOMMENDFLAGS}:              name := "RECOMMENDFLAGS";
      when {RPMTAG_SUGGESTNAME}:                 name := "SUGGESTNAME";
      when {RPMTAG_SUGGESTVERSION}:              name := "SUGGESTVERSION";
      when {RPMTAG_SUGGESTFLAGS}:                name := "SUGGESTFLAGS";
      when {RPMTAG_SUPPLEMENTNAME}:              name := "SUPPLEMENTNAME";
      when {RPMTAG_SUPPLEMENTVERSION}:           name := "SUPPLEMENTVERSION";
      when {RPMTAG_SUPPLEMENTFLAGS}:             name := "SUPPLEMENTFLAGS";
      when {RPMTAG_ENHANCENAME}:                 name := "ENHANCENAME";
      when {RPMTAG_ENHANCEVERSION}:              name := "ENHANCEVERSION";
      when {RPMTAG_ENHANCEFLAGS}:                name := "ENHANCEFLAGS";
      when {RPMTAG_RECOMMENDNEVRS}:              name := "RECOMMENDNEVRS";
      when {RPMTAG_SUGGESTNEVRS}:                name := "SUGGESTNEVRS";
      when {RPMTAG_SUPPLEMENTNEVRS}:             name := "SUPPLEMENTNEVRS";
      when {RPMTAG_ENHANCENEVRS}:                name := "ENHANCENEVRS";
      when {RPMTAG_ENCODING}:                    name := "ENCODING";
      when {RPMTAG_FILETRIGGERIN}:               name := "FILETRIGGERIN";
      when {RPMTAG_FILETRIGGERUN}:               name := "FILETRIGGERUN";
      when {RPMTAG_FILETRIGGERPOSTUN}:           name := "FILETRIGGERPOSTUN";
      when {RPMTAG_FILETRIGGERSCRIPTS}:          name := "FILETRIGGERSCRIPTS";
      when {RPMTAG_FILETRIGGERSCRIPTPROG}:       name := "FILETRIGGERSCRIPTPROG";
      when {RPMTAG_FILETRIGGERSCRIPTFLAGS}:      name := "FILETRIGGERSCRIPTFLAGS";
      when {RPMTAG_FILETRIGGERNAME}:             name := "FILETRIGGERNAME";
      when {RPMTAG_FILETRIGGERINDEX}:            name := "FILETRIGGERINDEX";
      when {RPMTAG_FILETRIGGERVERSION}:          name := "FILETRIGGERVERSION";
      when {RPMTAG_FILETRIGGERFLAGS}:            name := "FILETRIGGERFLAGS";
      when {RPMTAG_TRANSFILETRIGGERIN}:          name := "TRANSFILETRIGGERIN";
      when {RPMTAG_TRANSFILETRIGGERUN}:          name := "TRANSFILETRIGGERUN";
      when {RPMTAG_TRANSFILETRIGGERPOSTUN}:      name := "TRANSFILETRIGGERPOSTUN";
      when {RPMTAG_TRANSFILETRIGGERSCRIPTS}:     name := "TRANSFILETRIGGERSCRIPTS";
      when {RPMTAG_TRANSFILETRIGGERSCRIPTPROG}:  name := "TRANSFILETRIGGERSCRIPTPROG";
      when {RPMTAG_TRANSFILETRIGGERSCRIPTFLAGS}: name := "TRANSFILETRIGGERSCRIPTFLAGS";
      when {RPMTAG_TRANSFILETRIGGERNAME}:        name := "TRANSFILETRIGGERNAME";
      when {RPMTAG_TRANSFILETRIGGERINDEX}:       name := "TRANSFILETRIGGERINDEX";
      when {RPMTAG_TRANSFILETRIGGERVERSION}:     name := "TRANSFILETRIGGERVERSION";
      when {RPMTAG_TRANSFILETRIGGERFLAGS}:       name := "TRANSFILETRIGGERFLAGS";
      when {RPMTAG_REMOVEPATHPOSTFIXES}:         name := "REMOVEPATHPOSTFIXES";
      when {RPMTAG_FILETRIGGERPRIORITIES}:       name := "FILETRIGGERPRIORITIES";
      when {RPMTAG_TRANSFILETRIGGERPRIORITIES}:  name := "TRANSFILETRIGGERPRIORITIES";
      when {RPMTAG_FILETRIGGERCONDS}:            name := "FILETRIGGERCONDS";
      when {RPMTAG_FILETRIGGERTYPE}:             name := "FILETRIGGERTYPE";
      when {RPMTAG_TRANSFILETRIGGERCONDS}:       name := "TRANSFILETRIGGERCONDS";
      when {RPMTAG_TRANSFILETRIGGERTYPE}:        name := "TRANSFILETRIGGERTYPE";
      when {RPMTAG_FILESIGNATURES}:              name := "FILESIGNATURES";
      when {RPMTAG_FILESIGNATURELENGTH}:         name := "FILESIGNATURELENGTH";
      when {RPMTAG_PAYLOADDIGEST}:               name := "PAYLOADDIGEST";
      when {RPMTAG_PAYLOADDIGESTALGO}:           name := "PAYLOADDIGESTALGO";
      when {RPMTAG_AUTOINSTALLED}:               name := "AUTOINSTALLED";
      when {RPMTAG_IDENTITY}:                    name := "IDENTITY";
      when {RPMTAG_MODULARITYLABEL}:             name := "MODULARITYLABEL";
      when {RPMTAG_PAYLOADDIGESTALT}:            name := "PAYLOADDIGESTALT";
      otherwise: name := "tag " <& tag;
    end case;
  end func;


const proc: show (in rpmLead: lead) is func
  begin
    if lead.magic = RPM_LEAD_MAGIC then
      writeln("Version " <& lead.majorVersion <& "." <& lead.minorVersion);
      writeln("Type: " <& lead.fileType);
      writeln("Arch: " <& lead.arch);
      writeln("Name: " <& lead.name);
      writeln("Os: " <& lead.os);
      writeln("Signature version: " <& lead.sig);
    else
      writeln(" *** Rpm lead magic not okay.");
    end if;
  end func;


const proc: show (in rpmHeader: header) is func
  local
    var integer: idx is 0;
  begin
    if header.magic = RPM_HEADER_MAGIC then
      writeln("Header version: " <& header.version);
      writeln("IndexCount: " <& header.indexCount);
      writeln("StoreSize: " <& header.storeSize);
    else
      writeln(" *** Rpm header magic not okay.");
    end if;
  end func;


const func string: getStriValue (in string: store,
    in rpmIndexEntry: indexEntry) is func
  result
    var string: data is "";
  local
    var integer: index is 0;
    var integer: currPos is 0;
  begin
    case indexEntry.dataType of
      when {RPM_NULL_TYPE}:
        data := "";
      when {RPM_CHAR_TYPE}:
        data := store[succ(indexEntry.offset) len indexEntry.count];
      when {RPM_INT8_TYPE}:
        data := store[succ(indexEntry.offset) len indexEntry.count];
      when {RPM_INT16_TYPE}:
        data := store[succ(indexEntry.offset) len 2 * indexEntry.count];
      when {RPM_INT32_TYPE}:
        data := store[succ(indexEntry.offset) len 4 * indexEntry.count];
      when {RPM_INT64_TYPE}:
        data := store[succ(indexEntry.offset) len 8 * indexEntry.count];
      when {RPM_STRING_TYPE}:
        currPos := succ(indexEntry.offset);
        data := getAsciiz(store, currPos);
      when {RPM_BIN_TYPE}:
        data := store[succ(indexEntry.offset) len indexEntry.count];
      when {RPM_STRING_ARRAY_TYPE, RPM_I18NSTRING_TYPE}:
        currPos := succ(indexEntry.offset);
        for index range 1 to indexEntry.count do
          data &:= getAsciiz(store, currPos) & "\0;";
        end for;
    end case;
  end func;


const func array string: getArrayValue (in string: store,
    in rpmIndexEntry: indexEntry) is func
  result
    var array string: arrayValue is 0 times "";
  local
    var integer: index is 0;
    var integer: currPos is 0;
  begin
    case indexEntry.dataType of
      when {RPM_STRING_ARRAY_TYPE, RPM_I18NSTRING_TYPE}:
        currPos := succ(indexEntry.offset);
        for index range 1 to indexEntry.count do
          arrayValue &:= getAsciiz(store, currPos);
        end for;
    end case;
  end func;


const func integer: getIntValue (in string: store,
    in rpmIndexEntry: indexEntry) is func
  result
    var integer: number is 0;
  begin
    case indexEntry.dataType of
      when {RPM_INT8_TYPE}:
        number := bytes2Int(store[succ(indexEntry.offset) len 1], UNSIGNED, BE);
      when {RPM_INT16_TYPE}:
        number := bytes2Int(store[succ(indexEntry.offset) len 2], UNSIGNED, BE);
      when {RPM_INT32_TYPE}:
        number := bytes2Int(store[succ(indexEntry.offset) len 4], UNSIGNED, BE);
      when {RPM_INT64_TYPE}:
        number := bytes2Int(store[succ(indexEntry.offset) len 8], UNSIGNED, BE);
    end case;
  end func;


const proc: read (inout file: rpmFile, inout rpmLead: lead) is func
  begin
    lead.magic := gets(rpmFile, 4);
    # writeln("Magic: " <& hex(lead.magic));
    if lead.magic = RPM_LEAD_MAGIC then
      lead.majorVersion := ord(getc(rpmFile));
      lead.minorVersion := ord(getc(rpmFile));
      lead.fileType := getUInt16Be(rpmFile);
      lead.arch := getUInt16Be(rpmFile);
      lead.name := gets(rpmFile, 66);
      lead.os := getUInt16Be(rpmFile);
      lead.sig := getUInt16Be(rpmFile);
      ignore(gets(rpmFile, 16));
    end if;
  end func;


const func string: str (in rpmLead: lead) is
  return lead.magic <&
         chr(lead.majorVersion) <&
         chr(lead.minorVersion) <&
         int16AsTwoBytesBe(lead.fileType) <&
         int16AsTwoBytesBe(lead.arch) <&
         lead.name rpad 66 <&
         int16AsTwoBytesBe(lead.os) <&
         int16AsTwoBytesBe(lead.sig) <&
         "\0;" mult 16;


const proc: writeLead (inout file: outFile, inout rpmLead: lead) is func
  begin
    lead.magic := RPM_LEAD_MAGIC;
    lead.majorVersion := 3;
    lead.minorVersion := 0;
    lead.fileType := 1;
    lead.arch := 1;
    lead.name := "";
    lead.os := 1;
    lead.sig := 5;
    write(outFile, str(lead));
  end func;


const proc: read (inout file: rpmFile, inout rpmHeader: header) is func
  begin
    header.magic := gets(rpmFile, 3);
    # writeln("Magic: " <& hex(header.magic));
    if header.magic = RPM_HEADER_MAGIC then
      header.version := ord(getc(rpmFile));
      ignore(gets(rpmFile, 4));
      header.indexCount := getUInt32Be(rpmFile);
      header.storeSize := getUInt32Be(rpmFile);
    end if;
  end func;


const func string: str (in rpmHeader: header) is
  return header.magic <&
         chr(header.version) <&
         "\0;" mult 4 <&
         int32AsFourBytesBe(header.indexCount) <&
         int32AsFourBytesBe(header.storeSize);


const proc: read (inout file: rpmFile, inout rpmIndexEntry: indexEntry) is func
  begin
    indexEntry.tag := getUInt32Be(rpmFile);
    indexEntry.dataType := getUInt32Be(rpmFile);
    indexEntry.offset := getUInt32Be(rpmFile);
    indexEntry.count := getUInt32Be(rpmFile);
  end func;


const func string: str (in rpmIndexEntry: indexEntry) is
  return int32AsFourBytesBe(indexEntry.tag) <&
         int32AsFourBytesBe(indexEntry.dataType) <&
         int32AsFourBytesBe(indexEntry.offset) <&
         int32AsFourBytesBe(indexEntry.count);


const type: rpmCatalogEntry is new struct
    var integer: fileSize is 0;
    var integer: mode is 0;
    var integer: uid is 0;
    var integer: gid is 0;
    var integer: rdev is 0;
    var integer: mtime is 0;
    var string: digest is "";
    var string: linkTo is "";
    var integer: flags is 0;
    var string: userName is "";
    var string: groupName is "";
    var integer: verifyFlags is 0;
    var integer: device is 0;
    var integer: inode is 0;
    var string: lang is "";
    var integer: dirIndex is 0;
    var string: baseName is "";
    var integer: color is 0;
    var integer: fileClass is 0;
    var integer: dependX is 0;
    var integer: dependN is 0;
    var boolean: allDataPresent is FALSE;
    var string: filePath is "";
    var integer: fileNumber is 0;
    var boolean: dirty is FALSE;
  end struct;


const proc: write (in rpmCatalogEntry: catalogEntry) is func
  begin
    writeln("fileSize: " <& catalogEntry.fileSize);
    writeln("mode: " <& catalogEntry.mode);
    writeln("uid: " <& catalogEntry.uid);
    writeln("gid: " <& catalogEntry.gid);
    writeln("rdev: " <& catalogEntry.rdev);
    writeln("mtime: " <& catalogEntry.mtime);
    writeln("digest: " <& catalogEntry.digest);
    writeln("linkTo: " <& catalogEntry.linkTo);
    writeln("flags: " <& catalogEntry.flags);
    writeln("userName: " <& catalogEntry.userName);
    writeln("groupName: " <& catalogEntry.groupName);
    writeln("verifyFlags: " <& catalogEntry.verifyFlags);
    writeln("device: " <& catalogEntry.device);
    writeln("inode: " <& catalogEntry.inode);
    writeln("lang: " <& catalogEntry.lang);
    writeln("dirIndex: " <& catalogEntry.dirIndex);
    writeln("baseName: " <& catalogEntry.baseName);
    writeln("color: " <& catalogEntry.color);
    writeln("fileClass: " <& catalogEntry.fileClass);
    writeln("dependX: " <& catalogEntry.dependX);
    writeln("dependN: " <& catalogEntry.dependN);
    writeln("allDataPresent: " <& catalogEntry.allDataPresent);
    writeln("filePath: " <& catalogEntry.filePath);
    writeln("fileNumber: " <& catalogEntry.fileNumber);
    writeln("dirty: " <& catalogEntry.dirty);
  end func;


const type: rpmRegisterType is set of string;
const type: rpmCatalogType is hash [string] rpmCatalogEntry;

const type: rpmSection is new struct
    var rpmHeader: head is rpmHeader.value;
    var rpmTagMap: tagMap is rpmTagMap.value;
    var string: store is "";
  end struct;


(**
 *  [[filesys#fileSys|FileSys]] implementation type to access a RPM archive.
 *  File paths in a rpm archive can be absolute (they start with a slash)
 *  or relative (they do not start with a slash). The rpm file system does
 *  not support the concept of a current working directory. The functions
 *  chdir and getcwd are not supported by the rpm file system. Absolute
 *  and relative paths in a rpm archive can be accessed directly.
 *  Since "/" is just a normal path in a rpm archive the root path of a
 *  rpm file system is "". Possible usages of rpm file system functions are:
 *    getMTime(aRpmFile, "src/drivers")   # Relative path in the archive.
 *    fileType(aRpmFile, "/usr/include")  # Absolute path in the archive.
 *    fileSize(aRpmFile, "/image")        # Absolute path in the archive.
 *    readDir(aRpmFile, "")               # Return e.g.: "src" and "/"
 *    readDir(aRpmFile, "/")              # Return e.g.: "usr" and "image"
 *)
const type: rpmArchive is sub emptyFileSys struct
    var file: rpmFile is STD_NULL;
    var rpmLead: lead is rpmLead.value;
    var rpmSection: signature is rpmSection.value;
    var rpmSection: header is rpmSection.value;
    var integer: payloadPos is 0;
    var rpmRegisterType: register is rpmRegisterType.value;
    var rpmCatalogType: catalog is rpmCatalogType.value;
    var integer: fileDigestAlgo is RPM_DIGESTALGO_MD5;
    var array string: dirNameList is 0 times "";
    var array string: classDict is 0 times "";
    var file: payloadFile is STD_NULL;
    var fileSys: archive is fileSys.value;
  end struct;


const func array string: getStringArray (in rpmArchive: rpm, in integer: tag) is func
  result
    var array string: stringArray is 0 times "";
  local
    var rpmIndexEntry: stringArrayIndex is rpmIndexEntry.value;
    var integer: stringPos is 0;
    var integer: index is 0;
  begin
    if tag in rpm.header.tagMap then
      stringArrayIndex := rpm.header.tagMap[tag];
      stringPos := succ(stringArrayIndex.offset);
      stringArray := stringArrayIndex.count times "";
      for index range 1 to stringArrayIndex.count do
        stringArray[index] := getAsciiz(rpm.header.store, stringPos);
        # writeln(rpmtagName(tag) <& "[" <& index <& "]: " <& stringArray[index]);
      end for;
    end if;
  end func;


const proc: createMinimumOfCatalog (inout rpmArchive: rpm) is func
  local
    var integer: oldFileNamePos is 0;
    var integer: dirIndexPos is 0;
    var integer: baseNamePos is 0;
    var integer: digestPos is 0;
    var integer: linkToPos is 0;
    var integer: userNamePos is 0;
    var integer: groupNamePos is 0;
    var integer: langPos is 0;
    var integer: numberOfFiles is 0;
    var string: filePath8 is "";
    var rpmCatalogEntry: catalogEntry is rpmCatalogEntry.value;
    var integer: index is 0;
  begin
    if RPMTAG_OLDFILENAMES in rpm.header.tagMap then
      oldFileNamePos := succ(rpm.header.tagMap[RPMTAG_OLDFILENAMES].offset);
    else
      rpm.dirNameList := getStringArray(rpm, RPMTAG_DIRNAMES);
      dirIndexPos := succ(rpm.header.tagMap[RPMTAG_DIRINDEXES].offset);
      baseNamePos := succ(rpm.header.tagMap[RPMTAG_BASENAMES].offset);
    end if;
    digestPos := succ(rpm.header.tagMap[RPMTAG_FILEMD5S].offset);
    linkToPos := succ(rpm.header.tagMap[RPMTAG_FILELINKTOS].offset);
    userNamePos := succ(rpm.header.tagMap[RPMTAG_FILEUSERNAME].offset);
    groupNamePos := succ(rpm.header.tagMap[RPMTAG_FILEGROUPNAME].offset);
    langPos := succ(rpm.header.tagMap[RPMTAG_FILELANGS].offset);
    numberOfFiles := rpm.header.tagMap[RPMTAG_FILESIZES].count;
    for index range 1 to numberOfFiles do
      if oldFileNamePos <> 0 then
        filePath8 := getAsciiz(rpm.header.store, oldFileNamePos);
      else
        catalogEntry.dirIndex := succ(bytes2Int(rpm.header.store[dirIndexPos len 4], SIGNED, BE));
        dirIndexPos +:= 4;
        catalogEntry.baseName := getAsciiz(rpm.header.store, baseNamePos);
        filePath8 := rpm.dirNameList[catalogEntry.dirIndex];
        # writeln("dirName[" <& index <& "]: " <& filePath8);
        filePath8 &:= catalogEntry.baseName;
      end if;
      block
        catalogEntry.filePath := utf8ToStri(filePath8);
      exception
        catch RANGE_ERROR:
          catalogEntry.filePath := filePath8;
      end block;
      catalogEntry.digest := getAsciiz(rpm.header.store, digestPos);
      catalogEntry.linkTo := getAsciiz(rpm.header.store, linkToPos);
      catalogEntry.userName  := getAsciiz(rpm.header.store, userNamePos);
      catalogEntry.groupName := getAsciiz(rpm.header.store, groupNamePos);
      catalogEntry.lang := getAsciiz(rpm.header.store, langPos);
      catalogEntry.fileNumber := index;
      rpm.catalog @:= [catalogEntry.filePath] catalogEntry;
    end for;
  end func;


const proc: createRegister (inout rpmArchive: rpm) is func
  local
    var string: filePath is "";
  begin
    for key filePath range rpm.catalog do
      incl(rpm.register, filePath);
    end for;
  end func;


const func integer: getInt (in rpmArchive: rpm, in integer: tag, in integer: index) is func
  result
    var integer: number is 0;
  local
    var rpmIndexEntry: indexEntry is rpmIndexEntry.value;
    var integer: offset is 0;
    var integer: pos is 0;
  begin
    if tag in rpm.header.tagMap then
      indexEntry := rpm.header.tagMap[tag];
      if index <= indexEntry.count then
        offset := succ(indexEntry.offset);
        case indexEntry.dataType of
          when {RPM_INT8_TYPE}:
            pos := offset + pred(index);
            number := bytes2Int(rpm.header.store[pos len 1], UNSIGNED, BE);
          when {RPM_INT16_TYPE}:
            pos := offset + pred(index) * 2;
            number := bytes2Int(rpm.header.store[pos len 2], UNSIGNED, BE);
          when {RPM_INT32_TYPE}:
            pos := offset + pred(index) * 4;
            number := bytes2Int(rpm.header.store[pos len 4], UNSIGNED, BE);
          when {RPM_INT64_TYPE}:
            pos := offset + pred(index) * 8;
            number := bytes2Int(rpm.header.store[pos len 8], UNSIGNED, BE);
          otherwise:
            raise RANGE_ERROR;
        end case;
      else
        raise RANGE_ERROR;
      end if;
    end if;
  end func;


const proc: readCatalogEntry (inout rpmArchive: rpm, inout rpmCatalogEntry: catalogEntry) is func
  local
    var integer: fileNumber is 0;
  begin
    fileNumber := catalogEntry.fileNumber;
    if fileNumber = 0 then
      raise RANGE_ERROR;
    else
      catalogEntry.fileSize    := getInt(rpm, RPMTAG_FILESIZES,       fileNumber);
      catalogEntry.mode        := getInt(rpm, RPMTAG_FILEMODES,       fileNumber);
      catalogEntry.uid         := getInt(rpm, RPMTAG_FILEUIDS,        fileNumber);
      catalogEntry.gid         := getInt(rpm, RPMTAG_FILEGIDS,        fileNumber);
      catalogEntry.rdev        := getInt(rpm, RPMTAG_FILERDEVS,       fileNumber);
      catalogEntry.mtime       := getInt(rpm, RPMTAG_FILEMTIMES,      fileNumber);
      catalogEntry.flags       := getInt(rpm, RPMTAG_FILEFLAGS,       fileNumber);
      catalogEntry.verifyFlags := getInt(rpm, RPMTAG_FILEVERIFYFLAGS, fileNumber);
      catalogEntry.device      := getInt(rpm, RPMTAG_FILEDEVICES,     fileNumber);
      catalogEntry.inode       := getInt(rpm, RPMTAG_FILEINODES,      fileNumber);
      catalogEntry.color       := getInt(rpm, RPMTAG_FILECOLORS,      fileNumber);
      catalogEntry.fileClass   := getInt(rpm, RPMTAG_FILECLASS,       fileNumber);
      catalogEntry.dependX     := getInt(rpm, RPMTAG_FILEDEPENDSX,    fileNumber);
      catalogEntry.dependN     := getInt(rpm, RPMTAG_FILEDEPENDSN,    fileNumber);
      catalogEntry.allDataPresent := TRUE;
      rpm.catalog @:= [catalogEntry.filePath] catalogEntry;
      # write(catalogEntry);
    end if;
  end func;


const proc: updateIndexEntry (inout rpmSection: section, in integer: tag,
    in integer: dataType, in integer: value) is func
  local
    var rpmIndexEntry: indexEntry is rpmIndexEntry.value;
  begin
    if tag in section.tagMap then
      indexEntry := section.tagMap[tag];
    else
      indexEntry.tag := tag;
      indexEntry.dataType := dataType;
      indexEntry.count := 1;
    end if;
    case indexEntry.dataType of
      when {RPM_INT8_TYPE}:
        indexEntry.striValue := str(chr(value));
      when {RPM_INT16_TYPE}:
        indexEntry.striValue := int16AsTwoBytesBe(value);
      when {RPM_INT32_TYPE}:
        indexEntry.striValue := int32AsFourBytesBe(value);
      when {RPM_INT64_TYPE}:
        indexEntry.striValue := int64AsEightBytesBe(value);
      otherwise:
        raise RANGE_ERROR;
    end case;
    section.tagMap @:= [tag] indexEntry;
  end func;


const proc: updateIndexEntry (inout rpmSection: section, in integer: tag,
    in integer: dataType, in string: value) is func
  local
    var rpmIndexEntry: indexEntry is rpmIndexEntry.value;
  begin
    if tag in section.tagMap then
      indexEntry := section.tagMap[tag];
    else
      indexEntry.tag := tag;
      indexEntry.dataType := dataType;
    end if;
    case indexEntry.dataType of
      when {RPM_STRING_TYPE}:
        indexEntry.count := 1;
        indexEntry.striValue := value;
      when {RPM_BIN_TYPE}:
        indexEntry.count := length(value);
        indexEntry.striValue := value;
      otherwise:
        raise RANGE_ERROR;
    end case;
    section.tagMap @:= [tag] indexEntry;
  end func;


const proc: updateIndexEntry (inout rpmSection: section, in integer: tag,
    in integer: dataType, in array string: value) is func
  local
    var rpmIndexEntry: indexEntry is rpmIndexEntry.value;
  begin
    if tag in section.tagMap then
      indexEntry := section.tagMap[tag];
    else
      indexEntry.tag := tag;
      indexEntry.dataType := dataType;
      indexEntry.count := length(value);
    end if;
    indexEntry.arrayValue := value;
    section.tagMap @:= [tag] indexEntry;
  end func;


const proc: updateIndexEntry (inout rpmArchive: rpm, in integer: tag,
    in integer: dataType, in integer: fileNumber, in integer: value) is func
  local
    var rpmIndexEntry: indexEntry is rpmIndexEntry.value;
    var integer: dataSize is 0;
    var string: data is "";
  begin
    if tag in rpm.header.tagMap then
      indexEntry := rpm.header.tagMap[tag];
    else
      indexEntry.tag := tag;
      indexEntry.dataType := dataType;
    end if;
    case indexEntry.dataType of
      when {RPM_INT8_TYPE}:
        dataSize := 1;
        data := str(chr(value));
      when {RPM_INT16_TYPE}:
        dataSize := 2;
        data := int16AsTwoBytesBe(value);
      when {RPM_INT32_TYPE}:
        dataSize := 4;
        data := int32AsFourBytesBe(value);
      when {RPM_INT64_TYPE}:
        dataSize := 8;
        data := int64AsEightBytesBe(value);
      otherwise:
        raise RANGE_ERROR;
    end case;
    if fileNumber > indexEntry.count then
      indexEntry.striValue &:= "\0;" mult (fileNumber - indexEntry.count) * dataSize;
      indexEntry.count := fileNumber;
    end if;
    indexEntry.striValue @:= [succ(pred(fileNumber) * dataSize)] data;
    rpm.header.tagMap @:= [tag] indexEntry;
  end func;


const proc: updateIndexEntry (inout rpmArchive: rpm, in integer: tag,
    in integer: dataType, in integer: fileNumber, in string: value) is func
  local
    var rpmIndexEntry: indexEntry is rpmIndexEntry.value;
    var string: data is "";
  begin
    if tag in rpm.header.tagMap then
      indexEntry := rpm.header.tagMap[tag];
    else
      indexEntry.tag := tag;
      indexEntry.dataType := dataType;
    end if;
    case indexEntry.dataType of
      when {RPM_STRING_ARRAY_TYPE, RPM_I18NSTRING_TYPE}:
        data := value;
      otherwise:
        raise RANGE_ERROR;
    end case;
    if fileNumber > indexEntry.count then
      indexEntry.arrayValue &:= (fileNumber - indexEntry.count) times "";
      indexEntry.count := fileNumber;
    end if;
    indexEntry.arrayValue[fileNumber] := data;
    rpm.header.tagMap @:= [tag] indexEntry;
  end func;


const proc: updateHeader (inout rpmArchive: rpm, in rpmCatalogEntry: catalogEntry) is func
  begin
    updateIndexEntry(rpm, RPMTAG_FILESIZES, RPM_INT32_TYPE,
                     catalogEntry.fileNumber, catalogEntry.fileSize);
    updateIndexEntry(rpm, RPMTAG_FILEMODES, RPM_INT16_TYPE,
                     catalogEntry.fileNumber, catalogEntry.mode);
    # RPMTAG_FILEUIDS
    # RPMTAG_FILEGIDS
    updateIndexEntry(rpm, RPMTAG_FILERDEVS, RPM_INT16_TYPE,
                     catalogEntry.fileNumber, catalogEntry.rdev);
    updateIndexEntry(rpm, RPMTAG_FILEMTIMES, RPM_INT32_TYPE,
                     catalogEntry.fileNumber, catalogEntry.mtime);
    updateIndexEntry(rpm, RPMTAG_FILEMD5S, RPM_STRING_ARRAY_TYPE,
                     catalogEntry.fileNumber, catalogEntry.digest);
    updateIndexEntry(rpm, RPMTAG_FILELINKTOS, RPM_STRING_ARRAY_TYPE,
                     catalogEntry.fileNumber, catalogEntry.linkTo);
    updateIndexEntry(rpm, RPMTAG_FILEFLAGS, RPM_INT32_TYPE,
                     catalogEntry.fileNumber, catalogEntry.flags);
    updateIndexEntry(rpm, RPMTAG_FILEUSERNAME, RPM_STRING_ARRAY_TYPE,
                     catalogEntry.fileNumber, catalogEntry.userName);
    updateIndexEntry(rpm, RPMTAG_FILEGROUPNAME, RPM_STRING_ARRAY_TYPE,
                     catalogEntry.fileNumber, catalogEntry.groupName);
    updateIndexEntry(rpm, RPMTAG_FILEVERIFYFLAGS, RPM_INT32_TYPE,
                     catalogEntry.fileNumber, catalogEntry.verifyFlags);
    updateIndexEntry(rpm, RPMTAG_FILEDEVICES, RPM_INT32_TYPE,
                     catalogEntry.fileNumber, catalogEntry.device);
    updateIndexEntry(rpm, RPMTAG_FILEINODES, RPM_INT32_TYPE,
                     catalogEntry.fileNumber, catalogEntry.inode);
    updateIndexEntry(rpm, RPMTAG_FILELANGS, RPM_STRING_ARRAY_TYPE,
                     catalogEntry.fileNumber, catalogEntry.lang);
    updateIndexEntry(rpm, RPMTAG_DIRINDEXES, RPM_INT32_TYPE,
                     catalogEntry.fileNumber, pred(catalogEntry.dirIndex));
    updateIndexEntry(rpm, RPMTAG_BASENAMES, RPM_STRING_ARRAY_TYPE,
                     catalogEntry.fileNumber, catalogEntry.baseName);
    updateIndexEntry(rpm, RPMTAG_FILECOLORS, RPM_INT32_TYPE,
                     catalogEntry.fileNumber, catalogEntry.color);
    updateIndexEntry(rpm, RPMTAG_FILECLASS, RPM_INT32_TYPE,
                     catalogEntry.fileNumber, catalogEntry.fileClass);
    updateIndexEntry(rpm, RPMTAG_FILEDEPENDSX, RPM_INT32_TYPE,
                     catalogEntry.fileNumber, catalogEntry.dependX);
    updateIndexEntry(rpm, RPMTAG_FILEDEPENDSN, RPM_INT32_TYPE,
                     catalogEntry.fileNumber, catalogEntry.dependN);
  end func;


const func integer: findDirIndex (in array string: dirNameList, in string: dirName) is func
  result
    var integer: dirIndex is 0;
  local
    var integer: index is 0;
  begin
    for key index range dirNameList until dirIndex <> 0 do
      if dirNameList[index] = dirName then
        dirIndex := index;
      end if;
    end for;
  end func;


const proc: updateHeader (inout rpmArchive: rpm) is func
  local
    var rpmCatalogEntry: catalogEntry is rpmCatalogEntry.value;
    var integer: slashPos is 0;
    var string: dirName is "";
    var integer: dirIndex is 0;
  begin
    for catalogEntry range rpm.catalog do
      if catalogEntry.dirty then
        # writeln("updateHeader " <& catalogEntry.filePath);
        slashPos := rpos(catalogEntry.filePath, "/");
        if slashPos <> 0 then
          dirName := catalogEntry.filePath[.. slashPos];
          catalogEntry.baseName := catalogEntry.filePath[succ(slashPos) ..];
        else
          dirName := "";
          catalogEntry.baseName := catalogEntry.filePath;
        end if;
        dirIndex := findDirIndex(rpm.dirNameList, dirName);
        if dirIndex = 0 then
          rpm.dirNameList &:= dirName;
          dirIndex := length(rpm.dirNameList);
        end if;
        catalogEntry.dirIndex := dirIndex;
        rpm.catalog @:= [catalogEntry.filePath] catalogEntry;
        updateHeader(rpm, catalogEntry);
      end if;
    end for;
  end func;


const func boolean: isDirty (in rpmArchive: rpm) is func
  result
    var boolean: isDirty is FALSE;
  local
    var rpmCatalogEntry: catalogEntry is rpmCatalogEntry.value;
  begin
    for catalogEntry range rpm.catalog do
      if catalogEntry.dirty then
        isDirty := TRUE;
      end if;
    end for;
  end func;


const proc: readSection (inout file: rpmFile, inout rpmSection: section) is func
  local
    var rpmIndexEntry: indexEntry is rpmIndexEntry.value;
  begin
    read(rpmFile, section.head);
    if section.head.magic = RPM_HEADER_MAGIC then
      # show(section.head);
      for section.head.indexCount do
        read(rpmFile, indexEntry);
        section.tagMap @:= [indexEntry.tag] indexEntry;
      end for;
      # sectionStorePos := tell(rpmFile);
      section.store := gets(rpmFile, section.head.storeSize);
      # writeln("length(section.store): " <& length(section.store));
    end if;
  end func;


const func string: sectionStri (inout rpmSection: section, in integer: sectionTag) is func
  result
    var string: sectionStri is "";
  local
    var rpmIndexEntry: sectionIndexEntry is rpmIndexEntry.value;
    var rpmIndexEntry: indexEntry is rpmIndexEntry.value;
  begin
    section.head.magic := RPM_HEADER_MAGIC;
    section.head.version := 1;
    section.head.indexCount := succ(length(section.tagMap));
    section.head.storeSize := length(section.store) + 16;
    sectionStri := str(section.head);
    # The first entry in the index array must be the sectionTag (regionTag) entry.
    sectionIndexEntry.tag := sectionTag;
    sectionIndexEntry.dataType := RPM_BIN_TYPE;
    # The data of the sectionTag must be after all other data in the section store.
    sectionIndexEntry.offset := length(section.store);
    sectionIndexEntry.count := 16;
    sectionStri &:= str(sectionIndexEntry);
    for indexEntry range section.tagMap do
      # writeln("indexEntry: " <& literal(str(indexEntry)));
      sectionStri &:= str(indexEntry);
    end for;
    # section.store does not contain the data of the sectionTag index entry.
    sectionStri &:= section.store;
    # The data of the sectionTag entry is itself an index entry with the sectionTag.
    # The negative offset of this index entry refers back to the original sectionTag index entry.
    sectionIndexEntry.offset := -succ(length(section.tagMap)) * 16;
    sectionStri &:= str(sectionIndexEntry);
  end func;


const proc: assignIndexValues (inout rpmSection: section) is func
  local
    var rpmIndexEntry: indexEntry is rpmIndexEntry.value;
  begin
    if section.store <> "" then
      for indexEntry range section.tagMap do
        case indexEntry.dataType of
          when {RPM_INT8_TYPE, RPM_INT16_TYPE, RPM_INT32_TYPE, RPM_INT64_TYPE,
                RPM_STRING_TYPE, RPM_BIN_TYPE}:
            if indexEntry.striValue = "" then
              indexEntry.striValue := getStriValue(section.store, indexEntry);
            end if;
          when {RPM_STRING_ARRAY_TYPE, RPM_I18NSTRING_TYPE}:
            if length(indexEntry.arrayValue) = 0 then
              indexEntry.arrayValue := getArrayValue(section.store, indexEntry);
            end if;
        end case;
      end for;
    end if;
  end func;


const proc: updateStore (inout rpmSection: section) is func
  local
    var rpmIndexEntry: indexEntry is rpmIndexEntry.value;
    var string: store is "";
  begin
    for indexEntry range section.tagMap do
      indexEntry.offset := length(store);
      case indexEntry.dataType of
        when {RPM_INT8_TYPE}:
          store &:= indexEntry.striValue;
        when {RPM_INT16_TYPE}:
          # Align to 2 bytes
          if odd(length(store)) then
            store &:= "\0;";
          end if;
          indexEntry.offset := length(store);
          store &:= indexEntry.striValue;
        when {RPM_INT32_TYPE}:
          # Align to 4 bytes
          if length(store) mod 4 <> 0 then
            store &:= "\0;" mult (4 - length(store) mod 4);
          end if;
          indexEntry.offset := length(store);
          store &:= indexEntry.striValue;
        when {RPM_INT64_TYPE}:
          # Align to 8 bytes
          if length(store) mod 8 <> 0 then
            store &:= "\0;" mult (8 - length(store) mod 4);
          end if;
          indexEntry.offset := length(store);
          store &:= indexEntry.striValue;
        when {RPM_STRING_TYPE}:
          store &:= indexEntry.striValue & "\0;";
        when {RPM_BIN_TYPE}:
          store &:= indexEntry.striValue;
        when {RPM_STRING_ARRAY_TYPE, RPM_I18NSTRING_TYPE}:
          store &:= join(indexEntry.arrayValue, "\0;") & "\0;";
      end case;
      section.tagMap @:= [indexEntry.tag] indexEntry;
    end for;
    section.store := store;
  end func;


const proc: doSettings (inout rpmArchive: rpm) is func
  local
    var rpmIndexEntry: fileDigestAlgo is rpmIndexEntry.value;
  begin
    if rpm.fileDigestAlgo = RPM_DIGESTALGO_MD5 and
        RPMTAG_FILEDIGESTALGO in rpm.header.tagMap then
      fileDigestAlgo := rpm.header.tagMap[RPMTAG_FILEDIGESTALGO];
      rpm.fileDigestAlgo := bytes2Int(rpm.header.store[
          succ(fileDigestAlgo.offset) len 4], SIGNED, BE);
      # writeln("fileDigestAlgo: " <& rpm.fileDigestAlgo);
    end if;
    rpm.classDict := getStringArray(rpm, RPMTAG_CLASSDICT);
  end func;


const func string: getDigest (in string: content, in integer: digestAlgo) is func
  result
    var string: digest is "";
  begin
    case digestAlgo of
      when {RPM_DIGESTALGO_MD5}:         digest := md5(content);
      when {RPM_DIGESTALGO_SHA1}:        digest := sha1(content);
      when {RPM_DIGESTALGO_RIPEMD160}:   digest := ripemd160(content);
      when {RPM_DIGESTALGO_MD2}:         noop;
      when {RPM_DIGESTALGO_TIGER192}:    noop;
      when {RPM_DIGESTALGO_HAVAL_5_160}: noop;
      when {RPM_DIGESTALGO_SHA256}:      digest := sha256(content);
      when {RPM_DIGESTALGO_SHA384}:      digest := sha384(content);
      when {RPM_DIGESTALGO_SHA512}:      digest := sha512(content);
      when {RPM_DIGESTALGO_SHA224}:      digest := sha224(content);
    end case;
  end func;


const proc: checkPayloadDigest (inout rpmArchive: rpm) is func
  local
    var string: payloadContent is "";
    var integer: digestPos is 0;
    var string: referenceDigest is "";
    var string: computedDigest is "";
    var integer: digestAlgo is 0;
  begin
    seek(rpm.rpmFile, rpm.payloadPos);
    payloadContent := gets(rpm.rpmFile, integer.last);
    digestPos := succ(rpm.header.tagMap[RPMTAG_PAYLOADDIGEST].offset);
    referenceDigest := getAsciiz(rpm.header.store, digestPos);
    digestAlgo := getIntValue(rpm.header.store, rpm.header.tagMap[RPMTAG_PAYLOADDIGESTALGO]);
    # writeln("length(payloadContent): " <& length(payloadContent));
    computedDigest := hex(getDigest(payloadContent, digestAlgo));
    if referenceDigest <> computedDigest then
      # writeln("digestAlgo: " <& digestAlgo);
      # writeln("reference digest: " <& literal(referenceDigest));
      # writeln("computed digest:  " <& literal(computedDigest));
      raise FILE_ERROR;
    end if;
  end func;


const proc: getArchive (inout rpmArchive: rpm) is func
  local
    var string: compressor is "";
    var string: format is "";
    var file: payload is STD_NULL;
  begin
    if RPMTAG_PAYLOADDIGEST in rpm.header.tagMap and RPMTAG_PAYLOADDIGESTALGO in rpm.header.tagMap then
      checkPayloadDigest(rpm);
    end if;
    if rpm.payloadPos = 0 then
      rpm.payloadFile := openStrifile();
    else
      seek(rpm.rpmFile, rpm.payloadPos);
      if RPMTAG_PAYLOADCOMPRESSOR in rpm.header.tagMap then
        compressor := getStriValue(rpm.header.store, rpm.header.tagMap[RPMTAG_PAYLOADCOMPRESSOR]);
        # writeln("compressor: " <& compressor);
        case compressor of
          when {"gzip"}: payload := openGunzipFile(rpm.rpmFile);
          when {"lzma"}: payload := openLzmaFile(rpm.rpmFile);
          when {"xz"}:   payload := openXzFile(rpm.rpmFile);
          when {"zstd"}: payload := openZstdFile(rpm.rpmFile);
        end case;
      end if;
      if payload <> STD_NULL then
        # writeln("length uncompressed: " <& length(payload));
        rpm.payloadFile := openStrifile(gets(payload, integer.last));
        close(payload);
      end if;
    end if;
    if rpm.payloadFile <> STD_NULL then
      if RPMTAG_PAYLOADFORMAT in rpm.header.tagMap then
        format := getStriValue(rpm.header.store, rpm.header.tagMap[RPMTAG_PAYLOADFORMAT]);
        # writeln("format: " <& format);
        case format of
          when {"cpio"}: rpm.archive := openCpio(rpm.payloadFile);
        end case;
      elsif rpm.payloadPos = 0 then
        rpm.archive := openCpio(rpm.payloadFile);
      end if;
    end if;
  end func;


const func string: archiveFilePath (inout fileSys: archive, in string: filePath) is func
  result
    var string: archiveFilePath is "";
  begin
    if startsWith(filePath, "/") and
        fileTypeSL(archive, filePath) = FILE_ABSENT then
      archiveFilePath := "." & filePath;
    else
      archiveFilePath := filePath;
    end if;
  end func;


(**
 *  Open a RPM archive with the given rpmFile.
 *  @param rpmFile File that contains a RPM archive.
 *  @return a file system that accesses the RPM archive.
 *)
const func fileSys: openRpm (inout file: rpmFile) is func
  result
    var fileSys: newFileSys is fileSys.value;
  local
    var rpmArchive: rpm is rpmArchive.value;
  begin
    if length(rpmFile) = 0 then
      rpm.rpmFile := rpmFile;
      newFileSys := toInterface(rpm);
    else
      seek(rpmFile, 1);
      read(rpmFile, rpm.lead);
      if rpm.lead.magic = RPM_LEAD_MAGIC then
        rpm.rpmFile := rpmFile;
        readSection(rpmFile, rpm.signature);
        ignore(gets(rpmFile, 7 - pred(rpm.signature.head.storeSize) mod 8));
        readSection(rpmFile, rpm.header);
        # ignore(gets(rpmFile, 7 - pred(rpm.header.head.storeSize) mod 8));
        rpm.payloadPos := tell(rpmFile);
        doSettings(rpm);
        createMinimumOfCatalog(rpm);
        createRegister(rpm);
        newFileSys := toInterface(rpm);
      end if;
    end if;
  end func;


(**
 *  Open a RPM archive with the given rpmFileName.
 *  @param rpmFileName Name of the RPM archive to be opened.
 *  @return a file system that accesses the RPM archive.
 *)
const func fileSys: openRpm (in string: rpmFileName) is func
  result
    var fileSys: rpm is fileSys.value;
  local
    var file: rpmFile is STD_NULL;
  begin
    rpmFile := open(rpmFileName, "r");
    rpm := openRpm(rpmFile);
  end func;


(**
 *  Close a RPM archive.
 *)
const proc: close (inout rpmArchive: rpm) is func
  local
    var string: signatureSection is "";
    var string: headerSection is "";
    var string: payload is "";
  begin
    if isDirty(rpm) then
      writeln("in close");
      if length(rpm.rpmFile) = 0 then
        writeLead(rpm.rpmFile, rpm.lead);
      end if;

      writeln("prepare payload:");
      if rpm.archive <> fileSys.value then
        seek(rpm.payloadFile, 1);
        payload := gzip(gets(rpm.payloadFile, integer.last));
      end if;

      writeln("prepare header:");
      assignIndexValues(rpm.header);
      updateHeader(rpm);
      updateIndexEntry(rpm.header, RPMTAG_ARCHIVESIZE, RPM_INT32_TYPE,
                       length(rpm.payloadFile));
      updateIndexEntry(rpm.header, RPMTAG_DIRNAMES, RPM_STRING_ARRAY_TYPE, rpm.dirNameList);
      updateIndexEntry(rpm.header, RPMTAG_PAYLOADCOMPRESSOR, RPM_STRING_TYPE, "gzip");
      updateIndexEntry(rpm.header, RPMTAG_PAYLOADFORMAT, RPM_STRING_TYPE, "cpio");
      updateStore(rpm.header);
      headerSection := sectionStri(rpm.header, RPMTAG_HEADERIMMUTABLE);

      writeln("prepare signature:");
      updateIndexEntry(rpm.signature, RPMSIGTAG_SIZE, RPM_INT32_TYPE,
                       length(headerSection) + length(payload));
      updateIndexEntry(rpm.signature, RPMSIGTAG_PAYLOADSIZE, RPM_INT32_TYPE,
                       length(rpm.payloadFile));
      updateIndexEntry(rpm.signature, RPMSIGTAG_SHA1, RPM_STRING_TYPE,
                       hex(sha1(headerSection)));
      updateIndexEntry(rpm.signature, RPMSIGTAG_MD5, RPM_BIN_TYPE,
                       md5(headerSection & payload));
      assignIndexValues(rpm.signature);
      updateStore(rpm.signature);
      signatureSection := sectionStri(rpm.signature, RPMSIGTAG_HEADERSIGNATURES);

      write(rpm.rpmFile, signatureSection);
      write(rpm.rpmFile, "\0;" mult (7 - pred(length(signatureSection)) mod 8));
      write(rpm.rpmFile, headerSection);
      write(rpm.rpmFile, payload);
      truncate(rpm.rpmFile, pred(tell(rpm.rpmFile)));
      # close(rpm.rpmFile);
    end if;
    if rpm.archive <> fileSys.value then
      close(rpm.archive);
    end if;
    rpm.rpmFile := STD_NULL;
  end func;


const func boolean: implicitDir (inout rpmArchive: rpm, in string: dirPath) is func
  result
    var boolean: implicitDir is FALSE;
  local
    var string: filePath is "";
  begin
    if dirPath <> "" then
      for filePath range rpm.register do
        if startsWith(filePath, dirPath) and
            length(filePath) > length(dirPath) and
            (filePath[succ(length(dirPath))] = '/' or dirPath = "/") then
          implicitDir := TRUE;
        end if;
      end for;
    end if;
  end func;


const func rpmCatalogEntry: addImplicitDir (inout rpmArchive: rpm,
    in string: dirPath) is func
  result
    var rpmCatalogEntry: catalogEntry is rpmCatalogEntry.value;
  begin
    catalogEntry.filePath := dirPath;
    catalogEntry.mode := ord(MODE_FILE_DIR) + 8#775;
    catalogEntry.allDataPresent := TRUE;
    rpm.catalog @:= [dirPath] catalogEntry;
  end func;


const func string: followSymlink (inout rpmArchive: rpm, in var string: filePath,
    inout rpmCatalogEntry: catalogEntry) is func
  result
    var string: missingPath is "";
  local
    var integer: offset is 0;
    var integer: pos is 0;
    var integer: modeValue is 0;
    var integer: symlinkCount is MAX_SYMLINKS;
    var boolean: isSymlink is TRUE;
  begin
    repeat
      if filePath in rpm.catalog then
        catalogEntry := rpm.catalog[filePath];
      elsif implicitDir(rpm, filePath) then
        catalogEntry := addImplicitDir(rpm, filePath);
      else
        missingPath := filePath;
        isSymlink := FALSE;
        # writeln("missing: " <& missingPath);
      end if;
      if missingPath = "" then
        if not catalogEntry.allDataPresent then
          readCatalogEntry(rpm, catalogEntry);
        end if;
        if bin32(catalogEntry.mode) & MODE_FILE_TYPE_MASK = MODE_FILE_SYMLINK then
          filePath := catalogEntry.linkTo;
        else
          isSymlink := FALSE;
        end if;
      end if;
      decr(symlinkCount);
    until not isSymlink or symlinkCount = 0;
    if isSymlink then
      # Too many symbolic links.
      raise FILE_ERROR;
    end if;
  end func;


const func rpmCatalogEntry: followSymlink (inout rpmArchive: rpm, in string: filePath) is func
  result
    var rpmCatalogEntry: catalogEntry is rpmCatalogEntry.value;
  local
    var string: missingPath is "";
  begin
    missingPath := followSymlink(rpm, filePath, catalogEntry);
    if missingPath <> "" then
      # The file does not exist.
      raise FILE_ERROR;
    end if;
  end func;


(**
 *  Determine the file names in a directory inside a RPM archive.
 *  Note that the function returns only the file names.
 *  Additional information must be obtained with other calls.
 *  @param rpm Open RPM archive.
 *  @param dirPath path of a directory in the RPM archive.
 *  @return an array with the file names.
 *  @exception RANGE_ERROR ''dirPath'' does not use the standard path
 *             representation.
 *  @exception FILE_ERROR ''dirPath'' is not present in the RPM archive.
 *)
const func array string: readDir (inout rpmArchive: rpm, in string: dirPath) is func
  result
    var array string: fileNames is 0 times "";
  local
    var string: filePath is "";
    var boolean: dirExists is FALSE;
    var set of string: fileNameSet is (set of string).value;
    var string: fileName is "";
    var integer: slashPos is 0;
  begin
    if dirPath <> "/" and endsWith(dirPath, "/") then
      raise RANGE_ERROR;
    elsif dirPath = "" or dirPath = "." then
      for fileName range rpm.register do
        slashPos := pos(fileName, '/');
        if slashPos <> 0 then
          if slashPos = 1 then
            fileName := "/";
          else
            fileName := fileName[.. pred(slashPos)];
          end if;
        end if;
        if fileName not in fileNameSet then
          incl(fileNameSet, fileName);
        end if;
      end for;
    else
      for filePath range rpm.register do
        if startsWith(filePath, dirPath) then
          fileName := filePath[succ(length(dirPath)) ..];
          if fileName = "" then
            dirExists := TRUE;
          elsif startsWith(fileName, "/") then
            fileName := fileName[2 ..];
          elsif dirPath <> "/" then
            fileName := "";  # A file name <> dirPath starts with dirPath.
          end if;
          slashPos := pos(fileName, '/');
          if slashPos <> 0 then
            fileName := fileName[.. pred(slashPos)];
          end if;
          if fileName <> "" and fileName not in fileNameSet then
            incl(fileNameSet, fileName);
            dirExists := TRUE;
          end if;
        end if;
      end for;
      if not dirExists then
        raise FILE_ERROR;
      end if;
    end if;
    fileNames := sort(toArray(fileNameSet));
  end func;


(**
 *  Determine the file paths in a RPM archive.
 *  Note that the function returns only the file paths.
 *  Additional information must be obtained with other calls.
 *  @param rpm Open RPM archive.
 *  @return an array with the file paths.
 *)
const func array string: readDir (inout rpmArchive: rpm, RECURSIVE) is
  return sort(toArray(rpm.register));


(**
 *  Determine the type of a file in a RPM archive.
 *  The function follows symbolic links. If the chain of
 *  symbolic links is too long the function returns ''FILE_SYMLINK''.
 *  If a symbolic link refers to a place where the permission
 *  is denied the function returns ''FILE_SYMLINK''.
 *  A return value of ''FILE_ABSENT'' does not imply that a file
 *  with this name can be created, since missing directories and
 *  illegal file names cause also ''FILE_ABSENT''.
 *  @return the type of the file.
 *  @exception RANGE_ERROR ''filePath'' does not use the standard path
 *             representation.
 *)
const func fileType: fileType (inout rpmArchive: rpm, in var string: filePath) is func
  result
    var fileType: aFileType is FILE_UNKNOWN;
  local
    var rpmCatalogEntry: catalogEntry is rpmCatalogEntry.value;
    var integer: symlinkCount is MAX_SYMLINKS;
  begin
    # writeln("fileType: " <& filePath);
    if filePath <> "/" and endsWith(filePath, "/") then
      raise RANGE_ERROR;
    elsif filePath = "" then
      aFileType := FILE_DIR;
    else
      repeat
        if filePath in rpm.catalog then
          catalogEntry := rpm.catalog[filePath];
        elsif implicitDir(rpm, filePath) then
          catalogEntry := addImplicitDir(rpm, filePath);
        else
          aFileType := FILE_ABSENT;
        end if;
        if aFileType = FILE_UNKNOWN then
          if not catalogEntry.allDataPresent then
            readCatalogEntry(rpm, catalogEntry);
          end if;
          case bin32(catalogEntry.mode) & MODE_FILE_TYPE_MASK of
            when {MODE_FILE_REGULAR}: aFileType := FILE_REGULAR;
            when {MODE_FILE_DIR}:     aFileType := FILE_DIR;
            when {MODE_FILE_CHAR}:    aFileType := FILE_CHAR;
            when {MODE_FILE_BLOCK}:   aFileType := FILE_BLOCK;
            when {MODE_FILE_FIFO}:    aFileType := FILE_FIFO;
            when {MODE_FILE_SOCKET}:  aFileType := FILE_SOCKET;
            when {MODE_FILE_SYMLINK}:
              filePath := catalogEntry.linkTo;
            otherwise:
              raise RANGE_ERROR;
          end case;
        end if;
        decr(symlinkCount);
      until aFileType <> FILE_UNKNOWN or symlinkCount = 0;
    end if;
  end func;


(**
 *  Determine the type of a file in a RPM archive.
 *  The function does not follow symbolic links. Therefore it may
 *  return ''FILE_SYMLINK''. A return value of ''FILE_ABSENT'' does
 *  not imply that a file with this name can be created, since missing
 *  directories and illegal file names cause also ''FILE_ABSENT''.
 *  @return the type of the file.
 *  @exception RANGE_ERROR ''filePath'' does not use the standard path
 *             representation.
 *)
const func fileType: fileTypeSL (inout rpmArchive: rpm, in string: filePath) is func
  result
    var fileType: aFileType is FILE_UNKNOWN;
  local
    var rpmCatalogEntry: catalogEntry is rpmCatalogEntry.value;
    var integer: offset is 0;
    var integer: pos is 0;
    var integer: modeValue is 0;
  begin
    # writeln("fileTypeSL: " <& filePath);
    if filePath <> "/" and endsWith(filePath, "/") then
      raise RANGE_ERROR;
    elsif filePath = "" then
      aFileType := FILE_DIR;
    else
      if filePath in rpm.catalog then
        catalogEntry := rpm.catalog[filePath];
      elsif implicitDir(rpm, filePath) then
        catalogEntry := addImplicitDir(rpm, filePath);
      else
        aFileType := FILE_ABSENT;
      end if;
      if aFileType = FILE_UNKNOWN then
        if not catalogEntry.allDataPresent then
          readCatalogEntry(rpm, catalogEntry);
        end if;
        case bin32(catalogEntry.mode) & MODE_FILE_TYPE_MASK of
          when {MODE_FILE_REGULAR}: aFileType := FILE_REGULAR;
          when {MODE_FILE_DIR}:     aFileType := FILE_DIR;
          when {MODE_FILE_CHAR}:    aFileType := FILE_CHAR;
          when {MODE_FILE_BLOCK}:   aFileType := FILE_BLOCK;
          when {MODE_FILE_FIFO}:    aFileType := FILE_FIFO;
          when {MODE_FILE_SOCKET}:  aFileType := FILE_SOCKET;
          when {MODE_FILE_SYMLINK}: aFileType := FILE_SYMLINK;
          otherwise:
            raise RANGE_ERROR;
        end case;
      end if;
    end if;
  end func;


(**
 *  Determine the file mode (permissions) of a file in a RPM archive.
 *  The function follows symbolic links.
 *  @return the file mode.
 *  @exception RANGE_ERROR ''filePath'' does not use the standard path
 *             representation.
 *  @exception FILE_ERROR ''filePath'' is not present in the RPM archive, or
 *             the chain of symbolic links is too long.
 *)
const func fileMode: fileMode (inout rpmArchive: rpm, in string: filePath) is func
  result
    var fileMode: mode is fileMode.value;
  begin
    if filePath <> "/" and endsWith(filePath, "/") then
      raise RANGE_ERROR;
    else
      mode := fileMode(followSymlink(rpm, filePath).mode);
    end if;
  end func;


(**
 *  Change the file mode (permissions) of a file in a RPM archive.
 *  The function follows symbolic links.
 *  @exception RANGE_ERROR ''filePath'' does not use the standard path
 *             representation.
 *  @exception FILE_ERROR ''filePath'' is not present in the RPM archive, or
 *             the chain of symbolic links is too long.
 *)
const proc: setFileMode (inout rpmArchive: rpm, in string: filePath,
    in fileMode: mode) is func
  local
    var rpmCatalogEntry: catalogEntry is rpmCatalogEntry.value;
  begin
    if filePath <> "/" and endsWith(filePath, "/") then
      raise RANGE_ERROR;
    else
      catalogEntry := followSymlink(rpm, filePath);
      if catalogEntry.fileNumber <> 0 then
        catalogEntry.mode := (catalogEntry.mode >> 9 << 9) + integer(mode);
        catalogEntry.dirty := TRUE;
        rpm.catalog @:= [catalogEntry.filePath] catalogEntry;
        if rpm.archive = fileSys.value then
          getArchive(rpm);
        end if;
        setFileMode(rpm.archive,
            archiveFilePath(rpm.archive, catalogEntry.filePath), mode);
      else
        raise FILE_ERROR;
      end if;
    end if;
  end func;


(**
 *  Determine the size of a file in a RPM archive.
 *  The file size is measured in bytes.
 *  For directories a size of 0 is returned.
 *  The function follows symbolic links.
 *  @return the size of the file.
 *  @exception RANGE_ERROR ''filePath'' does not use the standard path
 *             representation.
 *  @exception FILE_ERROR ''filePath'' is not present in the RPM archive, or
 *             the chain of symbolic links is too long.
 *)
const func integer: fileSize (inout rpmArchive: rpm, in string: filePath) is func
  result
    var integer: size is 0;
  begin
    if filePath <> "/" and endsWith(filePath, "/") then
      raise RANGE_ERROR;
    else
      size := followSymlink(rpm, filePath).fileSize;
    end if;
  end func;


(**
 *  Determine the modification time of a file in a RPM archive.
 *  The function follows symbolic links.
 *  @return the modification time of the file.
 *  @exception RANGE_ERROR ''filePath'' does not use the standard path
 *             representation.
 *  @exception FILE_ERROR ''filePath'' is not present in the RPM archive, or
 *             the chain of symbolic links is too long.
 *)
const func time: getMTime (inout rpmArchive: rpm, in string: filePath) is func
  result
    var time: modificationTime is time.value;
  begin
    if filePath <> "/" and endsWith(filePath, "/") then
      raise RANGE_ERROR;
    else
      modificationTime := timestamp1970ToTime(
          followSymlink(rpm, filePath).mtime);
    end if;
  end func;


(**
 *  Set the modification time of a file in a RPM archive.
 *  The function follows symbolic links.
 *  @exception RANGE_ERROR ''filePath'' does not use the standard path
 *             representation.
 *  @exception RANGE_ERROR ''aTime'' is invalid or cannot be
 *             converted to the system file time.
 *  @exception FILE_ERROR ''filePath'' is not present in the RPM archive, or
 *             the chain of symbolic links is too long.
 *)
const proc: setMTime (inout rpmArchive: rpm, in string: filePath,
    in time: modificationTime) is func
  local
    var rpmCatalogEntry: catalogEntry is rpmCatalogEntry.value;
    var integer: mtime is 0;
  begin
    mtime := timestamp1970(modificationTime);
    if mtime < 0 or mtime >= 2 ** 31 or
        (filePath <> "/" and endsWith(filePath, "/")) then
      raise RANGE_ERROR;
    else
      catalogEntry := followSymlink(rpm, filePath);
      if catalogEntry.fileNumber <> 0 then
        catalogEntry.mtime := mtime;
        catalogEntry.dirty := TRUE;
        rpm.catalog @:= [catalogEntry.filePath] catalogEntry;
        if rpm.archive = fileSys.value then
          getArchive(rpm);
        end if;
        setMTime(rpm.archive,
                 archiveFilePath(rpm.archive, catalogEntry.filePath),
                 modificationTime);
      end if;
    end if;
  end func;


(**
 *  Determine the name of the owner (UID) of a file in a RPM archive.
 *  The function follows symbolic links.
 *  @return the name of the file owner.
 *  @exception RANGE_ERROR ''filePath'' does not use the standard path
 *             representation.
 *  @exception FILE_ERROR ''filePath'' is not present in the RPM archive, or
 *             the chain of symbolic links is too long.
 *)
const func string: getOwner (inout rpmArchive: rpm, in string: filePath) is func
  result
    var string: owner is "";
  local
    var rpmCatalogEntry: catalogEntry is rpmCatalogEntry.value;
  begin
    if filePath <> "/" and endsWith(filePath, "/") then
      raise RANGE_ERROR;
    else
      catalogEntry := followSymlink(rpm, filePath);
      if catalogEntry.userName <> "" then
        owner := catalogEntry.userName;
      else
        owner := str(catalogEntry.uid);
      end if;
    end if;
  end func;


const proc: setOwner (inout rpmArchive: rpm, in string: filePath,
    in string: owner) is func
  local
    var rpmCatalogEntry: catalogEntry is rpmCatalogEntry.value;
  begin
    if filePath <> "/" and endsWith(filePath, "/") then
      raise RANGE_ERROR;
    else
      catalogEntry := followSymlink(rpm, filePath);
      if catalogEntry.fileNumber <> 0 then
        if isDigitString(owner) then
          catalogEntry.uid := integer(owner);
          catalogEntry.userName := "";
        else
          catalogEntry.uid := 0;
          catalogEntry.userName := owner;
        end if;
        catalogEntry.dirty := TRUE;
        rpm.catalog @:= [catalogEntry.filePath] catalogEntry;
        if rpm.archive = fileSys.value then
          getArchive(rpm);
        end if;
        setOwner(rpm.archive,
                 archiveFilePath(rpm.archive, catalogEntry.filePath),
                 owner);
      end if;
    end if;
  end func;


(**
 *  Determine the name of the group (GID) of a file in a RPM archive.
 *  The function follows symbolic links.
 *  @return the name of the file group.
 *  @exception RANGE_ERROR ''filePath'' does not use the standard path
 *             representation.
 *  @exception FILE_ERROR ''filePath'' is not present in the RPM archive, or
 *             the chain of symbolic links is too long.
 *)
const func string: getGroup (inout rpmArchive: rpm, in string: filePath) is func
  result
    var string: group is "";
  local
    var rpmCatalogEntry: catalogEntry is rpmCatalogEntry.value;
  begin
    if filePath <> "/" and endsWith(filePath, "/") then
      raise RANGE_ERROR;
    else
      catalogEntry := followSymlink(rpm, filePath);
      if catalogEntry.groupName <> "" then
        group := catalogEntry.groupName;
      else
        group := str(catalogEntry.gid);
      end if;
     end if;
  end func;


const proc: setGroup (inout rpmArchive: rpm, in string: filePath,
    in string: group) is func
  local
    var rpmCatalogEntry: catalogEntry is rpmCatalogEntry.value;
  begin
    if filePath <> "/" and endsWith(filePath, "/") then
      raise RANGE_ERROR;
    else
      catalogEntry := followSymlink(rpm, filePath);
      if catalogEntry.fileNumber <> 0 then
        if isDigitString(group) then
          catalogEntry.gid := integer(group);
          catalogEntry.groupName := "";
        else
          catalogEntry.gid := 0;
          catalogEntry.groupName := group;
        end if;
        catalogEntry.dirty := TRUE;
        rpm.catalog @:= [catalogEntry.filePath] catalogEntry;
        if rpm.archive = fileSys.value then
          getArchive(rpm);
        end if;
        setGroup(rpm.archive,
                 archiveFilePath(rpm.archive, catalogEntry.filePath),
                 group);
      end if;
    end if;
  end func;


(**
 *  Reads the destination of a symbolic link in a RPM archive.
 *  @return The destination referred by the symbolic link.
 *  @exception RANGE_ERROR ''filePath'' does not use the standard path
 *             representation.
 *  @exception FILE_ERROR ''filePath'' is not present in the RPM archive,
 *             or is not a symbolic link.
 *)
const func string: readlink (inout rpmArchive: rpm, in string: filePath) is func
  result
    var string: linkPath is "";
  local
    var rpmCatalogEntry: catalogEntry is rpmCatalogEntry.value;
  begin
    if filePath <> "/" and endsWith(filePath, "/") then
      raise RANGE_ERROR;
    elsif filePath in rpm.catalog then
      catalogEntry := rpm.catalog[filePath];
    else
      raise FILE_ERROR;
    end if;
    if not catalogEntry.allDataPresent then
      readCatalogEntry(rpm, catalogEntry);
    end if;
    if bin32(catalogEntry.mode) & MODE_FILE_TYPE_MASK = MODE_FILE_SYMLINK then
      linkPath := catalogEntry.linkTo;
    else
      raise FILE_ERROR;
    end if;
  end func;


(**
 *  Get the contents of a file in a RPM archive.
 *  The function follows symbolic links.
 *  @return the specified file as string.
 *  @exception RANGE_ERROR ''filePath'' does not use the standard path
 *             representation.
 *  @exception FILE_ERROR ''filePath'' is not present in the RPM archive,
 *             or is not a regular file, or
 *             the chain of symbolic links is too long.
 *)
const func string: getFile (inout rpmArchive: rpm, in string: filePath) is func
  result
    var string: content is "";
  local
    var rpmCatalogEntry: catalogEntry is rpmCatalogEntry.value;
    var string: digest is "";
  begin
    if filePath <> "/" and endsWith(filePath, "/") then
      raise RANGE_ERROR;
    else
      catalogEntry := followSymlink(rpm, filePath);
      if bin32(catalogEntry.mode) & MODE_FILE_TYPE_MASK = MODE_FILE_REGULAR then
        if rpm.archive = fileSys.value then
          getArchive(rpm);
        end if;
        content := getFile(rpm.archive, archiveFilePath(rpm.archive, catalogEntry.filePath));
        digest := hex(getDigest(content, rpm.fileDigestAlgo));
        if catalogEntry.digest <> digest then
          # writeln("fileDigestAlgo: " <& rpm.fileDigestAlgo);
          # writeln("digest: " <& rpm.catalog[fileNumber].digest <&
          #         " computed digest: " <& digest);
          raise FILE_ERROR;
        end if;
      end if;
    end if;
  end func;


(**
 *  Write ''data'' to a RPM archive with the given ''filePath''.
 *  If the file exists already, it is overwritten.
 *  The function follows symbolic links.
 *  @exception RANGE_ERROR ''filePath'' does not use the standard path
 *             representation.
 *  @exception FILE_ERROR The file exists, but it is not a regular file.
 *)
const proc: putFile (inout rpmArchive: rpm, in string: filePath,
    in string: data) is func
  local
    var rpmCatalogEntry: catalogEntry is rpmCatalogEntry.value;
    var string: missingPath is "";
  begin
    if filePath = "" or filePath <> "/" and endsWith(filePath, "/") then
      raise RANGE_ERROR;
    else
      missingPath := followSymlink(rpm, filePath, catalogEntry);
      if missingPath = "" then
        # The file does exist.
        if bin32(catalogEntry.mode) & MODE_FILE_TYPE_MASK <> MODE_FILE_REGULAR then
          raise FILE_ERROR;
        else
          if rpm.archive = fileSys.value then
            getArchive(rpm);
          end if;
          catalogEntry.fileSize := length(data);
          catalogEntry.digest := hex(getDigest(data, rpm.fileDigestAlgo));
          catalogEntry.dirty := TRUE;
          rpm.catalog @:= [catalogEntry.filePath] catalogEntry;
          putFile(rpm.archive, catalogEntry.filePath, data);
        end if;
      else
        if rpm.archive = fileSys.value then
          getArchive(rpm);
        end if;
        catalogEntry.fileSize := length(data);
        catalogEntry.mode := ord(MODE_FILE_REGULAR) + 8#664;
        catalogEntry.uid := 100;
        catalogEntry.gid := 100;
        catalogEntry.rdev := 0;
        catalogEntry.mtime := timestamp1970(time(NOW));
        catalogEntry.digest := hex(getDigest(data, rpm.fileDigestAlgo));
        catalogEntry.linkTo := "";
        catalogEntry.flags := 0;
        catalogEntry.userName := "";
        catalogEntry.groupName := "";
        catalogEntry.verifyFlags := 0;
        catalogEntry.device := 0;
        catalogEntry.inode := succ(card(rpm.register));
        catalogEntry.lang := "";
        catalogEntry.dirIndex := 0;
        catalogEntry.baseName := "";
        catalogEntry.color := 0;
        catalogEntry.fileClass := 0;
        catalogEntry.dependX := 0;
        catalogEntry.dependN := 0;
        catalogEntry.allDataPresent := TRUE;
        catalogEntry.filePath := missingPath;
        catalogEntry.fileNumber := succ(card(rpm.register));
        catalogEntry.dirty := TRUE;
        rpm.catalog @:= [missingPath] catalogEntry;
        incl(rpm.register, missingPath);
        putFile(rpm.archive, missingPath, data);
      end if;
    end if;
  end func;


(**
 *  Creates a new directory in a RPM archive.
 *  The function does not follow symbolic links.
 *  @exception RANGE_ERROR ''dirPath'' does not use the standard path
 *             representation.
 *  @exception FILE_ERROR A system function returns an error.
 *)
const proc: mkdir (inout rpmArchive: rpm, in string: filePath) is func
  local
    var rpmCatalogEntry: catalogEntry is rpmCatalogEntry.value;
    var boolean: fileExists is TRUE;
    var integer: fileNumber is 0;
    var integer: length is 0;
    var string: filePath8 is "";
  begin
    if filePath = "" or filePath <> "/" and endsWith(filePath, "/") then
      raise RANGE_ERROR;
    elsif filePath in rpm.catalog then
      fileNumber := rpm.catalog[filePath].fileNumber;
    elsif implicitDir(rpm, filePath) then
      fileNumber := addImplicitDir(rpm, filePath).fileNumber;
    else
      fileExists := FALSE;
    end if;
    if fileExists and fileNumber <> 0 then
      # The file exists and it is not an implicit directory.
      raise FILE_ERROR;
    else
      if rpm.archive = fileSys.value then
        getArchive(rpm);
      end if;
      catalogEntry.fileSize := 0;
      catalogEntry.mode := ord(MODE_FILE_DIR) + 8#775;
      catalogEntry.uid := 100;
      catalogEntry.gid := 100;
      catalogEntry.rdev := 0;
      catalogEntry.mtime := timestamp1970(time(NOW));
      catalogEntry.digest := "";
      catalogEntry.linkTo := "";
      catalogEntry.flags := 0;
      catalogEntry.userName := "";
      catalogEntry.groupName := "";
      catalogEntry.verifyFlags := 0;
      catalogEntry.device := 0;
      catalogEntry.inode := succ(card(rpm.register));
      catalogEntry.lang := "";
      catalogEntry.dirIndex := 0;
      catalogEntry.baseName := "";
      catalogEntry.color := 0;
      catalogEntry.fileClass := 0;
      catalogEntry.dependX := 0;
      catalogEntry.dependN := 0;
      catalogEntry.allDataPresent := TRUE;
      catalogEntry.filePath := filePath;
      catalogEntry.fileNumber := succ(card(rpm.register));
      catalogEntry.dirty := TRUE;
      rpm.catalog @:= [filePath] catalogEntry;
      incl(rpm.register, filePath);
      mkdir(rpm.archive, filePath);
    end if;
  end func;


(**
 *  For-loop which loops recursively over the paths in a RPM archive.
 *)
const proc: for (inout string: filePath) range (inout rpmArchive: rpm) do
              (in proc: statements)
            end for is func
  begin
    for filePath range rpm.register do
      statements;
    end for;
  end func;


const func file: openFileInRpm (inout rpmArchive: rpm, in string: filePath,
    in string: mode) is func
  result
    var file: newFile is STD_NULL;
  begin
    if mode = "r" then
      if filePath <> "/" and endsWith(filePath, "/") then
        raise RANGE_ERROR;
      elsif filePath in rpm.catalog then
        if rpm.archive = fileSys.value then
          getArchive(rpm);
        end if;
        newFile := open(rpm.archive, filePath, mode);
      end if;
    end if;
  end func;


(**
 *  Open a file with ''filePath'' and ''mode'' in in a RPM archive.
 *)
const func file: open (inout rpmArchive: rpm, in string: filePath,
    in string: mode) is
  return openFileInRpm(rpm, filePath, mode);
