#!/usr/bin/perl

use strict;
use Getopt::Std;
use File::Copy "cp";

my($p_name)   = $0 =~ m|/?([^/]+)$|;
my $p_version = "20030322.0";
my $p_usage   = "Usage: $p_name [--help|--version] | [-ac] [-n<comment>] "
              . "[-p<path>] [-d<dirname>] <file> [<file>...]";
my $p_cp      = <<EOM;
Copyright (c) 2003
      John Jetmore <jj33\@pobox.com>.  All rights reserved.
This code freely redistributable provided my name and this copyright notice
are not removed.  Send email to the contact address if you use this program.
EOM
ext_usage(); # before we do anything else, check for --help

my %opts       = ();
getopts('ap:d:n:c',\%opts) || mexit(2);
# a - archive (boolean) - unlink after backup
# p - path to save dir, will be same as path to arg by default
# d - name of save dir, is 'BAK' by default
# n - add a note to the file (saved as file.comment) -n "this is a note"
# c - add a comment to the file - will read comment on STDIN until EOF

mexit(1) if (!@ARGV);

my $saw_error  = 0;
my $store      = $opts{d} || 'BAK';
my $store_mode = 0755;
my $from_dir   = '';
my $to_dir     = '';
my $file       = '';
my @stat       = ();
my @d          = localtime(time());
my $dstr       = sprintf("%4d%02d%02d", $d[5]+1900, $d[4]+1, $d[3]);
my $tstr       = sprintf("%02d.%02d.%04d %02d:%02d:%02d",
                         $d[4]+1, $d[3], $d[5]+1900, $d[2], $d[1], $d[0]);
my $note       = $opts{n};
$note         .= "\n\n" . get_comment() if ($opts{c});

foreach (@ARGV) {
  if (!-f $_) {
    print STDERR "$_ does not exist\n";
    $saw_error++;
    next;
  }
  if (m|^(.*)/([^/]+)$|) {
    $from_dir  = $1;
    $file = $2;
  } else {
    $from_dir  = './';
    $file = $_;
  }
  $to_dir = $opts{p} || $from_dir;
  if (!-d "$to_dir/$store") {
    if (!mkdir("$to_dir/$store", $store_mode)) {
      print STDERR "Couldn't mkdir $to_dir/$store: $!\n";
      $saw_error++;
      next;
    }
  }
  my $n = 0;
  while (-e "$to_dir/$store/$file.$dstr.$n") { $n++; }
  if (!(@stat = stat("$from_dir/$file"))) {
    print STDERR "Couldn't stat $from_dir/$file: $!\n";
    $saw_error++;
    next;
  }
  if (!cp("$from_dir/$file", "$to_dir/$store/$file.$dstr.$n")) {
    print STDERR "Couldn't copy to $to_dir/$store/$file.$dstr.$n: $!\n";
    $saw_error++;
    next;
  }
  if (!chown($stat[4],$stat[5],"$to_dir/$store/$file.$dstr.$n")) {
    print STDERR "Couldn't chown $to_dir/$store/$file.$dstr.$n: $!\n";
    $saw_error++;
  }
  if (!chmod($stat[2],"$to_dir/$store/$file.$dstr.$n")) {
    print STDERR "Couldn't chmod $to_dir/$store/$file.$dstr.$n: $!\n";
    $saw_error++;
  }
  if ($note) {
    if (!open(O, ">>$to_dir/$store/$file.note")) {
      warn "Couldn't open $to_dir/$store/$file.note to write: $!\n";
      $saw_error++;
      next;
    }
    print O "$dstr.$n ($tstr)\n$note\n\n";
    close(O);
    chown($stat[4],$stat[5],"$to_dir/$store/$file.$dstr.$n.note");
    chmod($stat[2]&0666,"$to_dir/$store/$file.$dstr.$n.note");
  }
  # Save this until the end in case something bad happens?
  if ($opts{a}) {
    if (!unlink("$from_dir/$file")) {
      warn "Couldn't unlink $from_dir/$file: $!\n";
      $saw_error++;
    }
  }
}

my $exit = 0;
if ($saw_error == scalar(@ARGV)) {
  $exit = 3;
} elsif ($saw_error) {
  $exit = 4;
}

exit($exit);

sub get_comment {
  my $n = '';

  while (<STDIN>) {
    $n .= $_;
  }
  return($n);
}

sub mexit {
  my $exit = shift || 11;
  my $msg  = shift || $p_usage;

  print STDERR "$msg\n";
  exit($exit);
}

sub ext_usage {
  if ($ARGV[0] =~ /^--help$/i) {
    require Config;
    $ENV{PATH} .= ":" unless $ENV{PATH} eq "";
    $ENV{PATH} = "$ENV{PATH}$Config::Config{'installscript'}";
    exec("perldoc", "-F", "-U", $0) || exit 1;
    # make parser happy
    %Config::Config = ();
  } elsif ($ARGV[0] =~ /^--version$/i) {
    print "$p_name version $p_version\n\n$p_cp\n";
  } else {
    return;
  }

  exit(0);
}

__END__

=head1 NAME

bak - Low-cognitive-load file archiving

=head1 USAGE

bak [--help|--version] | [-ac] [-n<comment>] [-p<path>] [-d<dirname>] <file> [<file>...]

=head1 OPTIONS

=over 4

=item -a

unlink <file> after saving

=item -n

specify a one-line note to be saved in <saved>/<file>.note

=item -c

read a comment from STDIN.  It will be saved in <saved>/<file>.note

=item -p

specify a new <path>.  Default is relative to the location of <file>

=item -d

specify name of save dir.  Default is 'BAK'

=item --help

this screen.

=item --version

version info.

=back

=head1 EXAMPLES

=over 4

=item bak main.h main.c

creates a copy of ./main.h as ./BAK/main.h.YYYYMMDD.S and
does same thing for main.c

=item bak -n 'archive before updates' -d ARCH /local/etc/str.conf

save a copy of /local/etc/str.conf in
/local/etc/ARCH/str.conf.YYYYMMDD.S.  Enters corresponding
serial number, date and time, and 'archive before updates'
in /local/etc/ARCH/str.conf.note

=item bak -p /var /src/proj/main.c

save a copy of /src/proj/main.c as /var/BAK/main.c.YYYYMMDD.S

=back

=head1 EXIT CODES

=item 0 - no errors

=item 1,2 - improper command line argument usage

=item 3 - it appears save of every file failed

=item 4 - at least one error occurred while saving files

=head1 CONTACT

=item proj-bak@jetmore.net
