[comp.sources.unix] v15i011: abcd - Automatic Backup Copy Daemon, Part01/01

sources-request@munnari.oz (05/27/88)

Submitted by: richb@sunaus.sun.oz (Rich Burridge)
Posting-number: Volume 15, Issue 11
Archive-name: abcd/Part01

		[ this compiles OK on SunOS 3.4, but not on a 4.3 vax,
		  so it probably contains 4.2 dependancies.  I don't
		  suppose they would be hard to fix.  I wouldn't bother
		  on SysV I think.				... kre ]


This is an automatic backup copy daemon, which copies files from one
filestore area to another using either cp or rcp. Note the second filestore
area could be on another machine if the file system is NFS mounted.

This means that the backup machine should have an identical copy of the
filestore being monitored, give or take a little bit.

With improvements and suggestions from various people, this program
could be very useful. I can see a lot of drawbacks and limitations to
this first version, but I'd like feedback before I work on any modifications.

    Rich.

------CUT HERE------CUT HERE------
#! /bin/sh
# this is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh to create the files:
#	abcd.c
#	abcd.h
#	patchlevel.h
#	Makefile
#	README
#	abcd.1
# This archive created: Wed May 25 11:56:25 EST 1988
#
#
export PATH; PATH=/bin:$PATH
#
if [ -f abcd.c ]
then
echo shar: will not over-write existing file abcd.c
else
echo shar: extracting 'abcd.c',    12906 characters
cat > abcd.c <<'Funky_Stuff'

/*  abcd.c
 *
 *  This is an automatic backup copy daemon, which copies files from one
 *  filestore area to another using either cp or rcp. Note the second filestore
 *  area could be on another machine if the file system is NFS mounted.
 *
 *  This means that the backup disk should have an identical copy of the
 *  filestore being monitored, give or take a little bit.
 *
 *  SWITCHES
 *
 *  -fdirectory  Directory to start copying from. Defaults to /usr.
 *
 *  -tdirectory  Directory to start copying to. Defaults to /usr2.
 *
 *  -rhostname   This is an alternative form of backup. Uses rcp and copies to
 *               hostname supplied.
 *
 *  -sseconds    Specifies the sleep period in seconds for when the program puts
 *               itself to sleep.
 *
 *  -c           If given, then abcd won't copy the first time through.
 *
 *  Copyright (c) Rich Burridge - Sun Microsystems Australia (Melbourne).
 *
 *  Version 1.1 - May 1988.
 *
 *  No responsibility is taken for any error in accuracies inherent
 *  either to the comments or the code of this program, but if
 *  reported to me then an attempt will be made to fix them.
 */

#include <stdio.h>
#include <strings.h>
#include <errno.h>
#include <signal.h>
#include <sys/file.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/dir.h>
#include "patchlevel.h"
#include "abcd.h"

char *malloc(), *sprintf() ;

struct finfo *cfile ;             /* Information on current file. */
struct finfo *files  = NULL ;     /* Files being monitored. */
struct dinfo *cdir ;              /* Pointer to current directory. */
struct dinfo *dirs = NULL ;       /* Directories being monitored. */
struct stat cstat ;               /* For statistics on current file. */
struct direct *cur ;              /* Pointer to current directory record. */

extern int errno ;                /* Standard error reply. */

char cname[MAXLINE] ;             /* Copy program to use. */
char coff[MAXLINE] ;              /* Name offset from initial start directory. */
char curdir[MAXLINE] ;            /* Current directory being monitored. */
char dbuf[DIRBLKSIZ] ;            /* Buffer for directory information. */
char fdir[MAXLINE] ;              /* Directory to start copying from. */
char flist[MAXFILES][MAXLINE] ;   /* Array of filenames to copy. */
char fname[MAXLINE] ;             /* Full pathname of current file. */
char pdir[MAXLINE] ;              /* Current directory for copy request. */
char rhost[MAXLINE] ;             /* Name of remote host for use by rcp. */
char tdir[MAXLINE] ;              /* Directory to start copying to. */

