home *** CD-ROM | disk | FTP | other *** search
/ Chip 2007 January, February, March & April / Chip-Cover-CD-2007-02.iso / boot / i386 / rescue / usr / lib / rpm / rpmdiff < prev    next >
Text File  |  2006-11-29  |  24KB  |  904 lines

  1. #!/usr/bin/perl
  2.  
  3. # RPM (and it's source code) is covered under two separate licenses. 
  4.  
  5. # The entire code base may be distributed under the terms of the GNU
  6. # General Public License (GPL), which appears immediately below.
  7. # Alternatively, all of the source code in the lib subdirectory of the
  8. # RPM source code distribution as well as any code derived from that
  9. # code may instead be distributed under the GNU Library General Public
  10. # License (LGPL), at the choice of the distributor. The complete text
  11. # of the LGPL appears at the bottom of this file.
  12.  
  13. # This alternatively is allowed to enable applications to be linked
  14. # against the RPM library (commonly called librpm) without forcing
  15. # such applications to be distributed under the GPL.
  16.  
  17. # Any questions regarding the licensing of RPM should be addressed to
  18. # Erik Troan <ewt@redhat.com>.
  19.  
  20.  
  21. # rpmdiff - a program for comparing two rpm files for differences.
  22. # Written by Ken Estes, Mail.com.
  23.  
  24. use Getopt::Long;
  25.  
  26. # much of this code comes from reading the book 
  27. #  "Maximum RPM" by Edward C. Bailey
  28.  
  29.  
  30. sub usage {
  31.   
  32.     my $args = "[--".join("] [--", @ARGS)."]";
  33.     my @default_args = map $RPMTAG->{$_}->{'arg'}, @DEFAULT_CMP;
  34.     my $default_args = "--".join(" --", @default_args)."";
  35.  
  36.     my $usage =<<EOF;
  37.  
  38. $0  [--version]  [--help]  [--cmpmode]  [--all] 
  39.     $args
  40.     old_rpm_file  new_rpm_file
  41.  
  42.  
  43. Arguments
  44.  
  45.  
  46. --version    Print version information for this program
  47.  
  48. --help        Show this usage page
  49.  
  50. --cmpmode    Do not send any information to stdout, instead
  51.         exit with zero if the rpm files are identical and 
  52.         exit with 1 if there are differences.
  53.  
  54. --all        Ensure that all possible comparisons will be performed.
  55.         This argument should not be used with any CMP arguments.
  56.  
  57.  
  58. CMP Arguments
  59.  
  60.  
  61. Many of the options are designed to select which comparisons the user
  62. is interested in so that spurious differences can be ignored.  If no
  63. CMP arguments are chosen then the default arguments are picked.
  64.  
  65. The CMP arguments are:
  66.     $args
  67.  
  68. The default arguments are:
  69.     $default_args
  70.  
  71. There are two methods of picking which comparisions will be performed.
  72. Any differences between the two files which are not in the list of
  73. performed comparisons will be ignored.
  74.  
  75. First arguments may be specified on the command line preceeded with a
  76. '--' as in '--md5' these arguments specify which comparisons will be
  77. performed. If a comparison is not mentioned on the command line it will
  78. not be performed.
  79.  
  80. Second arguments may be specified on the command line preceeded with a
  81. '--no' as in '--nomd5' these arguments specify which comparisons will
  82. not be performed. If a comparison is not mentioned on the command line
  83. it will be prefomed.
  84.  
  85. You can not mix the two types of arguments.
  86.  
  87.  
  88. Synopsis
  89.  
  90.  
  91. This script will compare old_rpm_file and new_rpm_file and print to
  92. standard out the differences between the two files.  The output is
  93. designed to help isolate the changes which will occur when upgrading
  94. an installed package.  The output looks like the output of 'rpm -V'.
  95. It is assumed that you have installed oldpackage and are thinking of
  96. upgrading to newpackage.  The output is as if you ran 'rpm -Va' after
  97. installing the new package with cpio so that the rpm database was not
  98. up todate.  Thus 'rpm -Va' will pick up the differences between the
  99. two pacakges.
  100.  
  101.  
  102. Additionally the RPM scripts (prein, postin, triggers, verify, etc)
  103. are compared and their results are displayed as if the scripts ended
  104. up as files in the filesystem.
  105.  
  106. Exit Code
  107.  
  108.  
  109. The if not run in cmpmode the program exists with the number of files
  110. which are different between the two packages, the exitcode will get no
  111. bigger than $MAX_EXIT.
  112.  
  113. If run in cmpmode then the program will exit with zero if the rpm
  114. files are identical and exit with 1 if there are differences.
  115.  
  116.  
  117. BUGS
  118.  
  119.  
  120. This program only parses the RPM file headers not the cpio payload
  121. inside the RPM file.  In the rare case where an tag is defined in one
  122. RPM and not in another we charitably assume that the RPMs match on
  123. this tag.  An example of this may be file uid/gid\'s.  Currently rpm
  124. does not store this information in the header, it appears only in the
  125. cpio payload.  If you were to compare two rpm files and one of them
  126. does not have the uid/gid\'s in the header then no difference in
  127. uid/gid will ever appear in the output regardless of what the RPMs
  128. actually contain.
  129.  
  130. The program only checks differences between files and scripts, any
  131. changes to dependencies, prefixes, or spec file header information are
  132. not checked.
  133.  
  134. There is no method provided to check changes in a files flags, this
  135. includes changes to the files documentation, MISSINGOK, NOREPLACE, or
  136. configuration status.
  137.  
  138.  
  139. Output Format
  140.  
  141.  
  142. The differences are sent to stdout.  There is one line for each file
  143. which is different.  The differences are encoded in a string using the
  144. following letters to represent the differerences and the following
  145. order to encode the information:
  146.  
  147.     S is the file size
  148.  
  149.     M is the file\'s mode
  150.  
  151.     5 is the MD5 checksum of the file
  152.  
  153.     D is the files major and monor numbers
  154.  
  155.     L is the files symbolic link contents.
  156.  
  157.     U is the owner of the file
  158.  
  159.     G is the file\'s group
  160.  
  161.     T is the modification time of the file
  162.  
  163.     added indicates the file was added to the old version
  164.  
  165.     missing indicates the file was deleted from the old version
  166.  
  167. Any attributes which match are denoted with a '.'. 
  168.  
  169.  
  170. Output Example
  171.  
  172.  
  173. S.5.....   PREIN
  174. .....UG.   /bin/echo
  175. ..5....T   /usr/bin/grep
  176. S.5....T   /etc/info-dir
  177. missing    /usr/doc/dhcpcd-0.70/README
  178. .M......   /usr/lib/libpanel.so.4
  179. added      /usr/lib/libmenu.so.4
  180. SM5....T   /usr/info/dir
  181.  
  182.  
  183. Usage Example
  184.  
  185.  
  186. $0 --help
  187. $0 --version
  188.  
  189. $0 java-jdk-1.1.7-2.rpm java-jdk-1.1.7-3.rpm
  190. $0 --md5 java-jdk-1.1.7-2.rpm java-jdk-1.1.7-3.rpm
  191. $0 --nomd5 java-jdk-1.1.7-2.rpm java-jdk-1.1.7-3.rpm
  192. $0 --md5 --link --mtime java-jdk-1.1.7-2.rpm java-jdk-1.1.7-3.rpm
  193. $0 --all java-jdk-1.1.7-2.rpm java-jdk-1.1.7-3.rpm
  194. $0 --cmpmode java-jdk-1.1.7-2.rpm java-jdk-1.1.7-3.rpm
  195. $0 --cmpmode --md5 java-jdk-1.1.7-2.rpm java-jdk-1.1.7-3.rpm
  196.  
  197.  
  198. EOF
  199.  
  200.     print $usage;
  201.     exit 0;
  202.  
  203. }
  204.  
  205.  
  206.  
  207. sub set_static_vars {
  208.  
  209. # This functions sets all the static variables which are often
  210. # configuration parameters.  Since it only sets variables to static
  211. # quantites it can not fail at run time. Some of these variables are
  212. # adjusted by parse_args() but asside from that none of these
  213. # variables are ever written to. All global variables are defined here
  214. # so we have a list of them and a comment of what they are for.
  215.   
  216.  
  217. # The numerical indicies to the hash comes from ~rpm/lib/rpmlib.h
  218. # the index is the number of the tag as it appears in the rpmfile.
  219.  
  220. # tag_name: the name of the tag as it appears in rpmlib.h.
  221.  
  222. # all entries have a tag_name if an entry has one of the next three
  223. # fields it must have the rest.  If a tag does not have the next three
  224. # fields we we will still gather data for it. This is for future
  225. # functionality as these fields look important, and for debugging.
  226.  
  227.  
  228. # diff_order: if the tag is used in the different output then this is
  229. #         the order of the character differences.
  230.  
  231. # diff_char: if the tag is used in the different output then this is
  232. #         the character which will appear when there is a 
  233. #        difference.
  234.  
  235. # arg: the name of the command line option to specify whether this tag
  236. #         is used in the difference or not
  237.  
  238. # The scripts contained in the rpm (preinstall, postinstall) do not
  239. # have the comparison information that files have.  Some of the
  240. # comparisons (md5, size) can be performed on scripts, using regular
  241. # perl functions, others (uid, modes, link) can not.  We use
  242. # script_cmp to link the perl comparison function to the comparison
  243. # arguments and to the diff_char.
  244.  
  245. # script_cmp: the perl comparion function to perform if this
  246. # difference can be performed on the rpm scripts
  247.  
  248. # is_script: if defined this indicates that the datatype is a script
  249. # and we can use script_cmp on it.  The data is stored as an array
  250. # containing a single string.
  251.  
  252.  
  253. # note: that although we may need to denote differences in the flags
  254. # this table may not be appropriate for that task.
  255.  
  256.  
  257. # The data from the RPM files is stored in $RPM0, $RPM1 they are HoL.
  258. # The hash is indexed with the same index as appears in $RPMTAG, the
  259. # other data is what ever comes out of the file.  Most of the data we
  260. # want is stored as arrays of values (occasionally it is a scalar
  261. # value).  We keep a hash to allow us to find the index number to use
  262. # in all arrays given the filename.  Some information like the package
  263. # name and the posinstall script are stored, in the hash table as an
  264. # array which contains a single string.
  265.  
  266.  
  267.  
  268.  
  269.   $RPMTAG = {
  270.          1000 => {
  271.               'tag_name' => 'NAME',
  272.              },
  273.          1001 => {
  274.               'tag_name' => 'VERSION',
  275.              },
  276.          1002 => {
  277.               'tag_name' => 'RELEASE',
  278.              },
  279.          1006 => {
  280.               'tag_name' => 'BUILDTIME',
  281.              },
  282.          1027 => {
  283.              'tag_name' => 'OLDFILENAMES',
  284.              },
  285.          1028 => {
  286.              'tag_name' => 'FILESIZES',
  287.               'diff_order' => 0,
  288.               'diff_char' => 'S',
  289.               'arg' => 'size',
  290.               'script_cmp' => sub { return (length($_[0]) ne 
  291.                             length($_[1])); },
  292.              },
  293.          1029 => {
  294.               'tag_name' => 'FILESTATES',
  295.              },
  296.          1030 => {
  297.               'tag_name' => 'FILEMODES',
  298.               'diff_order' => 1,
  299.               'diff_char' => 'M',
  300.               'arg' => 'mode',
  301.              },
  302.          1033 => {
  303.               'tag_name' => 'FILERDEVS',
  304.               'diff_order' => 3,
  305.               'diff_char' => 'D',
  306.               'arg' => 'dev',
  307.              },
  308.          1034 => {
  309.               'tag_name' => 'FILEMTIMES',
  310.               'diff_order' => 7,
  311.               'diff_char' => 'T',
  312.               'arg' => 'mtime',
  313.              },
  314.          1035 => {
  315.               'tag_name' => 'FILEMD5S',
  316.               'diff_order' => 2,
  317.               'diff_char' => '5',
  318.               'arg' => 'md5',
  319.               'script_cmp' => sub{ return ($_[0] ne 
  320.                                $_[1]); },
  321.              },
  322.         1036 => {
  323.              'tag_name' => 'FILELINKTOS',
  324.              'diff_order' => 4,
  325.              'diff_char' => 'L',
  326.              'arg' => 'link',
  327.             },
  328.          1037 => {
  329.               'tag_name' => 'FILEFLAGS',
  330.              },
  331.          1038 => {
  332.               'tag_name' => 'ROOT',
  333.              },
  334.          1039 => {
  335.               'tag_name' => 'FILEUSERNAME',
  336.               'diff_order' => 5,
  337.               'diff_char' => 'U',
  338.               'arg' => 'user',
  339.              },
  340.          1040 => {
  341.               'tag_name' => 'FILEGROUPNAME',
  342.               'diff_order' => 6,
  343.               'diff_char' => 'G',
  344.               'arg' => 'group',
  345.              },
  346.          1098 => {
  347.               'tag_name' => 'PREFIXES',
  348.              },
  349.          1099 => {
  350.               'tag_name' => 'INSTPREFIXES',
  351.              },
  352.  
  353.          #  support for differences of scripts
  354.          
  355.          1023 => {
  356.               'tag_name' => 'PREIN',
  357.               'is_script' => 1,
  358.              },
  359.          1024 => {                
  360.               'tag_name' => 'POSTIN',
  361.               'is_script' => 1,
  362.              },
  363.          1025 => {                
  364.               'tag_name' => 'PREUN',
  365.               'is_script' => 1,
  366.              },
  367.          1026 => {                
  368.               'tag_name' => 'POSTUN',
  369.               'is_script' => 1,
  370.              },
  371.          1079 => {                
  372.               'tag_name' => 'VERIFYSCRIPT',
  373.               'is_script' => 1,
  374.              },
  375.          1065 => {                
  376.               'tag_name' => 'TRIGGERSCRIPTS',
  377.               'is_script' => 1,
  378.              },
  379.          1091 => {                
  380.               'tag_name' => 'VERIFYSCRIPTPROG',
  381.               'is_script' => 1,
  382.              },
  383.          1092 => {                
  384.               'tag_name' => 'TRIGGERSCRIPTPROG',
  385.                'is_script' => 1,
  386.              },
  387.          
  388.        };
  389.   
  390.   # by default check these options, which are the "contents" of the
  391.   # files.
  392.   
  393.   @DEFAULT_CMP = ( 1028, 1035, 1036, );
  394.   
  395.   
  396.   $RPM_FILE_MAGIC   = chr(0xed).chr(0xab).chr(0xee).chr(0xdb);
  397.   $RPM_HEADER_MAGIC = chr(0x8e).chr(0xad).chr(0xe8);
  398.   
  399.   # we want the second header block, as the first header is the
  400.   # signature block.
  401.  
  402.   $HEADER_BLOCK_NUM = 2;
  403.  
  404.   # number of bytes in the file to skip when looking for the first
  405.   # header. Actually I think the lead is bigger then this like 96, but
  406.   # I am sure this minimum value is correct.
  407.  
  408.   $LEAD_LENGTH = 66;
  409.  
  410.   $HEADER_RECORD_SIZE = 16;
  411.   
  412.   # largest exit code we allow.
  413.  
  414.   $MAX_EXIT = 250;
  415.   
  416.   $NUM_DIFFERENCES = 0;
  417.  
  418.   $RCS_REVISION = ' $Revision: 1.8 $ ';
  419.   
  420.   # set a known path.
  421.  
  422.   $ENV{'PATH'}= (
  423.          '/opt/gnu/bin'.
  424.          ':/usr/local/bin'.
  425.          ':/usr/bin'.
  426.          ':/bin'.
  427.          '');
  428.   
  429.   # taint perl requires we clean up these bad environmental variables.
  430.   
  431.   delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
  432.   
  433.   
  434.   $VERSION = 'NONE';
  435.   if ( $RCS_REVISION =~ m/([.0-9]+)/ ) {
  436.     $VERSION = $1;
  437.   }
  438.   
  439.   return ;
  440. }
  441.  
  442.  
  443. sub parse_args{
  444.  
  445.   my $arg_include = '';
  446.   my $arg_exclude = '';
  447.   my %arg_tags= ();
  448.  
  449.   my @args_with_bang = ();
  450.   my %arg2tag = ();
  451.  
  452.   # find out what arguments are availible and build some
  453.   # data structures to work with them.
  454.  
  455.  foreach $tag (keys %$RPMTAG ) {
  456.    my $arg = $RPMTAG->{$tag}->{'arg'};
  457.    ($arg) || next;
  458.    push @ARGS, $arg;
  459.    push @ALL_CMP_TAGS, $tag;
  460.    push @args_with_bang, "$arg!";
  461.    $arg2tag{$arg}=$tag;
  462.  }
  463.   
  464.   # sort the tags to determine the proper comparison order.
  465.   # use the order stored in the RPMTAG table.
  466.   #  If this code is too confusing, look up
  467.   # 'Schwartzian Transform' in perlfaq4 or an advanced perl book.
  468.   
  469.   @ALL_CMP_TAGS  =     map { $_->[0] }
  470.              sort{ $a->[1] <=> $b->[1] }    
  471.              map { [ $_, $RPMTAG->{$_}->{'diff_order'} ] }
  472.              @ALL_CMP_TAGS;
  473.   
  474.   $FILES_EQ_STRING = '.' x scalar(@ALL_CMP_TAGS);
  475.  
  476.   if( !GetOptions("version", "help", "all", "cmpmode!", @args_with_bang) ) {
  477.     print("Illegal options in \@ARGV: '@ARGV'\n");
  478.     usage() ;
  479.     exit 1 ;
  480.  }
  481.   
  482.  if($opt_version) {
  483.    print "$0: Version: $VERSION\n";
  484.    exit 0;  
  485.  }
  486.  
  487.  if ($opt_help) {
  488.    usage();
  489.  }
  490.  
  491.  if ($opt_all) {
  492.    # all is just an exclude with nothing to exclude
  493.    $arg_exclude = 1;
  494.  }
  495.  
  496.  # process each of the arguments derived from the $RPMTAG hash
  497.  
  498.  foreach $arg (@ARGS) {
  499.    my $arg_var = "opt_$arg";
  500.    if (defined($$arg_var)) {
  501.      $arg_tags{$arg2tag{$arg}} = 1;
  502.      if ($$arg_var) {
  503.        $arg_include = 1;
  504.      } else {
  505.        $arg_exclude = 1;
  506.      }
  507.    }
  508.  }
  509.  
  510.  ($arg_include) && ($arg_exclude) &&
  511.    die("$0: Can not mix both include and exclude arguements ".
  512.        "on the command line.\n");
  513.  
  514.   if ($arg_include) {
  515.     # check only the options listed
  516.     foreach $tag (keys %arg_tags) {
  517.       $CMP_TAGS{$tag} = 1;
  518.     }
  519.   } elsif ($arg_exclude) {
  520.     # check everything but the options listed
  521.     foreach $tag (@ALL_CMP_TAGS) {
  522.       $CMP_TAGS{$tag} = 1;
  523.     }
  524.     foreach $tag (keys %arg_tags) {
  525.       delete $CMP_TAGS{$tag};
  526.     }
  527.   } else {
  528.     # check the default options 
  529.     foreach $tag (@DEFAULT_CMP) {
  530.       $CMP_TAGS{$tag} = 1;
  531.     }
  532.   }
  533.   
  534.   ($#ARGV == 1) ||
  535.     die("$0: Argument list must include two file names\n");
  536.   
  537.   $RPMFILE0 = $ARGV[0];
  538.   $RPMFILE1 = $ARGV[1];
  539.  
  540.   ( !(-f $RPMFILE0) || !(-r $RPMFILE0) ) &&
  541.     die("$0: '$RPMFILE0' is not a readable file\n");
  542.   
  543.   ( !(-f $RPMFILE1) || !(-r $RPMFILE1) ) && 
  544.     die("$0: '$RPMFILE1' is not a readable file\n");
  545.   
  546.   $CMP_MODE = ($opt_cmpmode == 1);
  547.  
  548.   return ;
  549. }
  550.  
  551. # read the rpmfile and extract the header information.
  552.  
  553. sub parse_rpm_headers {
  554.   my ($filename) = @_;
  555.  
  556.   my $file = '';
  557.   my $out = {};
  558.  
  559.   # read whole file into memory
  560.   {
  561.     open (RPMFILE, "<$filename")||
  562.       die("$0: Could not open: $filename for reading. $!\n");
  563.  
  564.     # not needed on unix but lets be very clear
  565.     binmode (RPMFILE);
  566.  
  567.     # slurp whole file
  568.     my $old_irs = $/;
  569.     undef $/;
  570.  
  571.     $file = <RPMFILE>;
  572.     
  573.     $/ = $old_irs;
  574.  
  575.     close(RPMFILE)||
  576.       die("$0: Could not close: $filename. $!\n");
  577.     
  578.     $file =~ m/^$RPM_FILE_MAGIC/ ||
  579.       die("$0: file: $filename is not an RPM file. ".
  580.       "No magic number found.\n");
  581.   }  
  582.  
  583.   # we want the second header block, as the first header is the
  584.   # signature block.
  585.  
  586.   my ($header_start, $store_start) = ($LEAD_LENGTH,0);
  587.   my ($_version, $_reserved, $num_header_entries, $num_store_bytes) = ();
  588.  
  589.   foreach $i (1 .. $HEADER_BLOCK_NUM) {
  590.  
  591.     # find beginning of header,
  592.     $header_start = index($file, $RPM_HEADER_MAGIC, $header_start);
  593.     ($header_start < 0) &&
  594.       die("$0: file: $filename is not an RPM file. ".
  595.       "No: $i, header found.\n");
  596.  
  597.     $header_start += length($RPM_HEADER_MAGIC);
  598.     
  599.     ($_version, $_reserved, $num_header_entries, $num_store_bytes) = 
  600.       unpack("CNNN", substr($file, $header_start, 1+(4*3)));
  601.     $header_start += 1+(4*3);
  602.     
  603.     # find beginning of store
  604.     $store_start = $header_start + 
  605.       ($num_header_entries * $HEADER_RECORD_SIZE);
  606.     
  607.     ( ($store_start + $num_store_bytes) < length($file) ) ||
  608.       die("$0: File Parse Error, file: $filename, ".
  609.       "is not long enough to hold store.\n");
  610.   }
  611.   
  612.   # the header is just a list of information about data.
  613.   # the data is stored in the store futher down the file.
  614.   my $header_position = $header_start;
  615.   foreach $i (0 .. $num_header_entries-1) {
  616.     
  617.     my ($tag, $data_type, $offset, $data_count)  =
  618.       unpack("N4", substr($file, $header_position, $HEADER_RECORD_SIZE));
  619.     $header_position += $HEADER_RECORD_SIZE;
  620.     
  621.     (    
  622.      ( ($tag < 60) || ($tag > 1200) ) ||
  623.      ( ($data_type < 0) || ($data_type > 10) ) ||
  624.      ($offset < 0)
  625.     ) && die("$0: Error parsing header in rpm file: $filename, ".
  626.          "record number: $i.\n");
  627.     
  628.     # we are only interested in the tags which are defined
  629.     $RPMTAG->{$tag} || next;
  630.     
  631.     foreach $j (0 .. $data_count-1) {
  632.       my $value ='';
  633.       if (0) {
  634.     # dummy for aliging the code like a case statement
  635.       } elsif ($data_type == 0) {
  636.     # null
  637.     $value = '';
  638.       } elsif ($data_type == 1) {
  639.     # char
  640.     $value = substr($file, $store_start+$offset, 1);
  641.     $offset += 1;
  642.       } elsif ($data_type == 2) {
  643.     # int8
  644.     $value = ord(substr($file, $store_start+$offset, 1));
  645.     $offset += 1;
  646.       } elsif ($data_type == 3) {
  647.     # int16
  648.     $value = unpack("n", substr($file, $store_start+$offset, 2));
  649.     $offset += 2;
  650.       } elsif ($data_type == 4) {
  651.     # int32
  652.     $value = unpack("N", substr($file, $store_start+$offset, 4));
  653.     $offset += 4;
  654.       } elsif ($data_type == 5) {
  655.     # int64
  656.     # ---- These aren't supported by RPM (yet) */
  657.     die("$0: int64 type found in rpm file: $filename, ".
  658.         "record number: $i.\n");
  659.       } elsif ($data_type == 6) {
  660.     # string
  661.     my $null_position = index ($file, "\0", $store_start+$offset);
  662.     my $length =  $null_position - ($store_start+$offset);
  663.     $value = substr($file, $store_start+$offset, $length);
  664.     $offset += $length;
  665.       } elsif ($data_type == 7) {
  666.     # bin
  667.     # to properly support this I need to move it outside the $j
  668.     # loop.  However I do not need it.
  669.     die("$0: Bin type found in rpm file: $filename, ".
  670.         "record number: $i.\n");
  671.       } elsif ($data_type == 8) {
  672.     # string_array
  673.     my $null_position = index ($file, "\0", $store_start+$offset);
  674.     my $length =  $null_position - ($store_start+$offset);
  675.     $value = substr($file, $store_start+$offset, $length);
  676.     $offset += $length+1
  677.       } elsif ($data_type == 9) {
  678.     # this is listed as both RPM_I18NSTRING_TYPE and RPM_MAX_TYPE
  679.     # in file ~rpm/lib/header.h but I ignore it
  680.     die("$0: I18NSTRING type found in rpm file: $filename, ".
  681.         "record number: $i.\n");
  682.       }
  683.       
  684.       push @{$out->{$tag}}, $value;
  685.       if ($RPMTAG->{$tag}->{"tag_name"} eq 'OLDFILENAMES') {
  686.     $out->{'name2index'}->{$value} = $j;
  687.       }
  688.     } # foreach $j
  689.     
  690.   } # foreach $i
  691.  
  692.   return $out;
  693. }
  694.  
  695.  
  696. # traverse the datastructures to create a text representation of the
  697. # critical differences between rpmscripts.  If we are running in
  698. # cmpmode and a difference is found exit early.
  699.  
  700.  
  701. sub format_script_differences {
  702.   my ($rpm0, $rpm1) = @_;
  703.  
  704.   my $out = '';;
  705.   my %seen = ();
  706.  
  707.   foreach $script ( sort (keys %$RPMTAG) ) {
  708.  
  709.     ($RPMTAG->{$script}->{'is_script'}) || next;
  710.  
  711.     ($rpm0->{$script} || $rpm1->{$script}) || next;
  712.  
  713.     my $prefix='';
  714.     
  715.     if ( ($rpm0->{$script}) && (!($rpm1->{$script})) ) {
  716.       $prefix = 'missing ';
  717.     } elsif ( (!($rpm0->{$script})) && ($rpm1->{$script}) ) {
  718.       $prefix = 'added   ';
  719.     } else {
  720.       my $diff_str = '';
  721.       foreach $cmp_tag (@ALL_CMP_TAGS) {
  722.     if ( !($CMP_TAGS{$cmp_tag}) || 
  723.          !($RPMTAG->{$cmp_tag}->{'script_cmp'}) ){
  724.       $diff_str .= '.';
  725.       next;
  726.     }
  727.     
  728.     # In the rare case where an tag is defined in one RPM and not
  729.     # in another we charitably assume that the RPMs match on this
  730.     # tag.  There is a warning in the stderr anyway.
  731.     
  732.     if (
  733.         ($rpm0->{$cmp_tag}) && 
  734.         ($rpm1->{$cmp_tag}) &&
  735.  
  736.         # use the anonymous comparison function (stored in the
  737.         # table) to compare the two scripts
  738.  
  739.         (&{$RPMTAG->{$cmp_tag}->{'script_cmp'}}
  740.                  ($rpm0->{$script}->[0], $rpm1->{$script}->[0]))
  741.        ) {
  742.       $diff_str .= $RPMTAG->{$cmp_tag}->{'diff_char'};
  743.     } else {
  744.       $diff_str .= '.';
  745.     }
  746.     
  747.       } # foreach $tag
  748.       if ($diff_str ne $FILES_EQ_STRING) {
  749.     $prefix = $diff_str;
  750.       }
  751.     }
  752.     
  753.     ($prefix) || next;
  754.  
  755.     if ($CMP_MODE) {
  756.       exit 1;
  757.     }
  758.  
  759.     ($NUM_DIFFERENCES < $MAX_EXIT) &&
  760.       $NUM_DIFFERENCES++;
  761.  
  762.     $out .= "$prefix   $RPMTAG->{$script}->{'tag_name'}\n";
  763.  
  764.   } # foreach $filename
  765.     
  766.   return $out;
  767. }
  768.  
  769.  
  770.  
  771. # traverse the datastructures to create a text representation of the
  772. # critical differences between file stored in the pacakge.  If we are
  773. # running in cmpmode and a difference is found exit early.
  774.  
  775.  
  776.  
  777. sub format_file_differences {
  778.   my ($rpm0, $rpm1) = @_;
  779.  
  780.   my $out = '';;
  781.   my %seen = ();
  782.  
  783.   foreach $filename ( sort (
  784.                 (keys %{$rpm0->{'name2index'}}), 
  785.                 (keys %{$rpm1->{'name2index'}})
  786.                ) ) {
  787.  
  788.     $seen{$filename} && next;
  789.     $seen{$filename} = 1;
  790.     $index0 = $rpm0->{'name2index'}->{$filename};
  791.     $index1 = $rpm1->{'name2index'}->{$filename};
  792.  
  793.     my $prefix='';
  794.     
  795.     if ( ($index0) && (!($index1)) ) {
  796.       $prefix = 'missing ';
  797.     } elsif ( (!($index0)) && ($index1) ) {
  798.       $prefix = 'added   ';
  799.     } else {
  800.       my $diff_str = '';
  801.       foreach $cmp_tag (@ALL_CMP_TAGS) {
  802.     if (!($CMP_TAGS{$cmp_tag})){
  803.       $diff_str .= '.';
  804.       next;
  805.     }
  806.     
  807.     # In the rare case where an tag is defined in one RPM and not
  808.     # in another we charitably assume that the RPMs match on this
  809.     # tag. There is a warning in the stderr anyway.
  810.     
  811.     if (
  812.         ($rpm0->{$cmp_tag}->[$index0]) && 
  813.         ($rpm1->{$cmp_tag}->[$index1]) &&
  814.         ($rpm0->{$cmp_tag}->[$index0] ne 
  815.         $rpm1->{$cmp_tag}->[$index1])
  816.        ) {
  817.       $diff_str .= $RPMTAG->{$cmp_tag}->{'diff_char'};
  818.     } else {
  819.       $diff_str .= '.';
  820.     }
  821.     
  822.       } # foreach $tag
  823.       if ($diff_str ne $FILES_EQ_STRING) {
  824.     $prefix = $diff_str;
  825.       }
  826.     }
  827.     
  828.     ($prefix) || next;
  829.  
  830.     if ($CMP_MODE) {
  831.       die 1;
  832.     }
  833.  
  834.     ($NUM_DIFFERENCES < $MAX_EXIT) &&
  835.       $NUM_DIFFERENCES++;
  836.  
  837.     # this set of blanks would contain information from the flags, if
  838.     # only I was not so lazy
  839.  
  840.     $out .= "$prefix   $filename\n";
  841.  
  842.   } # foreach $filename
  843.     
  844.   return $out;
  845. }
  846.  
  847. # warn user of a cmp that was requested can not be carried out due to
  848. # lack of data in the header of atleast one file.
  849.  
  850. sub data_missing_warnings {
  851.   my ($rpm0, $rpm1) = @_;
  852.   
  853.   my $out = '';;
  854.   
  855.   foreach $cmp_tag (@ALL_CMP_TAGS) {
  856.     if (!($CMP_TAGS{$cmp_tag})) {
  857.       next;
  858.     }
  859.     
  860.     if ( ($CMP_TAGS{$cmp_tag}) &&
  861.      (!$rpm0->{$cmp_tag}) 
  862.        ){
  863.       $out .= ("Comparison: '$RPMTAG->{$cmp_tag}->{'arg'}' ".
  864.            "specified, but data is not availible in ".
  865.            "rpm: $RPMFILE0.\n");
  866.     }
  867.     if ( ($CMP_TAGS{$cmp_tag}) &&
  868.          (!$rpm1->{$cmp_tag}) 
  869.        ){
  870.       $out .= ("Comparison: '$RPMTAG->{$cmp_tag}->{'arg'}' ".
  871.            "specified, but data is not availible in ".
  872.            "rpm: $RPMFILE1.\n");
  873.     }
  874.   }
  875.   return $out;
  876. }
  877.  
  878.  
  879.  
  880.  
  881. # -------------- main --------------
  882. {
  883.   set_static_vars();
  884.   parse_args();
  885.   $RPM0 = parse_rpm_headers($RPMFILE0);
  886.   $RPM1 = parse_rpm_headers($RPMFILE1);
  887.   
  888.   my $warnings = data_missing_warnings($RPM0, $RPM1);
  889.  
  890.   # we must print warnings before running diff as we may exit early.
  891.  
  892.   ($warnings) &&
  893.     warn($warnings);
  894.   
  895.   my $header = "oldpkg $RPMFILE0\n"."newpkg $RPMFILE1\n"."\n\n";
  896.   my $script_diffs = format_script_differences($RPM0, $RPM1);
  897.   my $file_diffs = format_file_differences($RPM0, $RPM1);
  898.   
  899.   ($script_diffs || $file_diffs) && 
  900.     print $header, $script_diffs, $file_diffs;
  901.   
  902.   exit $NUM_DIFFERENCES;
  903. }
  904.