(********************************************************************)
(*                                                                  *)
(*  rpm.s7i       Rpm archive library                               *)
(*  Copyright (C) 2020 - 2024  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";
include "archive_base.s7i";
include "magic.s7i";
include "elf.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 integer: RPM_LEAD_SIZE is 96;
const integer: RPM_HEADER_SIZE is 16;
const integer: RPM_INDEX_ENTRY_SIZE is 16;

const type: rpmPackageType is new enum
    RPM_BINARY_PACKAGE, RPM_SOURCE_PACKAGE
  end enum;

const type: rpmLead is new struct
    var string: magic is "";
    var integer: majorVersion is 0;
    var integer: minorVersion is 0;
    var rpmPackageType: packageType is RPM_BINARY_PACKAGE;
    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;
    # 4 reserved bytes are currently unused.
    var string: reservedBytes is "\0;" mult 4;
    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  First entry (its data is placed at the end and it refers back)
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;  # INT32        Optional  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;  # INT32                  Unix timestamp of package installation
const integer: RPMTAG_SIZE               is 1009;  # INT32        Required  Sum of regular file sizes and symlink sizes in archive
const integer: RPMTAG_DISTRIBUTION       is 1010;  # STRING
const integer: RPMTAG_VENDOR             is 1011;  # STRING                 Package vendor contact information
const integer: RPMTAG_GIF                is 1012;  # BIN          Deprecated
const integer: RPMTAG_XPM                is 1013;  # BIN          Deprecated
const integer: RPMTAG_LICENSE            is 1014;  # STRING       Required  Package license
const integer: RPMTAG_PACKAGER           is 1015;  # STRING                 Packager contact information
const integer: RPMTAG_GROUP              is 1016;  # I18NSTRING   Required  Package group
const integer: RPMTAG_CHANGELOG          is 1017;
const integer: RPMTAG_SOURCE             is 1018;  # STRING_ARRAY Optional  Source file names
const integer: RPMTAG_PATCH              is 1019;  # STRING_ARRAY           Patch file names
const integer: RPMTAG_URL                is 1020;  # STRING                 Package URL (typically project upstream website)
const integer: RPMTAG_OS                 is 1021;  # STRING       Required  The operating system the package is for
const integer: RPMTAG_ARCH               is 1022;  # STRING       Required  The architecture the package is for
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 Deprecated Filenames when not in "compressed format"
const integer: RPMTAG_FILESIZES          is 1028;  # INT32 ARRAY  Size of each file [index: fileNumber]
const integer: RPMTAG_FILESTATES         is 1029;  # CHAR         Per-file installed status information (only after installed)
const integer: RPMTAG_FILEMODES          is 1030;  # INT16 ARRAY  Mode of each file [index: fileNumber]
const integer: RPMTAG_FILEUIDS           is 1031;
const integer: RPMTAG_FILEGIDS           is 1032;
const integer: RPMTAG_FILERDEVS          is 1033;  # INT16 ARRAY  Device ID of device files (0 for other files) [index: fileNumber]
const integer: RPMTAG_FILEMTIMES         is 1034;  # INT32 ARRAY  Modification time seconds since epoch of each file [index: fileNumber]
const integer: RPMTAG_FILEDIGESTS        is 1035;  # STRING_ARRAY Cryptographic digest using algorithm from FILEDIGESTALGO [index: fileNumber]
const integer: RPMTAG_FILELINKTOS        is 1036;  # STRING_ARRAY Target of symbolic links ("" for other files) [index: fileNumber]
const integer: RPMTAG_FILEFLAGS          is 1037;  # INT32 ARRAY  Bits that classify and control how files installed [index: fileNumber]
const integer: RPMTAG_ROOT               is 1038;
const integer: RPMTAG_FILEUSERNAME       is 1039;  # STRING_ARRAY Owner name of each file [index: fileNumber]
const integer: RPMTAG_FILEGROUPNAME      is 1040;  # STRING_ARRAY Group name of each file [index: fileNumber]
const integer: RPMTAG_EXCLUDE            is 1041;
const integer: RPMTAG_EXCLUSIVE          is 1042;
const integer: RPMTAG_ICON               is 1043;  # BIN          Deprecated
const integer: RPMTAG_SOURCERPM          is 1044;  # STRING       Optional  Name of associated source rpm
const integer: RPMTAG_FILEVERIFYFLAGS    is 1045;  # INT32 ARRAY  Optional  Bits that control how files are to be verified after install [index: fileNumber]
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: provision]
const integer: RPMTAG_REQUIREFLAGS       is 1048;  # INT32 ARRAY  Bits to specify the dependency range and context [index: requirement]
const integer: RPMTAG_REQUIRENAME        is 1049;  # STRING_ARRAY List of requirement names [index: requirement]
const integer: RPMTAG_REQUIREVERSION     is 1050;  # STRING_ARRAY Version associated with Requirename [index: requirement]
const integer: RPMTAG_NOSOURCE           is 1051;  # INT32 ARRAY  Denotes a source number for which source is not supplied [indexed]
const integer: RPMTAG_NOPATCH            is 1052;
const integer: RPMTAG_CONFLICTFLAGS      is 1053;  # INT32 ARRAY  Bits to specify the conflict range and context [indexed]
const integer: RPMTAG_CONFLICTNAME       is 1054;  # STRING_ARRAY List of conflict names [indexed]
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 [indexed]
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  Opaque value for tracking packages from a single build operation
const integer: RPMTAG_FILEDEVICES        is 1095;  # INT32 ARRAY  Abstract device ID of each file [index: fileNumber]
const integer: RPMTAG_FILEINODES         is 1096;  # INT32 ARRAY  Abstract inode number of each file [index: fileNumber]
const integer: RPMTAG_FILELANGS          is 1097;  # STRING_ARRAY Per-file locale marker [index: fileNumber]
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;  # INT32        Optional  Denotes a source rpm
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 ARRAY  Bits to specify the provide range and context [index: provision]
const integer: RPMTAG_PROVIDEVERSION     is 1113;  # STRING_ARRAY Version associated with provide names [index: provision]
const integer: RPMTAG_OBSOLETEFLAGS      is 1114;  # INT32 ARRAY  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 ARRAY  Directory number (dirNumber) of each file [index: fileNumber]
const integer: RPMTAG_BASENAMES          is 1117;  # STRING_ARRAY Base name of each file path [index: fileNumber]
const integer: RPMTAG_DIRNAMES           is 1118;  # STRING_ARRAY List of file path directories [index: dirNumber]
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;  # INT32        Deprecated
const integer: RPMTAG_SHA1RHN            is 1130;
const integer: RPMTAG_RHNPLATFORM        is 1131;  # STRING       Deprecated
const integer: RPMTAG_PLATFORM           is 1132;  # STRING       Optional  Package platform (arch-os-vendor)
const integer: RPMTAG_PATCHESNAME        is 1133;  # STRING_ARRAY Deprecated
const integer: RPMTAG_PATCHESFLAGS       is 1134;  # INT32 ARRAY  Deprecated
const integer: RPMTAG_PATCHESVERSION     is 1135;  # STRING_ARRAY Deprecated
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 ARRAY  1 for 32bit ELF, 2 for 64bit ELF and 0 otherwise [index: fileNumber]
const integer: RPMTAG_FILECLASS          is 1141;  # INT32 ARRAY  Index into CLASSDICT for each file [index: fileNumber]
const integer: RPMTAG_CLASSDICT          is 1142;  # STRING_ARRAY List of file classes (output of the "file" command)
const integer: RPMTAG_FILEDEPENDSX       is 1143;  # INT32 ARRAY  Index into the entries in DependsDict for each file [index: fileNumber]
const integer: RPMTAG_FILEDEPENDSN       is 1144;  # INT32 ARRAY  Size of the entries in DependsDict for each file [index: fileNumber]
const integer: RPMTAG_DEPENDSDICT        is 1145;  # INT32 ARRAY  Type/Index into Requires/Provides/Conflicts/Obsoletes [indexed]
const integer: RPMTAG_SOURCEPKGID        is 1146;  # BIN          RPMSIGTAG_MD5 of the associated source rpm
const integer: RPMTAG_FILECONTEXTS       is 1147;  # STRING_ARRAY Deprecated
const integer: RPMTAG_FSCONTEXTS         is 1148;  # STRING_ARRAY Deprecated
const integer: RPMTAG_RECONTEXTS         is 1149;  # STRING_ARRAY Deprecated
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;  # STRING_ARRAY Deprecated
const integer: RPMTAG_OLDSUGGESTSVERSION is 1157;  # STRING_ARRAY Deprecated
const integer: RPMTAG_OLDSUGGESTSFLAGS   is 1158;  # INT32 ARRAY  Deprecated
const integer: RPMTAG_OLDENHANCESNAME    is 1159;  # STRING_ARRAY Deprecated
const integer: RPMTAG_OLDENHANCESVERSION is 1160;  # STRING_ARRAY Deprecated
const integer: RPMTAG_OLDENHANCESFLAGS   is 1161;  # INT32 ARRAY  Deprecated
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;  # INT64 ARRAY  Size of each file when files >= 4GB are present [index: fileNumber]
const integer: RPMTAG_LONGSIZE           is 5009;  # INT64        Used instead of RPMTAG_SIZE when >= 4GB
const integer: RPMTAG_FILECAPS           is 5010;
const integer: RPMTAG_FILEDIGESTALGO     is 5011;  # INT32        ID of file digest algorithm. If missing, considered to be MD5.
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;  # STRING   Optional Encoding of the header string data (always utf-8)
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;  # STRING_ARRAY Hex digest of the compressed payload
const integer: RPMTAG_PAYLOADDIGESTALGO           is 5093;  # INT32        ID of payload digest algorithm (PAYLOADDIGEST and PAYLOADDIGESTALT)
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;  # STRING_ARRAY Hex digest of the uncompressed payload
const integer: RPMTAG_ARCHSUFFIX                  is 5098;  # STRING       Package file arch suffix (“.src”, “.nosrc” or .arch)
const integer: RPMTAG_SPEC                        is 5099;  # STRING       Expanded and parsed spec contents