int copy_found  = 0 ;             /* Whether copy done on this pass. */
int delay = 300 ;                 /* Sleep time for abcd in seconds. */
int docopy = 1 ;                  /* Rcp files right from the beginning. */
int fd = 0 ;                      /* File id of the directory being monitored. */
int loc = 0 ;                     /* Current location in the directory block. */
int fcnt = 0 ;                    /* Number of files to copy in current request. */
int no_dirs = 1 ;                 /* Number of directories being monitored. */
int size ;                        /* Size of current directory block in bytes. */ 
int usercp = 0 ;                  /* Backup method, cp or rcp. */


get_options(argc,argv)            /* Get abcd options from command line. */
int argc ;
char *argv[] ;

{
  char *arg ;
  char *p ;                  /* Pointer to string following argument flag. */

  STRCPY(fdir,"/usr") ;      /* Default directory to monitor. */
  STRCPY(tdir,"/usr2") ;     /* Default directory to copy to. */
  STRCPY(rhost,"") ;         /* Default is no remote host. */

  while (argc > 1 && (arg = argv[1])[0] == '-')
    {
      p = arg + 2 ;
      switch (arg[1])
        {
          case 'c' : docopy = 0 ;           /* Don't copy files the first time. */
                     break ;
          case 'f' : STRCPY(fdir,p) ;       /* Directory to start monitoring from. */
                     break ;
          case 'r' : STRCPY(rhost,p) ;      /* Name of remote host. */
                     usercp = 1 ;
                     break ;
          case 's' : delay = atoi(p) ;      /* Sleep period in seconds. */
                     break ;
          case 't' : STRCPY(tdir,p) ;       /* Directory to start copying to. */
                     break ;
          default  : FPRINTF(stderr,"USAGE: abcd [-c] [-ffromdir] [-rhostname]") ;
                     FPRINTF(stderr," [-ssleep] [-ttodir]\n") ;
                     exit(1) ;
        }
      argc-- ;
      argv++ ;
    }
}


setup()

{
  dirs = (struct dinfo *) malloc(sizeof(struct dinfo)) ;
  STRCPY(dirs->d_name,"") ;
  STRCPY(pdir,fdir) ;                          /* Start directory for current cp request. */
  STRCPY(coff,"") ;                            /* Name offset from start directory. */
  if (usercp) STRCPY(cname,"/usr/ucb/rcp") ;   /* Used by the output routine. */
  else STRCPY(cname,"/bin/cp") ;
  dirs->next = dirs ;
  cdir = dirs ;
}


get_next_dir()        /* Get next directory name to monitor. */

{
  struct dinfo *temp ;
  int dirfound ;

  dirfound = 0 ;
  if (fd)
    {
      CLOSE(fd) ;
      if (!strlen(cdir->next->d_name))     /* Complete pass done? */
        {
          docopy = 1 ;                     /* Always copy after first pass. */
          if (!copy_found)
            sleep((unsigned int) delay) ;  /* Nothing happening, go to sleep. */
          else copy_found = 0 ;
        }
      if (strcmp(curdir,cdir->next->d_name) != 0)
        if (fcnt) output("",REGULAR) ;
    }
  do
    {
      STRCPY(curdir,fdir) ;
      if (strlen(cdir->next->d_name))
        {
          STRCAT(curdir,"/") ;
          STRCAT(curdir,cdir->next->d_name) ;
        }
      STRCPY(coff,cdir->next->d_name) ;
      if ((fd = open(curdir,0)) == -1)
        {
          if (EQUAL(curdir,fdir)) exit(0) ;  /* Nothing left to monitor. */
          else
            {
              temp = cdir->next ;
              if (cdir->next == dirs) dirs = cdir ;
              cdir->next = cdir->next->next ;
              free((char *) temp) ;                /* Lose this directory record. */
              no_dirs-- ;
            }
        }
      else dirfound = 1 ;     /* Directory found. */
    }
  while (!dirfound) ;
  cdir = cdir->next ;         /* Point to current directory. */
  loc = 0 ;                   /* Reset directory buffer pointer. */
}


make_dir_entry()       /* If not there already, create a new directory record. */

