diff options
Diffstat (limited to 'pwx/check_tree.pl')
-rwxr-xr-x | pwx/check_tree.pl | 1696 |
1 files changed, 1696 insertions, 0 deletions
diff --git a/pwx/check_tree.pl b/pwx/check_tree.pl new file mode 100755 index 000000000..e0a3c0881 --- /dev/null +++ b/pwx/check_tree.pl @@ -0,0 +1,1696 @@ +#!/usr/bin/perl -w + +# ================================================================ +# === ==> -------- HISTORY -------- <== === +# ================================================================ +# +# Version Date Maintainer Changes, Additions, Fixes +# 0.0.1 2017-04-08 sed, PrydeWorX First basic design +# ... a lot of undocumented and partly lost development ... +# 0.8.0 2018-02-23 sed, PrydeWorX 2nd gen rewritten due to loss of the original thanks to stupidity. +# 0.8.1 2018-03-05 sed, PrydeWorX Fixed all outstanding issues and optimized rewriting of shell files. +# 0.8.2 2018-03-06 sed, PrydeWorX Added checks for elogind_*() function call removals. +# +# ======================== +# === Little TODO list === +# ======================== +# - Add masking for XML code. It is a nuisance that the XML templates in the +# python tools and man sources always generate useless patches. Real patches +# are in danger of getting overlooked. +# - Add handling of the *.sym files. +# - Detect when "mask includes undos" are moved into the elogind block by diff. +# - Renames must also include "elogind <=> systemd-login" +# - Find a masking solution for login/logind-gperf.gperf +# +use strict; +use warnings; +use Cwd qw(getcwd abs_path); +use File::Basename; +use File::Find; +use Readonly; + +# ================================================================ +# === ==> ------ Help Text and Version ----- <== === +# ================================================================ +Readonly my $VERSION => "0.8.1"; ## Please keep this current! +Readonly my $VERSMIN => "-" x length($VERSION); +Readonly my $PROGNAME => basename($0); +Readonly my $WORKDIR => getcwd(); +Readonly my $USAGE_SHORT => "$PROGNAME <--help|[OPTIONS] <path to upstream tree>>"; +Readonly my $USAGE_LONG => qq# +elogind git tree checker V$VERSION +--------------------------$VERSMIN + + Check the current tree, from where this program is called, against an + upstream tree for changes, and generate a patchset out of the differences, + that does not interfere with elogind development markings. + +Usage : +------- + $USAGE_SHORT + + The path to the upstream tree should have the same layout as the place from + where this program is called. It is best to call this program from the + elogind root path and use a systemd upstream root path for comparison. + +Options : +--------- + -c|--commit <refid> | Check out <refid> in the upstream tree. + -f|--file <path> | Do not search recursively, check only <path>. + | For the check of multiple files, you can either + | specify -f multiple times, or concatenate paths + | with a comma, or mix both methods. + -h|--help | Print this help text and exit. +#; + +# ================================================================ +# === ==> -------- Global variables -------- <== === +# ================================================================ + +my $file_fmt = ""; ## Format string built from the longest file name in generate_file_list(). +my $have_wanted = 0; ## Helper for finding relevant files (see wanted()) +my %hWanted = (); ## Helper hash for finding relevant files (see wanted()) +my $in_else_block = 0; ## Set to 1 if we switched from mask/unmask to 'else'. +my $in_mask_block = 0; ## Set to 1 if we entered an elogind mask block +my $in_insert_block = 0; ## Set to 1 if we entered an elogind addition block +my $main_result = 1; ## Used for parse_args() only, as simple $result is local everywhere. +my @only_here = (); ## List of files that do not exist in $upstream_path. +my $previous_commit = ""; ## Store current upstream state, so we can revert afterwards. +my $show_help = 0; +my @source_files = (); ## Final file list to process, generated in in generate_file_list(). +my $upstream_path = ""; +my $wanted_commit = ""; +my @wanted_files = (); ## User given file list (if any) to limit generate_file_list() + +# ================================================================ +# === ==> ------- MAIN DATA STRUCTURES ------ <== === +# ================================================================ +my %hFile = (); ## Main data structure to manage a complete compare of two files. (See: build_hFile() ) + ## Note: %hFile is used globaly for each file that is processed. + ## The structure is: + ## ( count : Number of hunks stored + ## hunks : Arrayref with the Hunks, stored in %hHunk instances + ## output : Arrayref with the lines of the final patch + ## part : local relative file path + ## patch : WORKDIR/patches/<part>.patch (With / replaced by _ in <part>) + ## source : WORKDIR/<part> + ## target : UPSTREAM/<part> + ## ) +my $hHunk = {}; ## Secondary data structure to describe one diff hunk. (See: build_hHunk() ) + ## Note: $hHunk is used globally for each Hunk that is processed and points to the + ## current $hFile{hunks}[] instance. + ## The structure is: + ## { count : Number of lines in {lines} + ## lines : list of the lines themselves, + ## idx : Index of this hunk in %hFile{hunks} + ## src_start : line number this hunk starts in the source file. + ## tgt_start : line number this hunk becomes in the target file. + ## useful : 1 if the hunk is transported, 0 if it is to be omitted. + ## } +my %hIncs = ();## Hash for remembered includes over different hunks. + ## Note: Only counted in the first step, actions are only in the second step. + ## The structure is: + ## { include => { + ## applied : Set to 1 once check_includes() has applied any rules on it. + ## elogind = { hunkid : Hunk id ; Position where a "needed by elogin" include is + ## lineid : Line id ; wanted to be removed by the patch. + ## } + ## insert = { hunkid : Hunk id ; Position where a commented out include is + ## lineid : Line id ; wanted to be removed by the patch. + ## spliceme: Set to 1 if this insert is later to be spliced. + ## sysinc : Set to 1 if it is <include>, 0 otherwise. + ## } + ## remove = { hunkid : Hunk id ; Position where a new include is wanted to be + ## lineid : Line id ; added by the patch. + ## sysinc : Set to 1 if it is <include>, 0 otherwise. + ## } + ## } } +my @lFails = ();## List of failed hunks. These are worth noting down in an extra structure, as these + ## mean that something is fishy about a file, like elogind mask starts within masks. + ## The structure is: + ## ( { hunk : the failed hunk for later printing + ## msg : a message set by the function that failed + ## part : local relative file part + ## } + ## ) + +# ================================================================ +# === ==> -------- Function list -------- <== === +# ================================================================ + +sub build_hFile; ## Inititializes and fills %hFile. +sub build_hHunk; ## Adds a new $hFile{hunks}[] instance. +sub build_output; ## Writes $hFile{output} from all useful $hFile{hunks}. +sub check_blanks; ## Check that useful blank line additions aren't misplaced. +sub check_comments; ## Check comments we added for elogind specific information. +sub check_debug; ## Check for debug constructs +sub check_func_removes; ## Check for attempts to remove elogind_*() special function calls. +sub check_includes; ## performe necessary actions with the help of %hIncs (built by read_includes()) +sub check_masks; ## Check for elogind preprocessor masks +sub check_musl; ## Check for musl_libc compatibility blocks +sub check_name_reverts; ## Check for attempts to revert 'elogind' to 'systemd' +sub checkout_upstream; ## Checkout the given refid on $upstream_path +sub clean_hFile; ## Completely clean up the current %hFile data structure. +sub diff_hFile; ## Generates the diff and builds $hFile{hunks} if needed. +sub generate_file_list; ## Find all relevant files and store them in @wanted_files +sub get_hunk_head; ## Generate the "@@ -xx,n +yy,m @@" hunk header line out of $hHunk. +sub hunk_failed; ## Generates a new @lFails entry and terminates the progress line. +sub hunk_is_useful; ## Prunes the hunk and checks whether it stil does anything +sub parse_args; ## Parse ARGV for the options we support +sub prepare_shell; ## Prepare shell (and meson) files for our processing +sub prune_hunk; ## remove unneeded prefix and postfix lines. +sub read_includes; ## map include changes +sub splice_includes; ## Splice all includes that were marked for splicing +sub unprepare_shell; ## Unprepare shell (and meson) files after our processing +sub wanted; ## Callback function for File::Find + +# ================================================================ +# === ==> -------- Prechecks -------- <== == +# ================================================================ + +$main_result = parse_args(@ARGV); +( (!$main_result) ## Note: Error or --help given, then exit. + or ( $show_help and print "$USAGE_LONG" ) ) + and exit(!$main_result); +length($wanted_commit) + and (checkout_upstream($wanted_commit) ## Note: Does nothing if $wanted_commit is already checked out. + or exit 1); +generate_file_list or exit 1; ## Note: @wanted_files is heeded. + +# ================================================================ +# === ==> -------- = MAIN PROGRAM = -------- <== === +# ================================================================ + +for my $file_part (@source_files) { + printf("$file_fmt: ", $file_part); + + build_hFile($file_part) or next; + diff_hFile or next; + + # Reset global state helpers + $in_else_block = 0; + $in_mask_block = 0; + $in_insert_block = 0; + + # Empty the include manipulation hash + %hIncs = (); + + # --------------------------------------------------------------------- + # --- Go through all hunks and check them against our various rules --- + # --------------------------------------------------------------------- + for (my $pos = 0; $pos < $hFile{count}; ++$pos) { + $hHunk = $hFile{hunks}[$pos]; ## Global shortcut + + # === 1) Check for elogind masks ================================== + check_masks and hunk_is_useful and prune_hunk or next; + + # === 2) Check for musl_libc compatibility blocks ================= + check_musl and hunk_is_useful and prune_hunk or next; + + # === 3) Check for debug constructs =============================== + check_debug and hunk_is_useful and prune_hunk or next; + + # === 4) Check for elogind extra comments and information ========= + check_comments and hunk_is_useful and prune_hunk or next; + + # === 5) Check for useful blank line additions ==================== + check_blanks and hunk_is_useful and prune_hunk or next; + + # === 6) Check for 'elogind' => 'systemd' reverts ================= + check_name_reverts and hunk_is_useful and prune_hunk or next; + + # === 7) Check for elogind_*() function removals ================== + check_func_removes and hunk_is_useful and prune_hunk or next; + + # ===> IMPORTANT: From here on no more pruning, lines must *NOT* change any more! <=== + + # === 8) Analyze includes and note their appearance in $hIncs ===== + read_includes; ## Never fails, doesn't change anything. + + } ## End of first hunk loop + + # --------------------------------------------------------------------- + # --- Make sure saved include data is sane --- + # --------------------------------------------------------------------- + for my $inc (keys %hIncs) { + $hIncs{$inc}{applied} = 0; + defined ($hIncs{$inc}{elogind}) or $hIncs{$inc}{elogind} = { hunkid => -1, lineid => -1 }; + defined ($hIncs{$inc}{insert}) or $hIncs{$inc}{insert} = { hunkid => -1, lineid => -1, spliceme => 0, sysinc => 0 }; + defined ($hIncs{$inc}{remove}) or $hIncs{$inc}{remove} = { hunkid => -1, lineid => -1, sysinc => 0 }; + } + + # --------------------------------------------------------------------- + # --- Go through all hunks and apply remaining changes and checks --- + # --------------------------------------------------------------------- + for (my $pos = 0; $pos < $hFile{count}; ++$pos) { + $hHunk = $hFile{hunks}[$pos]; ## Global shortcut + + # (pre -> early out) + hunk_is_useful or next; + + # === 1) Apply what we learned to changed includes ================ + check_includes and hunk_is_useful or next; + + } ## End of second hunk loop + + # --------------------------------------------------------------------- + # --- Splice all include insertions that are marked for splicing --- + # --------------------------------------------------------------------- + splice_includes; + + # --------------------------------------------------------------------- + # --- Go through all hunks for a last prune and check --- + # --------------------------------------------------------------------- + my $have_hunk = 0; + for (my $pos = 0; $pos < $hFile{count}; ++$pos) { + $hHunk = $hFile{hunks}[$pos]; ## Global shortcut + + # (pre -> early out) + hunk_is_useful or next; + + prune_hunk and ++$have_hunk; + } + + # If we have at least 1 useful hunk, create the output and tell the user what we've got. + $have_hunk + and build_output # (Always returns 1) + and printf("%d Hunk%s\n", $have_hunk, $have_hunk > 1 ? "s" : "") + or print("clean\n"); + + # Shell and meson files must be unprepared. See unprepare_shell() + $hFile{source} =~ m/\.pwx$/ and unprepare_shell; + + # Now skip the writing if there are no hunks + $have_hunk or next; + + # That's it, write the file and be done! + if (open(my $fOut, ">", $hFile{patch})) { + for my $line (@{$hFile{output}}) { + print $fOut "$line\n"; + } + close($fOut); + } else { + printf("ERROR: %s could not be opened for writing!\n%s\n", + $hFile{patch}, $!); + die("Please fix this first!"); + } +} ## End of main loop + +# ------------------------------------------------------------------------- +# --- Print out the list of files that only exist here and not upstream --- +# ------------------------------------------------------------------------- +if (scalar @only_here) { + my $count = scalar @only_here; + my $fmt = sprintf("%%d %d: %%s\n", length("$count")); + + printf("\n%d file%s only found in $WORKDIR:\n", $count, $count > 1 ? "s": ""); + + for (my $i = 0; $i < $count; ++$i) { + printf($fmt, $i + 1, $only_here[$i]); + } +} + +# ------------------------------------------------------------------------- +# --- Print out the list of files that only exist here and not upstream --- +# ------------------------------------------------------------------------- +if (scalar @lFails) { + my $count = scalar @lFails; + + printf("\n%d file%s %s have at least one fishy hunk:\n", $count, + $count > 1 ? "s" : "", $count > 1 ? "have" : "has"); + + for (my $i = 0; $i < $count; ++$i) { + print "=== $lFails[$i]{part} ===\n"; + print " => $lFails[$i]{msg} <=\n"; + print "---------------------------\n"; + print "$_\n" foreach(@{$lFails[$i]{hunk}}); + } +} + +# =========================== +# === END OF MAIN PROGRAM === +# =========================== + +# ================================================================ +# === ==> -------- Cleanup -------- <== === +# ================================================================ + +length($previous_commit) and checkout_upstream($previous_commit); + +# ================================================================ +# === ==> ---- Function Implementations ---- <== === +# ================================================================ + +# ----------------------------------------------------------------------- +# --- Inititializes and fills %hFile. Old values are discarded. --- +# --- Adds files, that do not exist upstream, to @only_here. --- +# --- Returns 1 on success, 0 otherwise. --- +# ----------------------------------------------------------------------- +sub build_hFile { + my ($part) = @_; + + defined($part) and length($part) or print("ERROR\n") and die("build_hfile: part is empty ???"); + + # We only prefixed './' to unify things. Now it is no longer needed: + $part =~ s,^\./,,; + + # Pre: erase current $hFile, as that is what is expected. + clean_hFile(); + + # Check the target file + my $tgt = "$upstream_path/$part"; + $tgt =~ s/elogind/systemd/g; + $tgt =~ s/\.pwx$//; + -f $tgt or push(@only_here, $part) + and print "only here\n" + and return 0; + + # Build the patch name + my $patch = $part; + $patch =~ s/\//_/g; + + # Build the central data structure. + %hFile = ( + count => 0, + hunks => [ ], + output => [ ], + part => "$part", + patch => "patches/${patch}.patch", + source => "$WORKDIR/$part", + target => "$tgt" + ); + + return 1; +} + +# ----------------------------------------------------------------------- +# --- Build a new $hHunk instance and add it to $hFile{hunks} --- +# ----------------------------------------------------------------------- +sub build_hHunk { + my ($head, @lHunk) = @_; + my $pos = $hFile{count}++; + + # The first line must be the hunk positional and size data. + # Example: @@ -136,6 +136,8 @@ + # That is @@ -<source line>,<source length> +<target line>,<target length> @@ + if ( $head =~ m/^@@ -(\d+),\d+ \+(\d+),\d+ @@/ ) { + %{$hFile{hunks}[$pos]} = ( + count => 0, + idx => $pos, + offset => 0, + src_start => $1, + tgt_start => $2, + useful => 1 + ); + + # We need to chomp the lines: + for my $line (@lHunk) { + defined($line) or next; + chomp $line; + push @{$hFile{hunks}[$pos]{lines}}, $line; + $hFile{hunks}[$pos]{count}++; + } + return 1; + } + + print "Illegal hunk no $hFile{count}\n(Head: \"$head\")\n"; + + return 0; +} + + +# ----------------------------------------------------------------------- +# --- Writes $hFile{output} from all useful $hFile{hunks}. --- +# --- Important: No more checks, just do it! --- +# ----------------------------------------------------------------------- +sub build_output { + + my $offset = 0; ## Count building up target offsets + + for (my $pos = 0; $pos < $hFile{count}; ++$pos) { + $hHunk = $hFile{hunks}[$pos]; ## Global shortcut + $hHunk->{useful} or next; ## And the skip of the useless. + + # --- Add the header line ----------------- + # ----------------------------------------- + push(@{$hFile{output}}, get_hunk_head(\$offset)); + + # --- Add the hunk lines ------------------ + # ----------------------------------------- + for my $line (@{$hHunk->{lines}}) { + push(@{$hFile{output}}, $line); + } + } ## End of walking the hunks + + return 1; +} + + +# ----------------------------------------------------------------------- +# --- Check that useful blank line additions aren't misplaced. --- +# ---- Note: Sometimes the masks aren't placed correctly, and the diff--- +# ---- wants to add a missing blank line. As it tried to remove --- +# ---- our mask first, it'll be added after. That's fine for --- +# ---- #endif, but not for #if 0. --- +# ----------------------------------------------------------------------- +sub check_blanks { + + # Early exits: + defined($hHunk) or return 0; + $hHunk->{useful} or return 0; + + for (my $i = 0; $i < $hHunk->{count}; ++$i) { + my $line = \$hHunk->{lines}[$i]; ## Shortcut + + if ( ($$line =~ m/^\+\s*$/) + && ($i > 0) + && ($hHunk->{lines}[$i-1] =~ m/^[ -+]#if\s+[01].*elogind/) ) { + # Simply swap the lines + my $tmp = $$line; + $$line = $hHunk->{lines}[$i-1]; + $hHunk->{lines}[$i-1] = $tmp; + next; + } + } + + return 1; +} + + +# ----------------------------------------------------------------------- +# --- Check comments we added for elogind specific information. --- +# --- These are all comments, and can be both single and multi line. --- +# ----------------------------------------------------------------------- +sub check_comments { + + # Early exits: + defined($hHunk) or return 0; + $hHunk->{useful} or return 0; + + my $in_comment_block = 0; + + for (my $i = 0; $i < $hHunk->{count}; ++$i) { + my $line = \$hHunk->{lines}[$i]; ## Shortcut + + # Check for comment block start + # ----------------------------- + if ($$line =~ m,^-\s*(/[*]+|/[/]+).*elogind,) { + + # Sanity check: + $in_comment_block + and return hunk_failed("check_comments: Comment block start found in comment block!"); + + substr($$line, 0, 1) = " "; + + # Only start the comment block if this is really a multiline comment + (!($$line =~ m,\*/[^/]*$,) ) + and $in_comment_block = 1; + + next; + } + + # Check for comment block end + # ----------------------------- + if ($in_comment_block && ($$line =~ m,^-.*\*/\s*$,)) { + substr($$line, 0, 1) = " "; + $in_comment_block = 0; + next; + } + + # Check for comment block line + # ----------------------------- + if ($in_comment_block && ($$line =~ m,^-,)) { + # Note: We do not check for anything else, as empty lines must be allowed. + substr($$line, 0, 1) = " "; + next; + } + + # If none of the above applied, the comment block is over. + $in_comment_block = 0; + } + + return 1; +} + +# ----------------------------------------------------------------------- +# --- Check for debug constructs --- +# --- Rules: ENABLE_DEBUG_ELOGIND must be taken like #if 1, *but* --- +# --- here an #else is valid and must stay fully. --- +# --- Further there might be multiline calls to --- +# --- log_debug_elogind() that must not be removed either. --- +# ----------------------------------------------------------------------- +sub check_debug { + + # Early exits: + defined($hHunk) or return 0; + $hHunk->{useful} or return 0; + + # Count non-elogind block #ifs. This is needed, so normal + # #if/#else/#/endif constructs can be put inside both the + # debug and the release block. + my $regular_ifs = 0; + my $is_debug_func = 0; ## Special for log_debug_elogind() + + for (my $i = 0; $i < $hHunk->{count}; ++$i) { + my $line = \$hHunk->{lines}[$i]; ## Shortcut + + # Entering a debug construct block + # --------------------------------------- + if ( $$line =~ m/^-#if.+ENABLE_DEBUG_ELOGIND/ ) { + ## Note: Here it is perfectly fine to be in an elogind mask or insert block. + substr($$line, 0, 1) = " "; ## Remove '-' + $in_insert_block++; ## Increase instead of setting this to 1. + next; + } + + # Count regular #if + $$line =~ m/^-#if/ and $in_insert_block and ++$regular_ifs; + + # Switching to the release variant. + # --------------------------------------- + if ( ($$line =~ m/^-#else/ ) && $in_insert_block && !$regular_ifs) { + substr($$line, 0, 1) = " "; ## Remove '-' + $in_else_block++; ## Increase instead of setting this to 1. + next; + } + + # Ending a debug construct block + # --------------------------------------- + if ( $$line =~ m,^-#endif\s*///?.*ENABLE_DEBUG_, ) { + (!$in_insert_block) + and return hunk_failed("check_debug: #endif // ENABLE_DEBUG_* found outside any debug construct"); + substr($$line, 0, 1) = " "; ## Remove '-' + $in_insert_block--; ## Decrease instead of setting to 0. This allows such + $in_else_block--; ## blocks to reside in regular elogind mask/insert blocks. + next; + } + + # End regular #if + $$line =~ m/^-#endif/ and $in_insert_block and --$regular_ifs; + + # Check for log_debug_elogind() + # --------------------------------------- + if ($$line =~ m/^-.*log_debug_elogind\s*\(/ ) { + substr($$line, 0, 1) = " "; ## Remove '-' + $$line =~ m/\)\s*;/ or ++$is_debug_func; + next; + } + + # Remove '-' prefixes in all lines within the debug construct block + # ------------------------------------------------------------------- + if ( ($$line =~ m,^-,) && ($in_insert_block || $is_debug_func) ) { + substr($$line, 0, 1) = " "; ## Remove '-' + # Note: Everything in *any* insert block must not be removed anyway. + } + + # Check for the end of a multiline log_debug_elogind() call + # --------------------------------------------------------- + $is_debug_func and $$line =~ m/\)\s*;/ and --$is_debug_func; + + } ## End of looping lines + + return 1; +} + + +# ----------------------------------------------------------------------- +# --- Check for attempts to remove elogind_*() special function calls. -- +# --- We have som special functions, needed oly by elogind. --- +# --- One of the most important ones is elogind_set_program_name(), --- +# --- which has an important role in musl_libc compatibility. --- +# --- These calls must not be removed of course. --- +# ----------------------------------------------------------------------- +sub check_func_removes { + + # Early exits: + defined($hHunk) or return 1; + $hHunk->{useful} or return 1; + + # Needed for multi-line calls + my $is_func_call = 0; + + for (my $i = 0; $i < $hHunk->{count}; ++$i) { + my $line = \$hHunk->{lines}[$i]; ## Shortcut + + # Check for elogind_*() call + # ------------------------------------------------------------------- + if ($$line =~ m/^-.*elogind_\S+\s*\(/ ) { + substr($$line, 0, 1) = " "; ## Remove '-' + $$line =~ m/\)\s*;/ or ++$is_func_call; + next; + } + + # Remove '-' prefixes in all lines that belong to an elogind_*() call + # ------------------------------------------------------------------- + if ( ($$line =~ m,^-,) && $is_func_call ) { + substr($$line, 0, 1) = " "; ## Remove '-' + } + + # Check for the end of a multiline elogind_*() call + # ------------------------------------------------------------------- + $is_func_call and $$line =~ m/\)\s*;/ and --$is_func_call; + } + + return 1; +} + + +# ----------------------------------------------------------------------- +# --- Check hunk for include manipulations we must step in --- +# --- This is basically read_include(), but this time we actually act --- +# --- on what we found. --- +# --- Rules: --- +# --- 1) Removals of includes that we commented out: --- +# --- a) If there is no insertion of this include, let it be --- +# --- removed, it seems to be obsolete. --- +# --- b) If there is an insertion of the very same include in one --- +# --- of the surrounding lines, mark the insert for splicing --- +# --- and undo the removal. --- +# --- c) If there is an insertion somewhere else, let it be --- +# --- removed here, and handle the insertion: --- +# --- - Outside a "needed by elogind" block: comment out the --- +# --- inserted include. --- +# --- - Inside a "needed by elogind" block: undo the removal --- +# --- there. --- +# --- 2) Insertions of new includes, where 1) does not apply: --- +# --- a) If the include is new, comment it out to force a later --- +# --- check. The compiler will tell us if it is needed. --- +# --- b) If the include is part of the "needed by elogind" block --- +# --- already, allow the removal there and accept the regular --- +# --- insertion here. --- +# --- 3) Removals of includes in "needed by elogind" blocks: --- +# --- As 1) and 2) do not apply, simply undo any removal here. --- +# ----------------------------------------------------------------------- +sub check_includes { + + # Early exits: + defined($hHunk) or return 1; + $hHunk->{useful} or return 1; + + # We must know when "needed by elogind blocks" start + my $in_elogind_block = 0; + + # The simple undo check will fail, if we do at least one at once. + # Delay the undoing of the removals until after the hunk was checked. + my %undos = (); + + for (my $i = 0; $i < $hHunk->{count}; ++$i) { + my $line = \$hHunk->{lines}[$i]; ## Shortcut + + # === Ruleset 1 : Handling of removals of includes we commented out === + # ===================================================================== + if ( $$line =~ m,^-\s*//+\s*#include\s+[<"']([^>"']+)[>"'], ) { + $hIncs{$1}{applied} and next; ## No double handling + + my $inc = $1; + + # Pre: Sanity check: + defined($hIncs{$inc}{remove}{hunkid}) and $hIncs{$inc}{remove}{hunkid} > -1 + or return hunk_failed("check_includes: Unrecorded removal found!"); + + # a) Check for removals of obsolete includes. + $hIncs{$inc}{elogind}{hunkid} > -1 ## If no insert was found, then the include was + or $hIncs{$inc}{insert}{hunkid} > -1 ## removed by systemd devs for good. + or ( ++$hIncs{$inc}{applied} and next); + + # b) Check for a simple undo of our commenting out + if ( ($hIncs{$inc}{insert}{hunkid} == $hIncs{$inc}{remove}{hunkid} ) + && ($hIncs{$inc}{insert}{sysinc} == $hIncs{$inc}{remove}{sysinc} ) ) { + my $ins_diff = $hIncs{$inc}{insert}{lineid} - $hIncs{$inc}{remove}{lineid}; + my $all_same = 1; + my $direction = $ins_diff > 0 ? 1 : -1; + + # Let's see whether there are undos between this and its addition + # in the same order, meaning there has been no resorting. + for (my $j = $direction; $all_same && (abs($j) < abs($ins_diff)); $j += $direction) { + $all_same = 0; + + if ( ( $hHunk->{lines}[$i+$j] =~ m,^-\s*//+\s*#include\s+[<"']([^>"']+)[>"'], ) + || ( $hHunk->{lines}[$i+$j] =~ m,^\+\s*#include\s+[<"']([^>"']+)[>"'], ) ) { + + $hIncs{$1}{insert}{hunkid} == $hIncs{$1}{remove}{hunkid} + and $ins_diff == ($hIncs{$1}{insert}{lineid} - $hIncs{$1}{remove}{lineid}) + and $hIncs{$inc}{insert}{sysinc} == $hIncs{$inc}{remove}{sysinc} + and $all_same = 1; + } + } + if ($all_same) { + # The insertion is right before or after the removal. That's pointless. + $undos{$i} = 1; + $undos{$hIncs{$inc}{insert}{lineid}} = 1; + $hIncs{$inc}{applied} = 1; + $hIncs{$inc}{insert}{spliceme} = 1; + next; + } + } ## end of insert and remove being in the same hunk + + # c) Move somewhere else, or change include type. Can't be anything else here. + if ($hIncs{$inc}{elogind}{hunkid} > -1) { + # Just undo the removal of the elogind insert. + my $hId = $hIncs{$inc}{elogind}{hunkid}; + my $lId = $hIncs{$inc}{elogind}{lineid}; + substr($hFile{hunks}[$hId]{lines}[$lId], 0, 1) = " "; + $hIncs{$inc}{applied} = 1; + } else { + # Just comment out the insert. + my $hId = $hIncs{$inc}{insert}{hunkid}; + my $lId = $hIncs{$inc}{insert}{lineid}; + substr($hFile{hunks}[$hId]{lines}[$lId], 0, 1) = "+//"; + $hIncs{$inc}{applied} = 1; + } + + next; + } ## End of ruleset 1 + + # === Ruleset 2 : Handling of insertions, not handled by 1 === + # ===================================================================== + if ( $$line =~ m,^\+\s*#include\s+[<"']([^>"']+)[>"'], ) { + $hIncs{$1}{applied} and next; ## No double handling + + # Pre: Sanity check: + defined($hIncs{$1}{insert}{hunkid}) and $hIncs{$1}{insert}{hunkid} > -1 + or return hunk_failed("check_includes: Unrecorded insertion found!"); + + # Nicely enough we only have to check whether this is removed from + # any elogind includes block or not. + (-1 == $hIncs{$1}{elogind}{hunkid}) + and substr($$line, 0, 1) = "+//"; ## comment out for later check + $hIncs{$1}{applied} = 1; + + # That's it. Cool, eh? + + next; + } + + # === Ruleset 3 : Handling of "needed by elogind" blocks === + # ===================================================================== + if ( $in_elogind_block + && ($$line =~ m,^-\s*#include\s+[<"']([^>"']+)[>"'], ) ) { + $hIncs{$1}{applied} and next; ## No double handling + + # Pre: Sanity check: + defined($hIncs{$1}{elogind}{hunkid}) and $hIncs{$1}{elogind}{hunkid} > -1 + or return hunk_failed("check_includes: Unrecorded elogind include found!"); + + # As 1 and 2 do not apply, simply undo the removal. + substr($$line, 0, 1) = " "; + $hIncs{$1}{applied} = 1; + + next; + } + + # === Other 1 : Look for "needed by elogind" block starts === + # ===================================================================== + if ($$line =~ m,^[- ]\s*//+.*needed by elogind.*,i) { + $in_elogind_block = 1; + + # Never remove the block start + ($$line =~ m,^-,) and substr($$line, 0, 1) = " "; + + # While we are here, we can see to it, that the additional empty + # line above our marker does not get removed: + ($i > 0) and ($hHunk->{lines}[$i-1] =~ m/^-\s*$/) + and substr($hHunk->{lines}[$i-1], 0, 1) = " "; + + next; + } + + # === Other 2 : elogind include blocks end, when the first not === + # === removed line is found === + # ===================================================================== + $in_elogind_block and ($$line =~ m,^[ +]$,) and $in_elogind_block = 0; + + # === Other 3 : Undo all other removals in elogind include blocks === + # ===================================================================== + $in_elogind_block and ($$line =~ m,^-,) and substr($$line, 0, 1) = " "; + + # Note: Although it looks like all rules are out the window here, all + # elogind includes that are handled above, end in a 'next', so + # those won't reach here. Actually 'Other 3' would be never + # reached with an #include line. + + } ## End of looping lines + + # Before we can leave, we have to neutralize the %undo lines: + for my $lId (keys %undos) { + substr($hHunk->{lines}[$lId], 0, 1) = " "; + } + + return 1; +} + +# ----------------------------------------------------------------------- +# --- Check $hHunk for elogind preprocessor masks and additions --- +# --- Rules: --- +# --- 1) If we need an alternative for an existing systemd code block,--- +# --- we do it like this: --- +# --- #if 0 /// <some comment with the word "elogind" in it> --- +# --- (... systemd code block, unaltered ...) --- +# --- -> Any change in this block is okay. --- +# --- #else --- +# --- (... elogind alternative code ...) --- +# --- -> Any change in this block is *NOT* okay. --- +# --- #endif // 0 --- +# --- 2) If we need an addition for elogind, we do it like this: --- +# --- #if 1 /// <some comment with the word "elogind" in it> --- +# --- (... elogind additional code ...) --- +# --- -> Any change in this block is *NOT* okay. --- +# --- #endif // 1 --- +# ----------------------------------------------------------------------- +sub check_masks { + + # Early exits: + defined($hHunk) or return 0; + $hHunk->{useful} or return 0; + + # Count non-elogind block #ifs. This is needed, so normal + # #if/#else/#/endif constructs can be put inside elogind mask blocks. + my $regular_ifs = 0; + + for (my $i = 0; $i < $hHunk->{count}; ++$i) { + my $line = \$hHunk->{lines}[$i]; ## Shortcut + + # Entering an elogind mask + # --------------------------------------- + if ( $$line =~ m/^-#if\s+0.+elogind/ ) { + $in_mask_block + and return hunk_failed("check_masks: Mask start found while being in a mask block!"); + $in_insert_block + and return hunk_failed("check_masks: Mask start found while being in an insert block!"); + substr($$line, 0, 1) = " "; ## Remove '-' + $in_mask_block = 1; + next; + } + + # Entering an elogind insert + # --------------------------------------- + if ( $$line =~ m/^-#if\s+1.+elogind/ ) { + $in_mask_block + and return hunk_failed("check_masks: Insert start found while being in a mask block!"); + $in_insert_block + and return hunk_failed("check_masks: Insert start found while being in an insert block!"); + substr($$line, 0, 1) = " "; ## Remove '-' + $in_insert_block = 1; + next; + } + + # Count regular #if + $$line =~ m/^-#if/ and $in_mask_block and ++$regular_ifs; + + # Switching from Mask to else. + # Note: Inserts have no #else, they make no sense. + # --------------------------------------- + if ( ($$line =~ m/^-#else/ ) && $in_mask_block && !$regular_ifs) { + substr($$line, 0, 1) = " "; ## Remove '-' + $in_else_block = 1; + next; + } + + # Ending a Mask/Insert block + # --------------------------------------- + # Note: The regex supports "#endif /** 0 **/", too. + if ( $$line =~ m,^-#endif\s*/(?:[*/]+)\s*([01]), ) { + if (0 == $1) { + (!$in_mask_block) + and return hunk_failed("check_masks: #endif // 0 found outside any mask block"); + substr($$line, 0, 1) = " "; ## Remove '-' + $in_mask_block = 0; + $in_else_block = 0; + next; + } + + # As we explicitly ask for "#endif // [01]", only one is left. + (!$in_insert_block) + and return hunk_failed("check_masks: #endif // 1 found outside any insert block"); + substr($$line, 0, 1) = " "; ## Remove '-' + $in_insert_block = 0; + $in_else_block = 0; + next; + } + + # End regular #if + $$line =~ m/^-#endif/ and $in_mask_block and --$regular_ifs; + + # Remove '-' prefixes in all lines within insert and mask-else blocks + # ------------------------------------------------------------------- + if ( ($$line =~ m,^-,) + && ( $in_insert_block || ($in_mask_block && $in_else_block) ) ) { + substr($$line, 0, 1) = " "; ## Remove '-' + } + } ## End of looping lines + + return 1; +} + +# ----------------------------------------------------------------------- +# --- Check for musl_libc compatibility blocks --- +# --- Rules: --- +# --- For musl-libc compatibility, there are some --- +# --- #ifdef __GLIBC__ (...) #else (...) #endif // __GLIBC__ --- +# --- helpers. --- +# --- These can also be "#if defined(__GLIBC__)" --- +# --- Note: We handle them like regular mask blocks, because the --- +# --- __GLIBC__ block is considered to be the original, while --- +# --- the musl_libc compat block is the #else block. --- +# ----------------------------------------------------------------------- +sub check_musl { + + # Early exits: + defined($hHunk) or return 0; + $hHunk->{useful} or return 0; + + # Count non-elogind block #ifs. This is needed, so normal + # #if/#else/#/endif constructs can be put inside both the original + # and the alternative block. + my $regular_ifs = 0; + + for (my $i = 0; $i < $hHunk->{count}; ++$i) { + my $line = \$hHunk->{lines}[$i]; ## Shortcut + + # Entering a __GLIBC__ block + # --------------------------------------- + if ( $$line =~ m/^-#if.+__GLIBC__/ ) { + ## Note: Here it is perfectly fine to be in an elogind mask block. + substr($$line, 0, 1) = " "; ## Remove '-' + $in_mask_block++; ## Increase instead of setting this to 1. + next; + } + + # Count regular #if + $$line =~ m/^-#if/ and $in_mask_block and ++$regular_ifs; + + # Switching from __GLIBC__ to else - the alternative for musl_libc. + # --------------------------------------- + if ( ($$line =~ m/^-#else/ ) && $in_mask_block && !$regular_ifs) { + substr($$line, 0, 1) = " "; ## Remove '-' + $in_else_block++; ## Increase instead of setting this to 1. + next; + } + + # Ending a __GLBC__ block + # --------------------------------------- + if ( $$line =~ m,^-#endif\s*///?.*__GLIBC__, ) { + (!$in_mask_block) + and return hunk_failed("check_musl: #endif // __GLIBC__ found outside any __GLIBC__ block"); + substr($$line, 0, 1) = " "; ## Remove '-' + $in_mask_block--; ## Decrease instead of setting to 0. This allows such + $in_else_block--; ## blocks to reside in regular elogind mask/insert blocks. + next; + } + + # End regular #if + $$line =~ m/^-#endif/ and $in_mask_block and --$regular_ifs; + + # Remove '-' prefixes in all lines within the musl (#else) blocks + # ------------------------------------------------------------------- + if ( ($$line =~ m,^-,) && $in_mask_block && $in_else_block ) { + substr($$line, 0, 1) = " "; ## Remove '-' + } + } ## End of looping lines + + return 1; +} + + +# ----------------------------------------------------------------------- +# --- Check for attempts to revert 'elogind' to 'systemd' --- +# --- Note: We only check for single line reverts like: --- +# --- |- // This is how elogind does it --- +# --- |+ // This is how systemd does it --- +# ----------------------------------------------------------------------- +sub check_name_reverts { + + # Early exits: + defined($hHunk) or return 0; + $hHunk->{useful} or return 0; + + # Note down what is changed, so we can have inline updates + my %hRemovals = (); + + for (my $i = 0; $i < $hHunk->{count}; ++$i) { + my $line = \$hHunk->{lines}[$i]; ## Shortcut + + defined($$line) or return hunk_failed("check_name_reverts: Line " . ($i + 1) . "/$hHunk->{count} is undef?\n"); + + # Note down removals + # --------------------------------- + if ($$line =~ m/^-\s*(.*elogind.*)\s*$/) { + $hRemovals{$1}{line} = $i; + next; + } + + # Check Additions + # --------------------------------- + if ($$line =~ m/^\+\s*(.*systemd.*)\s*$/) { + my $our_text = $1; + $our_text =~ s/systemd/elogind/g; + + # If this is a simple switch, undo it: + if (defined($hRemovals{$our_text})) { + substr($hHunk->{lines}[$hRemovals{$our_text}{line}], 0, 1) = " "; + splice(@{$hHunk->{lines}}, $i--, 1); + $hHunk->{count}--; + next; + } + + # Otherwise replace the addition with our text. ;-) + $$line =~ s/^\+(\s*).*systemd.*(\s*)$/+${1}${our_text}${2}/; + } + } + + return 1; +} + + +# ----------------------------------------------------------------------- +# --- Checkout the given refid on $upstream_path --- +# --- Returns 1 on success, 0 otherwise. --- +# ----------------------------------------------------------------------- +sub checkout_upstream { + my ($commit) = @_; + my $errmsg = ""; + my $new_commit = ""; + + # It is completely in order to not wanting to checkout a specific commit. + defined($commit) and length($commit) or return 1; + + # Save the previous commit + $previous_commit = qx(cd $upstream_path ; git rev-parse --short HEAD 2>&1); + if ($?) { + print "ERROR: Couldn't rev-parse $upstream_path HEAD\n"; + print "Exit Code : " . ($? >> 8) . "\n"; + print "$previous_commit\n"; + return 0; + } + + # Get the shortened commit hash of $commit + $new_commit = qx(cd $upstream_path ; git rev-parse --short "$commit" 2>&1); + if ($?) { + print "ERROR: Couldn't rev-parse $upstream_path \"$commit\"\n"; + print "Exit Code : " . ($? >> 8) . "\n"; + print "$new_commit\n"; + return 0; + } + + # Now check it out, unless we are already there: + if ($previous_commit ne $new_commit) { + $errmsg = qx(cd $upstream_path ; git checkout "$new_commit" 2>&1); + if ($?) { + print "ERROR: Couldn't checkout \"new_commit\" in $upstream_path\n"; + print "Exit Code : " . ($? >> 8) . "\n"; + print "$errmsg\n"; + return 0; + } + } + + return 1; +} + +# ----------------------------------------------------------------------- +# --- Completely clean up the current %hFile data structure. --- +# ----------------------------------------------------------------------- +sub clean_hFile { + defined($hFile{count}) or return 1; + + for (my $i = $hFile{count} - 1; $i > -1; --$i) { + defined($hFile{hunks}[$i]) and undef(%{$hFile{hunks}[$i]}); + } + + $hFile{count} = 0; + $hFile{hunks} = [ ]; + $hFile{output} = [ ]; + + return 1; +} + +# ----------------------------------------------------------------------- +# --- Builds the diff between source and target file, and stores all --- +# --- hunks in $hFile{hunks} - if any. --- +# --- Returns 1 on success and 0 if the files are the same. --- +# ----------------------------------------------------------------------- +sub diff_hFile { + + # Do they differ at all? + `diff -qu "$hFile{source}" "$hFile{target}" 1>/dev/null 2>&1`; + $? or print "same\n" and return 0; + + # Shell and meson files must be prepared. See prepare_meson() + ( $hFile{source} =~ m/meson/ or + $hFile{source} =~ m/\.in$/ or + $hFile{source} =~ m/\.pl$/ or + $hFile{source} =~ m/\.sh$/ ) and prepare_shell; + + # Let's have two shortcuts: + my $src = $hFile{source}; + my $tgt = $hFile{target}; + my $prt = $hFile{part}; + + # Now the diff can be built ... + my @lDiff = `diff -u "$src" "$tgt"`; + + # ... the head of the output can be generated ... + @{$hFile{output}} = splice(@lDiff, 0, 2); + chomp $hFile{output}[0]; # These now have absolute paths, and source meson files have a + chomp $hFile{output}[1]; # .pwx extensions. That is not what the result shall look like. + $hFile{output}[0] =~ s,$src,a/$prt,; # But we have $hFile{part}, which is already the + $hFile{output}[1] =~ s,$tgt,b/$prt,; # relative file name of the file we are processing. + + # ... and the raw hunks can be stored. + for (my $line_no = 1; $line_no < scalar @lDiff; ++$line_no) { + ("@@" eq substr($lDiff[$line_no], 0, 2)) + and (build_hHunk(splice(@lDiff, 0, $line_no)) or return 0) + and $line_no = 0; + } + scalar @lDiff and build_hHunk(@lDiff); + + return 1; +} + +# ----------------------------------------------------------------------- +# --- Finds all relevant files and store them in @wanted_files --- +# --- Returns 1 on success, 0 otherwise. --- +# ----------------------------------------------------------------------- +sub generate_file_list { + + # Do some cleanup first. Just to be sure. + `rm -rf build`; + `find -iname '*.orig' -or -iname '*.bak' -or -iname '*.rej' -or -iname '*~' -or -iname '*.gc??' | xargs rm -f`; + + # Build wanted files hash + while (my $want = shift @wanted_files) { + $want =~ m,^\./, or $want = "./$want"; + $hWanted{$want} = 1; + $want =~ s/elogind/systemd/g; + $hWanted{$want} = 1; + $have_wanted = 1; + } + + # The idea now is, that we use File::Find to search for files + # in all legal directories this program allows. Checking against + # the built %hWanted ensures that a user provided list of files + # is heeded. + for my $xDir ("docs", "factory", "m4", "man", "po", "shell-completion", "src", "tools") { + if ( -d "$xDir" ) { + find(\&wanted, "$xDir"); + } + } + + # Just to be sure... + scalar @source_files + or print("ERROR: No source files found? Where the hell are we?\n") + and return 0; + + # Get the maximum file length and build $file_fmt + my $mlen = 0; + for my $f (@source_files) { + length($f) > $mlen and $mlen = length($f); + } + $file_fmt = sprintf("%%-%d%s", $mlen, "s"); + + return 1; +} + +# ------------------------------------------------------------------------ +# --- Generate the "@@ -xx,n +yy,m @@" hunk header line out of $hHunk. --- +# --- Returns the generated string, with 0 values if $hHunk is undef. --- +# --- IMPORTANT: This function does *NOT* prune $hHunk->{lines} ! --- +# ------------------------------------------------------------------------ +sub get_hunk_head { + my ($offset) = @_; + + my $src_len = 0; + my $tgt_len = 0; + my $lCount = $hHunk->{count}; + my $src_start = $hHunk->{src_start}; + my $tgt_start = defined($offset) ? $src_start + $$offset : $hHunk->{tgt_start}; + + for (my $i = 0; $i < $lCount; ++$i) { + if ($hHunk->{lines}[$i] =~ m/^\+/ ) { + $tgt_len++; + } elsif ($hHunk->{lines}[$i] =~ m/^\-/ ) { + $src_len++; + } else { + $src_len++; + $tgt_len++; + } + } + + # If an offset reference was given, add back the size diff + defined($offset) + and $$offset += $tgt_len - $src_len; + + return sprintf("@@ -%d,%d +%d,%d @@", $src_start, $src_len, $tgt_start, $tgt_len); +} + +# ----------------------------------------------------------------------- +# --- Whenever a check finds an illegal situation, it has to call --- +# --- this subroutine which terminates the progress line and creaes --- +# --- an entry in @lFails. --- +# --- Param: An error message, preferably with the name of the failed --- +# --- check. --- +# --- Return: Always zero. --- +# ----------------------------------------------------------------------- +sub hunk_failed { + my ($msg) = @_; + my $num = scalar @lFails; + + # Generate entry: + push @lFails, { + hunk => [ get_hunk_head ], + msg => $msg, + part => $hFile{part} + }; + + # Add the hunk itself + for my $line (@{$hHunk->{lines}}) { + push @{$lFails[$num]{hunk}}, $line; + } + + # And terminate the progress line: + print "$msg\n"; + + return 0; +} + +# ----------------------------------------------------------------------- +# --- Check the current $hHunk whether it still does anything. --- +# --- While being at it, prune it to what a diff needs: --- +# --- 3 lines before the first and 3 lines after the last change. --- +# --- Returns 1 if at least one change was found, 0 otherwise. --- +# ----------------------------------------------------------------------- +sub hunk_is_useful { + + # Early exits: + defined($hHunk) or return 0; + $hHunk->{useful} or return 0; + + # Go through the lines and see whether we have any changes + $hHunk->{useful} = 0; + + for (my $i = 0; $i < $hHunk->{count}; ++$i) { + if ($hHunk->{lines}[$i] =~ m/^[-+]/ ) { + $hHunk->{useful} = 1; + return 1; + } + } + + return 0; +} + +# ----------------------------------------------------------------------- +# --- parse the given list for arguments. --- +# --- returns 1 on success, 0 otherwise. --- +# --- sets global $show_help to 1 if the long help should be printed. --- +# ----------------------------------------------------------------------- +sub parse_args { + my @args = @_; + my $result = 1; + + for (my $i = 0; $i < @args; ++$i) { + + # Check for -c|--commit option + # ------------------------------------------------------------------------------- + if ($args[$i] =~ m/^-(?:c|-commit)$/) { + if ( ( ($i + 1) >= @args ) + || ( $args[$i+1] =~ m,^[-/.], ) ) { + print "ERROR: Option $args[$i] needs a refid as argument!\n\nUsage: $USAGE_SHORT\n"; + $result = 0; + next; + } + $wanted_commit = $args[++$i]; + } + + # Check for -f|--file option + # ------------------------------------------------------------------------------- + elsif ($args[$i] =~ m/^-(?:f|-file)$/) { + if ( ( ($i + 1) >= @args ) + || ( $args[$i+1] =~ m,^[-], ) ) { + print "ERROR: Option $args[$i] needs a path as argument!\n\nUsage: $USAGE_SHORT\n"; + $result = 0; + next; + } + push @wanted_files, split(/,/, $args[++$i]); + } + + # Check for -h|--help option + # ------------------------------------------------------------------------------- + elsif ($args[$i] =~ m/^-(?:h|-help)$/) { + $show_help = 1; + } + + # Check for unknown options: + # ------------------------------------------------------------------------------- + elsif ($args[$i] =~ m/^-/) { + print "ERROR: Unknown option \"$args[$1]\" encountered!\n\nUsage: $USAGE_SHORT\n"; + $result = 0; + } + + # Everything else is considered to the path to upstream + # ------------------------------------------------------------------------------- + else { + # But only if it is not set, yet: + if (length($upstream_path)) { + print "ERROR: Superfluous upstream path \"$args[$1]\" found!\n\nUsage: $USAGE_SHORT\n"; + $result = 0; + next; + } + if ( ! -d "$args[$i]") { + print "ERROR: Upstream path \"$args[$i]\" does not exist!\n\nUsage: $USAGE_SHORT\n"; + $result = 0; + next; + } + $upstream_path = $args[$i]; + } + } ## End looping arguments + + # If we have no upstream path now, show short help. + if ($result && !$show_help && !length($upstream_path)) { + print "ERROR: Please provide a path to upstream!\n\nUsage: $USAGE_SHORT\n"; + $result = 0; + } + + # If any of the wanted files do not exist, error out. + if ($result && !$show_help && defined($wanted_files[0])) { + foreach my $f (@wanted_files) { + -f $f or print "ERROR: $f does not exist!\n" and $result = 0; + } + } + + return $result; +} ## parse_srgs() end + +# ----------------------------------------------------------------------- +# --- Prepare shell and meson files for our processing. --- +# --- If this is a shell or meson file, we have to adapt it first: --- +# --- To be able to use our patch building system, the files use the --- +# --- same masking technology as the C files. But as these are not --- +# --- handled by any preprocessor, it is necessary to comment out all --- +# --- masked blocks. --- +# --- If we do not do this, diff creates patches which move all --- +# --- commented blocks behind the #endif and uncomment them. --- +# ----------------------------------------------------------------------- +sub prepare_shell { + my $in = $hFile{source}; + my $out = $in . ".pwx"; + my @lIn = (); + my @lOut = (); + + # Leech the source file + if (open(my $fIn, "<", $in)) { + @lIn = <$fIn>; + close($fIn); + } else { + die("$in can not be opened for reading! [$!]"); + } + + # Now prepare the output, line by line. + my $is_block = 0; + my $is_else = 0; + my $line_no = 0; + for my $line (@lIn) { + + chomp $line; + ++$line_no; + + if ($line =~ m,^[ ]?#if 0 /* .*elogind.*,) { + if ($is_block) { + print "ERROR: $in:$line_no : Mask start in mask!\n"; + die("Illegal file"); + } + $is_block = 1; + } elsif ($is_block && ($line =~ m,^[ ]?#else,) ) { + $is_else = 1; + } elsif ($line =~ m,^[ ]?#endif /* 0,) { + if (!$is_block) { + print "ERROR: $in:$line_no : Mask end outside mask!\n"; + die("Illegal file"); + } + $is_block = 0; + $is_else = 0; + } elsif ($is_block && !$is_else) { + $line =~ s,^#\s?,,; + } + + push @lOut, $line; + } + + # Now write the outfile: + if (open(my $fOut, ">", $out)) { + for my $line (@lOut) { + print $fOut "$line\n"; + } + close($fOut); + } else { + die("$out can not be opened for writing! [$!]"); + } + + # The temporary file is our new source + $hFile{source} = $out; + + return 1; +} + +# ----------------------------------------------------------------------- +# --- Remove unused prefix and postfix lines. Recalculates offsets. --- +# ----------------------------------------------------------------------- +sub prune_hunk { + + # Early exits: + defined($hHunk) or return 0; + $hHunk->{useful} or return 0; + + # Go through the lines and see what we've got. + my $prefix = 0; + my $postfix = 0; + my $changed = 0; ## Set to 1 once the first change was found. + + for (my $i = 0; $i < $hHunk->{count}; ++$i) { + if ($hHunk->{lines}[$i] =~ m/^[-+]/ ) { + $changed = 1; + $postfix = 0; + } else { + $changed or ++$prefix; + ++$postfix; + } + } + + # Now let's prune it: + if ($prefix > 3) { + $prefix -= 3; + splice(@{$hHunk->{lines}}, 0, $prefix); + $hHunk->{src_start} += $prefix; + $hHunk->{count} -= $prefix; + } + if ($postfix > 3) { + $postfix -= 3; + splice(@{$hHunk->{lines}}, $hHunk->{count} - $postfix, $postfix); + $hHunk->{count} -= $postfix; + } + + return 1; +} + + +# ----------------------------------------------------------------------- +# --- Unprepare shell (and meson) files after our processing --- +# --- In prepare_shell() we have commented in all content between our --- +# --- elogind markers, to help diff not to juggle our blocks around. --- +# --- Now these blocks must be commented out again. --- +# --- We have an advantage and a disadvantage here. On one hand, we --- +# --- can do the changes within $hFile{output}, as that is the final --- +# --- patch. On the other hand, we do not know whether really all --- +# --- lines in the source where commented out. The latter means, that --- +# --- if we blindly comment out all block lines, the resulting patch --- +# --- may fail. We therefore write the temporary .pwx file back, and --- +# --- and ensure that all lines are commented out. --- +# ----------------------------------------------------------------------- +sub unprepare_shell { + my $in = $hFile{source}; + my $out = substr($in, 0, -4); + my @lIn = (); + my @lOut = (); + + # Leech the temporary file + if (open(my $fIn, "<", $in)) { + @lIn = <$fIn>; + close($fIn); + } else { + die("$in can not be opened for reading! [$!]"); + } + + # Now prepare the output, line by line. + my $is_block = 0; + my $is_else = 0; + my $line_no = 0; + for my $line (@lIn) { + + chomp $line; + ++$line_no; + + if ($line =~ m,^[ ]?#if 0 /* .*elogind.*,) { + if ($is_block) { + print "ERROR: $in:$line_no : Mask start in mask!\n"; + die("Illegal file"); + } + $is_block = 1; + } elsif ($is_block && ($line =~ m,^[ ]?#else,) ) { + $is_else = 1; + } elsif ($line =~ m,^[ ]?#endif /* 0,) { + if (!$is_block) { + print "ERROR: $in:$line_no : Mask end outside mask!\n"; + die("Illegal file"); + } + $is_block = 0; + $is_else = 0; + } elsif ($is_block && !$is_else + && ("# " ne substr($line, 0, 2)) ) { + $line = "# " . $line; + } + + push @lOut, $line; + } + + # Now write the outfile: + if (open(my $fOut, ">", $out)) { + for my $line (@lOut) { + print $fOut "$line\n"; + } + close($fOut); + } else { + die("$out can not be opened for writing! [$!]"); + } + + # Remove the temporary file + unlink($in); + + # Now prepare the patch. It is like above, but with less checks. + # We have to move out the lines first, and then write them back. + @lIn = (); + $is_block = 0; + $is_else = 0; + @lIn = splice(@{$hFile{output}}); + for my $line (@lIn) { + $line =~ m,^[ ]+#endif /* 0, and $is_block = 0; + $is_block or $is_else = 0; + $line =~ m,^[ ]+#if 0 /* .*elogind.*, and $is_block = 1; + $is_block and $line =~ m,^[ ]?#else, and $is_else = 1; + $is_block and (!$is_else) and (" # " ne substr($line, 0, 2)) + and $line = " #" . $line; + + push @{$hFile{output}}, $line; + } + + # Now source is the written back original: + $hFile{source} = $out; + + return 1; +} + + +# ----------------------------------------------------------------------- +# --- Analyze the hunk and map all include changes --- +# --- The gathered knowledge is used in check_includes(), see there --- +# --- for the rules applied. --- +# ----------------------------------------------------------------------- +sub read_includes { + + # Early exits: + defined($hHunk) or return 1; + $hHunk->{useful} or return 1; + + # We must know when "needed by elogind blocks" start + my $in_elogind_block = 0; + + for (my $i = 0; $i < $hHunk->{count}; ++$i) { + my $line = \$hHunk->{lines}[$i]; ## Shortcut + + # Note down removes of includes we commented out + if ( $$line =~ m,^-\s*//+\s*#include\s+([<"'])([^>"']+)[>"'], ) { + $hIncs{$2}{remove} = { hunkid => $hHunk->{idx}, lineid => $i, sysinc => $1 eq "<" }; + next; + } + + # Note down inserts of possibly new includes we might want commented out + if ( $$line =~ m,^\+\s*#include\s+([<"'])([^>"']+)[>"'], ) { + $hIncs{$2}{insert} = { hunkid => $hHunk->{idx}, lineid => $i, spliceme => 0, sysinc => $1 eq "<" }; + next; + } + + # Note down removals of includes we explicitly added for elogind + if ( $in_elogind_block + && ($$line =~ m,^-\s*#include\s+([<"'])([^>"']+)[>"'], ) ) { + $hIncs{$2}{elogind} = { hunkid => $hHunk->{idx}, lineid => $i }; + next; + } + + # elogind include blocks are started by a comment featuring "needed by elogind" + if ($$line =~ m,^-\s*//+.*needed by elogind.*,i) { + $in_elogind_block = 1; + next; + } + + # elogind include blocks end, when the first not removed line is found + $in_elogind_block and ($$line =~ m,^[ +]$,) and $in_elogind_block = 0; + } + + return 1; +} + + +# ----------------------------------------------------------------------- +# --- Splice all includes that were marked for splicing. --- +# --- This is not as easy as it seems. It can be, that if we just go --- +# --- through the %hIncs keys, that we splice one include that is --- +# --- before another. That leads to the wrong line to be spliced, or --- +# --- worse, the splicing being attempted out of bounds. --- +# ----------------------------------------------------------------------- +sub splice_includes { + + # First build a tree of the includes to splice: + my %incMap = (); + for my $inc (keys %hIncs) { + if ($hIncs{$inc}{insert}{spliceme}) { + my $hId = $hIncs{$inc}{insert}{hunkid}; + my $lId = $hIncs{$inc}{insert}{lineid}; + + # Sanity checks: + $hId > -1 or print "splice_includes : Inc $inc has Hunk Id -1!\n" and next; + if ( -1 == $lId ) { + $hHunk = $hFile{hunks}[$hId]; + hunk_failed("splice_includes: $inc has line id -1!"); + next; + } + if ( $lId >= $hFile{hunks}[$hId]{count} ) { + $hHunk = $hFile{hunks}[$hId]; + hunk_failed("splice_includes: $inc line id $lId/$hFile{hunks}[$hId]{count}!"); + next; + } + + # Record the include line + $incMap{$hId}{$lId} = 1; + } + } + + # Now we can do the splicing in an ordered way: + for my $hId (sort { $a <=> $b } keys %incMap) { + # Go through the lines in reverse, that should be save: + for my $lId (sort { $b <=> $a } keys %{$incMap{$hId}}) { + splice(@{$hFile{hunks}[$hId]{lines}}, $lId, 1); + $hFile{hunks}[$hId]{count}--; + } + } + + return 1; +} + + +# Callback function for File::Find +sub wanted { + -f $_ and ( (0 == $have_wanted) + or defined($hWanted{$File::Find::name}) + or defined($hWanted{"./$File::Find::name"}) ) + and (! ($_ =~ m/\.pwx$/ )) + and push @source_files, $File::Find::name; + return 1; +} + |