#!/usr/bin/perl use strict; use warnings; my($app_svnid) = '$HeadURL$ $LastChangedRevision$'; ## no critic (RequireInterpolationOfMetachars) use lib substr(`ade-config ade_share_prefix`,0,-1) . '/include'; ## no critic (ProhibitBacktickOperators) use ADE; use lib substr(`fad-config fad_share_prefix`,0,-1) . '/include'; ## no critic (ProhibitBacktickOperators) use FAD; use Getopt::Long qw(:config no_ignore_case); use Fatal qw( close unlink ); # obviate checking close()'s return code use experimental 'smartmatch'; my(%faddiff_defined_errors) = ( faddiff_err_misc => { fmt => '%s' }, faddiff_err_access => { fmt => '%s: can\'t %s' }, ); # Options my($opt_outfile, $opt_ignowner, $opt_igngroup, $opt_ignperms); sub faddiff ## no critic (ProhibitExcessComplexity) { my($errstack_ref) = @_; my($fad1_file, $fad2_file, $out_handle, $fad1_handle, $fad2_handle); my($rc); my(%data1_store, %data2_store, $count); # Register application-specific errors ADE::register_error_types(\%faddiff_defined_errors); # Defaults for options $opt_outfile = '-'; $opt_ignowner = 0; $opt_igngroup = 0; $opt_ignperms = 0; # Register options if (($rc=ADE::register_options($errstack_ref, 'gumo:s', 'ignore-gid,ignore-uid,ignore-mode,file:s', 'main::faddiff_opt_handler_%s')) != $ADE::OK) { return($rc); } # Register handler functions if (($rc=ADE::set_callbacks($errstack_ref, \&faddiff_usage_help, \&faddiff_version, \&faddiff_paths)) != $ADE::OK) { return($rc); } # Process options ADE::debug($errstack_ref, 10, 'faddiff: processing options ...'); if (($rc=ADE::process_options($errstack_ref)) != $ADE::OK) { return($rc); } # Process arguments ADE::show_bad_usage($errstack_ref) if (not defined $ARGV[1] or defined $ARGV[2]); ADE::debug($errstack_ref, 10, "faddiff: processing arguments (\$ARGV[0]=$ARGV[0], \$ARGV[1]=$ARGV[1]) ... "); $fad1_file = $ARGV[0]; $fad2_file = $ARGV[1]; if ($fad1_file eq '-' and $fad2_file eq '-') { ADE::error($errstack_ref, 'faddiff_err_misc', 'only one file can be read from standard input'); return($ADE::FAIL); } # Guts ADE::debug($errstack_ref, 5, 'faddiff: opening input and output files'); if ($opt_outfile eq '-') { $out_handle = \*STDOUT; } else { ADE::register_temp_file($errstack_ref, $opt_outfile); if (($rc=ADE::open_compressed_file_for_writing($errstack_ref, $opt_outfile, \*OUT_HANDLE)) != $ADE::OK) { ADE::error($errstack_ref, 'faddiff_err_access', $opt_outfile, 'open'); return($rc); } $out_handle = \*OUT_HANDLE; } if ($fad1_file eq '-') { $fad1_handle = \*STDIN; } else { ADE::debug($errstack_ref, 5, "faddiff: opening input file $fad1_file"); if (($rc=ADE::open_compressed_file_for_reading($errstack_ref, $fad1_file, \*FAD1_HANDLE)) != $ADE::OK) { ADE::error($errstack_ref, 'faddiff_err_access', $fad1_file, 'open'); return($rc); } $fad1_handle = \*FAD1_HANDLE; } if ($fad2_file eq '-') { $fad2_handle = \*STDIN; } else { ADE::debug($errstack_ref, 5, "faddiff: opening input file $fad2_file"); if (($rc=ADE::open_compressed_file_for_reading($errstack_ref, $fad2_file, \*FAD2_HANDLE)) != $ADE::OK) { ADE::error($errstack_ref, 'faddiff_err_access', $fad2_file, 'open'); return($rc); } $fad2_handle = \*FAD2_HANDLE; } ADE::debug($errstack_ref, 5, 'faddiff: loading first FAD file ...'); if (($rc=FAD::load($errstack_ref, $fad1_handle, \%data1_store, undef, undef)) != $ADE::OK) { ADE::error($errstack_ref, 'faddiff_err_misc', "$fad1_file: corrupt FAD file"); return($rc) } ADE::debug($errstack_ref, 5, 'faddiff: loading second FAD file ...'); if (($rc=FAD::load($errstack_ref, $fad2_handle, \%data2_store, undef, undef)) != $ADE::OK) { ADE::error($errstack_ref, 'faddiff_err_misc', "$fad2_file: corrupt FAD file"); return($rc) } ADE::debug($errstack_ref, 5, 'faddiff: diffing indices ...'); $count = 0; return ($rc) if (($rc=FAD::diff($errstack_ref, \%data1_store, \%data2_store, \&faddiff_cb, { count_ref => \$count, out_handle => $out_handle })) != $ADE::OK); ADE::debug($errstack_ref, 5, 'faddiff: closing input and output files'); close FAD1_HANDLE if ($fad1_handle == \*FAD1_HANDLE); close FAD2_HANDLE if ($fad2_handle == \*FAD2_HANDLE); if ($out_handle == \*OUT_HANDLE) { close OUT_HANDLE; ADE::deregister_temp_file($errstack_ref, $opt_outfile); } # The fad_diff function has been called and any errors it returned have # already resulted in 'return' being called, so if we get here then # nothing has failed. # # Now we need to return a return code based on whether there # are any differences or not (just as /usr/bin/diff diff does). # But if we return non-zero then the parent _ade_main_withstack() # function will think this function faddiff() has failed and display # the error stack (which will be empty apart from the 'application # entry function failed' frame that _ade_main_withstack() puts on it). # # Well, if we can suppress the displaying of the error message and # leave it still to return an error return code then that is compliant # with diff's behaviour. We can suppress the displaying of the error # message by changing the error message display function. ADE::set_messaging_parameters($errstack_ref, writers=>['devnull']); # The exit code is derived from whether there were differences or not. return($count ? $ADE::FAIL : $ADE::OK); } sub faddiff_opt_handler_g ## no critic (RequireArgUnpacking) { return(faddiff_opt_handler_ignore_gid(@_)); } sub faddiff_opt_handler_u ## no critic (RequireArgUnpacking) { return(faddiff_opt_handler_ignore_uid(@_)); } sub faddiff_opt_handler_m ## no critic (RequireArgUnpacking) { return(faddiff_opt_handler_ignore_mode(@_)); } sub faddiff_opt_handler_o ## no critic (RequireArgUnpacking) { return(faddiff_opt_handler_file(@_)); } sub faddiff_opt_handler_ignore_gid { my($errstack_ref) = @_; $opt_igngroup = 1; return($ADE::OK); } sub faddiff_opt_handler_ignore_uid { my($errstack_ref) = @_; $opt_ignowner = 1; return($ADE::OK); } sub faddiff_opt_handler_ignore_mode { my($errstack_ref) = @_; $opt_ignperms = 1; return($ADE::OK); } sub faddiff_opt_handler_file { my($errstack_ref, $outfile) = @_; $opt_outfile = $outfile; return($ADE::OK); } sub faddiff_version { my($errstack_ref, $version_text_ref) = @_; return(ADE::extract_version($errstack_ref, $app_svnid, $version_text_ref)); } sub faddiff_paths { my($errstack_ref, $pathlist_text_ref) = @_; my($rc); ${$pathlist_text_ref} = undef; return($ADE::OK); } sub faddiff_usage_help { my($errstack_ref, $usage_text_short_ref, $usage_text_long_ref) = @_; ${$usage_text_short_ref} = ' '; ${$usage_text_long_ref} = " -u | --ignore-uid ignore different UIDs\n" . " -g | --ignore-gid ignore different GIDs\n" . " -m | --ignore-mode ignore different file permissions\n" . " -o | --file= send output to \n"; return($ADE::OK); } sub faddiff_cb ## no critic (ProhibitExcessComplexity) { my($errstack_ref, $param, $change_ref) = @_; my($oldstate, $newstate, $file, $changetype); my($have_displayed_a_difference_for_current_file_flag); my($count_ref, $out_handle); # Unpack the two parameters from the one the callback interface allowed to pass. $count_ref = ${$param}{'count_ref'}; $out_handle = ${$param}{'out_handle'}; # For each sort of difference detected for the one file for which this callback was called ... $have_displayed_a_difference_for_current_file_flag = 0; foreach my $i (0..$#{@{$change_ref}{'changes'}}) { # Create some aliases $changetype = ${$change_ref}{'changes'}[$i]{'type'}; $oldstate = ${$change_ref}{'changes'}[$i]{'old'}; $newstate = ${$change_ref}{'changes'}[$i]{'new'}; $file = ${$change_ref}{'file'}; # Show the change but don't attempt to display variables that are not set. if ($changetype ~~ [ $FAD::CT_ADDED, $FAD::CT_DELETED ]) { ADE::debug($errstack_ref, 4, "faddiff_cb: changetype=$changetype, file=$file"); } else { ADE::debug($errstack_ref, 4, "faddiff_cb: changetype=$changetype, oldstate=$oldstate, newstate=$newstate, file=$file"); } # Skip to next change if flags say ignore this type of change. next if ($changetype eq $FAD::CT_OWNER and $opt_ignowner); next if ($changetype eq $FAD::CT_GROUP and $opt_igngroup); next if ($changetype eq $FAD::CT_MODE and $opt_ignperms); # Print name of file or comma separator (for second change type onwards) print $out_handle $have_displayed_a_difference_for_current_file_flag ? ', ' : "$file: "; # Display in on the change if ($changetype eq $FAD::CT_TYPE) { print $out_handle "type($oldstate -> $newstate)"; } elsif ($changetype eq $FAD::CT_OWNER) { print $out_handle "owner($oldstate -> $newstate)"; } elsif ($changetype eq $FAD::CT_GROUP) { print $out_handle "group($oldstate -> $newstate)"; } elsif ($changetype eq $FAD::CT_MODE) { print $out_handle sprintf 'mode(%o -> %o)', $oldstate, $newstate; } elsif ($changetype eq $FAD::CT_CONT_MAJMIN) { print $out_handle "major/minor($oldstate -> $newstate)"; } elsif ($changetype eq $FAD::CT_LINKS) { print $out_handle "links($oldstate -> $newstate)"; } elsif ($changetype eq $FAD::CT_CONT_SYMLINK) { print $out_handle "linked_to($oldstate -> $newstate)"; } elsif ($changetype eq $FAD::CT_CONT_CRC) { print $out_handle "checksum($oldstate -> $newstate)"; } elsif ($changetype eq $FAD::CT_ADDED) { print $out_handle 'added()'; } elsif ($changetype eq $FAD::CT_DELETED) { print $out_handle 'deleted()'; } else { # Uncatered for change type ADE::internal($errstack_ref, "faddiff_cb: unexpected CT: $changetype"); } $have_displayed_a_difference_for_current_file_flag++; } print $out_handle $have_displayed_a_difference_for_current_file_flag ? "\n" : ''; # Increment count of files changed (not changes) ${$count_ref}++; return($ADE::OK); } ADE::main(\&faddiff);