{
  int i ;
  char tempdir[MAXLINE] ;          /* Temporary directory name. */
  struct dinfo *temp ;

  temp = cdir ;
  for (i = 0; i < no_dirs; i++)    /* Is the directory already being monitored? */
    {
      temp = temp->next ;
      STRCPY(tempdir,fdir) ;
      if (strlen(temp->d_name))
        {
          STRCAT(tempdir,"/") ;
          STRCAT(tempdir,temp->d_name) ;
        }
      if (EQUAL(tempdir,fname)) return(0) ;
    }

  temp = (struct dinfo *) malloc(sizeof(struct dinfo)) ;
  temp->next = dirs->next ;
  dirs->next = temp ;
  dirs = temp ;

  STRCPY(dirs->d_name,"") ;
  if (strlen(coff))
    {
      STRCAT(dirs->d_name,coff) ;
      STRCAT(dirs->d_name,"/") ;
    }
  STRCAT(dirs->d_name,cur->d_name) ;
  no_dirs++ ;
  return(1) ;
}


get_next_entry(fd)     /* Get next directory filename entry. */
int fd ;

{
  int tfd ;            /* Temporary file descriptor for file entry. */

  for (;;)
    {
      if (!loc)
        if ((size = read(fd,dbuf,DIRBLKSIZ)) <= 0) return(0) ;
      if (loc >= size)
        {
          loc = 0 ;
          continue ;
        }
      cur = (struct direct *) (dbuf+loc) ;
      if (cur->d_fileno == 0) return(0) ;
      SPRINTF(fname,"%s/%s",curdir,cur->d_name) ;
      if ((tfd = open(fname,0)) == -1)
        {
          loc += cur->d_reclen ;
          continue ;
        }
      else
        {
          CLOSE(tfd) ;
          cur = (struct direct *) malloc(sizeof(struct direct)) ;
          bcopy(dbuf+loc,(char *) cur,(int) DIRSIZ((struct direct *)(dbuf+loc))) ;
          loc += cur->d_reclen ;
          STAT(fname,&cstat) ;
          return(1) ;
        }
    }
}


no_record()     /* Check is this file is already being monitored. */

{
  if (files == NULL) return(1) ;
  cfile = files ;
  do
    if (cfile->direct->d_fileno == cur->d_fileno) return(0) ;
  while ((cfile = cfile->next) != NULL) ;
  return(1) ;
}


make_file_entry()      /* Make a record for this new file. */

{
  struct finfo *temp ;

  if ((cstat.st_mode & S_IFMT) == REGULAR)
    {
      temp = (struct finfo *) malloc(sizeof(struct finfo)) ;
      temp->next = files ;
      files = temp ;

      files->direct = (struct direct *) malloc(sizeof(struct direct)) ;
      files->direct = cur ;
      files->mtime = cstat.st_mtime ;
    }
}


file_modified()    /* Check if this file has been changed. */

{
  if (cfile->mtime == cstat.st_mtime) return(0) ;
  cfile->mtime = cstat.st_mtime ;
  return(1) ;
}


copy(filetype)                     /* Copy file to another directory. */
int filetype ;

{
  char name[MAXLINE] ;

  if (docopy)     /* Are we copying this time around. */
    {
      copy_found = 1 ;
      STRCPY(name,"") ;
      if (strlen(coff))
        {
          STRCAT(name,coff) ;
          STRCAT(name,"/") ;
        }
      STRCAT(name,cur->d_name) ;
      if (strcmp(curdir,pdir) != 0)        /* Current dir. different from cp dir.? */
        {
          if (fcnt) output(name,REGULAR) ;
          STRCPY(pdir,curdir) ;
        }
      if (filetype == DIRECTORY)
        {
          if (fcnt) output(name,REGULAR) ;
          output(name,DIRECTORY) ;
        }
      else if (fcnt >= MAXFILES)           /* Room in file list for this filename? */
        {
          output(name,REGULAR) ;
          STRCPY(flist[fcnt++],name) ;
        }
      else STRCPY(flist[fcnt++],name) ;   /* Save filename in file list. */
    }
}


output(name,filetype)
char name[MAXLINE] ;
int filetype ;