const integer: RPMSIGTAG_HEADERSIGNATURES is   62;  # BIN    Optional  First entry (its data is placed at the end and it refers back)
const integer: RPMSIGTAG_SIZE             is 1000;  # INT32  Required  Byte count of header section + compressed payload
const integer: RPMSIGTAG_PGP              is 1002;  # BIN    Optional
const integer: RPMSIGTAG_MD5              is 1004;  # BIN    Required  MD5 digest of header section + compressed payload
const integer: RPMSIGTAG_GPG              is 1005;  # BIN    Optional
const integer: RPMSIGTAG_PAYLOADSIZE      is 1007;  # INT32  Optional  Byte count of the uncompressed payload
const integer: RPMSIGTAG_RESERVEDSPACE    is 1008;  # BIN              Area with zero bytes to fill the signature section up to a fixed size
const integer: RPMSIGTAG_SIGSIZE          is  257;  # INT32            Header + payload size
const integer: RPMSIGTAG_RSA              is  268;  # BIN              OpenPGP RSA signature of the header (if thus signed)
const integer: RPMSIGTAG_SHA1             is  269;  # STRING           SHA-1 digest of the header section
const integer: RPMSIGTAG_LONGSIGSIZE      is  270;  # INT64            Header + payload size if >= 4GB
const integer: RPMSIGTAG_SHA256           is  273;  # STRING Optional  SHA-256 digest of the header section

# Flags used by RPMTAG_FILEFLAGS:
const integer: RPMFILE_NONE      is 0;
const integer: RPMFILE_CONFIG    is 1 <<  0;  # The file is a configuration file. Upgrade should save an existing file and remove should not remove it.
const integer: RPMFILE_DOC       is 1 <<  1;  # The file contains documentation.
const integer: RPMFILE_DONOTUSE  is 1 <<  2;  # This value is reserved for future use. Conforming packages may not use this flag.
const integer: RPMFILE_MISSINGOK is 1 <<  3;  # The file need not exist on the installed system.
const integer: RPMFILE_NOREPLACE is 1 <<  4;  # Similar to the RPMFILE_CONFIG. Upgrade should not alter this file.
const integer: RPMFILE_SPECFILE  is 1 <<  5;  # The file is a package specification.
const integer: RPMFILE_GHOST     is 1 <<  6;  # The file is not actually included in the payload, but should still be considered as a part of the package.
const integer: RPMFILE_LICENSE   is 1 <<  7;  # The file contains the license conditions.
const integer: RPMFILE_README    is 1 <<  8;  # The file contains high level notes about the package.
const integer: RPMFILE_EXCLUDE   is 1 <<  9;  # The file is not a part of the package, and should not be installed.
const integer: RPMFILE_UNPATCHED is 1 << 10;
const integer: RPMFILE_PUBKEY    is 1 << 11;
const integer: RPMFILE_ARTIFACT  is 1 << 12;

const integer: RPMSENSE_ANY           is 0;
const integer: RPMSENSE_LESS          is 1 <<  1;
const integer: RPMSENSE_GREATER       is 1 <<  2;
const integer: RPMSENSE_EQUAL         is 1 <<  3;
# bit 4 unused
const integer: RPMSENSE_POSTTRANS     is 1 <<  5;  # %posttrans dependency
const integer: RPMSENSE_PREREQ        is 1 <<  6;  # legacy prereq dependency
const integer: RPMSENSE_PRETRANS      is 1 <<  7;  # Pre-transaction dependency
const integer: RPMSENSE_INTERP        is 1 <<  8;  # Interpreter used by scriptlet
const integer: RPMSENSE_SCRIPT_PRE    is 1 <<  9;  # %pre dependency
const integer: RPMSENSE_SCRIPT_POST   is 1 << 10;  # %post dependency
const integer: RPMSENSE_SCRIPT_PREUN  is 1 << 11;  # %preun dependency
const integer: RPMSENSE_SCRIPT_POSTUN is 1 << 12;  # %postun dependency
const integer: RPMSENSE_SCRIPT_VERIFY is 1 << 13;  # %verify dependency
const integer: RPMSENSE_FIND_REQUIRES is 1 << 14;  # find-requires generated dependency
const integer: RPMSENSE_FIND_PROVIDES is 1 << 15;  # find-provides generated dependency
const integer: RPMSENSE_TRIGGERIN     is 1 << 16;  # %triggerin dependency
const integer: RPMSENSE_TRIGGERUN     is 1 << 17;  # %triggerun dependency
const integer: RPMSENSE_TRIGGERPOSTUN is 1 << 18;  # %triggerpostun dependency
const integer: RPMSENSE_MISSINGOK     is 1 << 19;  # suggests/enhances hint
# bits 20-23 unused
const integer: RPMSENSE_RPMLIB        is 1 << 24;  # rpmlib(feature) dependency
const integer: RPMSENSE_TRIGGERPREIN  is 1 << 25;  # %triggerprein dependency
const integer: RPMSENSE_KEYRING       is 1 << 26;
# bit 27 unused
const integer: RPMSENSE_CONFIG        is 1 << 28;

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 integer: RPM_ALL_VERIFY_FLAGS is 4294967295;


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 := "LONGSIGSIZE";
      when { 271}: name := "LONGARCHIVESIZE";
      when { 273}: name := "SHA256";
      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_FILEDIGESTS}:                 name := "FILEDIGESTS";
      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";
      when {RPMTAG_ARCHSUFFIX}:                  name := "ARCHSUFFIX";
      when {RPMTAG_SPEC}:                        name := "SPEC";
      otherwise: name := "tag " <& tag;
    end case;
  end func;


const func string: rpmDependencyFlagsString (in integer: flags) is func
  result
    var string: flagsString is "";
  local
    var integer: bitNum is 0;
  begin
    for bitNum range 0 to 28 do
      # writeln(bin32(flags) radix 16 <& " " <& bin32(1) << bitNum radix 16 <& " " <& bin32(flags) & (bin32(1) << bitNum) <& " " <& bin32(flags) & (bin32(1) << bitNum) <> bin32(0) <& " " <& bitNum);
      if bin32(flags) & (bin32(1) << bitNum) <> bin32(0) then
        if flagsString <> "" then
          flagsString &:= ", ";
        end if;
        case integer(bin32(1) << bitNum) of
          when {RPMSENSE_LESS}:          flagsString &:= "LESS";
          when {RPMSENSE_GREATER}:       flagsString &:= "GREATER";
          when {RPMSENSE_EQUAL}:         flagsString &:= "EQUAL";
          when {RPMSENSE_POSTTRANS}:     flagsString &:= "POSTTRANS";
          when {RPMSENSE_PREREQ}:        flagsString &:= "PREREQ";
          when {RPMSENSE_PRETRANS}:      flagsString &:= "PRETRANS";
          when {RPMSENSE_INTERP}:        flagsString &:= "INTERP";
          when {RPMSENSE_SCRIPT_PRE}:    flagsString &:= "SCRIPT_PRE";
          when {RPMSENSE_SCRIPT_POST}:   flagsString &:= "SCRIPT_POST";
          when {RPMSENSE_SCRIPT_PREUN}:  flagsString &:= "SCRIPT_PREUN";
          when {RPMSENSE_SCRIPT_POSTUN}: flagsString &:= "SCRIPT_POSTUN";
          when {RPMSENSE_SCRIPT_VERIFY}: flagsString &:= "SCRIPT_VERIFY";
          when {RPMSENSE_FIND_REQUIRES}: flagsString &:= "FIND_REQUIRES";
          when {RPMSENSE_FIND_PROVIDES}: flagsString &:= "FIND_PROVIDES";
          when {RPMSENSE_TRIGGERIN}:     flagsString &:= "TRIGGERIN";
          when {RPMSENSE_TRIGGERUN}:     flagsString &:= "TRIGGERUN";
          when {RPMSENSE_TRIGGERPOSTUN}: flagsString &:= "TRIGGERPOSTUN";
          when {RPMSENSE_MISSINGOK}:     flagsString &:= "MISSINGOK";
          when {RPMSENSE_RPMLIB}:        flagsString &:= "RPMLIB";
          when {RPMSENSE_TRIGGERPREIN}:  flagsString &:= "TRIGGERPREIN";
          when {RPMSENSE_KEYRING}:       flagsString &:= "KEYRING";
          when {RPMSENSE_CONFIG}:        flagsString &:= "CONFIG";
          otherwise:                     flagsString &:= "UNUSED_" <& bitNum;
        end case;
      end if;
    end for;
    flagsString &:= " " <& flags radix 16;
  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: " <& ord(lead.packageType));
      writeln("Arch: " <& lead.arch);
      writeln("Name: " <& literal(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 proc: show (in rpmIndexEntry: indexEntry) is func
  local
    var string: striValue is "";
  begin
    write("Tag: " <& indexEntry.tag);
    write(" type: " <& indexEntry.dataType);
    write(" offset: " <& indexEntry.offset);
    writeln(" count: " <& indexEntry.count);
  end func;


const func string: getUtf8z (in string: stri, inout integer: currPos) is func
  result
    var string: resultStri is "";
  local
    var string: utf8Stri is "";
  begin
    utf8Stri := getAsciiz(stri, currPos);
    block
      resultStri := fromUtf8(utf8Stri);
    exception
      catch RANGE_ERROR:
        resultStri := utf8Stri;
    end block;
  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) fixLen indexEntry.count];
      when {RPM_INT8_TYPE}:
        data := store[succ(indexEntry.offset) fixLen indexEntry.count];
      when {RPM_INT16_TYPE}:
        data := store[succ(indexEntry.offset) fixLen 2 * indexEntry.count];
      when {RPM_INT32_TYPE}:
        data := store[succ(indexEntry.offset) fixLen 4 * indexEntry.count];
      when {RPM_INT64_TYPE}:
        data := store[succ(indexEntry.offset) fixLen 8 * indexEntry.count];
      when {RPM_STRING_TYPE}:
        currPos := succ(indexEntry.offset);
        data := getAsciiz(store, currPos);
      when {RPM_BIN_TYPE}:
        data := store[succ(indexEntry.offset) fixLen 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;
        if data <> "" then
          data := data[.. pred(length(data))];
        end if;
    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) fixLen 1], UNSIGNED, BE);
      when {RPM_INT16_TYPE}:
        number := bytes2Int(store[succ(indexEntry.offset) fixLen 2], UNSIGNED, BE);
      when {RPM_INT32_TYPE}:
        number := bytes2Int(store[succ(indexEntry.offset) fixLen 4], UNSIGNED, BE);
      when {RPM_INT64_TYPE}:
        number := bytes2Int(store[succ(indexEntry.offset) fixLen 8], UNSIGNED, BE);
    end case;
  end func;


