mark@s.cc.purdue.edu.UUCP (09/17/87)
# This is a shell archive.
# Remove everything above and including the cut line.
# Then run the rest of the file through sh.
#----cut here-----cut here-----cut here-----cut here----#
#!/bin/sh
# Xshar: Extended Shell Archiver.
# This is part 1 out of 2.
# This archive created: Wed Sep 16 20:51:21 1987
# By: Craig Norborg (Purdue University Computing Center)
# Run the following text with /bin/sh to create:
# About.c
# Compress.c
# Gadget.h
# MRBackup.DOC
# Makefile
# Menu.c
# Menu.h
# MiscRequest.c
# ReadME
# Restore.c
cat << \SHAR_EOF > About.c
/* About.c: Tell the user a little about MRBackup. */
static char *abouts[] = {
"This is MRBackup, a hard disk backup utility written by Mark Rinfret.\n",
"This program allows you to perform complete or incremental backups.\n",
"Data compression / decompression are provided to economize on floppies.\n",
"If you find this program useful or if you have any suggestions,\n",
"Send the author mail at the following addresses:\n",
" Mark R. Rinfret\n",
" 348 Indian Avenue\n",
" Portsmouth, Rhode Island 02871\n\n",
" or, UseNet: mark@unisec.USI.COM\n",
(char *) 0L};
About()
{
int i;
for (i = 0;abouts[i];++i) {
TypeAndSpeak(abouts[i]);
}
}
SHAR_EOF
cat << \SHAR_EOF > Compress.c
/*
* Compress.c - data compression/decompression routines.
* This is an adaptation of the public domain Un*x compress v4.0.
*/
#include <stdio.h>
#include <ctype.h>
#define min(a,b) ((a>b) ? b : a)
#ifdef DEBUG
#define DBG(x) x
#else
#define DBG(x)
#endif
extern int errno;
/*
* Set USERMEM to the maximum amount of physical user memory available
* in bytes. USERMEM is used to determine the maximum BITS that can be used
* for compression.
*
* SACREDMEM is the amount of physical memory saved for others; compress
* will hog the rest.
*/
#ifndef SACREDMEM
#define SACREDMEM 0
#endif
#ifndef USERMEM
# define USERMEM 450000 /* default user memory */
#endif
# define MAXFILES 100 /* arbitrary maximum - see note below */
# define BITS 12 /* > 12 crashes system (?) */
# undef USERMEM
long filesize();
char *scdir();
#ifdef USERMEM
# if USERMEM >= (433484+SACREDMEM)
# define PBITS 16
# else
# if USERMEM >= (229600+SACREDMEM)
# define PBITS 15
# else
# if USERMEM >= (127536+SACREDMEM)
# define PBITS 14
# else
# if USERMEM >= (73464+SACREDMEM)
# define PBITS 13
# else
# define PBITS 12
# endif
# endif
# endif
# endif
# undef USERMEM
#endif /* USERMEM */
#ifdef PBITS /* Preferred BITS for this memory size */
# ifndef BITS
# define BITS PBITS
# endif BITS
#endif /* PBITS */
#if BITS == 16
# define HSIZE 69001L /* 95% occupancy */
#endif
#if BITS == 15
# define HSIZE 35023L /* 94% occupancy */
#endif
#if BITS == 14
# define HSIZE 18013L /* 91% occupancy */
#endif
#if BITS == 13
# define HSIZE 9001L /* 91% occupancy */
#endif
#if BITS <= 12
# define HSIZE 5003L /* 80% occupancy */
#endif
/*
* a code_int must be able to hold 2**BITS values of type int, and also -1
*/
#if BITS > 15
typedef long int code_int;
#else
typedef int code_int;
#endif
#ifdef SIGNED_COMPARE_SLOW
typedef unsigned long int count_int;
typedef unsigned short int count_short;
#else
typedef long int count_int;
#endif
#ifdef NO_UCHAR
typedef char char_type;
#else
typedef unsigned char char_type;
#endif /* UCHAR */
char_type magic_header[]= {
"\037\235"
}; /* 1F 9D */
/* Defines for third byte of header */
#define BIT_MASK 0x1f
#define BLOCK_MASK 0x80
/* Masks 0x40 and 0x20 are free. I think 0x20 should mean that there is
a fourth header byte (for expansion).
*/
#define INIT_BITS 9 /* initial number of bits/code */
/*
* compress.c - File compression ala IEEE Computer, June 1984.
*
* Authors:
* Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas)
* Jim McKie (decvax!mcvax!jim)
* Steve Davies (decvax!vax135!petsd!peora!srd)
* Ken Turkowski (decvax!decwrl!turtlevax!ken)
* James A. Woods (decvax!ihnp4!ames!jaw)
* Joe Orost (decvax!vax135!petsd!joe)
* Mark R. Rinfret (mods) (mark@unisec.USI.COM)
*/
int n_bits; /* number of bits/code */
int maxbits = BITS; /* user settable max # bits/code */
code_int maxcode; /* maximum code, given n_bits */
code_int maxmaxcode = 1 << BITS; /* NEVER generate this code */
#define MAXCODE(n_bits) ((1 << (n_bits)) - 1)
count_int htab [HSIZE];
unsigned short codetab [HSIZE];
#define htabof(i) htab[i]
#define codetabof(i) codetab[i]
code_int hsize = HSIZE; /* for dynamic table sizing */
count_int fsize;
static FILE *infile, *outfile;
static char iname[256], oname[256];
/*
* To save much memory, we overlay the table used by compress() with those
* used by decompress(). The tab_prefix table is the same size and type
* as the codetab. The tab_suffix table needs 2**BITS characters. We
* get this from the beginning of htab. The output stack uses the rest
* of htab, and contains characters. There is plenty of room for any
* possible stack (stack used to be 8000 characters).
*/
#define tab_prefixof(i) codetabof(i)
#define tab_suffixof(i) ((char_type *)(htab))[i]
#define de_stack ((char_type *)&tab_suffixof(1<<BITS))
code_int free_ent = 0; /* first unused entry */
int exit_stat = 0;
code_int getcode();
int nomagic = 0; /* Use a 3-byte magic number header,
unless old file */
int zcat_flg = 0; /* Write output on stdout, suppress
messages */
int quiet = 1; /* don't tell me about compression */
/*
* block compression parameters -- after all codes are used up,
* and compression rate changes, start over.
*/
int block_compress = BLOCK_MASK;
int clear_flg = 0;
long int ratio = 0;
#define CHECK_GAP 10000 /* ratio check interval */
count_int checkpoint = CHECK_GAP;
/*
* the next two codes should not be changed lightly, as they must not
* lie within the contiguous general code space.
*/
#define FIRST 257 /* first free entry */
#define CLEAR 256 /* table clear output code */
int force = 0;
char ofname [100];
#ifdef DEBUG
int verbose = 0;
#endif /* DEBUG */
int (*bgnd_flag)();
int do_decomp = 0;
/*****************************************************************
* TAG( main )
*
* Algorithm from "A Technique for High Performance Data Compression",
* Terry A. Welch, IEEE Computer Vol 17, No 6 (June 1984), pp 8-19.
*
* Outputs:
* file.Z: Compressed form of file with same mode, owner, and utimes
*
* Assumptions:
* When filenames are given, replaces with the compressed version
* (.Z suffix) only if the file decreases in size.
* Algorithm:
* Modified Lempel-Ziv method (LZW). Basically finds common
* substrings and replaces them with a variable size code. This is
* deterministic, and can be done on the fly. Thus, the decompression
* procedure needs no input table, but tracks the way the table was built.
*/
comp_init()
{
if(maxbits < INIT_BITS)
maxbits = INIT_BITS;
if (maxbits > BITS)
maxbits = BITS;
maxmaxcode = 1 << maxbits;
}
static int offset;
static long int in_count = 1; /* length of input */
static long int bytes_out; /* length of compressed output */
static long int out_count = 0; /* # of codes output (for debugging) */
/*
* compress "from" to "to"
*
* Algorithm: use open addressing double hashing (no chaining) on the
* prefix code / next character combination. We do a variant of Knuth's
* algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime
* secondary probe. Here, the modular division first probe is gives way
* to a faster exclusive-or manipulation. Also do block compression with
* an adaptive reset, whereby the code table is cleared when the compression
* ratio decreases, but after the table fills. The variable-length output
* codes are re-sized at this point, and a special CLEAR code is generated
* for the decompressor. Late addition: construct the table according to
* file size for noticeable speed improvement on small files. Please direct
* questions about this implementation to ames!jaw.
*/
int
compress(from, to)
char *from, *to;
{
register long fcode;
register code_int i = 0;
register int c;
register code_int ent;
register int disp;
register code_int hsize_reg;
register int hshift;
int fail = 0;
char *s;
char *err_msg[256];
comp_init();
/*
* tune hash table size for small files -- ad hoc,
* but the sizes match earlier #defines, which
* serve as upper bounds on the number of output codes.
*/
hsize = HSIZE;
if (fsize < (1L << 12))
hsize = min (5003L,HSIZE );
else
if (fsize < (1L << 13))
hsize = min (9001L,HSIZE );
else
if (fsize < (1L << 14))
hsize = min (18013L,HSIZE );
else
if (fsize < (1L << 15))
hsize = min (35023L,HSIZE );
else
if (fsize < 47000L )
hsize = min (50021L,HSIZE );
if (!(infile = fopen(from,"r"))) {
perror(from);
return 1;
}
if (!(outfile = fopen(to,"w"))) {
perror(to);
fclose(infile);
return 1;
}
if (nomagic == 0){
putc(magic_header[0],outfile);
putc(magic_header[1],outfile);
putc((char)(maxbits | block_compress),outfile);
if(ferror(outfile)) {
writeerr();
++fail;
cleanup:
fclose(infile);
fclose(outfile);
return fail;
}
}
offset = 0;
bytes_out = 3; /* includes 3-byte header mojo */
out_count = 0;
clear_flg = 0;
ratio = 0L;
in_count = 1;
checkpoint = CHECK_GAP;
maxcode = MAXCODE(n_bits = INIT_BITS);
free_ent = ((block_compress)?FIRST :256 );
ent = getc (infile);
hshift = 0;
for (fcode = (long)hsize;fcode < 65536L;fcode *= 2L )
hshift++;
hshift = 8 - hshift; /* set hash code range bound */
hsize_reg = hsize;
cl_hash((count_int)hsize_reg); /* clear hash table */
while ((c = getc(infile))!= EOF ){
in_count++;
fcode = (long)(((long)c << maxbits)+ ent);
i = ((c << hshift)^ ent); /* xor hashing */
if (htabof (i)== fcode ){
ent = codetabof (i);
continue;
}
else
if ((long)htabof (i)< 0 )/* empty slot */
goto nomatch;
disp = hsize_reg - i; /* secondary hash (after G. Knott) */
if (i == 0 )
disp = 1;
probe:
if ((i -= disp)< 0 )
i += hsize_reg;
if (htabof (i)== fcode ){
ent = codetabof (i);
continue;
}
if ((long)htabof (i)> 0 )
goto probe;
nomatch:
output ((code_int)ent );
out_count++;
ent = c;
if (free_ent < maxmaxcode ){
codetabof (i)= free_ent++;/* code -> hashtable */
htabof (i)= fcode;
}
else
if ((count_int)in_count >= checkpoint && block_compress )
cl_block ();
}
/*
* Put out the final code.
*/
output((code_int)ent );
out_count++;
output((code_int)-1 );
/*
* Print out stats on stderr
*/
if(zcat_flg == 0 && !quiet){
#ifdef DEBUG
fprintf(stderr,
"%ld chars in, %ld codes (%ld bytes) out, compression factor: ",
in_count,out_count,bytes_out );
prratio(stderr,in_count,bytes_out );
fprintf(stderr,"\n");
fprintf(stderr,"\tCompression as in compact: " );
prratio(stderr,in_count-bytes_out,in_count );
fprintf(stderr,"\n");
fprintf(stderr,"\tLargest code (of last block) was %d (%d bits)\n",
free_ent - 1,n_bits );
#else /* !DEBUG */
fprintf(stderr,"Compression: " );
prratio(stderr,in_count-bytes_out,in_count );
#endif /* DEBUG */
}
if(bytes_out > in_count) /* exit(2) if no savings */
exit_stat = 2;
goto cleanup;
}
/*****************************************************************
* TAG( output )
*
* Output the given code.
* Inputs:
* code: A n_bits-bit integer. If == -1, then EOF. This assumes
* that n_bits =< (long)wordsize - 1.
* Outputs:
* Outputs code to the file.
* Assumptions:
* Chars are 8 bits long.
* Algorithm:
* Maintain a BITS character long buffer (so that 8 codes will
* fit in it exactly). Use the VAX insv instruction to insert each
* code in turn. When the buffer fills up empty it and start over.
*/
static char buf[BITS];
char_type lmask[9]= {
0xff,0xfe,0xfc,0xf8,0xf0,0xe0,0xc0,0x80,0x00
};
char_type rmask[9]= {
0x00,0x01,0x03,0x07,0x0f,0x1f,0x3f,0x7f,0xff
};
output(code )
code_int code;
{
#ifdef DEBUG
static int col = 0;
#endif /* DEBUG */
register int r_off = offset,bits= n_bits;
register char * bp = buf;
#ifdef DEBUG
if (verbose )
fprintf(stderr,"%5d%c",code,
(col+=6)>= 74 ?(col = 0,'\n'):' ' );
#endif /* DEBUG */
if (code >= 0 ){
/*
* byte/bit numbering on the VAX is simulated by the following code
*/
/*
* Get to the first byte.
*/
bp += (r_off >> 3);
r_off &= 7;
/*
* Since code is always >= 8 bits, only need to mask the first
* hunk on the left.
*/
*bp = (*bp & rmask[r_off])| (code << r_off)& lmask[r_off];
bp++;
bits -= (8 - r_off);
code >>= 8 - r_off;
/* Get any 8 bit parts in the middle (<=1 for up to 16 bits). */
if (bits >= 8 ){
*bp++= code;
code >>= 8;
bits -= 8;
}
/* Last bits. */
if(bits)
*bp = code;
offset += n_bits;
if (offset == (n_bits << 3)){
bp = buf;
bits = n_bits;
bytes_out += bits;
do
putc(*bp++, outfile);
while(--bits);
offset = 0;
}
/*
* If the next entry is going to be too big for the code size,
* then increase it, if possible.
*/
if (free_ent > maxcode || (clear_flg > 0)){
/*
* Write the whole buffer, because the input side won't
* discover the size increase until after it has read it.
*/
if (offset > 0 ){
if(fwrite(buf,1,n_bits,outfile)!= n_bits) {
writeerr();
return 1;
}
bytes_out += n_bits;
}
offset = 0;
if (clear_flg ){
maxcode = MAXCODE (n_bits = INIT_BITS);
clear_flg = 0;
}
else {
n_bits++;
if (n_bits == maxbits )
maxcode = maxmaxcode;
else
maxcode = MAXCODE(n_bits);
}
#ifdef DEBUG
if (debug ){
fprintf(stderr,"\nChange to %d bits\n",n_bits );
col = 0;
}
#endif /* DEBUG */
}
}
else {
/*
* At EOF, write the rest of the buffer.
*/
if (offset > 0 )
fwrite(buf,1,(offset + 7)/ 8,outfile);
bytes_out += (offset + 7)/ 8;
offset = 0;
fflush(outfile);
#ifdef DEBUG
if (verbose )
fprintf(stderr,"\n" );
#endif /* DEBUG */
if(ferror(outfile)) {
writeerr();
return 1;
}
}
return 0;
}
/*
* Decompress infile to outfile. This routine adapts to the codes in the
* file building the "string" table on-the-fly; requiring no table to
* be stored in the compressed file. The tables used herein are shared
* with those of the compress() routine. See the definitions above.
*/
int
decompress(from, to)
char *from, *to;
{
register char_type *stackp;
register int finchar;
register code_int code,oldcode,incode;
int fail = 0;
if (!(infile = fopen(from, "r"))) {
printf("decompress: %s failed to open for input!\n",from);
return errno;
}
if (!(outfile = fopen(to, "w"))) {
printf("decompress: %s failed to open for output!\n",to);
fclose(infile);
return errno;
}
if (nomagic == 0){
if ((getc(infile)!= (magic_header[0]& 0xFF))
|| (getc(infile)!= (magic_header[1]& 0xFF))){
fprintf(stderr,"decompress: %s not in compressed format\n",
from);
++fail;
cleanup:
fclose(infile);
fclose(outfile);
return fail;
}
maxbits = getc(infile);/* set -b from file */
block_compress = maxbits & BLOCK_MASK;
maxbits &= BIT_MASK;
maxmaxcode = 1 << maxbits;
if(maxbits > BITS){
fprintf(stderr,
"%s: compressed with %d bits, can only handle %d bits\n",
from,maxbits,BITS);
++fail;
goto cleanup;
}
}
/*
* As above, initialize the first 256 entries in the table.
*/
maxcode = MAXCODE(n_bits = INIT_BITS);
for (code = 255;code >= 0;code--){
tab_prefixof(code)= 0;
tab_suffixof(code)= (char_type)code;
}
free_ent = ((block_compress)?FIRST :256 );
finchar = oldcode = getcode();
if(oldcode == -1) /* EOF already? */
/*return ;*/ /* Get out of here */
goto cleanup;
putc((char)finchar,outfile ); /* first code must be 8 bits = char */
if(ferror(outfile)) { /* Crash if can't write */
writeerr ();
++fail;
goto cleanup;
}
stackp = de_stack;
while ((code = getcode())> -1 ){
if ((code == CLEAR)&& block_compress ){
for (code = 255;code >= 0;code--)
tab_prefixof(code)= 0;
clear_flg = 1;
free_ent = FIRST - 1;
if ((code = getcode ())== -1 )/* O, untimely death! */
break ;
}
incode = code;
/*
* Special case for KwKwK string.
*/
if (code >= free_ent ){
*stackp++= finchar;
code = oldcode;
}
/*
* Generate output characters in reverse order
*/
while (code >= 256 ){
*stackp++= tab_suffixof(code);
code = tab_prefixof(code);
}
*stackp++= finchar = tab_suffixof(code);
/*
* And put them out in forward order
*/
do
putc (*--stackp , outfile);
while (stackp > de_stack );
/*
* Generate the new entry.
*/
if ((code=free_ent)< maxmaxcode ){
tab_prefixof(code)= (unsigned short)oldcode;
tab_suffixof(code)= finchar;
free_ent = code+1;
}
/*
* Remember previous code.
*/
oldcode = incode;
}
fflush(outfile);
if(ferror(outfile)) {
writeerr();
++fail;
}
goto cleanup;
}
/*****************************************************************
* TAG( getcode )
*
* Read one code from the standard input. If EOF, return -1.
* Inputs:
* infile
* Outputs:
* code or -1 is returned.
*/
code_int
getcode(){
/*
* On the VAX, it is important to have the register declarations
* in exactly the order given, or the asm will break.
*/
register code_int code;
static int offset = 0,size = 0;
static char_type buf[BITS];
register int r_off,bits;
register char_type *bp = buf;
if (clear_flg > 0 || offset >= size || free_ent > maxcode ){
/*
* If the next entry will be too big for the current code
* size, then we must increase the size. This implies reading
* a new buffer full, too.
*/
if (free_ent > maxcode ){
n_bits++;
if (n_bits == maxbits )
maxcode = maxmaxcode;/* won't get any bigger now */
else
maxcode = MAXCODE(n_bits);
}
if (clear_flg > 0){
maxcode = MAXCODE (n_bits = INIT_BITS);
clear_flg = 0;
}
size = fread(buf,1,n_bits,infile);
if (size <= 0 )
return -1; /* end of file */
offset = 0;
/* Round size down to integral number of codes */
size = (size << 3)- (n_bits - 1);
}
r_off = offset;
bits = n_bits;
/*
* Get to the first byte.
*/
bp += (r_off >> 3);
r_off &= 7;
/* Get first part (low order bits) */
#ifdef NO_UCHAR
code = ((*bp++>> r_off)& rmask[8 - r_off])& 0xff;
#else
code = (*bp++>> r_off);
#endif /* NO_UCHAR */
bits -= (8 - r_off);
r_off = 8 - r_off; /* now, offset into code word */
/* Get any 8 bit parts in the middle (<=1 for up to 16 bits). */
if (bits >= 8 ){
#ifdef NO_UCHAR
code |= (*bp++& 0xff)<< r_off;
#else
code |= *bp++<< r_off;
#endif /* NO_UCHAR */
r_off += 8;
bits -= 8;
}
/* high order bits. */
code |= (*bp & rmask[bits])<< r_off;
offset += n_bits;
return code;
}
char *
rindex(s,c) /* For those who don't have it in libc.a
*/
register char *s,c;
{
char *p;
for (p = NULL;*s;s++)
if (*s == c)
p = s;
return(p);
}
writeerr(){
perror (ofname );
unlink (ofname );
}
/*
* This routine returns 1 if we are running in the foreground and stderr
* is a tty.
*/
foreground(){
/* I don't know how to test for background, yet. */
return (isatty(2));
}
onintr (){
unlink (ofname );
exit (1 );
}
oops (){ /* wild pointer -- assume bad input */
if (do_decomp == 1 )
fprintf (stderr,"uncompress: corrupt input\n" );
unlink (ofname );
exit (1 );
}
cl_block (){ /* table clear for block compress */
register long int rat;
checkpoint = in_count + CHECK_GAP;
#ifdef DEBUG
if (debug ){
fprintf (stderr,"count: %ld, ratio: ",in_count );
prratio (stderr,in_count,bytes_out );
fprintf (stderr,"\n");
}
#endif /* DEBUG */
if(in_count > 0x007fffff){ /* shift will overflow */
rat = bytes_out >> 8;
if(rat == 0){ /* Don't divide by zero */
rat = 0x7fffffff;
}
else {
rat = in_count / rat;
}
}
else {
rat = (in_count << 8)/ bytes_out;/* 8 fractional bits */
}
if (rat > ratio ){
ratio = rat;
}
else {
ratio = 0;
#ifdef DEBUG
if(verbose)
dump_tab(); /* dump string table */
#endif
cl_hash ((count_int)hsize );
free_ent = FIRST;
clear_flg = 1;
output ((code_int)CLEAR );
#ifdef DEBUG
if(debug)
fprintf (stderr,"clear\n" );
#endif /* DEBUG */
}
}
cl_hash(hsize) /* reset code table */
register count_int hsize;
{
register count_int *htab_p = htab+hsize;
register long i;
register long m1 = -1;
i = hsize - 16;
do { /* might use Sys V memset(3) here */
*(htab_p-16)= m1;
*(htab_p-15)= m1;
*(htab_p-14)= m1;
*(htab_p-13)= m1;
*(htab_p-12)= m1;
*(htab_p-11)= m1;
*(htab_p-10)= m1;
*(htab_p-9)= m1;
*(htab_p-8)= m1;
*(htab_p-7)= m1;
*(htab_p-6)= m1;
*(htab_p-5)= m1;
*(htab_p-4)= m1;
*(htab_p-3)= m1;
*(htab_p-2)= m1;
*(htab_p-1)= m1;
htab_p -= 16;
}while ((i -= 16)>= 0);
for (i += 16;i > 0;i--)
*--htab_p = m1;
}
prratio(stream,num,den)
FILE *stream;
long int num,den;
{
register int q; /* Doesn't need to be long */
if(num > 214748L){ /* 2147483647/10000 */
q = num / (den / 10000L);
}
else {
q = (10000L*num)/ den; /* Long calculations, though */
}
if (q < 0){
putc('-',stream);
q = -q;
}
fprintf(stream,"%d.%02d%%",q / 100,q % 100);
}
SHAR_EOF
cat << \SHAR_EOF > Gadget.h
/* Definitions for Gadget ID numbers */
#define HOMEPATHGAD 0
#define BACKPATHGAD 1
#define LISTPATHGAD 2
#define STOPGAD 3
SHAR_EOF
cat << \SHAR_EOF > MRBackup.DOC
MRBackup Version 1.0
A Hard Disk Backup Utility
for the
Commodore Amiga
6 August, 1987
Author: Mark R. Rinfret
Introduction
This document describes the first release of a program which
will backup an Amiga hard disk drive to floppy disk media. It
also represents the author's first serious (are you kidding?)
attempt to write an Amiga program using Intuition.
MRBackup is reasonably flexible, allowing you to backup
individual directories, directory trees or a whole disk. You can
backup from one directory hierarchy and restore to another.
Incremental backups can be performed based on file modification
dates. Just for fun, MRBackup also talks. Though this is mostly
frivolous, the speech capability provides a method for alerting
you that a new output disk is required for formatting.
MRBackup is not fast. When choosing a method for packing
the backup data, a fast-scan approach with specialized backup
directory structure was considered. However, there is an
inherent danger in committing multiple files to a specialized
file system which can only be accessed through a solitary piece
of software. I decided to maintain the AmigaDOS file system
structure which has a great deal of integrity and allows the
backup media to be accessed by standard software.
The user should take a serious and organized approach to his
backup methods, however. I highly recommend that backup listings
be kept in a safe place (I use a 3-ring binder) and backup
floppies be stored safe from magnetic damage or other hazards
(like spilled coffee - argh!). A truly committed individual will
backup his entire disk once a month (date = 00/00/00), once a
week (automatic default) and "areas of interest" once a day. One
shortcoming(?) of the current version of MRBackup is that all
directory nodes from the specified home path and down are
preserved on the backup media, even if some of those nodes have
no files eligible for backup by way of the backup date. Perhaps
a later version of this program will address this issue.
MRBackup attempts to economize on output media usage by
using data compression/decompression (at the cost of time). This
is an option which can be enabled/disabled via menu selection.
The compression routine used was lifted from the Un*x "compress"
program.
Operation
To use MRBackup, click on the program icon or type MRBackup
at the CLI prompt. A new window will open in which you will see
a STOP sign and a couple of embedded windows. The "Pathname
Specifications" window is where you tell MRBackup where data is
coming from and going to. It is important to note that the
meanings of "Home Path" and "Backup Path" remain the same for a
backup or a restore operation. That is, the "Home Path" always
refers to the hard disk pathname and "Backup Path" always refers
to the floppy disk pathname. The "Listing Path" refers to the
destination of a detailed listing of the contents of the backup
floppies and may specify the printer (default) or a file on the
hard disk. The listing is an option which may be
enabled/disabled via menu selection.
Backing Up a Disk
To back up your hard disk, or a section of it, first get an
indication of the size of the area with the Info command, ls (if
you have it), List, etc. If you multiply the total number of
bytes (blocks * 512) by 0.65, then divide by 800000, you should
get a very rough estimate (probably high) of the number of floppy
disks required to back up that area. The floppies selected for
backup need not be preformatted, as MRBackup will format them as
needed. You should be sure, however, that no valuable data
exists on them since it will be destroyed by the formatting
process.
Once your floppies are ready and stacked neatly within
reach, the fun begins. First, modify the pathname specifications
according to your requirements. The "Home Path" may include
device names DH0..DH9 (depending upon your configuration) and may
optionally specify a directory component, as well. If only the
device name is specified, all files on the drive will be backed
up. If a directory is specified, the backup will be limited to
all files and subdirectories at or below that level. The backup
path should be the DEVICE name of one of your floppy drives
(DF0..DF3 are the allowed values here). Finally, the listing
path may be set to the printer device (PRT:) or to the name of a
file on a hard drive or any available floppy drive not being used
by the backup. The listing path will be ignored if you set the
No Listing option in the Flags menu.
Make sure that you have set the desired options in the Flags
menu, then select Backup from the Project menu. You will be
prompted with a date requester. The default date value that
appears is one week earlier than the current date. If that is
satisfactory, simply depress the RETURN key and the backup will
commence. If you desire to change the date, edit the date value
as necessary, using the standard Amiga conventions. Remember
that to clear the gadget you may press right-Amiga-x. A date
value of 00/00/00 is allowed should you want to backup all files
in the home path, regardless of their creation date. Otherwise,
the required date and time format is MM/DD/YY HH:MM:SS (24 hour
clock), the time specification being optional.
Once the date has been entered, the rest is fairly
automatic. You will be prompted immediately for a blank floppy
disk. Insert it into the floppy drive that you specified in the
backup path and the disk requester will be satisfied. Should you
want to abort, simply hit the CANCEL gadget in the requester.
Also, you may abort the backup process at any time by hitting the
STOP gadget which appears in the top left area of the screen.
This gadget is only checked prior to the start of the next file
copy operation, so be patient if it does not respond immediately.
As each floppy disk is filled, you should promptly label it.
MRBackup automatically generates volume names of the form:
Backup MM-DD-YY.<sequence number>
Also to be noted is the fact that the files on the backup media
retain the creation/modification date (I wish there were two) of
their counterparts on the home device.
You will find that the Amiga's multitasking environment
shines when using this program. For those long-haul backups, get
everything started, then shove all the windows to the back and go
start something useful. MRBackup will carry on without your
watchful eye. When it needs a disk, the disk requester will pop
out in front of everything and MRBackup will ask (out loud, if
you allow it) for another disk. Having something else to do will
make the time pass faster.
Restoring the Backups
No, this isn't always the bad part. Backup and restore can
also be useful when your hard disk is crowded and you have to
"juggle" data around. The restoration process is quite similar,
mechanically, to the backup process - it's just reversed. In
addition, the meanings of the pathname specifications are altered
somewhat. The "Home path" describes the destination for the
restored files and, as with the backup process, may specify the
hard drive only or a directory specification. The "Backup path"
describes the file or files to be restored and thus may be
defined down to the file level (1 file).
Note that on a restore operation, the file creation date of
the backup file is compared to its matching file (if it exists)
on the home device. If the file on the home device is newer, the
file will not be replaced. If this is desired, the file on the
home device must be deleted first. A later version of this
program will probably offer a "force" or "force with prompt"
option.
Warranties
There are no warranties, either expressed or implied, with
regard to the use of this program. You didn't pay for it (did
you?), so how you gonna' get you money back? Seriously, folk,
I've tested this beastie fairly thoroughly (I intend to USE it!),
but you know how things go...there may be a bugaboo or two
lurking in there. Please exercise it a little before committing
your life (your precious data) to its care.
Permissions
This program is contributed to the public domain, as a token
of gratitude for the many wonderful programs I have received
through the same channels. Feel free to enhance, destroy, sell,
distribute or otherwise benefit from the legal use of this
program. I would love to hear from those of you who find this
either useful or useless (with specific criticisms in the latter
case). If you make any enhancements (there's room for PLENTY),
please share them with me and the rest of the world. I can be
reached as:
mark@unisec.USI.COM (at least a couple more months)
or
Mark R. Rinfret
348 Indian Avenue
Portsmouth, RI 02871
401-846-7639 (home)
401-849-4174 (work)
SHAR_EOF
cat << \SHAR_EOF > Makefile
CFLAGS = -b
OBJ = About.o Backup.o Compress.o Menu.o MiscRequest.o Restore.o
# The requester/gadgets module is treated separately. It must be loaded
# into chip memory since it contains image data.
CHIPOBJ = pathrequest.o
# Designate the directory which holds the source and object for the
# miscellaneous support routines. Actually, it would be nice to
# collect these into a library...real soon now...
MISC = sys:src/misc
MISCOBJ = $(MISC)/CopyFile.o $(MISC)/DateRequest.o $(MISC)/Dates.o \
$(MISC)/DiskMisc.o $(MISC)/FileMisc.o $(MISC)/FormatDisk.o \
$(MISC)/sendpkt.o $(MISC)/Speech.o
$(MISC)/CopyFile.o: $(MISC)/CopyFile.c
cc $(CFLAGS) -o $(MISC)/CopyFile.o $(MISC)/CopyFile.c
$(MISC)/DateRequest.o: $(MISC)/DateRequest.c
cc $(CFLAGS) -o $(MISC)/DateRequest.o $(MISC)/DateRequest.c
$(MISC)/Dates.o: $(MISC)/Dates.c
cc $(CFLAGS) -o $(MISC)/Dates.o $(MISC)/Dates.c
$(MISC)/DiskMisc.o: $(MISC)/DiskMisc.c
cc $(CFLAGS) -o $(MISC)/DiskMisc.o $(MISC)/DiskMisc.c
$(MISC)/FileMisc.o: $(MISC)/FileMisc.c
cc $(CFLAGS) -o $(MISC)/FileMisc.o $(MISC)/FileMisc.c
$(MISC)/FormatDisk.o: $(MISC)/FormatDisk.c
cc $(CFLAGS) -o $(MISC)/FormatDisk.o $(MISC)/FormatDisk.c
$(MISC)/sendpkt.o: $(MISC)/sendpkt.c
cc $(CFLAGS) -o $(MISC)/sendpkt.o $(MISC)/sendpkt.c
$(MISC)/Speech.o: $(MISC)/Speech.c
cc $(CFLAGS) -o $(MISC)/Speech.o $(MISC)/Speech.c
BackUp.o: menu.h gadget.h
Menu.o: Menu.h
PathRequest.o: Gadget.h
# Note: $(CHIPOBJ) must follow the +C option to insure that its image
# data gets loaded into chip ram.
MRBackup: $(OBJ) $(MISCOBJ) $(CHIPOBJ)
ln -w -o MRBackUp $(OBJ) $(MISCOBJ) +C $(CHIPOBJ) -lc
SHAR_EOF
cat << \SHAR_EOF > Menu.c
/*****************************************************
* Menu Definitions *
* *
* Created with Menu Creator *
* by *
* David Pehrson *
* *
* Copyright (C) 1986 David Pehrson *
* Mantis Development *
* *
*****************************************************/
#include <exec/types.h>
#include <intuition/intuition.h>
char stTopaz[] = "topaz.font";
struct TextAttr taPlain =
{
(STRPTR) stTopaz, 8, FS_NORMAL, FPF_ROMFONT
};
struct TextAttr taBIU =
{
(STRPTR) stTopaz, 8, FSF_BOLD | FSF_ITALIC | FSF_UNDERLINED, FPF_ROMFONT
};
struct TextAttr taBU =
{
(STRPTR) stTopaz, 8, FSF_BOLD | FSF_UNDERLINED, FPF_ROMFONT
};
struct TextAttr taBI =
{
(STRPTR) stTopaz, 8, FSF_BOLD | FSF_ITALIC, FPF_ROMFONT
};
struct TextAttr taB =
{
(STRPTR) stTopaz, 8, FSF_BOLD, FPF_ROMFONT
};
struct TextAttr taIU =
{
(STRPTR) stTopaz, 8, FSF_ITALIC | FSF_UNDERLINED, FPF_ROMFONT
};
struct TextAttr taI =
{
(STRPTR) stTopaz, 8, FSF_ITALIC, FPF_ROMFONT
};
struct TextAttr taU =
{
(STRPTR) stTopaz, 8, FSF_UNDERLINED, FPF_ROMFONT
};
struct IntuiText ItemText[] =
{
{ 0, 1, JAM1, 0, 1, &taPlain, "Backup", NULL },
{ 0, 1, JAM1, 0, 1, &taPlain, "Restore", NULL },
{ 0, 1, JAM1, 0, 1, &taPlain, "About", NULL },
{ 0, 1, JAM1, 0, 1, &taPlain, "Quit", NULL },
{ 0, 1, JAM1, 0, 1, &taPlain, " Compression", NULL },
{ 0, 1, JAM1, 0, 1, &taPlain, " No Compression", NULL },
{ 0, 1, JAM1, 0, 1, &taPlain, " Listing", NULL },
{ 0, 1, JAM1, 0, 1, &taPlain, " No Listing", NULL },
{ 0, 1, JAM1, 0, 1, &taPlain, " Speech", NULL },
{ 0, 1, JAM1, 0, 1, &taPlain, " No Speech", NULL },
};
struct MenuItem Items[] =
{
{ /* Backup */
&Items[1], 0, 0, 72, 10,
ITEMENABLED | ITEMTEXT | HIGHCOMP ,
0, (APTR)&ItemText[0], NULL, NULL, NULL, NULL
},
{
&Items[2], 0, 10, 72, 10,
ITEMENABLED | ITEMTEXT | HIGHCOMP ,
0, (APTR)&ItemText[1], NULL, NULL, NULL, NULL
},
{
&Items[3], 0, 20, 72, 10,
ITEMENABLED | ITEMTEXT | HIGHCOMP,
0, (APTR)&ItemText[2], NULL, NULL, NULL, NULL
},
{
NULL, 0, 30, 72, 10,
ITEMENABLED | ITEMTEXT | HIGHCOMP,
0, (APTR)&ItemText[3], NULL, NULL, NULL, NULL
},
{
&Items[5], 0, 0, 152, 10, /* Compression */
ITEMENABLED | ITEMTEXT | CHECKIT | HIGHCOMP | CHECKED,
2, (APTR)&ItemText[4], NULL, NULL, NULL, NULL
},
{
&Items[6], 0, 10, 152, 10, /* No Compression */
ITEMENABLED | ITEMTEXT | CHECKIT | HIGHCOMP,
1, (APTR)&ItemText[5], NULL, NULL, NULL, NULL
},
{
&Items[7], 0, 20, 152, 10, /* Listing */
ITEMENABLED | ITEMTEXT | CHECKIT | HIGHCOMP | CHECKED,
8, (APTR)&ItemText[6], NULL, NULL, NULL, NULL
},
{
&Items[8], 0, 30, 152, 10, /* No Listing */
ITEMENABLED | ITEMTEXT | CHECKIT | HIGHCOMP,
4, (APTR)&ItemText[7], NULL, NULL, NULL, NULL
},
{
&Items[9], 0, 40, 152, 10, /* Speech */
ITEMENABLED | ITEMTEXT | CHECKIT | HIGHCOMP | CHECKED,
32, (APTR)&ItemText[8], NULL, NULL, NULL, NULL
},
{
NULL, 0, 50, 152, 10, /* No Speech */
ITEMENABLED | ITEMTEXT | CHECKIT | HIGHCOMP,
16, (APTR)&ItemText[9], NULL, NULL, NULL, NULL
},
};
struct Menu Titles[] =
{
{ &Titles[1], 3, 0, 70, 0, MENUENABLED, "Project", &Items[0],0,0,0,0 },
{ NULL, 72, 0, 54, 0, MENUENABLED, "Flags", &Items[4],0,0,0,0 },
};
SHAR_EOF
cat << \SHAR_EOF > Menu.h
/* Menu constant definitions */
#define MENU_PROJECT 0
#define ITEM_BACKUP 0
#define ITEM_RESTORE 1
#define ITEM_ABOUT 2
#define ITEM_QUIT 3
#define MENU_FLAGS 1
#define ITEM_COMPRESS 0
#define ITEM_NOCOMPRESS 1
#define ITEM_LIST 2
#define ITEM_NOLIST 3
#define ITEM_SPEECH 4
#define ITEM_NOSPEECH 5
SHAR_EOF
cat << \SHAR_EOF > MiscRequest.c
/* Miscellaneous requester support routines. */
#include <exec/memory.h>
#include <intuition/intuition.h>
#include <intuition/intuitionbase.h>
#include <libraries/dosextens.h>
#include <graphics/text.h>
#include <functions.h>
#include <ctype.h>
struct IntuiText diskreq_body_text = {
AUTOFRONTPEN, /* FrontPen */
AUTOBACKPEN, /* BackPen */
AUTODRAWMODE, /* DrawMode */
AUTOLEFTEDGE, /* LeftEdge */
AUTOTOPEDGE, /* TopEdge */
NULL, /* ITextFont Pointer */
(UBYTE *)"", /* IText */
NULL /* NextText */
};
struct IntuiText diskreq_pos_text = {
AUTOFRONTPEN, /* FrontPen */
AUTOBACKPEN, /* BackPen */
AUTODRAWMODE, /* DrawMode */
AUTOLEFTEDGE, /* LeftEdge */
AUTOTOPEDGE, /* TopEdge */
NULL, /* ITextFont Pointer */
(UBYTE *)"OK", /* IText */
NULL /* NextText */
};
struct IntuiText diskreq_neg_text = {
AUTOFRONTPEN, /* FrontPen */
AUTOBACKPEN, /* BackPen */
AUTODRAWMODE, /* DrawMode */
AUTOLEFTEDGE, /* LeftEdge */
AUTOTOPEDGE, /* TopEdge */
NULL, /* ITextFont Pointer */
(UBYTE *)"Cancel", /* IText */
NULL /* NextText */
};
/* Generate an autorequest for the insertion of a floppy disk.
* Called with:
* window: pointer to window for request
* drivespec: the disk drive name
*/
int
RequestDisk(window, drivespec)
struct Window *window; char *drivespec;
{
UBYTE prompt[41];
strcpy(prompt,"Insert blank disk in ");
strncat(prompt,drivespec,(sizeof(prompt)-strlen(prompt)-1));
diskreq_body_text.IText = prompt;
return AutoRequest(
window, &diskreq_body_text, &diskreq_pos_text,
&diskreq_neg_text, DISKINSERTED, NULL, 300L, 50L);
}
SHAR_EOF
cat << \SHAR_EOF > ReadME
This is MRBackup, version 1.0. This program was written under Manx
Aztec C, version 3.40a, to run under KickStart 1.2. The source was
written with 4 character tab settings, using Z. If you would like a
copy of my file retab utility to alter the tab settings, just ask.
Mark Rinfret
mark@unisec.USI.COM
SHAR_EOF
cat << \SHAR_EOF > Restore.c
/* Filename: restore.c
* Author: Mark R. Rinfret
* Date: 08/02/87
* Description: Restore processing module for MRBackup
*/
#include <libraries/dosextens.h>
#include <exec/memory.h>
#include <functions.h>
extern char conmsg[];
extern char backpath[], homepath[];
#ifdef DEBUG
extern char debugmsg[];
#endif
extern char destpath[], destvol[];
extern unsigned do_compress;
extern struct DateStamp *since;
extern char srcpath[], srcvol[];
static char fullbackpath[256], fullhomepath[256];
static unsigned home_is_device; /* true => home is "DH<N>:" */
static char temp[256];
/* Restore files from floppy disk. */
int
Restore()
{
int status = 0;
Speak("And away we go!");
BreakPath(backpath, srcvol, srcpath);
if (homepath[strlen(homepath)-1] == ':')
home_is_device = 1;
else
home_is_device = 0;
status = RestoreFile(srcpath);
if (status == 0) {
sprintf(conmsg,"Restore completed successfully\n");
Speak("I have completed my mission successfully.\n");
}
else {
sprintf(conmsg,"Restore terminated with status %d\n",status);
Speak("Perhaps you should check things out and try it again.\n");
}
WriteConsole(conmsg);
}
^L
/* Restore all the files in a directory.
* Called with:
* lock: lock on the directory
* fib: pointer to file info block
* path: directory pathname (without volume)
* Returns:
* status (0 => success)
*/
int
RestoreDir(lock, fib, path)
struct Lock *lock; struct FileInfoBlock *fib; char *path;
{
struct Lock *dirlock = NULL, *filelock = NULL;
char newpath[256];
int status = 0;
strcpy(temp, homepath);
if (*path) {
if (!home_is_device) strcat(temp, "/");
strcat(temp, path);
}
#ifdef DEBUG
sprintf(debugmsg,"Checking for directory %s\n",temp);
DebugWrite(debugmsg);
#endif
if (!(dirlock = Lock(temp, SHARED_LOCK))) {
if ((status = IoErr()) == ERROR_OBJECT_NOT_FOUND) {
#ifdef DEBUG
sprintf(debugmsg,"Creating directory %s\n",temp);
DebugWrite(debugmsg);
#endif
if (!(dirlock = CreateDir(temp))) {
status = IoErr();
sprintf(conmsg,"Unable to create directory %s: %d\n",
temp, status);
TypeAndSpeak(conmsg);
goto done;
}
status = 0;
}
else {
sprintf(conmsg,"RestoreDir cannot lock %s: %d\n",temp, status);
TypeAndSpeak(conmsg);
return status;
}
}
if (dirlock) UnLock(dirlock);
while (ExNext(lock,fib)) {
strcpy(newpath, path);
if (*newpath)
strcat(newpath, "/");
strcat(newpath, fib->fib_FileName);
if (status = RestoreFile(newpath)) {
/* filter out "permissable:" errors */
if (status == ERROR_OBJECT_IN_USE ||
status == ERROR_WRITE_PROTECTED)
status = 0;
else
break;
}
}
done:
return status;
}
^L
/* Restore one or more files according to the calling pathname.
* The path argument does not contain the backup volume name.
*/
int
RestoreFile(path)
char *path;
{
struct FileInfoBlock *fib = NULL, *fib2 = NULL;
UBYTE exists = FALSE, ignore = FALSE;
struct Lock *lock = NULL;
USHORT namelength;
UBYTE savechar;
int status = 0;
if (status = CheckStop()) return status;
if (!(fib = (struct FileInfoBlock *)
AllocMem((long) sizeof (struct FileInfoBlock),
MEMF_PUBLIC | MEMF_CHIP))) {
TypeAndSpeak("RestoreFile could not allocate FIB!\n");
return ERROR_NO_FREE_STORE;
}
sprintf(fullbackpath, "%s:%s",srcvol,path);
strcpy(fullhomepath, homepath);
if (*path) {
if (!home_is_device) strcat(fullhomepath, "/");
strcat(fullhomepath, path);
}
#ifdef DEBUG
sprintf(conmsg,"fullbackpath = %s\n",fullbackpath);
DebugWrite(conmsg);
sprintf(conmsg,"fullhomepath = %s\n",fullhomepath);
DebugWrite(conmsg);
#endif
if (!(lock = Lock(fullbackpath, SHARED_LOCK))) {
status = IoErr();
sprintf(conmsg, "RestoreFile: can't lock %s: %d\n",
fullbackpath, status);
TypeAndSpeak(conmsg);
goto done;
}
if (!Examine(lock, fib)) {
status = IoErr();
sprintf(conmsg, "RestoreFile can't examine %s: %d\n",
fullbackpath, status);
TypeAndSpeak(conmsg);
goto done;
}
if (fib->fib_DirEntryType > 0) { /* path is a directory */
status = RestoreDir(lock, fib, path);
UnLock(lock);
lock = NULL;
}
else {
UnLock(lock);
lock = NULL;
/*#define NOCOPY*/
#ifndef NOCOPY
/* If this file exists, then check its modification date. If
* it's newer than the backup, don't replace it.
*/
if ((lock = Lock(fullhomepath, SHARED_LOCK))) {
if (!(fib2 = (struct FileInfoBlock *)
AllocMem((long) sizeof (struct FileInfoBlock),
MEMF_PUBLIC | MEMF_CHIP))) {
TypeAndSpeak("RestoreFile could not allocate FIB!\n");
status = ERROR_NO_FREE_STORE;
goto done;
}
Examine(lock, fib2);
UnLock(lock);
lock = NULL;
if (CompareDS(&fib2->fib_Date, &fib->fib_Date) >= 0)
ignore = TRUE;
}
if (ignore) {
sprintf(conmsg,"Skipping %s. Current version is newer.\n",
path);
TypeAndSpeak(conmsg);
}
else {
if (!do_compress || !IsCompressed(fullhomepath)) {
copyfile:
sprintf(conmsg,"Copying %s\n", fullbackpath);
WriteConsole(conmsg);
status = CopyFile(fullbackpath, fullhomepath);
}
else {
/* truncate the destination pathname (remove ".z") */
namelength = strlen(fullhomepath);
fullhomepath[namelength-2] = '\0';
sprintf(conmsg, "Decompressing %s\n", fullbackpath);
WriteConsole(conmsg);
if (decompress(fullbackpath, fullhomepath)) {
sprintf(conmsg,
"Decompression of %s failed - will copy.\n",
fullbackpath);
TypeAndSpeak(conmsg);
/* restore ".z" to name */
fullhomepath[namelength-2] = '.';
goto copyfile;
}
CopyFileDate(fullbackpath, fullhomepath);
}
}
#endif
}
done:
if (lock) UnLock(lock);
if (fib) FreeMem(fib, (long) sizeof(struct FileInfoBlock));
if (fib2) FreeMem(fib2, (long) sizeof(struct FileInfoBlock));
return status;
}
SHAR_EOF