{
  char command[MAXLINE*7],rdirname[MAXLINE] ;
  int i ;

  switch (filetype)
    {
      case DIRECTORY : if (usercp)
                         SPRINTF(command,"%s -r %s/%s %s:%s",cname,fdir,name,rhost,curdir) ;
                       else
                         {
                           STRCPY(rdirname,tdir) ;
                           STRCAT(rdirname,"/") ;
                           STRCAT(rdirname,name) ;
                           if ((fd = open(rdirname,0)) == -1)
                             SPRINTF(command,"mkdir %s",rdirname) ;
                           else return ;
                         }
                       break ;
      case REGULAR   : if (usercp) SPRINTF(rdirname,"%s:%s",rhost,curdir) ;
                       else
                         {
                           STRCPY(rdirname,tdir) ;
                           if (strlen(coff))
                             {
                               STRCAT(rdirname,"/") ;
                               STRCAT(rdirname,coff) ;
                             }
                         }
                       STRCPY(command,cname) ;
                       for (i = 0; i < fcnt; i++)
                         {
                           STRCAT(command," ") ;
                           STRCAT(command,fdir) ;
                           STRCAT(command,"/") ;
                           STRCAT(command,flist[i]) ;
                         }
                       STRCAT(command," ") ;
                       STRCAT(command,rdirname) ;
    }
  if (system(command))
    FPRINTF(stderr,"abcd failed: %s\n",command) ;
  else FPRINTF(stderr,"abcd succeeded: %s\n",command) ;
  FPRINTF(stderr,"\n\n\n") ;

  fcnt = 0 ;
}


main(argc,argv)
int argc ;
char *argv[] ;

{
  get_options(argc,argv) ;               /* Get command line options. */
  setup() ;                              /* Initialise parameters. */
  while (MACHINE_WORKING)                /* Do it until the machine crashes. */
    {
      get_next_dir() ;                   /* Is there another directory? */
        while (get_next_entry(fd))       /* Is there another file in dir? */
          {
            if (!DOTS(cur->d_name))      /* Is it the . or .. entry? */
              {
                if (no_record())         /* Is this file already monitored? */
                  {
                    if ((cstat.st_mode & S_IFMT) == DIRECTORY)      /* Directory? */
                      {
                        if (make_dir_entry()) copy(DIRECTORY) ;
                      }
                    else
                      {
                        make_file_entry() ;      /* Make a file entry. */
                        copy(REGULAR) ;          /* Copy it to backup machine. */
                      }
                  }
                else if (file_modified()) copy(REGULAR) ;   /* File been modified? */
              }
            else free((char *) cur) ;
          }
    }
}
Funky_Stuff
len=`wc -c < abcd.c`
if [ $len !=    12906 ] ; then
echo error: abcd.c was $len bytes long, should have been    12906
fi
fi # end of overwriting check
if [ -f abcd.h ]
then
echo shar: will not over-write existing file abcd.h
else
echo shar: extracting 'abcd.h',     1645 characters
cat > abcd.h <<'Funky_Stuff'

/*  abcd.h
 *
 *  Definitions used by abcd, the automatic backup copy daemon.
 *
 *  Copyright (c) Rich Burridge - Sun Microsystems Australia (Melbourne).
 * 
 *  Version 1.1 - May 1988.
 *
 *  No responsibility is taken for any error in accuracies inherent
 *  either to the comments or the code of this program, but if
 *  reported to me then an attempt will be made to fix them.
 */

#define  CLOSE            (void) close     /* To make lint happy. */
#define  FPRINTF          (void) fprintf
#define  SPRINTF          (void) sprintf
#define  SIGNAL           (void) signal
#define  STAT             (void) stat
#define  STRCAT           (void) strcat
#define  STRCPY           (void) strcpy

#define  DIRECTORY        S_IFDIR          /* Type of files being monitored. */
#define  REGULAR          S_IFREG

#define  DIRBLKSIZ        512              /* Block size for directory read. */
#define  DOTS(A)          (A[0] == '.' && (A[1] == 0 || (A[1] == '.' && A[2] == 0)))
#define  EQUAL(a,b)       !strcmp(a,b)     /* Test for string equality. */
#define  MACHINE_WORKING  1                /* Forever and a day .... */
#define  MAXFILES         5                /* Max no of files to copy in one go. */
#define  MAXLINE          MAXNAMLEN+1      /* Maximum length of path names. */


