Prva inačica za bullseye
[carnet-tools-cn.git] / cp-update
index fdbfaf9..5514ee6 100755 (executable)
--- a/cp-update
+++ b/cp-update
@@ -3,6 +3,7 @@
 ## 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
+## Quite a few improvements by Damir Dzeko on 2005-03-18 (see bellow)
 ##
 ## 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
 ## along with this program; if not, write to the Free Software
 ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 
-## $Id: cp-update,v 2.2 2004/02/24 jelly Exp $
+## 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 harmful
+## -> added in-place mode as option
 
 use strict;
+use Fcntl qw(:DEFAULT :flock :seek);
 
-my ($name, $usage);
-
-($name = $0) =~ s%.*/%%;   # Looks nicer without the slashes
-$usage =  "Usage:\n";
-$usage .= "$name [-r] [-m] [-i AFTER|-t] [-b START -e STOP] [-c CHAR] PACKAGE FILE\n";
-$usage .= "                 -r   Remove entry PACKAGE from FILE\n";
-$usage .= "                      Default is to add lines from stdin.\n";
-$usage .= "                 -m   Allow multiple blocks of the same type\n";
-$usage .= "                      By default, old blocks are replaced with the new one.\n";
-$usage .= "\n";
-$usage .= "                      Special placement of config blocks:\n";
-$usage .= "                 -i   Insert after this line\n";
-$usage .= "                 -t   Insert on top\n";
-$usage .= "                      The default is to append at the bottom.\n";
-$usage .= "\n";
-$usage .= "                      Manipulating block marks:\n";
-$usage .= "                 -c   Use alternative comment character (default: #)\n";
-$usage .= "                      Alternative block marks:\n";
-$usage .= "                 -b   Start mark (ie 'service ftp')\n";
-$usage .= "                 -e   Stop mark (ie. '}')\n";
-$usage .= "                      These will delete to the end of the file if no end mark\n";
-$usage .= "                      is found.  The deletion is otherwise not greedy and\n";
-$usage .= "                      stops at the first end mark found.  You can use\n";
-$usage .= "                      %P to insert the PACKAGE argument, and\n";
-$usage .= "                      %% to insert a literal % sign into the mark.\n";
-
-
-
-
-my ($begin, $end, $trailer);
-my ($parambegin, $paramend, $insert);
-my ($pkg, $file);
-my ($block, $multi, $addafter, $addontop);
-
-my $add = 1;
-my $comment = "#";
-my $SEEK_END = 2;
-
-while (@ARGV)
-{
-    $_ = shift;
-    if (/^-c$/ || /^--comment$/)
-    {
-       defined ($comment = shift)
-           || die "$name: `-c' must be followed by an argument\n";
-    }
-    elsif (/^-r$/ || /^--remove$/)
-    {
-       $add = 0;
-    }
-    elsif (/^-m$/ || /^--allow-multiple$/)
-    {
-       $multi = 1;
-    }
-    elsif (/^-b$/ || /^--begin$/)
-    {
-       defined ($parambegin = shift)
-           || die "$name: '-b' must be followed by an argument\n";
-       $block = 1;
-    }
-    elsif (/^-e$/ || /^--end$/)
-    {
-       defined ($paramend = shift)
-       || die "$name: '-e' must be followed by an argument\n";
-       $block = 1;
-    }
-    elsif (/^-i$/ || /^--insert-after$/)
-    {
-       defined($insert = shift)
-       || die "$name: '-i' must be followed by an anrgument\n";
-       #$multi = 1;  # uncomment for -i backward compatibility
-       $addafter = 1;
-    }
-    elsif (/^-t$/ || /^--insert-on-top$/)
-    {
-       $insert = "";
-       $addafter = 1;
-    }
-    elsif (/^-/)
-    {
-       die "$name: Unrecognized option \`$_'\n";
-    }
-    else
-    {
-       unshift(@ARGV, $_);
-       last;
-    }
+sub DEBUG () { 0 };
+
+# coding convention: CamelCase vars are global here
+#
+my ($ProgramName, $UsageLong, $UsageShort, $VERSION);
+
+$VERSION = '2.1';
+
+# 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.
+
+           -x | --change            Modify existing block, or add it if
+                                    it does not exist but the begin mark
+                                    can be found.
+
+           -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 (usage reference).
+
+                --version           Print version 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.
+
+  - 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, $LinesCount);
+
+# 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;
+use constant DO_CHANGE => 2;
+
+# Operation defaults
+$InsertRemove = DO_INSERT;
+$Placement    = APPEND_AT_END;
+$RegexpMatch  = 1;
+$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 (/^-x$/ || /^--change$/) {
+    $InsertRemove = DO_CHANGE;
+  }
+  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; # it's the default
+  }
+  elsif (/^-h$/ || /^--help$/) {
+    die $UsageLong;
+  }
+  elsif (/^-p$/ || /^--in-place$/) {
+    $InPlace = 1;
+  }
+  elsif (/^-n$/ || /^--no-close$/) {
+    $NoClose = 1;
+  }
+  elsif (/^--version$/) {
+    die "$ProgramName (CARNet Packaging file update) $VERSION\n"
+    .   "Copyright (C) 1998-2005 Free Software Foundation, Inc.\n"
+    .   "This is free software; see the source for copying conditions.  There is NO\n"
+    .   "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n";
+  }
+  elsif (/^-/) {
+    die "$ProgramName: Unrecognized option \`$_'\n";
+  }
+  else {
+    unshift(@ARGV, $_);
+    last;
+  }
+  
+  if ($Multi and DO_CHANGE == $InsertRemove) {
+    die "$ProgramName: Cannot use both `--change' and `--allow-multiple'\n";
+  }
+}
+
+$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;
 }
 