const proc: initLead (inout rpmLead: lead) is func
  begin
    lead.magic := RPM_LEAD_MAGIC;
    lead.majorVersion := 3;
    lead.minorVersion := 0;
    lead.packageType := RPM_SOURCE_PACKAGE;
    lead.arch := 1;
    lead.name := "";
    lead.os := 1;
    lead.sig := 5;
  end func;


const proc: readLead (inout file: rpmFile, inout rpmLead: lead) is func
  local
    var string: leadStri is "";
  begin
    leadStri := gets(rpmFile, RPM_LEAD_SIZE);
    if length(leadStri) = RPM_LEAD_SIZE then
      lead.magic        :=                               leadStri[ 1 fixLen  4];
      lead.majorVersion :=                     bytes2Int(leadStri[ 5 fixLen  1], UNSIGNED, BE);
      lead.minorVersion :=                     bytes2Int(leadStri[ 6 fixLen  1], UNSIGNED, BE);
      lead.packageType  := rpmPackageType conv bytes2Int(leadStri[ 7 fixLen  2], UNSIGNED, BE);
      lead.arch         :=                     bytes2Int(leadStri[ 9 fixLen  2], UNSIGNED, BE);
      lead.name         :=                    fromAsciiz(leadStri[11 fixLen 66], 1);
      lead.os           :=                     bytes2Int(leadStri[77 fixLen  2], UNSIGNED, BE);
      lead.sig          :=                     bytes2Int(leadStri[79 fixLen  2], UNSIGNED, BE);
      # 16 reserved bytes are currently unused.
    end if;
  end func;


const func string: str (in rpmLead: lead) is
  return lead.magic <&
         bytes(    lead.majorVersion, UNSIGNED, BE, 1) <&
         bytes(    lead.minorVersion, UNSIGNED, BE, 1) <&
         bytes(ord(lead.packageType), UNSIGNED, BE, 2) <&
         bytes(    lead.arch,         UNSIGNED, BE, 2) <&
                   lead.name[.. 66] <&
         length(   lead.name) < 66 ? "\0;" mult 66 - length(lead.name) : "" <&
         bytes(    lead.os,           UNSIGNED, BE, 2) <&
         bytes(    lead.sig,          UNSIGNED, BE, 2) <&
         "\0;" mult 16;   # 16 reserved bytes


const proc: writeLead (inout file: outFile, inout rpmLead: lead) is func
  begin
    write(outFile, str(lead));
  end func;


const proc: readHeader (inout file: rpmFile, inout rpmHeader: header) is func
  local
    var string: headerStri is "";
  begin
    headerStri := gets(rpmFile, RPM_HEADER_SIZE);
    if length(headerStri) = RPM_HEADER_SIZE then
      header.magic         :=           headerStri[ 1 fixLen 3];
      header.version       := bytes2Int(headerStri[ 4 fixLen 1], UNSIGNED, BE);
      header.reservedBytes :=           headerStri[ 5 fixLen 4];
      header.indexCount    := bytes2Int(headerStri[ 9 fixLen 4], UNSIGNED, BE);
      header.storeSize     := bytes2Int(headerStri[13 fixLen 4], UNSIGNED, BE);
    end if;
  end func;


const func string: str (in rpmHeader: header) is
  return       header.magic <&
         bytes(header.version,    UNSIGNED, BE, 1) <&
               header.reservedBytes <&
         bytes(header.indexCount, UNSIGNED, BE, 4) <&
         bytes(header.storeSize,  UNSIGNED, BE, 4);


const func string: str (in rpmIndexEntry: indexEntry) is
  return bytes(indexEntry.tag,      UNSIGNED, BE, 4) <&
         bytes(indexEntry.dataType, UNSIGNED, BE, 4) <&
         bytes(indexEntry.offset,   SIGNED,   BE, 4) <&
         bytes(indexEntry.count,    UNSIGNED, BE, 4);


const type: rpmDependency is new struct
    var string: name is "";
    var string: version is "";
    var integer: flags is 0;
  end struct;


const func boolean: (in rpmDependency: depend1) = (in rpmDependency: depend2) is
  return depend1.name    = depend2.name and
         depend1.version = depend2.version and
         depend1.flags   = depend2.flags;


const func boolean: (in rpmDependency: depend1) <> (in rpmDependency: depend2) is
  return depend1.name    <> depend2.name or
         depend1.version <> depend2.version or
         depend1.flags   <> depend2.flags;


const func integer: compare (in rpmDependency: depend1, in rpmDependency: depend2) is func
  result
    var integer: signumValue is 0;
  begin
    signumValue := compare(depend1.name, depend2.name);
    if signumValue = 0 then
      signumValue := compare(depend1.version, depend2.version);
      if signumValue = 0 then
        signumValue := compare(depend1.flags, depend2.flags);
      end if;
    end if;
  end func;


const func integer: findDependency (in array rpmDependency: dependencies, in rpmDependency: dependency) is func
  result
    var integer: indexFound is 0;
  local
    var integer: index is 0;
  begin
    for key index range dependencies until indexFound <> 0 do
      if dependencies[index] = dependency then
        indexFound := index;
      end if;
    end for;
  end func;