struct dinfo             /* Info record for directories being monitored. */
         {
           struct dinfo *next ;
           char d_name[MAXLINE] ;
         } ;

struct finfo            /* Information record for monitored files. */
         {
           struct finfo *next ;
           struct direct *direct ;
           time_t mtime ;
          } ;
Funky_Stuff
len=`wc -c < abcd.h`
if [ $len !=     1645 ] ; then
echo error: abcd.h was $len bytes long, should have been     1645
fi
fi # end of overwriting check
if [ -f patchlevel.h ]
then
echo shar: will not over-write existing file patchlevel.h
else
echo shar: extracting 'patchlevel.h',      443 characters
cat > patchlevel.h <<'Funky_Stuff'

/*  patchlevel.h
 *
 *  This is the current patch level for this version of abcd.
 *
 *  Copyright (c) Rich Burridge - May 1988.
 *              Sun Microsystems, Australia - All rights reserved.
 *
 *  Version 1.1.
 *
 *  No responsibility is taken for any errors or inaccuracies inherent
 *  either to the comments or the code of this program, but if
 *  reported to me then an attempt will be made to fix them.
 */

#define  PATCHLEVEL  0
Funky_Stuff
len=`wc -c < patchlevel.h`
if [ $len !=      443 ] ; then
echo error: patchlevel.h was $len bytes long, should have been      443
fi
fi # end of overwriting check
if [ -f Makefile ]
then
echo shar: will not over-write existing file Makefile
else
echo shar: extracting 'Makefile',      955 characters
cat > Makefile <<'Funky_Stuff'
#
#  Makefile for abcd, an automatic backup copy daemon.
#
#  Copyright (c) Rich Burridge - Sun Microsystems Australia (Melbourne).
#
#  Version 1.1 - May 1988.
#
#  No responsibility is taken for any errors inherent either in the comments
#  or the code of this program, but if reported to me then an attempt will
#  be made to fix them.
#

BINARIES        = abcd
BINDIR          = .
CFLAGS          = -g
LDFLAGS         = -g
OBJS            = abcd.o
SRCS            = abcd.c
HDRS            = abcd.h patchlevel.h
LIBS            =
OTHERS          = Makefile README abcd.1

all:            $(BINARIES)

release:        $(BINARIES)
		strip $(BINARIES)
		mv $(BINARIES) $(BINDIR)

backup:
		cp abcd.c abcd.c~
		cp abcd.h abcd.h~

shar:;          shar.script $(SRCS) $(HDRS) $(OTHERS) > archive

clean:
		rm -f abcd *.o *.c~ core

lint:
		lint $(SRCS) $(LIBS)

abcd:           $(OBJS)
		cc $(LDFLAGS) -o abcd $(OBJS) $(LIBS)

abcd.o:         abcd.c $(HDRS)
Funky_Stuff
len=`wc -c < Makefile`
if [ $len !=      955 ] ; then
echo error: Makefile was $len bytes long, should have been      955
fi
fi # end of overwriting check
if [ -f README ]
then
echo shar: will not over-write existing file README
else
echo shar: extracting 'README',     2345 characters
cat > README <<'Funky_Stuff'

  ABCD v1.1 - May 1988.
  ---------------------

  This is an automatic backup copy daemon, which copies files from one
  filestore area to another using either cp or rcp. Note the second filestore
  area could be on another machine if the file system is NFS mounted.

  This means that the backup machine should have an identical copy of the
  filestore being monitored, give or take a little bit.

  NOTES

  (1) A start directory for monitoring is given. All the files including
      sub-directories and their files are monitored. Whenever any one of
      them is changed it is automatically copied to another machine as
      specified by the initial parameters to the program.

  (2) If it finds a new directory, then it will make that new directory
      in the other area (on the other machine) if not present.

  (3) If a sub-directory is deleted, then that directory is removed from
      the list of directories being monitored. If the original start directory
      is removed, then the program terminates because there is nothing left
      to monitor.

  (4) If nothing has changed in one complete pass of all the directories
      been monitored, then the program puts itself to sleep for 5 minutes.

  (5) There is a restart facility whereby if the machine has crashed, then it
      is possible to tell the program not to copy anything for the first pass,
      but to start copying changed files from the second pass onwards.

  (6) Abcd uses the "system" system call to do its copying, so as to prevent
      hundreds or child processes being spawned.

  SWITCHES

  -fdirectory  Directory to start copying from. Defaults to /usr.

  -tdirectory  Directory to start copying to. Defaults to /usr2.

  -rhostname   This is an alternative form of backup. Uses rcp and copies to
               hostname supplied.

  -sseconds    Specifies the sleep period in seconds for when the program puts
               itself to sleep.

  -c           If given, then abcd won't copy the first time through.


  Suggestions, comments, flames and bugs to me please.

      Rich.