-($pkg = shift)  || die $usage;
-($file = shift) || die $usage;
-(! $block || ($parambegin && $paramend))
-    || die ("$name: must provide both begin and end marks.\n");
-
-if ($block)
-{
-    $parambegin =~ s,%P,$pkg,g;
-    $parambegin =~ s,%%,%,g;
-    $paramend =~ s,%P,$pkg,g;
-    $paramend =~ s,%%,%,g;
-    $begin = $parambegin;
-    $end = $paramend;
-    $trailer = "";
+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; };
 }
-else
-{
-    $begin   = "$comment Begin update by CARNet package $pkg";
-    $end     = "$comment End update by CARNet package $pkg";
-    $trailer = " -- DO NOT DELETE THIS LINE!";
+if ($@) {
+  die "$ProgramName: regexp compilation failed: $@ ($!)\n";
 }
 
-if ($add)
-{
-    if (! $multi)
-    {
-       &del;
-    }
-    &add;
+do_it() and exit(0);
+
+
+# main procedure
+# --------------
+
+sub do_it {
+  $LinesCount = slurp();
+  if (DO_CHANGE == $InsertRemove) {
+    $StdinContent = &stdin_content;
+    change() or add();
+  }
+  elsif (DO_INSERT == $InsertRemove) {
+    $StdinContent = &stdin_content;
+    del() unless $Multi;
+    add();
+  }
+  else {
+    del();
+  }
+  actualize();
 }
-else
-{
-    &del;
+
+
+# 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";
+  }
+  
+  return scalar @Lines;
 }
 
 sub add() {
-    $trailer .= "\n";
-    $begin   .= $trailer;
-    $end     .= $trailer;
-    my $byte = "";        # @#$@#$@#$$@#$@#$#@$! -w
-    my $prefix = "";
-
-    open (HANDLE, "<$file") || die "$file: $!\n";
-
-    # Need to load it all into memory if we want to write it
-    # out to the same file.
-    my @lines = <HANDLE> if ($addafter);
-
-    # If FILE does not have a trailing newline, be suire to add it
-    # before appending anything else.
-
-    # Unless it is size 0!
-    if ((-s $file))
-    {
-       seek HANDLE, -1, $SEEK_END;
-       defined (read HANDLE, $byte, 1) && ($byte ne "\n") && ($prefix = "\n");
-       close HANDLE;
-    }
-
-    if(!$addafter)
-    {
-       # The actual work is in the following three lines:
-       open (HANDLE, ">>$file") || die "$file: $!\n";
-       print HANDLE $prefix . $begin . join('', <STDIN>) . $end;
-       close HANDLE;
-    }
-    else
-    {
-       # Overwrite it
-       open (HANDLE, ">$file") || die "$file: $!\n";
-       my $added = 0;
-       foreach (@lines)
-       {
-           if (! $added && /^$insert/o)
-           {
-               print HANDLE if ($insert ne ""); 
-               print HANDLE $begin . join('', <STDIN>) . $end;
-               print HANDLE if ($insert eq "");
-               $added = 1;
-           }
-           else
-           {
-               print HANDLE;
-           }
+  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);
        }
-       if(! $added)
-       {
-           print HANDLE $prefix . $begin . join('', <STDIN>) . $end;
+       elsif (INSERT_BEFORE == $Placement) {
+         DEBUG and print STDERR "    - INSERT_BEFORE\n";
+         splice(@Lines, $lineNo, 0, $StdinContent);
        }
-       close HANDLE;
+       # once added and we're done
+       $added = 1;
+       last;
+      }
+      ++ $lineNo;
+    }
+    if ($MatchLine and ! $added) {
+      warn "$ProgramName: Inserting lines at the end implicitly! No '$MatchLine'\n";
+      push(@Lines, $StdinContent);
     }
+  }
+  # add the number of lines in added content to safety counter
+  $LinesCount += ($StdinContent =~ tr/\n//);
+  return $LinesCount; # whatever true
 }
 
 sub del() {
-    # saving vars for &add function...
-    my ($mybegin, $myend, $mytrailer);
-
-    $mybegin = $begin;
-    $myend = $end;
-    $mytrailer = $trailer;
-
-    # Make the strings regexp-friendly by quoting non-word chars.
-    $mybegin   =~ s/\W/\\$&/g;
-    $myend     =~ s/\W/\\$&/g;
-    $mytrailer =~ s/\W/\\$&/g;
-    open (HANDLE, "<$file") || die "$file: $!\n";
-    # Need to load it all into memory, because we'll want to write it
-    # out to the same file.
-    my @lines = <HANDLE>;
-    close HANDLE;
-    open (HANDLE, ">$file") || die "$file: $!\n";
-    foreach (@lines)
-    {
-       print HANDLE unless (/^$mybegin(?:$mytrailer)?$/o
-                         .. /^$myend(?:$mytrailer)?$/o);
+  my ($mytrailer, $mybegin, $myend) =
+    ($Trailer, $MarkBegin, $MarkEnd);
+
+  my ($bm_found, $em_found); # begin/end mark found indicator
+
+  # 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) {
+    if (/^$mybegin(?:$mytrailer)?$/o .. /^$myend(?:$mytrailer)?$/o) {
+      # additional safety counter for double-check
+      -- $LinesCount;
+    } else {
+      push (@filtered, $_);
     }
-    close HANDLE;
+    # for safety check:
+    $bm_found = 1 if (/^$mybegin(?:$mytrailer)?$/o);
+    $em_found = 1 if (/^$myend(?:$mytrailer)?$/o);
+  }
+  if ($bm_found and $em_found) {
+    DEBUG and print STDERR "Deleted ". (@Lines - @filtered) ." out of ".scalar(@Lines)." lines\n";
+    @Lines = @filtered;
+  }
+  elsif ($bm_found and ! $em_found) {
+    # safety exit    
+    die "$ProgramName: no end-mark after begin-mark!\n";
+  }
+  return $LinesCount; # whatever
+}
+
+sub change() {
+  my ($mytrailer, $mybegin, $myend) =
+    ($Trailer, $MarkBegin, $MarkEnd);
+
+  my ($bm_found, $em_found); # begin/end mark found indicator
+
+  # Make the strings regexp-friendly by quoting non-word chars.
+  $mybegin   =~ s/\W/\\$&/g;
+  $myend     =~ s/\W/\\$&/g;
+  $mytrailer =~ s/\W/\\$&/g;
+
+  my (@filtered, $done, $skip);
+  $done = 0; # job done once
+  $skip = 0; # skip original block
+  foreach (@Lines) {
+    if (! $done and $skip > 0) {
+      -- $LinesCount;
+      if (/^$myend(?:$mytrailer)?$/o) {
+        ++ $done; # skipped all that was to skip
+      } else {
+        ++ $skip; # count lines being skipped
+      }
+    }
+    elsif (0 == $skip and $_ =~ /^$mybegin(?:$mytrailer)?$/o) {
+      push (@filtered, $StdinContent);
+      $LinesCount += ($StdinContent =~ tr/\n//);
+      $skip = 1;
+    }
+    else {
+      push (@filtered, $_)
+    }
+    # for safety check:
+    $bm_found = 1 if (/^$mybegin(?:$mytrailer)?$/o);
+    $em_found = 1 if (/^$myend(?:$mytrailer)?$/o);
+  }
+  if ($bm_found and $em_found) {
+    -- $skip; # correct the counter
+    DEBUG and print STDERR "Replaced block of $skip lines\n";
+    @Lines = @filtered;
+  }
+  elsif ($bm_found and ! $em_found) {
+    # safety exit    
+    die "$ProgramName: no end-mark after begin-mark!\n";
+  }
+  return $done;
+}
+
+# written by ddzeko@srce.hr, 2005-03-18
+# to improve reliabilitah :)
+#
+sub actualize() {
+  # put it all thogether
+  my $newContent = join('', @Lines);
+
+  unless (length($newContent) or 0 == $LinesCount) {
+    # 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";
+    }
+    if (length($newContent)) {
+      my $wb = syswrite($FileHandle, $newContent);
+      if (! $wb or length($newContent) != $wb) {
+        # FIXME: try restoring backup copy
+        my $ncl = length($newContent);
+        die "$ProgramName: Failed to write the content to '$File' (wb=$wb, len=$ncl, err:$!)\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, (stat($File))[2])
+      or die "$ProgramName: Failed to open file '$File' for writing ($!)\n";
+    if (length($newContent)) {
+      my $wb = syswrite($FileHandle, $newContent);
+      if (! $wb or length($newContent) != $wb) {
+        unlink($file_new);
+        my ($ncl) = length($newContent);
+        die "$ProgramName: Failed to write the content to '$File' (wb=$wb, len=$ncl, err:$!)\n";
+      }
+    }
+    close($FileHandle);
+    # do the moving (should be atomic)
+    
+    eval { require File::Copy; };
+    if ($@) {
+      # do it the classical way: successive renames
+      rename($File, $file_old)
+        or die "$ProgramName: Failed to rename file '$File' to '$file_old' ($!)\n";
+    }
+    else {
+      import File::Copy;
+      # do it with increased safety: overstepping 
+      #      the old file with it's modified copy
+      copy($File, $file_old)
+        or die "$ProgramName: Failed to create backup of '$File' as '$file_old' ($!)\n";
+      # if the filesystem is absolutelly filled up this will fail 
+      # and the original file will remain unchanged
+    }
+    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";
+  }
+  DEBUG and print STDERR "actualize: LinesCount=$LinesCount\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";
 }