#!/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] " . "[-p] [-d] [...]"; my $p_cp = <. 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 () { $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] [-p] [-d] [...] =head1 OPTIONS =over 4 =item -a unlink after saving =item -n specify a one-line note to be saved in /.note =item -c read a comment from STDIN. It will be saved in /.note =item -p specify a new . Default is relative to the location of =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