#!/usr/bin/env perl

#---------------------------------------------------------------------
#                          file information
#---------------------------------------------------------------------

# File:     fix-nss-pkg-nspr-inc.pl
# Purpose:  "nss" build-related utility
# License:  BSD-style (for this file only)
# Revision: 170706

#---------------------------------------------------------------------
#                           important note
#---------------------------------------------------------------------

# This software is provided on an  AS IS basis with ABSOLUTELY NO WAR-
# RANTY.  The  entire risk as to the  quality and  performance of  the
# software is with you.  Should the software prove defective,  you as-
# sume the cost of all necessary  servicing, repair or correction.  In
# no event will any of the developers,  or any other party, be  liable
# to anyone for damages arising out of use of the software, or inabil-
# ity to use the software.

#---------------------------------------------------------------------
#                            module setup
#---------------------------------------------------------------------

require 5.10.1;
use strict;
use Carp;
use warnings;
use Cwd;
use File::Find;
                                # Trap warnings
$SIG {__WARN__} = sub { die @_; };

#---------------------------------------------------------------------
#                           basic constants
#---------------------------------------------------------------------

use constant ZERO  => 0;        # Zero
use constant ONE   => 1;        # One
use constant TWO   => 2;        # Two

use constant FALSE => 0;        # Boolean FALSE
use constant TRUE  => 1;        # Boolean TRUE

#---------------------------------------------------------------------
#                         program parameters
#---------------------------------------------------------------------

my $PROGNAME;                   # Program name (without path)
   $PROGNAME =  $0;
   $PROGNAME =~ s@.*/@@;

my $PURPOSE       = 'nss package build utility';
my $REVISION      = '170706';
my $USE_LESS      = FALSE;      # Flag: Use "less" for usage text

my $MAX_FILE_SIZE = 15 * 1024 * 1024;
my $FlagKeepTimes = FALSE;      # Flag: Preserve timestamps

#---------------------------------------------------------------------
#                          global variables
#---------------------------------------------------------------------

my $CWD;                        # Current working directory
my @Paths = ();                 # List of pathname entries

#---------------------------------------------------------------------

# @NSPRH  should contain  a  list of all  ".h" files that are, or will
# be, stored  in the installed "nspr" package tree, omitting path com-
# ponents. Order isn't significant.  The list might need to be updated
# if "nspr" is updated.

my @NSPRH = qw
(
    nspr.h      plarena.h       plarenas.h      plbase64.h
    plerror.h   plgetopt.h      plhash.h        plstr.h
    pratom.h    prbit.h         prclist.h       prcmon.h
    prcountr.h  prcpucfg.h      prcvar.h        prdtoa.h
    prenv.h     prerr.h         prerror.h       prinet.h
    prinit.h    prinrval.h      prio.h          pripcsem.h
    prlink.h    prlock.h        prlog.h         prlong.h
    prmem.h     prmon.h         prmwait.h       prnetdb.h
    prolock.h   prpdce.h        prprf.h         prproces.h
    prrng.h     prrwlock.h      prshm.h         prshma.h
    prsystem.h  prthread.h      prtime.h        prtpool.h
    prtrace.h   prtypes.h       prvrsion.h      prwin16.h
    pprio.h     pprthred.h      prpriv.h

    pralarm.h   probslet.h      protypes.h      prsem.h
);

my $NSPRH = join '|', @NSPRH;

#---------------------------------------------------------------------
#                             usage text
#---------------------------------------------------------------------

                                # Label must be single-quoted here
my $USAGE_TEXT = << 'END_OF_USAGE_TEXT';
Usage: $PROGNAME --run

This is an "nss" package build utility script.  It must be run, before
the package is compiled,  in  the source directory  which contains the
directory tree named "nss".  It makes some patches to the files stored
in the "nss" tree.
END_OF_USAGE_TEXT

#---------------------------------------------------------------------
#                         usage-text routine
#---------------------------------------------------------------------

# "UsageError" prints  usage text for the current program,  then term-
# inates the program with exit status one.

#---------------------------------------------------------------------

sub UsageError
{
    $USAGE_TEXT =~ s@^\s+@@s;
    $USAGE_TEXT =~ s@\$PROGNAME@$PROGNAME@g;

    $USAGE_TEXT = << "END";     # "END" must be double-quoted here
$PROGNAME $REVISION - $PURPOSE

$USAGE_TEXT
END
    $USAGE_TEXT =~ s@\s*\z@\n@s;

    if ($USE_LESS && (-t STDOUT) && open (OFD, "|/usr/bin/less"))
    {
                                # "END" must be double-quoted here
        $USAGE_TEXT = << "END";
To exit this "help" text, press "q" or "Q".  To scroll up or down, use
PGUP, PGDN, or the arrow keys.

$USAGE_TEXT
END
        print OFD $USAGE_TEXT;
        close OFD;
    }
    else
    {
        print "\n", $USAGE_TEXT, "\n";
    }

    exit ONE;
}

#---------------------------------------------------------------------
#                    directory traversal routines
#---------------------------------------------------------------------

# "AdjustList"  is a  "find"-compatible  list preprocessor.  This pre-
# processor is used by a "find" command in the main routine.

#---------------------------------------------------------------------

sub AdjustList
{
    my (@list) = @_;
    grep { !-l && !m@^\.{1,2}\z@; } @list;
}

#---------------------------------------------------------------------

# "ProcEntry"  is a  "find"-compatible file processor.  This processor
# is used by a "find" command in the main routine.