Rich Burridge,           JANET richb%sunaus.oz@uk.ac.ucl.cs
ACSnet  richb@sunaus.oz  UUCP {uunet,mcvax,ukc}!munnari!sunaus.oz!richb
PHONE: +61 2 436 4699    ARPAnet rburridge@Sun.COM
Sun Microsystems, Unit 2, 49-53 Hotham Pde, Artarmon, N.S.W. 2164, AUSTRALIA.
Funky_Stuff
len=`wc -c < README`
if [ $len !=     2345 ] ; then
echo error: README was $len bytes long, should have been     2345
fi
fi # end of overwriting check
if [ -f abcd.1 ]
then
echo shar: will not over-write existing file abcd.1
else
echo shar: extracting 'abcd.1',     2407 characters
cat > abcd.1 <<'Funky_Stuff'
.\" @(#)abcd.1 1.1 25/05/88;
.TH ABCD 1L "25 May 1988"
.SH NAME
abcd \- automatic backup copy daemon
.SH SYNOPSIS
.B "abcd
[
.B -f
.I directory
]
[
.B -t
.I directory
]
[
.B -r
.I hostname
]
[
.B -s
.I seconds
]
[
.B -c
]
.SH DESCRIPTION
.I Abcd
is an automatic backup copy daemon, which copies files from one
filestore area to another using either cp or rcp. Note the second filestore
area could be on another machine if the file system is NFS mounted.
.LP
This means that the backup machine should have an almost identical copy
of the filestore being monitored.
.LP
A start directory for monitoring is given. All the files including
sub-directories and their files are monitored. Whenever any one of
them is changed it is automatically copied to another machine as
specified by the initial parameters to the program.
.LP
If it finds a new directory, then it will make that new directory
in the other area if not present.
.LP
If a sub-directory is deleted, then that directory is removed from
the list of directories being monitored. If the original start directory
is removed, then the program terminates because there is nothing left
to monitor.
.LP
If nothing has changed in one complete pass of all the directories
been monitored, then the program puts itself to sleep for a specified
period.
.LP
There is a restart facility whereby if the machine has crashed, then it
is possible to tell the program not to copy anything for the first pass,
but to start copying changed files from the second pass onwards.
.SH OPTIONS
.TP
.BI -f "directory"
Directory to start copying from. Defaults to /usr.
.TP
.BI -t "directory"
Directory to start copying to. Defaults to /usr2.
.TP
.BI -r "hostname"
This is an alternative form of backup. Uses rcp and copies to
hostname supplied.
.TP
.BI -s "seconds"
Specifies the sleep period in seconds for when the program puts
itself to sleep. Default is 5 minutes.
.TP
.B -c
If given, then abcd won't copy the first time through.
.SH BUGS
This program is currently totally inpractical for monitoring filestore
areas containing large files which are being continuously updated, such
as databases.
.SH AUTHOR
Rich Burridge, Sun Microsystems, Unit 2, 49-53 Hotham Pde, Artarmon, N.S.W.
2164, AUSTRALIA.  PHONE: +61 2 436 4699
.nf
JANET: richb%sunaus.oz@uk.ac.ucl.cs
ACSnet:  richb@sunaus.oz
UUCP: {uunet,hplabs,mcvax,ukc}!munnari!sunaus.oz!richb
ARPAnet: rburridge@Sun.COM
.fi
Funky_Stuff
len=`wc -c < abcd.1`
if [ $len !=     2407 ] ; then
echo error: abcd.1 was $len bytes long, should have been     2407
fi
fi # end of overwriting check