const proc: addDependency (inout array rpmDependency: dependencies, in rpmDependency: dependency) is func
  local
    var integer: index is 0;
    var boolean: found is FALSE;
  begin
    for key index range dependencies until found do
      found := dependencies[index] = dependency;
    end for;
    if not found then
      dependencies &:= dependency;
    end if;
  end func;


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 array rpmDependency: dependencies is 0 times rpmDependency.value;
    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: 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: indexData is "";
    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(aRpmArchive, "src/drivers")   # Relative path in the archive.
 *    fileType(aRpmArchive, "/usr/include")  # Absolute path in the archive.
 *    fileSize(aRpmArchive, "/image")        # Absolute path in the archive.
 *    readDir(aRpmArchive, "")               # Return e.g.: "src" and "/"
 *    readDir(aRpmArchive, "/")              # 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 integer: maximumInode is 0;
    var boolean: useLongFileSizes is FALSE;
    var archiveRegisterType: register is archiveRegisterType.value;
    var rpmCatalogType: catalog is rpmCatalogType.value;
    var integer: fileDigestAlgo is RPM_DIGESTALGO_MD5;
    var array string: dirNames is 0 times "";
    var array string: classDict is 0 times "";
    var array rpmDependency: provided is 0 times rpmDependency.value;
    var array rpmDependency: required is 0 times rpmDependency.value;
    var array integer: dependsDict is 0 times 0;
    var file: payloadFile is STD_NULL;
    var fileSys: archive is fileSys.value;
    var boolean: dirty is FALSE;
  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] := getUtf8z(rpm.header.store, stringPos);
        # writeln(rpmtagName(tag) <& "[" <& index <& "]: " <& literal(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: inodePos is 0;
    var integer: langPos is 0;
    var integer: numberOfFiles is 0;
    var rpmCatalogEntry: catalogEntry is rpmCatalogEntry.value;
    var integer: fileNumber is 0;
  begin
    if RPMTAG_OLDFILENAMES in rpm.header.tagMap then
      oldFileNamePos := succ(rpm.header.tagMap[RPMTAG_OLDFILENAMES].offset);
      numberOfFiles := rpm.header.tagMap[RPMTAG_OLDFILENAMES].count;
    else
      rpm.dirNames := getStringArray(rpm, RPMTAG_DIRNAMES);
      dirIndexPos := succ(rpm.header.tagMap[RPMTAG_DIRINDEXES].offset);
      baseNamePos := succ(rpm.header.tagMap[RPMTAG_BASENAMES].offset);
      numberOfFiles := rpm.header.tagMap[RPMTAG_BASENAMES].count;
    end if;
    digestPos := succ(rpm.header.tagMap[RPMTAG_FILEDIGESTS].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);
    inodePos := succ(rpm.header.tagMap[RPMTAG_FILEINODES].offset);
    langPos := succ(rpm.header.tagMap[RPMTAG_FILELANGS].offset);
    for fileNumber range 1 to numberOfFiles do
      if oldFileNamePos <> 0 then
        catalogEntry.filePath := getUtf8z(rpm.header.store, oldFileNamePos);
      else
        catalogEntry.dirIndex := bytes2Int(
            rpm.header.store[dirIndexPos fixLen 4], UNSIGNED, BE);
        dirIndexPos +:= 4;
        catalogEntry.baseName := getUtf8z(rpm.header.store, baseNamePos);
        catalogEntry.filePath := rpm.dirNames[succ(catalogEntry.dirIndex)];
        # writeln("dirName[" <& fileNumber <& "]: " <& catalogEntry.filePath);
        catalogEntry.filePath &:= catalogEntry.baseName;
      end if;
      catalogEntry.digest := getAsciiz(rpm.header.store, digestPos);
      catalogEntry.linkTo := getUtf8z(rpm.header.store, linkToPos);
      catalogEntry.userName  := getAsciiz(rpm.header.store, userNamePos);
      catalogEntry.groupName := getAsciiz(rpm.header.store, groupNamePos);
      catalogEntry.inode := bytes2Int(
          rpm.header.store[inodePos fixLen 4], UNSIGNED, BE);
      inodePos +:= 4;
      rpm.maximumInode := max(rpm.maximumInode, catalogEntry.inode);
      catalogEntry.lang := getAsciiz(rpm.header.store, langPos);
      catalogEntry.fileNumber := fileNumber;
      rpm.catalog @:= [catalogEntry.filePath] catalogEntry;
      rpm.register @:= [catalogEntry.filePath] fileNumber;
    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 fixLen 1], UNSIGNED, BE);
          when {RPM_INT16_TYPE}:
            pos := offset + pred(index) * 2;
            number := bytes2Int(rpm.header.store[pos fixLen 2], UNSIGNED, BE);
          when {RPM_INT32_TYPE}:
            pos := offset + pred(index) * 4;
            number := bytes2Int(rpm.header.store[pos fixLen 4], UNSIGNED, BE);
          when {RPM_INT64_TYPE}:
            pos := offset + pred(index) * 8;
            number := bytes2Int(rpm.header.store[pos fixLen 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
      if rpm.useLongFileSizes then
        catalogEntry.fileSize    := getInt(rpm, RPMTAG_LONGFILESIZES,   fileNumber);
      else
        catalogEntry.fileSize    := getInt(rpm, RPMTAG_FILESIZES,       fileNumber);
      end if;
      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.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 := bytes(value, UNSIGNED, BE, 1);
      when {RPM_INT16_TYPE}:
        indexEntry.striValue := bytes(value, UNSIGNED, BE, 2);
      when {RPM_INT32_TYPE}:
        indexEntry.striValue := bytes(value, UNSIGNED, BE, 4);
      when {RPM_INT64_TYPE}:
        indexEntry.striValue := bytes(value, UNSIGNED, BE, 8);
      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;
      when {RPM_STRING_ARRAY_TYPE, RPM_I18NSTRING_TYPE}:
        indexEntry.arrayValue := split(value, '\0;');
        indexEntry.count := length(indexEntry.arrayValue);
        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;
    var string: stri is "";
  begin
    if tag in section.tagMap then
      indexEntry := section.tagMap[tag];
    else
      indexEntry.tag := tag;
      indexEntry.dataType := dataType;
      indexEntry.count := length(value);
    end if;
    for stri range value do
      indexEntry.arrayValue &:= toUtf8(stri);
    end for;
    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 := bytes(value, UNSIGNED, BE, 1);
      when {RPM_INT16_TYPE}:
        dataSize := 2;
        data := bytes(value, UNSIGNED, BE, 2);
      when {RPM_INT32_TYPE}:
        dataSize := 4;
        data := bytes(value, UNSIGNED, BE, 4);
      when {RPM_INT64_TYPE}:
        dataSize := 8;
        data := bytes(value, UNSIGNED, BE, 8);
      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: updateProvisions (inout rpmArchive: rpm) is func
  local
    var rpmDependency: dependency is rpmDependency.value;
    var integer: index is 0;
    var array string: provideName is 0 times "";
    var array string: provideVersion is 0 times "";
  begin
    for dependency key index range rpm.provided do
      provideName &:= dependency.name;
      provideVersion &:= dependency.version;
      updateIndexEntry(rpm, RPMTAG_PROVIDEFLAGS, RPM_INT32_TYPE, index, dependency.flags);
    end for;
    if length(provideName) <> 0 then
      updateIndexEntry(rpm.header, RPMTAG_PROVIDENAME, RPM_STRING_ARRAY_TYPE, provideName);
      updateIndexEntry(rpm.header, RPMTAG_PROVIDEVERSION, RPM_STRING_ARRAY_TYPE, provideVersion);
    end if;
  end func;


const proc: updateRequirements (inout rpmArchive: rpm) is func
  local
    var rpmDependency: dependency is rpmDependency.value;
    var integer: index is 0;
    var array string: requireName is 0 times "";
    var array string: requireVersion is 0 times "";
  begin
    for dependency key index range rpm.required do
      requireName &:= dependency.name;
      requireVersion &:= dependency.version;
      updateIndexEntry(rpm, RPMTAG_REQUIREFLAGS, RPM_INT32_TYPE, index, dependency.flags);
    end for;
    if length(requireName) <> 0 then
      updateIndexEntry(rpm.header, RPMTAG_REQUIRENAME, RPM_STRING_ARRAY_TYPE, requireName);
      updateIndexEntry(rpm.header, RPMTAG_REQUIREVERSION, RPM_STRING_ARRAY_TYPE, requireVersion);
    end if;
  end func;


const proc: updateDependencies (inout rpmArchive: rpm) is func
  local
    const integer: requiresFlag is bytes2Int("R\0;\0;\0;", UNSIGNED, BE);
    var rpmCatalogEntry: catalogEntry is rpmCatalogEntry.value;
    var rpmDependency: dependency is rpmDependency.value;
    var integer: dependencyIndex is 0;
    var integer: dependsDictEntry is 0;
  begin
    for catalogEntry range rpm.catalog do
      if catalogEntry.dirty then
        if length(catalogEntry.dependencies) <> 0 then
          rpm.catalog[catalogEntry.filePath].dependX := length(rpm.dependsDict);
          rpm.catalog[catalogEntry.filePath].dependN := length(catalogEntry.dependencies);
          for dependency range catalogEntry.dependencies do
            dependencyIndex := findDependency(rpm.required, dependency);
            if dependencyIndex <> 0 then
              dependsDictEntry := requiresFlag + pred(dependencyIndex);
              rpm.dependsDict &:= dependsDictEntry;
              updateIndexEntry(rpm, RPMTAG_DEPENDSDICT, RPM_INT32_TYPE,
                               length(rpm.dependsDict), dependsDictEntry);
            else
              raise RANGE_ERROR;
            end if;
          end for;
        end if;
      end if;
    end for;
  end func;


const proc: updateHeader (inout rpmArchive: rpm, in rpmCatalogEntry: catalogEntry) is func
  begin
    if rpm.useLongFileSizes then
      updateIndexEntry(rpm, RPMTAG_LONGFILESIZES, RPM_INT64_TYPE,
                       catalogEntry.fileNumber, catalogEntry.fileSize);
    else
      updateIndexEntry(rpm, RPMTAG_FILESIZES, RPM_INT32_TYPE,
                       catalogEntry.fileNumber, catalogEntry.fileSize);
    end if;
    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_FILEDIGESTS, RPM_STRING_ARRAY_TYPE,
                     catalogEntry.fileNumber, catalogEntry.digest);
    updateIndexEntry(rpm, RPMTAG_FILELINKTOS, RPM_STRING_ARRAY_TYPE,
                     catalogEntry.fileNumber, toUtf8(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, catalogEntry.dirIndex);
    updateIndexEntry(rpm, RPMTAG_BASENAMES, RPM_STRING_ARRAY_TYPE,
                     catalogEntry.fileNumber, toUtf8(catalogEntry.baseName));
    updateIndexEntry(rpm, RPMTAG_FILECOLORS, RPM_INT32_TYPE,
                     catalogEntry.fileNumber, catalogEntry.color);
    updateIndexEntry(rpm, RPMTAG_FILECLASS, RPM_INT32_TYPE,
                     catalogEntry.fileNumber, catalogEntry.fileClass);
    if length(rpm.dependsDict) <> 0 then
      updateIndexEntry(rpm, RPMTAG_FILEDEPENDSX, RPM_INT32_TYPE,
                       catalogEntry.fileNumber, catalogEntry.dependX);
      updateIndexEntry(rpm, RPMTAG_FILEDEPENDSN, RPM_INT32_TYPE,
                       catalogEntry.fileNumber, catalogEntry.dependN);
    end if;
  end func;


const proc: updateHeader (inout rpmArchive: rpm) is func
  local
    var integer: fileNumber is 0;
    var rpmCatalogEntry: catalogEntry is rpmCatalogEntry.value;
  begin
    if rpm.useLongFileSizes and RPMTAG_FILESIZES in rpm.header.tagMap then
      for fileNumber range 1 to rpm.header.tagMap[RPMTAG_FILESIZES].count do
        updateIndexEntry(rpm, RPMTAG_LONGFILESIZES, RPM_INT64_TYPE, fileNumber,
                         getInt(rpm, RPMTAG_FILESIZES, fileNumber));
      end for;
      excl(rpm.header.tagMap, RPMTAG_FILESIZES);
    end if;
    for catalogEntry range rpm.catalog do
      if catalogEntry.dirty then
        # writeln("updateHeader " <& catalogEntry.filePath);
        updateHeader(rpm, catalogEntry);
      end if;
    end for;
  end func;


const proc: update (inout rpmArchive: rpm, RPMTAG_SIZE) is func
  local
    var rpmCatalogEntry: catalogEntry is rpmCatalogEntry.value;
    var integer: sumOfRegularFileSizes is 0;
  begin
    for catalogEntry range rpm.catalog do
      case bin32(catalogEntry.mode) & MODE_FILE_TYPE_MASK of
        when {MODE_FILE_REGULAR, MODE_FILE_SYMLINK}:
          sumOfRegularFileSizes +:= catalogEntry.fileSize;
      end case;
    end for;
    if sumOfRegularFileSizes >= 2 ** 32 then
      updateIndexEntry(rpm.header, RPMTAG_LONGSIZE, RPM_INT64_TYPE, sumOfRegularFileSizes);
    else
      updateIndexEntry(rpm.header, RPMTAG_SIZE, RPM_INT32_TYPE, sumOfRegularFileSizes);
    end if;
  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
    if rpm.dirty then
      isDirty := TRUE;
    else
      for catalogEntry range rpm.catalog do
        if catalogEntry.dirty then
          isDirty := TRUE;
        end if;
      end for;
    end if;
  end func;


const proc: fillTagMap (in string: indexData, inout rpmTagMap: tagMap) is func
  local
    var integer: pos is 1;
    var rpmIndexEntry: indexEntry is rpmIndexEntry.value;
  begin
    for pos range 1 to length(indexData) step 16 do
      indexEntry.tag      := bytes2Int(indexData[pos      fixLen 4], UNSIGNED, BE);
      indexEntry.dataType := bytes2Int(indexData[pos +  4 fixLen 4], UNSIGNED, BE);
      indexEntry.offset   := bytes2Int(indexData[pos +  8 fixLen 4], SIGNED,   BE);
      indexEntry.count    := bytes2Int(indexData[pos + 12 fixLen 4], UNSIGNED, BE);
      tagMap @:= [indexEntry.tag] indexEntry;
    end for;
  end func;


const proc: readSection (inout file: rpmFile, inout rpmSection: section) is func
  begin
    readHeader(rpmFile, section.head);
    if section.head.magic = RPM_HEADER_MAGIC then
      # show(section.head);
      section.indexData := gets(rpmFile, section.head.indexCount * RPM_INDEX_ENTRY_SIZE);
      fillTagMap(section.indexData, section.tagMap);
      # 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 integer: tag is 0;
  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 tag range sort(keys(section.tagMap)) do
      # show(section.tagMap[tag]);
      sectionStri &:= str(section.tagMap[tag]);
    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 integer: tag is 0;
    var rpmIndexEntry: indexEntry is rpmIndexEntry.value;
    var string: store is "";
  begin
    for tag range sort(keys(section.tagMap)) do
      indexEntry := section.tagMap[tag];
      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}:
          if length(indexEntry.arrayValue) <> 0 then
            store &:= join(indexEntry.arrayValue, "\0;") & "\0;";
          end if;
      end case;
      section.tagMap @:= [indexEntry.tag] indexEntry;
    end for;
    section.store := store;
  end func;


const func boolean: checkHeaderDigest (inout rpmArchive: rpm) is func
  result
    var boolean: okay is TRUE;
  local
    var string: referenceDigest is "";
    var string: computedDigest is "";
  begin
    if RPMSIGTAG_SHA1 in rpm.signature.tagMap then
      referenceDigest := getStriValue(rpm.signature.store, rpm.signature.tagMap[RPMSIGTAG_SHA1]);
      computedDigest := hex(sha1(str(rpm.header.head) & rpm.header.indexData & rpm.header.store));
      if referenceDigest <> computedDigest then
        okay := FALSE;
        # writeln("reference sha1 digest: " <& literal(referenceDigest));
        # writeln("computed sha1 digest:  " <& literal(computedDigest));
      end if;
    end if;
    if RPMSIGTAG_SHA256 in rpm.signature.tagMap then
      referenceDigest := getStriValue(rpm.signature.store, rpm.signature.tagMap[RPMSIGTAG_SHA256]);
      computedDigest := hex(sha256(str(rpm.header.head) & rpm.header.indexData & rpm.header.store));
      if referenceDigest <> computedDigest then
        okay := FALSE;
        # writeln("reference sha256 digest: " <& literal(referenceDigest));
        # writeln("computed sha256 digest:  " <& literal(computedDigest));
      end if;
    end if;
  end func;


const proc: doSettings (inout rpmArchive: rpm) is func
  local
    var rpmIndexEntry: fileDigestAlgo is rpmIndexEntry.value;
    var integer: dependsDictPos is 0;
    var integer: count is 0;
    var integer: index is 0;
  begin
    if RPMTAG_FILEDIGESTALGO in rpm.header.tagMap then
      fileDigestAlgo := rpm.header.tagMap[RPMTAG_FILEDIGESTALGO];
      rpm.fileDigestAlgo := bytes2Int(
          rpm.header.store[succ(fileDigestAlgo.offset) fixLen 4], SIGNED, BE);
      # writeln("fileDigestAlgo: " <& rpm.fileDigestAlgo);
    end if;
    rpm.classDict := getStringArray(rpm, RPMTAG_CLASSDICT);
    if RPMTAG_DEPENDSDICT in rpm.header.tagMap then
      dependsDictPos := succ(rpm.header.tagMap[RPMTAG_DEPENDSDICT].offset);
      count := rpm.header.tagMap[RPMTAG_DEPENDSDICT].count;
      rpm.dependsDict := count times 0;
      for index range 1 to count do
        rpm.dependsDict[index] := bytes2Int(
            rpm.header.store[dependsDictPos fixLen 4], UNSIGNED, BE);
        dependsDictPos +:= 4;
      end for;
    end if;
  end func;


const proc: readDependencies (inout rpmArchive: rpm) is func
  local
    var integer: flagsPos is 0;
    var integer: namePos is 0;
    var integer: versionPos is 0;
    var integer: count is 0;
    var integer: index is 0;
  begin
    if RPMTAG_PROVIDEFLAGS in rpm.header.tagMap and
        RPMTAG_PROVIDENAME in rpm.header.tagMap and
        RPMTAG_PROVIDEVERSION in rpm.header.tagMap then
      flagsPos := succ(rpm.header.tagMap[RPMTAG_PROVIDEFLAGS].offset);
      namePos := succ(rpm.header.tagMap[RPMTAG_PROVIDENAME].offset);
      versionPos := succ(rpm.header.tagMap[RPMTAG_PROVIDEVERSION].offset);
      count := rpm.header.tagMap[RPMTAG_PROVIDEFLAGS].count;
      rpm.provided := count times rpmDependency.value;
      for index range 1 to count do
        rpm.provided[index].flags := bytes2Int(
            rpm.header.store[flagsPos fixLen 4], UNSIGNED, BE);
        flagsPos +:= 4;
        rpm.provided[index].name := getAsciiz(rpm.header.store, namePos);
        rpm.provided[index].version := getAsciiz(rpm.header.store, versionPos);
        # writeln("provided: " <& rpmDependencyFlagsString(rpm.provided[index].flags) <&
        #         " " <& rpm.provided[index].name <& " " <& rpm.provided[index].version);
      end for;
    end if;
    if RPMTAG_REQUIREFLAGS in rpm.header.tagMap and
        RPMTAG_REQUIRENAME in rpm.header.tagMap and
        RPMTAG_REQUIREVERSION in rpm.header.tagMap then
      flagsPos := succ(rpm.header.tagMap[RPMTAG_REQUIREFLAGS].offset);
      namePos := succ(rpm.header.tagMap[RPMTAG_REQUIRENAME].offset);
      versionPos := succ(rpm.header.tagMap[RPMTAG_REQUIREVERSION].offset);
      count := rpm.header.tagMap[RPMTAG_REQUIREFLAGS].count;
      rpm.required := count times rpmDependency.value;
      for index range 1 to count do
        rpm.required[index].flags := bytes2Int(
            rpm.header.store[flagsPos fixLen 4], UNSIGNED, BE);
        flagsPos +:= 4;
        rpm.required[index].name := getAsciiz(rpm.header.store, namePos);
        rpm.required[index].version := getAsciiz(rpm.header.store, versionPos);
        # writeln("required: " <& rpmDependencyFlagsString(rpm.required[index].flags) <&
        #         " " <& rpm.required[index].name <& " " <& rpm.required[index].version);
      end for;
    end if;
  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
    if RPMTAG_PAYLOADDIGEST in rpm.header.tagMap and
        RPMTAG_PAYLOADDIGESTALGO in rpm.header.tagMap then
      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 if;
    if RPMSIGTAG_MD5 in rpm.signature.tagMap then
      if payloadContent = "" then
        seek(rpm.rpmFile, rpm.payloadPos);
        payloadContent := gets(rpm.rpmFile, integer.last);
      end if;
      referenceDigest := getStriValue(rpm.signature.store, rpm.signature.tagMap[RPMSIGTAG_MD5]);
      computedDigest := md5(str(rpm.header.head) & rpm.header.indexData & rpm.header.store & payloadContent);
      if referenceDigest <> computedDigest then
        # writeln("reference md5 digest: " <& literal(referenceDigest));
        # writeln("computed md5 digest:  " <& literal(computedDigest));
        raise FILE_ERROR;
      end if;
    end if;
  end func;


const proc: checkUncompressedDigest (inout rpmArchive: rpm) is func
  local
    var integer: digestPos is 0;
    var string: referenceDigest is "";
    var string: computedDigest is "";
    var integer: digestAlgo is 0;
  begin
    if RPMTAG_PAYLOADDIGESTALT in rpm.header.tagMap and
        RPMTAG_PAYLOADDIGESTALGO in rpm.header.tagMap then
      digestPos := succ(rpm.header.tagMap[RPMTAG_PAYLOADDIGESTALT].offset);
      referenceDigest := getAsciiz(rpm.header.store, digestPos);
      digestAlgo := getIntValue(rpm.header.store, rpm.header.tagMap[RPMTAG_PAYLOADDIGESTALGO]);
      computedDigest := hex(getDigest(gets(rpm.payloadFile, integer.last), digestAlgo));
      seek(rpm.payloadFile, 1);
      if referenceDigest <> computedDigest then
        # writeln("digestAlgo: " <& digestAlgo);
        # writeln("reference digest: " <& literal(referenceDigest));
        # writeln("computed digest:  " <& literal(computedDigest));
        raise FILE_ERROR;
      end if;
    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 rpm.payloadPos = 0 then
      rpm.payloadFile := openStriFile();
    else
      checkPayloadDigest(rpm);
      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 := openGzipFile(rpm.rpmFile, READ);
          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
      # checkUncompressedDigest(rpm);
      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, or
 *          fileSys.value if it could not be opened.
 *)
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
      initLead(rpm.lead);
      rpm.rpmFile := rpmFile;
      rpm.fileDigestAlgo := RPM_DIGESTALGO_SHA256;
      newFileSys := toInterface(rpm);
    else
      seek(rpmFile, 1);
      readLead(rpmFile, rpm.lead);
      if rpm.lead.magic = RPM_LEAD_MAGIC then
        rpm.rpmFile := rpmFile;
        readSection(rpmFile, rpm.signature);
        if rpm.signature.head.magic = RPM_HEADER_MAGIC then
          ignore(gets(rpmFile, 7 - pred(rpm.signature.head.storeSize) mod 8));
          readSection(rpmFile, rpm.header);
          if rpm.header.head.magic = RPM_HEADER_MAGIC then
            # ignore(gets(rpmFile, 7 - pred(rpm.header.head.storeSize) mod 8));
            rpm.payloadPos := tell(rpmFile);
            if checkHeaderDigest(rpm) then
              doSettings(rpm);
              createMinimumOfCatalog(rpm);
              rpm.useLongFileSizes := RPMTAG_FILESIZES not in rpm.header.tagMap;
              readDependencies(rpm);
              newFileSys := toInterface(rpm);
            end if;
          end if;
        end if;
      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, or
 *          fileSys.value if it could not be opened.
 *)
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. The RPM file below stays open.
 *)
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
        # writeln("size payloadFile: " <& length(rpm.payloadFile));
        seek(rpm.payloadFile, 1);
        payload := gzip(gets(rpm.payloadFile, integer.last));
        # writeln("size payload: " <& length(payload));
      end if;

      # writeln("prepare header:");
      assignIndexValues(rpm.header);
      rpm.provided := sort(rpm.provided);
      updateProvisions(rpm);
      rpm.required := sort(rpm.required);
      updateRequirements(rpm);
      updateDependencies(rpm);
      updateHeader(rpm);
      update(rpm, RPMTAG_SIZE);
      # Optional. Might overflow 32-bit signed integer.
      # updateIndexEntry(rpm.header, RPMTAG_ARCHIVESIZE, RPM_INT32_TYPE,
      #                  length(rpm.payloadFile));
      updateIndexEntry(rpm.header, RPMTAG_DIRNAMES, RPM_STRING_ARRAY_TYPE, rpm.dirNames);
      updateIndexEntry(rpm.header, RPMTAG_CLASSDICT, RPM_STRING_ARRAY_TYPE, rpm.classDict);
      updateIndexEntry(rpm.header, RPMTAG_PAYLOADCOMPRESSOR, RPM_STRING_TYPE, "gzip");
      updateIndexEntry(rpm.header, RPMTAG_PAYLOADFLAGS, RPM_STRING_TYPE, "9");
      updateIndexEntry(rpm.header, RPMTAG_PAYLOADFORMAT, RPM_STRING_TYPE, "cpio");
      if rpm.fileDigestAlgo <> RPM_DIGESTALGO_MD5 then
        updateIndexEntry(rpm.header, RPMTAG_FILEDIGESTALGO, RPM_INT32_TYPE, rpm.fileDigestAlgo);
      end if;
      updateIndexEntry(rpm.header, RPMTAG_PAYLOADDIGEST, RPM_STRING_ARRAY_TYPE,
                       [] (hex(getDigest(payload, RPM_DIGESTALGO_SHA256))));
      updateIndexEntry(rpm.header, RPMTAG_PAYLOADDIGESTALGO, RPM_INT32_TYPE, RPM_DIGESTALGO_SHA256);
      seek(rpm.payloadFile, 1);
      updateIndexEntry(rpm.header, RPMTAG_PAYLOADDIGESTALT, RPM_STRING_ARRAY_TYPE,
                       [] (hex(getDigest(gets(rpm.payloadFile, integer.last), RPM_DIGESTALGO_SHA256))));
      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_SHA256, RPM_STRING_TYPE,
                       hex(sha256(headerSection)));
      updateIndexEntry(rpm.signature, RPMSIGTAG_MD5, RPM_BIN_TYPE,
                       md5(headerSection & payload));

      assignIndexValues(rpm.signature);
      updateStore(rpm.signature);
      signatureSection := sectionStri(rpm.signature, RPMSIGTAG_HEADERSIGNATURES);
      # writeln(length(signatureSection));

      # writeln(4505 - length(signatureSection) -
      #    (7 - pred(length(signatureSection)) mod 8) -
      #    RPM_LEAD_SIZE - RPM_INDEX_ENTRY_SIZE - 1);
      updateIndexEntry(rpm.signature, RPMSIGTAG_RESERVEDSPACE, RPM_BIN_TYPE,
                       "\0;" mult 4505 - length(signatureSection) -
                       (7 - pred(length(signatureSection)) mod 8) -
                       RPM_LEAD_SIZE - RPM_INDEX_ENTRY_SIZE - 1);

      rpm.signature.tagMap[RPMSIGTAG_RESERVEDSPACE].offset := length(rpm.signature.store);
      rpm.signature.store &:= rpm.signature.tagMap[RPMSIGTAG_RESERVEDSPACE].striValue;
      signatureSection := sectionStri(rpm.signature, RPMSIGTAG_HEADERSIGNATURES);
      # writeln(length(signatureSection));

      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 := rpmArchive.value;
  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: symlinkCount is MAX_SYMLINK_CHAIN_LENGTH;
    var boolean: isSymlink is TRUE;
  begin
    # writeln("followSymlink: " <& filePath);
    repeat
      if filePath in rpm.catalog then
        catalogEntry := rpm.catalog[filePath];
      elsif implicitDir(rpm.register, 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
          decr(symlinkCount);
          filePath := symlinkDestination(filePath, catalogEntry.linkTo);
        else
          isSymlink := FALSE;
        end if;
      end if;
    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;


const proc: fixRegisterAndCatalog (inout rpmArchive: rpm, in integer: insertPos,
    in integer: numEntries) is func
  local
    var integer: headerPos is 1;
    var string: filePath is "";
  begin
    for key filePath range rpm.register do
      if rpm.register[filePath] >= insertPos then
        rpm.register[filePath] +:= numEntries;
      end if;
    end for;
    for key filePath range rpm.catalog do
      if rpm.catalog[filePath].fileNumber >= insertPos then
        rpm.catalog[filePath].fileNumber +:= numEntries;
      end if;
    end for;
  end func;


const func integer: findNameIndex (in array string: nameList, in string: name) is func
  result
    var integer: nameIndex is 0;
  local
    var integer: index is 0;
  begin
    for key index range nameList until nameIndex <> 0 do
      if nameList[index] = name then
        nameIndex := index;
      end if;
    end for;
  end func;


const func integer: getNameIndex (inout array string: nameList, in string: name) is func
  result
    var integer: nameIndex is 0;
  begin
    nameIndex := findNameIndex(nameList, name);
    if nameIndex = 0 then
      nameList &:= name;
      nameIndex := length(nameList);
    end if;
    decr(nameIndex);
  end func;


const func integer: fileClass (inout array string: nameList, in string: filePath,
    in string: data) is func
  result
    var integer: fileClass is 0;
  local
    var string: description is "";
  begin
    description := magicDescription(getMagic(data));
    if description = "" then
      if endsWith(filePath, ".sd7") then
        description := "Seed7 source";
      elsif endsWith(filePath, ".s7i") then
        description := "Seed7 library";
      end if;
    end if;
    fileClass := getNameIndex(nameList, description);
  end func;


const proc: setDependencies (inout rpmArchive: rpm,
    inout rpmCatalogEntry: catalogEntry, in string: data) is func
  local
    var file: elfFile is STD_NULL;
    var elfData: elf is elfData.value;
    var rpmDependency: dependency is rpmDependency.value;
    var string: name is "";
  begin
    elfFile := openStriFile(data);
    elf := openElf(elfFile);
    if elf.header.magic = ELF_MAGIC then
      dependency.flags := RPMSENSE_FIND_REQUIRES;
      for name range getVerNeeds(elf) do
        dependency.name := name;
        catalogEntry.dependencies &:= dependency;
        addDependency(rpm.required, dependency);
      end for;
      for name range getDynamicNeeds(elf) do
        dependency.name := name <& "()(" <& elf.header.addressSize <& "bit)";
        catalogEntry.dependencies &:= dependency;
        addDependency(rpm.required, dependency);
      end for;
    end if;
  end func;


const proc: setDirIndexAndBaseName (inout rpmArchive: rpm,
    inout rpmCatalogEntry: catalogEntry) is func
  local
    var integer: slashPos is 0;
    var string: dirName is "";
  begin
    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;
    catalogEntry.dirIndex := getNameIndex(rpm.dirNames, dirName);
  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
  return readDir(rpm.register, dirPath);


(**
 *  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(keys(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''.
 *  A return value of ''FILE_ABSENT'' does not imply that a file
 *  with this name can be created, since missing directories and
 *  invalid 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_SYMLINK_CHAIN_LENGTH;
    var boolean: isSymlink is FALSE;
  begin
    # writeln("fileType: " <& filePath);
    if filePath <> "/" and endsWith(filePath, "/") then
      raise RANGE_ERROR;
    elsif filePath = "" then
      aFileType := FILE_DIR;
    else
      repeat
        isSymlink := FALSE;
        if filePath in rpm.catalog then
          catalogEntry := rpm.catalog[filePath];
        elsif implicitDir(rpm.register, 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}:
              isSymlink := TRUE;
              decr(symlinkCount);
              filePath := symlinkDestination(filePath, catalogEntry.linkTo);
            otherwise:                aFileType := FILE_UNKNOWN;
          end case;
        end if;
      until not isSymlink or symlinkCount < 0;
      if isSymlink then
        aFileType := FILE_SYMLINK;
      end if;
    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 invalid 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;
  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.register, 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:                aFileType := FILE_UNKNOWN;
        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: getFileMode (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 mod 8#1000);
    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 ''modificationTime'' 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 integer: mtime is 0;
    var rpmCatalogEntry: catalogEntry is rpmCatalogEntry.value;
  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;


(**
 *  Set the owner 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: 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);
        else
          catalogEntry.uid := 0;
        end if;
        catalogEntry.userName := owner;
        catalogEntry.dirty := TRUE;
        rpm.catalog @:= [catalogEntry.filePath] catalogEntry;
        if rpm.archive = fileSys.value then
          getArchive(rpm);
        end if;
        block
          setOwner(rpm.archive,
                   archiveFilePath(rpm.archive, catalogEntry.filePath),
                   owner);
        exception
          catch RANGE_ERROR: noop;
        end block;
      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;


(**
 *  Set the group 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: 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);
        else
          catalogEntry.gid := 0;
        end if;
        catalogEntry.groupName := group;
        catalogEntry.dirty := TRUE;
        rpm.catalog @:= [catalogEntry.filePath] catalogEntry;
        if rpm.archive = fileSys.value then
          getArchive(rpm);
        end if;
        block
          setGroup(rpm.archive,
                   archiveFilePath(rpm.archive, catalogEntry.filePath),
                   group);
        exception
          catch RANGE_ERROR: noop;
        end block;
      end if;
    end if;
  end func;


(**
 *  Determine the file mode (permissions) of a symbolic link in a RPM archive.
 *  The function only works for symbolic links and does not follow the
 *  symbolic link.
 *  @return the file mode.
 *  @exception RANGE_ERROR ''filePath'' does not use the standard path
 *             representation.
 *  @exception FILE_ERROR The file described with ''filePath'' is not
 *             present in the RPM archive, or it is not a symbolic link.
 *)
const func fileMode: getFileMode (inout rpmArchive: rpm, in string: filePath, SYMLINK) is func
  result
    var fileMode: mode is fileMode.value;
  local
    var rpmCatalogEntry: catalogEntry is rpmCatalogEntry.value;
  begin
    # writeln("getFileMode: " <& filePath);
    if filePath <> "/" and endsWith(filePath, "/") then
      raise RANGE_ERROR;
    elsif filePath = "" then
      raise FILE_ERROR;
    else
      if 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
        mode := fileMode(catalogEntry.mode mod 8#1000);
      else
        raise FILE_ERROR;
      end if;
    end if;
  end func;


(**
 *  Determine the modification time of a symbolic link in a RPM archive.
 *  The function only works for symbolic links and does not follow the
 *  symbolic link.
 *  @return the modification time of the symbolic link.
 *  @exception RANGE_ERROR ''filePath'' does not use the standard path
 *             representation.
 *  @exception FILE_ERROR The file described with ''filePath'' is not
 *             present in the RPM archive, or it is not a symbolic link.
 *)
const func time: getMTime (inout rpmArchive: rpm, in string: filePath, SYMLINK) is func
  result
    var time: modificationTime is time.value;
  local
    var rpmCatalogEntry: catalogEntry is rpmCatalogEntry.value;
  begin
    # writeln("getMTime: " <& filePath);
    if filePath <> "/" and endsWith(filePath, "/") then
      raise RANGE_ERROR;
    elsif filePath = "" then
      raise FILE_ERROR;
    else
      if 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
        modificationTime := timestamp1970ToTime(catalogEntry.mtime);
      else
        raise FILE_ERROR;
      end if;
    end if;
  end func;


(**
 *  Set the modification time of a symbolic link in a RPM archive.
 *  The function only works for symbolic links and does not follow the
 *  symbolic link.
 *  @exception RANGE_ERROR ''filePath'' does not use the standard path
 *             representation.
 *  @exception RANGE_ERROR ''modificationTime'' is invalid or it cannot be
 *             converted to the system file time.
 *  @exception FILE_ERROR The file described with ''filePath'' is not
 *             present in the RPM archive, or it is not a symbolic link.
 *)
const proc: setMTime (inout rpmArchive: rpm, in string: filePath,
    in time: modificationTime, SYMLINK) is func
  local
    var integer: mtime is 0;
    var rpmCatalogEntry: catalogEntry is rpmCatalogEntry.value;
  begin
    mtime := timestamp1970(modificationTime);
    if mtime < 0 or mtime >= 2 ** 31 or
        (filePath <> "/" and endsWith(filePath, "/")) then
      raise RANGE_ERROR;
    elsif filePath = "" then
      raise FILE_ERROR;
    else
      if 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
        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, SYMLINK);
      else
        raise FILE_ERROR;
      end if;
    end if;
  end func;


(**
 *  Determine the name of the owner (UID) of a symbolic link in a RPM archive.
 *  The function only works for symbolic links and does not follow the
 *  symbolic link.
 *  @return the name of the file owner.
 *  @exception RANGE_ERROR ''filePath'' does not use the standard path
 *             representation.
 *  @exception FILE_ERROR The file described with ''filePath'' is not
 *             present in the RPM archive, or it is not a symbolic link.
 *)
const func string: getOwner (inout rpmArchive: rpm, in string: filePath, SYMLINK) is func
  result
    var string: owner is "";
  local
    var rpmCatalogEntry: catalogEntry is rpmCatalogEntry.value;
  begin
    # writeln("getOwner: " <& filePath);
    if filePath <> "/" and endsWith(filePath, "/") then
      raise RANGE_ERROR;
    elsif filePath = "" then
      raise FILE_ERROR;
    else
      if 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
        if catalogEntry.userName <> "" then
          owner := catalogEntry.userName;
        else
          owner := str(catalogEntry.uid);
        end if;
      else
        raise FILE_ERROR;
      end if;
    end if;
  end func;


(**
 *  Set the owner of a symbolic link in a RPM archive.
 *  The function only works for symbolic links and does not follow the
 *  symbolic link.
 *  @exception RANGE_ERROR ''filePath'' does not use the standard path
 *             representation.
 *  @exception FILE_ERROR The file described with ''filePath'' is not
 *             present in the RPM archive, or it is not a symbolic link.
 *)
const proc: setOwner (inout rpmArchive: rpm, in string: filePath,
    in string: owner, SYMLINK) is func
  local
    var rpmCatalogEntry: catalogEntry is rpmCatalogEntry.value;
    var integer: mtime is 0;
  begin
    if filePath <> "/" and endsWith(filePath, "/") then
      raise RANGE_ERROR;
    elsif filePath = "" then
      raise FILE_ERROR;
    else
      if 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
        if isDigitString(owner) then
          catalogEntry.uid := integer(owner);
        else
          catalogEntry.uid := 0;
        end if;
        catalogEntry.userName := owner;
        catalogEntry.dirty := TRUE;
        rpm.catalog @:= [catalogEntry.filePath] catalogEntry;
        if rpm.archive = fileSys.value then
          getArchive(rpm);
        end if;
        block
          setOwner(rpm.archive,
                   archiveFilePath(rpm.archive, catalogEntry.filePath),
                   owner, SYMLINK);
        exception
          catch RANGE_ERROR: noop;
        end block;
      else
        raise FILE_ERROR;
      end if;
    end if;
  end func;


(**
 *  Determine the name of the group (GID) of a symbolic link in a RPM archive.
 *  The function only works for symbolic links and does not follow the
 *  symbolic link.
 *  @return the name of the file group.
 *  @exception RANGE_ERROR ''filePath'' does not use the standard path
 *             representation.
 *  @exception FILE_ERROR The file described with ''filePath'' is not
 *             present in the RPM archive, or it is not a symbolic link.
 *)
const func string: getGroup (inout rpmArchive: rpm, in string: filePath, SYMLINK) is func
  result
    var string: group is "";
  local
    var rpmCatalogEntry: catalogEntry is rpmCatalogEntry.value;
  begin
    # writeln("getGroup: " <& filePath);
    if filePath <> "/" and endsWith(filePath, "/") then
      raise RANGE_ERROR;
    elsif filePath = "" then
      raise FILE_ERROR;
    else
      if 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
        if catalogEntry.groupName <> "" then
          group := catalogEntry.groupName;
        else
          group := str(catalogEntry.gid);
        end if;
      else
        raise FILE_ERROR;
      end if;
    end if;
  end func;


(**
 *  Set the group of a symbolic link in a RPM archive.
 *  The function only works for symbolic links and does not follow the
 *  symbolic link.
 *  @exception RANGE_ERROR ''filePath'' does not use the standard path
 *             representation.
 *  @exception FILE_ERROR The file described with ''filePath'' is not
 *             present in the RPM archive, or it is not a symbolic link.
 *)
const proc: setGroup (inout rpmArchive: rpm, in string: filePath,
    in string: group, SYMLINK) is func
  local
    var rpmCatalogEntry: catalogEntry is rpmCatalogEntry.value;
    var integer: mtime is 0;
  begin
    if filePath <> "/" and endsWith(filePath, "/") then
      raise RANGE_ERROR;
    elsif filePath = "" then
      raise FILE_ERROR;
    else
      if 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
        if isDigitString(group) then
          catalogEntry.gid := integer(group);
        else
          catalogEntry.gid := 0;
        end if;
        catalogEntry.groupName := group;
        catalogEntry.dirty := TRUE;
        rpm.catalog @:= [catalogEntry.filePath] catalogEntry;
        if rpm.archive = fileSys.value then
          getArchive(rpm);
        end if;
        block
          setGroup(rpm.archive,
                   archiveFilePath(rpm.archive, catalogEntry.filePath),
                   group, SYMLINK);
        exception
          catch RANGE_ERROR: noop;
        end block;
      else
        raise FILE_ERROR;
      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;


(**
 *  Create a symbolic link in a RPM archive.
 *  The symbolic link ''symlinkPath'' will refer to ''targetPath'' afterwards.
 *  The function does not follow symbolic links.
 *  @param rpm Open RPM archive.
 *  @param symlinkPath Name of the symbolic link to be created.
 *  @param targetPath String to be contained in the symbolic link.
 *  @exception RANGE_ERROR ''targetPath'' or ''symlinkPath'' does not use the
 *             standard path representation.
 *  @exception FILE_ERROR A system function returns an error.
 *)
const proc: makeLink (inout rpmArchive: rpm, in string: symlinkPath,
    in string: targetPath) is func
  local
    var rpmCatalogEntry: catalogEntry is rpmCatalogEntry.value;
    var integer: length is 0;
  begin
    # writeln("makeLink: " <& literal(symlinkPath) <& " " <& literal(targetPath));
    if symlinkPath <> "/" and endsWith(symlinkPath, "/") then
      raise RANGE_ERROR;
    elsif symlinkPath = "" or symlinkPath in rpm.catalog or
        implicitDir(rpm.register, symlinkPath) then
      raise FILE_ERROR;
    else
      catalogEntry.fileSize := length(toUtf8(targetPath));
      catalogEntry.mode := ord(MODE_FILE_SYMLINK) + 8#777;
      catalogEntry.uid := 0;
      catalogEntry.gid := 0;
      catalogEntry.rdev := 0;
      catalogEntry.mtime := timestamp1970(time(NOW));
      catalogEntry.digest := "";
      catalogEntry.linkTo := targetPath;
      catalogEntry.flags := 0;
      catalogEntry.userName := "";
      catalogEntry.groupName := "";
      catalogEntry.verifyFlags := RPM_ALL_VERIFY_FLAGS;
      catalogEntry.device := 1;
      incr(rpm.maximumInode);
      catalogEntry.inode := rpm.maximumInode;
      catalogEntry.lang := "";
      catalogEntry.color := 0;
      catalogEntry.fileClass := getNameIndex(rpm.classDict, "symbolic link");
      catalogEntry.dependX := 0;
      catalogEntry.dependN := 0;
      catalogEntry.allDataPresent := TRUE;
      catalogEntry.filePath := symlinkPath;
      catalogEntry.fileNumber := succ(length(rpm.register));
      catalogEntry.dirty := TRUE;
      setDirIndexAndBaseName(rpm, catalogEntry);
      rpm.catalog @:= [symlinkPath] catalogEntry;
      rpm.register @:= [symlinkPath] catalogEntry.fileNumber;
      makeLink(rpm.archive, archiveFilePath(rpm.archive, symlinkPath),
                            archiveFilePath(rpm.archive, targetPath));
    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));
          if startsWith(data, "\127;ELF\1;") then
            catalogEntry.color := 1;  # 32bit ELF
            setDependencies(rpm, catalogEntry, data);
          elsif startsWith(data, "\127;ELF\2;") then
            catalogEntry.color := 2;  # 64bit ELF
            setDependencies(rpm, catalogEntry, data);
          else
            catalogEntry.color := 0;
          end if;
          catalogEntry.fileClass := fileClass(rpm.classDict, filePath, data);
          catalogEntry.dirty := TRUE;
          if length(data) >= 2 ** 32 then
            rpm.useLongFileSizes := TRUE;
          end if;
          rpm.catalog @:= [catalogEntry.filePath] catalogEntry;
          putFile(rpm.archive, archiveFilePath(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 := 0;
        catalogEntry.gid := 0;
        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 := RPM_ALL_VERIFY_FLAGS;
        catalogEntry.device := 1;
        incr(rpm.maximumInode);
        catalogEntry.inode := rpm.maximumInode;
        catalogEntry.lang := "";
        if startsWith(data, "\127;ELF\1;") then
          catalogEntry.color := 1;  # 32bit ELF
          setDependencies(rpm, catalogEntry, data);
        elsif startsWith(data, "\127;ELF\2;") then
          catalogEntry.color := 2;  # 64bit ELF
          setDependencies(rpm, catalogEntry, data);
        else
          catalogEntry.color := 0;
        end if;
        catalogEntry.fileClass := fileClass(rpm.classDict, filePath, data);
        catalogEntry.dependX := 0;
        catalogEntry.dependN := 0;
        catalogEntry.allDataPresent := TRUE;
        catalogEntry.filePath := missingPath;
        catalogEntry.fileNumber := succ(length(rpm.register));
        catalogEntry.dirty := TRUE;
        if length(data) >= 2 ** 32 then
          rpm.useLongFileSizes := TRUE;
        end if;
        setDirIndexAndBaseName(rpm, catalogEntry);
        rpm.catalog @:= [missingPath] catalogEntry;
        rpm.register @:= [missingPath] catalogEntry.fileNumber;
        putFile(rpm.archive, archiveFilePath(rpm.archive, missingPath), data);
      end if;
    end if;
  end func;


(**
 *  Create a new directory in a RPM archive.
 *  The function does not follow symbolic links.
 *  @param rpm Open RPM archive.
 *  @param dirPath Name of the directory to be created.
 *  @exception RANGE_ERROR ''dirPath'' does not use the standard path
 *             representation.
 *  @exception FILE_ERROR The file ''dirPath'' already exists.
 *)
const proc: makeDir (inout rpmArchive: rpm, in string: dirPath) is func
  local
    var rpmCatalogEntry: catalogEntry is rpmCatalogEntry.value;
    var boolean: fileExists is TRUE;
    var integer: fileNumber is 0;
    var integer: length is 0;
  begin
    if dirPath = "" or dirPath <> "/" and endsWith(dirPath, "/") then
      raise RANGE_ERROR;
    elsif dirPath in rpm.catalog then
      fileNumber := rpm.catalog[dirPath].fileNumber;
    elsif implicitDir(rpm.register, dirPath) then
      fileNumber := addImplicitDir(rpm, dirPath).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 := 0;
      catalogEntry.gid := 0;
      catalogEntry.rdev := 0;
      catalogEntry.mtime := timestamp1970(time(NOW));
      catalogEntry.digest := "";
      catalogEntry.linkTo := "";
      catalogEntry.flags := 0;
      catalogEntry.userName := "";
      catalogEntry.groupName := "";
      catalogEntry.verifyFlags := RPM_ALL_VERIFY_FLAGS;
      catalogEntry.device := 1;
      incr(rpm.maximumInode);
      catalogEntry.inode := rpm.maximumInode;
      catalogEntry.lang := "";
      catalogEntry.color := 0;
      catalogEntry.fileClass := getNameIndex(rpm.classDict, "directory");
      catalogEntry.dependX := 0;
      catalogEntry.dependN := 0;
      catalogEntry.allDataPresent := TRUE;
      catalogEntry.filePath := dirPath;
      catalogEntry.fileNumber := succ(length(rpm.register));
      catalogEntry.dirty := TRUE;
      setDirIndexAndBaseName(rpm, catalogEntry);
      rpm.catalog @:= [dirPath] catalogEntry;
      rpm.register @:= [dirPath] catalogEntry.fileNumber;
      makeDir(rpm.archive, archiveFilePath(rpm.archive, dirPath));
    end if;
  end func;


(**
 *  Remove any file except non-empty directories from a RPM archive.
 *  The function does not follow symbolic links. An attempt to remove a
 *  directory that is not empty triggers FILE_ERROR.
 *  @param rpm Open RPM archive.
 *  @param filePath Name of the file to be removed.
 *  @exception RANGE_ERROR ''filePath'' does not use the standard path
 *             representation.
 *  @exception FILE_ERROR The file does not exist or it is a directory
 *             that is not empty.
 *)
const proc: removeFile (inout rpmArchive: rpm, in string: filePath) is func
  local
    var rpmCatalogEntry: catalogEntry is rpmCatalogEntry.value;
    var boolean: fileExists is TRUE;
  begin
    # writeln("removeFile(" <& literal(filePath) <& ")");
    if filePath <> "/" and endsWith(filePath, "/") then
      raise RANGE_ERROR;
    elsif filePath in rpm.catalog then
      catalogEntry := rpm.catalog[filePath];
    elsif implicitDir(rpm.register, filePath) then
      catalogEntry := addImplicitDir(rpm, filePath);
    else
      fileExists := FALSE;
    end if;
    if fileExists and
        (bin32(catalogEntry.mode) & MODE_FILE_TYPE_MASK = MODE_FILE_REGULAR or
         bin32(catalogEntry.mode) & MODE_FILE_TYPE_MASK = MODE_FILE_CHAR or
         bin32(catalogEntry.mode) & MODE_FILE_TYPE_MASK = MODE_FILE_BLOCK or
         bin32(catalogEntry.mode) & MODE_FILE_TYPE_MASK = MODE_FILE_FIFO or
         bin32(catalogEntry.mode) & MODE_FILE_TYPE_MASK = MODE_FILE_SYMLINK or
         bin32(catalogEntry.mode) & MODE_FILE_TYPE_MASK = MODE_FILE_SOCKET or
         (bin32(catalogEntry.mode) & MODE_FILE_TYPE_MASK = MODE_FILE_DIR and
          isEmptyDir(rpm.register, filePath))) then
      if rpm.archive = fileSys.value then
        getArchive(rpm);
      end if;
      excl(rpm.register, filePath);
      excl(rpm.catalog, filePath);
      fixRegisterAndCatalog(rpm, catalogEntry.fileNumber, -1);
      removeFile(rpm.archive, archiveFilePath(rpm.archive, filePath));
      rpm.dirty := TRUE;
    else
      raise FILE_ERROR;
    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 key 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, archiveFilePath(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);