-#! /usr/bin/perl -w
-
-## Copyright (C) 1998 Hrvoje Niksic
-## Modification by Zeljko Boros (block entries, removing old entries)
-## More options and use strict by Zoran Dzelajlija on 2004-02-24
-##
-## This program is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published by
-## the Free Software Foundation; either version 2 of the License, or
-## (at your option) any later version.
-##
-## This program is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with this program; if not, write to the Free Software
-## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-## altered by ddzeko, 2005-03-18
-## -> reformated code, introduced coding conventions
-## -> increased reliability through atomic actualization
-## -> everything is done in the memory - more mem, less i/o
-## -> removed append mode - in-place editing considered harmfull
-## -> added in-place mode as option
-
-use strict;
-use Fcntl qw(:DEFAULT :flock :seek);
-
-sub DEBUG () { 0 };
-
-# coding convention: CamelCase vars are global here
-#
-my ($ProgramName, $UsageLong, $UsageShort);
-
-# Looks nicer without the slashes and dots
-($ProgramName = $0) =~ s!.*/!!; # strip dir
-$ProgramName =~ s!\.[^.]+$!!; # strip last ext
-
-# 34567890 34567890 34567890 34567890 34567890 34567890 34567890 345678
-
-$UsageLong = "
- $ProgramName -- versatile line-based file updating tool
- usually used by package configuration scripts
-
- Usage: $ProgramName [options] PACKAGE FILE < stdin-content
-
-
- - General options:
-
- -r | --remove Remove entry PACKAGE from FILE.
- Default is to add lines from stdin.
-
- -m | --allow-multiple Allow multiple blocks of the same type.
- By default, old blocks are replaced with
- the new one.
-
- -h | --help Print this message.
-
- - Placement control:
-
- -t | --insert-on-top Insert stdin-content block on top.
- The default is to add it at the bottom.
-
- -i | --insert-after x Insert after this/matching line.
- -f | --insert-before x Insert before this/matching line.
- -R | --regexp-match Use regexp mode for line matching.
-
- - Manipulating block marks:
-
- -c | --comment x Use alternative comment char/string.
- The default is shell-style \#-sign.
-
- --comment-end x Use this marker for comment ending.
- The default is none. Ie. '-->', '*/'.
-
- -b | --begin-mark x Block starting mark (ie. 'service ftp')
- -e | --end-mark x Block ending mark (ie. '}')
-
- These will delete to the end of the file if no end mark
- is found. The deletion is otherwise not greedy and stops
- at the first end mark found. You can use \%P to insert
- the PACKAGE argument, and \%\% to insert a literal \% sign
- into the mark.
-
- - File handling options:
-
- -p | --in-place Try to preserve original inode.
- -n | --no-close Do not close and reopen file when
- editing it in place.
-
- Options marked with 'x' take single argument. Others do not.
-
-\n";
-
-$UsageShort = "$ProgramName -- versatile line-based file updating tool\n";
-$UsageShort .= " Options: [-r] [-m] [-i AFTER|-f BEFORE|-t] [-b START -e STOP] [-c CHAR] PACKAGE FILE\n";
-$UsageShort .= " or type $ProgramName --help to be choked with help.\n";
-
-my ($MarkBegin, $MarkEnd, $Trailer, $ParamBegin, $ParamEnd, $Placement,
- $Package, $File, $Block, $Multi, $InsertRemove, $Comment, $CommentEnd,
- $MatchLine, $RegexpMatch, $StdinContent, $NewContent, $InPlace,
- $NoClose, $FileHandle, @Lines);
-
-# Placement modes
-use constant APPEND_AT_END => 0;
-use constant INSERT_BEFORE => 1;
-use constant INSERT_AFTER => 2;
-use constant INSERT_ON_TOP => 3;
-
-# InsertRemove modes
-use constant DO_REMOVE => 0;
-use constant DO_INSERT => 1;
-
-# Operation defaults
-$InsertRemove = DO_INSERT;
-$Placement = APPEND_AT_END;
-$Comment = '#';
-$CommentEnd = '';
-$MatchLine = '';
-$InPlace = 0;
-$NoClose = 0;
-
-while (@ARGV) {
- $_ = shift;
- if (/^-c$/ || /^--comment$/) {
- defined ($Comment = shift)
- || die "$ProgramName: `-c|--comment' must be followed by an argument\n";
- }
- elsif (/^--comment-end$/) {
- defined ($CommentEnd = shift)
- || die "$ProgramName: `--comment-end' must be followed by an argument\n";
- }
- elsif (/^-r$/ || /^--remove$/) {
- $InsertRemove = DO_REMOVE;
- }
- elsif (/^-m$/ || /^--allow-multi(?:ple)?$/) {
- $Multi = 1;
- }
- elsif (/^-b$/ || /^--begin(?:-mark)?$/) {
- defined ($ParamBegin = shift)
- || die "$ProgramName: '-b|--begin-mark' must be followed by an argument\n";
- $Block = 1;
- }
- elsif (/^-e$/ || /^--end(?:-mark)?$/) {
- defined ($ParamEnd = shift)
- || die "$ProgramName: '-e|--end-mark' must be followed by an argument\n";
- $Block = 1;
- }
- elsif (/^-i$/ || /^--insert-after$/) {
- defined($MatchLine = shift)
- || die "$ProgramName: '-i|--insert-after' must be followed by an argument\n";
- $Placement = INSERT_AFTER;
- }
- elsif (/^-f$/ || /^--insert-before$/) {
- defined($MatchLine = shift)
- || die "$ProgramName: '-f|--insert-before' must be followed by an argument\n";
- $Placement = INSERT_BEFORE;
- }
- elsif (/^-t$/ || /^--insert-on-top$/) {
- $Placement = INSERT_ON_TOP;
- }
- elsif (/^-R$/ || /^--regexp(?:-match|-mode)?$/) {
- $RegexpMatch = 1;
- }
- elsif (/^-h$/ || /^--help$/) {
- die $UsageLong;
- }
- elsif (/^-p$/ || /^--in-place$/) {
- $InPlace = 1;
- }
- elsif (/^-n$/ || /^--no-close$/) {
- $NoClose = 1;
- }
- elsif (/^-/) {
- die "$ProgramName: Unrecognized option \`$_'\n";
- }
- else {
- unshift(@ARGV, $_);
- last;
- }
-}
-
-$Package = shift or die $UsageShort;
-$File = shift or die $UsageShort;
-
-# prepare block begin and end marks
-#
-(! $Block || ($ParamBegin && $ParamEnd))
- or die ("$ProgramName: must provide both begin and end marks.\n");
-#
-if ($Block) {
- $ParamBegin =~ s, %P , $Package ,gx;
- $ParamBegin =~ s, %% , % ,gx;
- $ParamEnd =~ s, %P , $Package ,gx;
- $ParamEnd =~ s, %% , % ,gx;
- $MarkBegin = $ParamBegin;
- $MarkEnd = $ParamEnd;
- $Trailer = '';
-} else {
- $MarkBegin = "$Comment Begin update by CARNet package $Package";
- $MarkEnd = "$Comment End update by CARNet package $Package";
- $Trailer = " -- DO NOT DELETE THIS LINE!".$CommentEnd;
-}
-
-DEBUG and print STDERR "MarkBegin='$MarkBegin'\nMarkEnd='$MarkEnd'\nTrailer='$Trailer'\n";
-
-# compile regexp if provided
-#
-if ($RegexpMatch) {
- # compile user's regexp
- eval { $MatchLine = qr<$MatchLine> };
-}
-else {
- # we'll do regexp matching anyway :)
- # just not with user's specials interfering
- eval { $MatchLine = qr<^\Q$MatchLine\E>o; };
-}
-if ($@) {
- die "$ProgramName: regexp compilation failed: $@ ($!)\n";
-}
-
-do_it() and exit(0);
-
-
-# main procedure
-# --------------
-
-sub do_it {
- slurp();
- if (DO_INSERT == $InsertRemove) {
- $StdinContent = &stdin_content;
- del() unless $Multi;
- add();
- }
- else {
- del();
- }
- actualize();
-}
-
-
-# subroutines
-# -----------
-
-sub slurp() {
- sysopen($FileHandle, $File, O_RDWR) or die "$ProgramName: Cannot open $File: $!\n";
- @Lines = <$FileHandle>; # slurp the whole file as lines
- close($FileHandle) unless ($InPlace && $NoClose);
-
- # If FILE does not have a trailing newline, be sure to add it
- # before appending anything else.
-
- if (@Lines and $Lines[$#Lines] !~ m/\n$/s) {
- $Lines[$#Lines] .= "\n";
- }
-}
-
-sub add() {
- if (APPEND_AT_END == $Placement) {
- # append stuff at the end of the file
- push(@Lines, $StdinContent);
- }
- elsif (INSERT_ON_TOP == $Placement) {
- # throw the stuff on top of the file
- unshift(@Lines, $StdinContent);
- }
- else {
- # insert stuff where needed
- my ($line, $lineNo, $added);
- $lineNo = 0;
- $added = 0;
- foreach $line (@Lines) {
- DEBUG and print STDERR "at: $line";
- if ($line =~ m/$MatchLine/) {
- DEBUG and print STDERR " - MATCH\n";
- if (INSERT_AFTER == $Placement) {
- DEBUG and print STDERR " - INSERT_AFTER\n";
- splice(@Lines, $lineNo + 1, 0, $StdinContent);
- }
- elsif (INSERT_BEFORE == $Placement) {
- DEBUG and print STDERR " - INSERT_BEFORE\n";
- splice(@Lines, $lineNo, 0, $StdinContent);
- }
- # once added and we're done
- $added = 1;
- last;
- }
- ++ $lineNo;
- }
- if (! $added) {
- warn "$ProgramName: Inserting lines at the end implicitly!\n";
- push(@Lines, $StdinContent);
- }
- }
- return scalar(@Lines); # whatever
-}
-
-sub del() {
- my ($mytrailer, $mybegin, $myend) =
- ($Trailer, $MarkBegin, $MarkEnd);
-
- # Make the strings regexp-friendly by quoting non-word chars.
- $mybegin =~ s/\W/\\$&/g;
- $myend =~ s/\W/\\$&/g;
- $mytrailer =~ s/\W/\\$&/g;
-
- my (@filtered);
- foreach (@Lines) {
- push (@filtered, $_)
- unless (/^$mybegin(?:$mytrailer)?$/o .. /^$myend(?:$mytrailer)?$/o);
- }
- DEBUG and print STDERR "Deleted ". (@Lines - @filtered) ." out of ".scalar(@Lines)." lines\n";
- @Lines = @filtered;
- return scalar(@Lines); # whatever
-}
-
-# written by ddzeko@srce.hr, 2005-03-18
-# to improve reliabilitah :)
-#
-sub actualize() {
- # put it all thogether
- my $newContent = join('', @Lines);
-
- unless (length($newContent)) {
- # safety exit in last second :)
- die "$ProgramName: New content empty -- aborting file alteration!\n";
- }
-
- if ($InPlace) {
- # Provdided as means of changing the file content
- # and keeping it's inode number still.
- # -------------------------------------------------------
- # this is dangerous since it can leave file in bad state
- # do not use this for highly critical files (ie. inittab)
- # -------------------------------------------------------
- # (REVISIT: add File::Copy call to back-up the file so
- # we can at least try undoing the file content change)
- # backup_copy();
- unless (fileno($FileHandle)) {
- sysopen($FileHandle, $File, O_WRONLY|O_TRUNC)
- or die "$ProgramName: Failed to open file '$File' for writing ($!)\n";
- }
- else {
- sysseek(*$FileHandle, 0, SEEK_SET)
- or die "$ProgramName: Failed to seek to the begining of file ($!)\n";
- }
- my $wb = syswrite($FileHandle, $newContent);
- if (! $wb or length($newContent) != $wb) {
- # FIXME: try restoring backup copy
- die "$ProgramName: Failed to write the content to '$File' ($!)\n";
- }
- if ($NoClose) {
- # this could be handy for files that had stuff appended
- # at the end of the file
- truncate($FileHandle, length($newContent))
- or die "$ProgramName: Failed to truncate the file ($!)\n";
- }
- close($FileHandle);
- }
- else {
- my ($file_new, $file_old) = ($File, $File);
- $file_new .= '.cp-update.new'; # our .new file
- $file_old .= '.cp-update.old'; # our .old file
- # write content in new file in single write op
- sysopen ($FileHandle, $file_new, O_CREAT|O_TRUNC|O_WRONLY)
- or die "$ProgramName: Failed to open file '$File' for writing ($!)\n";
- my $wb = syswrite($FileHandle, $newContent);
- if (! $wb or length($newContent) != $wb) {
- unlink($file_new);
- die "$ProgramName: Failed to write the content to '$File' ($!)\n";
- }
- close($FileHandle);
- # do the moving (should be atomic)
- rename($File, $file_old)
- or die "$ProgramName: Failed to rename file '$File' to '$file_old' ($!)\n";
- rename($file_new, $File)
- or die "$ProgramName: Failed to rename file '$file_new' to '$File' ($!)\n";
- unlink($file_old)
- or warn "$ProgramName: Failed to remove file '$file_old' ($!)\n";
- }
-}
-
-# return content from standard input
-sub stdin_content() {
- my ($stdin_content) = join('', <STDIN>);
- unless (length($stdin_content)) {
- die "$ProgramName: No stdin-content provided!\n";
- }
- if ($stdin_content !~ m/\n$/s) {
- # add trailing newline
- $stdin_content .= "\n";
- }
-
- my ($mytrailer, $mybegin, $myend) =
- ($Trailer, $MarkBegin, $MarkEnd);
-
- $mytrailer .= "\n";
- $mybegin .= $mytrailer;
- $myend .= $mytrailer;
-
- $stdin_content = ($mybegin . $stdin_content . $myend);
- return $stdin_content;
-}
-
-# create backup copy of altered file
-sub backup_copy() {
- die "$ProgramName: backup_copy() not implemented";
-}