# Note: This routine must preserve "$_".

#---------------------------------------------------------------------

sub ProcEntry
{
    my $path;                   # Absolute pathname
                                # Filename (provided by "File::Find")
    my $name = "$File::Find::name";

    $name =~ s@^(\./)+@@;       # Strip leading occurrences of "./"
    $path =  "$CWD/$name";      # Absolute pathname
    $path =~ s@^//+@/@;         # Kludge

    push (@Paths, $path);       # Save path
    undef;
}

#---------------------------------------------------------------------
#                            main routine
#---------------------------------------------------------------------

sub Main
{
    my $OptRun;                 # Flag: "--run" option
    my $BaseDir;                # Base directory
    my $OutPath;                # Pathname to display
    my $LEN_CWD;                # Length of CWD path

    my $data;                   # Data buffer
    my $path;                   # Absolute path
    my $str;                    # Scratch

#---------------------------------------------------------------------
# Initial setup.

    select (STDERR); $| = ONE;
    select (STDOUT); $| = ONE;
                                # Fix problems for some filesystems
    $File::Find::dont_use_nlink = ONE;

    undef $/;

#---------------------------------------------------------------------
# Process the command line.

    for my $arg (@ARGV)
    {
        if ($arg =~ m@^-+run\z@i)
        {
            $OptRun = TRUE;
        }
        else
        {
            die "Invalid argument: $arg\n";
        }
    }
                                # Check for required switch
    &UsageError() unless $OptRun;

#---------------------------------------------------------------------
# Directory setup operations.

    if (!chdir ('nss') || (!-d 'nss-tool'))
    {
        print STDERR << "END";

Error: $PROGNAME must be executed:
in the top-level directory of an "nss-3-..." source tree;  i.e., in the
directory which contains the  directory named "nss".  This must be done
before the package is compiled.  The script  makes  some patches to the
files stored in the "nss" tree.
END
        exit (ONE);
    }

    $BaseDir =  '.';            # Base is '.' (current directory)

                                # The  next  four  statements  have no
                                # effect now,  but they  may be useful
                                # later
    $BaseDir =  '.' if !defined ($BaseDir);
    $BaseDir =~ s@^(.+)/$@$1@;
    while ($BaseDir =~ s@/(\./)+@/@g) {}
    while ($BaseDir =~ s@/[^/]+/\.\./@/@g) {}

                                # Go to base directory
    chdir ($BaseDir) ||
        die "Error #01: Can't access directory: $!\n$BaseDir";

    $CWD     = getcwd();        # Get absolute path for directory
    $LEN_CWD = length ($CWD);   # Save length of absolute path

#---------------------------------------------------------------------
# Future change: Document this code.

    my $base;
    ($base) = $CWD =~ m@/([^/]+)\z@;
    die "Error #02\n" if !defined ($base);

#---------------------------------------------------------------------
# Build a pathname list.

    my %args =
    (
        bydepth    => TRUE ,
        preprocess => \&AdjustList ,
        wanted     => \&ProcEntry
    );

    find (\%args, '.');
    @Paths = sort @Paths;

#---------------------------------------------------------------------
# Process directory tree.

    for $path (@Paths)
    {
        $str     =  $path;
        $str     =~ s@^(.{$LEN_CWD})@@;
        $str     =~ s@^/+@@;
        $OutPath =  $str;

# Note:  ".pdf" files  *must* be skipped,  because the "-T" test isn't
# reliable for PDFs. For most other file types, filename-based exclus-
# ion isn't strictly necessary,  but it speeds things up,  because the
# time required for "-l", "-f", and "-T" tests may be  significant for
# large directory trees.

        next if $path =~
m@\.(bz2|gif|gz|jpe?g|mp[234]|pdf|png|tar|tgz|xls|wav|wmv|zip)\z@i;

        next if -l $path;
        next unless $path =~ m@\.([ch]|cc)\z@i;

        my @stat = stat $path;
        next if !-f _;
        next if !-T _;
        next if $stat [7] > $MAX_FILE_SIZE;

        open (IFD, "<$path") || die "Error #03: $OutPath\n";
        binmode (IFD);
        $data = <IFD>;
        $data = "" unless defined ($data);
        close (IFD);

        next if $data =~ m@sub\s+ProcEntry@;
        my $orig_data = $data;

#---------------------------------------------------------------------
# Make the appropriate change.

        my $IPAT =  '#include[\011\040]+';
        my $INC  =  '#include';
        my $PR   =  'private';
        my $QC   =  '["<>]';
        $data    =~
            s@$IPAT${QC}($NSPRH)${QC}@$INC <nspr/$1>@g;
        $data    =~
            s@$IPAT${QC}$PR/($NSPRH)${QC}@$INC <nspr/$PR/$1>@g;

#---------------------------------------------------------------------
# Save changes to current file (if necessary).

        next if $data eq $orig_data;

        my ($mtime) = (stat $path) [9];
        die "Error #04: $path\n" if !defined ($mtime);

        open (OFD, ">$path") || die "Error #05: $path\n";
        print OFD $data;
        close (OFD) || die "Error #06: $path\n";
        utime ($mtime, $mtime, $path) if $FlagKeepTimes;
        print "$path\n";
    }

#---------------------------------------------------------------------
# Wrap it up.

                                # Print a status message
    print "$PROGNAME done\n";
    undef;
}

#---------------------------------------------------------------------
#                            main program
#---------------------------------------------------------------------

&Main();                        # Call the main routine
exit ZERO;                      # Normal exit
