/*****************************************************************************
 * Backup Copy v2.0                                                          *
 * November 16, 1998                                                         *
 * Programmed by: Kevin Lindsay                                              *
 * Copyright (c) 1998 NetNation Communications Inc                           *
 *                                                                           *
 * Report any Bugs to: klindsay@mkintraweb.com                               *
 *                                                                           *
 * Special Thanks to:                                                        *
 *                                                                           *
 *   Michael Oswell <oswell@mkintraweb.com> - Ported Backup Copy to AIX,     *
 *                                    HPUX, SUNOS, and IRIX                  *
 *   John Quinn <jquinn@soggey.ilo.dec.com> - Ported Backup Copy to Digital  *
 *                                    UNIX V4.x gcc 2.8.0                    *
 *   Karl Iorio <iorio@i-cafe.net>  - Provided patches and beta testing.     *
 *   Jeff Trawick <trawick@ibm.net> - Provided beta testing and partial      *
 *                                    patches.                               *
 *   Robert Schouwenburg <rds@stack.nl> - Provided helpful support and       *
 *                                    beta testing.                          *
 *                                                                           *
 *****************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h> 
#include <sys/mman.h>
#include <errno.h>
#include <string.h>
#include <dirent.h>
#include <utime.h>

#include "config.h"
#include "link.h"
#include "statmalloc.h"
#include "misc.h"
#include "rmrec.h"
#include "trashbin.h"
#include "modechange.h"

#define VERSION "Backup Copy v2.0 - Released November 16, 1998\n"

/* Global Variables */

/*******************************************************************
 * *MODE.value must be a 5 or greater digit number                 *
 *                                                                 *
 * Note that the "*_MODE_ADD" values will be added to the current  *
 * permissions.  These are equivalent to a chmod [u,g,o]+[r,w,x]   *
 *******************************************************************/

struct mode_change FILE_MODE;
struct mode_change DIR_MODE;
struct mode_change SPC_MODE; 

/* File Default Mode */
unsigned short FILE_MODE_ADD = 00600;
F_MODE FILE_DEF_MODE;

/* Directory Default Mode */
unsigned short DIR_MODE_ADD = 00700;
F_MODE DIR_DEF_MODE;

/* Special File Default Mode */
unsigned short SPC_MODE_ADD = 00600;
F_MODE SPC_DEF_MODE;

/* Trash Bin Directory Default Mode */
unsigned short TRASH_DIR_DEF_MODE = 00700;

char *progname;
char *optchars = "cet";
char *SOURCE;
char *DEST;
char *EXCLUDE_BUF = "";
char *TRASHBINDIR = "";
int TRASHFD = 0;
int OPTINT = 0;
uid_t UID = 0;
int TEMP_FD1;
int TEMP_FD2;
dir_item *TEMP_PTR;
int MOD_CHECK_SEC;
char *cur_dir=NULL;

#define BUFSIZE 4096

/* Flags */
int VERBOSE = 0;
int RECURSIVE = 0;
int FIND = 0;
int CONFIG = 0;
int EXCLUDE = 0;
int SAVEEXCDIR = 0;
int NEWONLY = 0;
int NEWDIR = 0;
int TRASHBIN = 0;
int MOD_CHECK = 0;
int INODE_CHECK = 1;
int BYBYTE = 0;
int FILESIZE = 1;
int QUIET=0;

/* Usage */

void
usage()
{
   printf("\n%s\n"
          "Usage:  %s [options] <source1>...[source2]... <destination>\n"
          "   or:  %s -f [options] <source>\n"
          "   or:  %s -c <conf file>\n\n"
          "\tOptions:\n\n"
          "\t-c <conf file>  Specify a configuration file.\n"
          "\t-e \"<dir1,...>\" Exclude specified directories. Note that any excluded\n"
          "\t                directories will be removed from an incremental backup.\n"    
          "\t-E \"<dir1,...>\" Exclude specified directories but do not remove them\n"
          "\t                from an incremental backup.\n"
          "\t-f              Find mode. Does not copy files. There is no need to\n"
          "\t                specify a destination with this option.\n"
          "\t-n              Incremental backup. Compare last modified time and copy\n"
          "\t                New files only.  This will remove files and directories\n"
          "\t                that have been deleted from the source argument.\n"
          "\t-r              Recursive mode.  Copy files from <source> recusively\n"
          "\t                to directory <destination>.\n"
          "\t-t <directory>  Move all old and deleted files from the last backup\n"
          "\t                to the specified trashbin.  -n flag is automatically\n"
          "\t                used when -t is specified.\n"
          "\t-s <seconds>    Maximum time in seconds that comparison on file\n"
          "\t                modification time can differ between source and\n"
          "\t                destination files.\n"
          "\t-i              Toggle inode information file comparison checking OFF.\n"
          "\t-z              Toggle file size comparison checking OFF.\n"
          "\t-q              Quiet mode. Suppress all error message.\n"
          "\t-v              Verbose mode.\n"
          "\t-V              Version.\n\n"
          "Report any bugs or ideas to:\n\n"
          "\tKevin Lindsay\n"
          "\tklindsay@mkintraweb.com\n"
          "",VERSION,progname,progname,progname);
   
   exit(1);
}

/* My fchdir() so that I can do cleaner error checking */
int
MYfchdir(int fd, char *filepath, char *func)
{
   
   if (fchdir(fd) == -1) {
      if (!QUIET)
          fprintf(stderr,"%s: %s: fchdir: %s: %s\n",progname,func,filepath,strerror(errno));
      return(-1);
   }
   return(0);
}

/* My chdir() so that I can do cleaner error checking */
int
MYchdir(char *filepath, char *func)
{
   if (chdir(filepath) == -1) {
      if (!QUIET)
          fprintf(stderr,"%s: %s: chdir: %s: %s\n",progname,func,filepath,strerror(errno));
      return(-1);
   }
   return(0);
}

/* Grab and check trash bin from command line */
int
trash_dir(char *directory)
{
   
   char *ptr1;
   struct stat tbfs;
   struct stat tmpfs;
   char *dirptr2;
   
   if (directory[0] != '/') {
      if (!QUIET)
          fprintf(stderr,"%s: trash bin: %s must be an absolute directory name.\n",progname,directory);
      exit(1);
   }
   
   if (strstr(directory,"..") != NULL || strstr(directory,"./") != NULL) {
      if (!QUIET)
          fprintf(stderr,"%s: trash bin: %s is an invalid absolute directory name.\n",progname,directory);
      exit(1);
   }
   
   ptr1 = directory + (strlen(directory));
   ptr1--;
   
   if (ptr1[0] != '/') ptr1++;
   
   TRASHBINDIR = statmalloc((ptr1-directory)+1);
   memcpy(TRASHBINDIR,directory,ptr1-directory);
   TRASHBINDIR[ptr1-directory] = '\0';
   
   if (lstat(TRASHBINDIR,&tbfs) == -1) {
      if (errno == 2) {
         if (mkdir(TRASHBINDIR,00700) == -1) {
            if (VERBOSE)
                fprintf(stdout,"%s: trash bin: %s does not exist.  Creating directory.\n",progname,TRASHBINDIR);
            dirptr2 = TRASHBINDIR+1;
            while (dirptr2 != NULL) {
               if ((dirptr2 = strchr(dirptr2,'/')) != NULL) {
                  *dirptr2 = '\0';
               }
               
               if (lstat(TRASHBINDIR,&tmpfs) == -1) {
                  if (mkdir(TRASHBINDIR,00700) == -1) {
                     fprintf(stderr,"%s: trash bin: %s: %s\n",progname,TRASHBINDIR,strerror(errno));
                     exit(1);
                  }
               }
               if (dirptr2 != NULL) {
                  *dirptr2 = '/';
                  dirptr2++;
               }
               
            }            
         }
      } else {
         if (!QUIET)
             fprintf(stderr,"%s: trash bin: %s: %s\n",progname,TRASHBINDIR,strerror(errno));
         exit(1);
      }
   } else {
      if (!S_ISDIR(tbfs.st_mode)) {
         if (!QUIET)
             fprintf(stderr,"%s: trash bin: %s already exists, but must be a directory.\n",progname,TRASHBINDIR);
         exit(1);
      }
      if (VERBOSE)
          fprintf(stdout,"%s: trash bin: %s will be used as the trash bin.\n",progname,TRASHBINDIR);
   }
   
   if ((TRASHFD = open(TRASHBINDIR,O_RDONLY)) == -1) {
      if (!QUIET)   
          fprintf(stderr,"%s: trash bin: %s: %s\n",progname,TRASHBINDIR,strerror(errno));
      exit(1);
   }
   
   return(0);
}



/* Parse Config File Organizer */

char **
parse_config(char **argv, int argnum)
{
   char *fbuf;
   char **filelist;
   int f = 0;
   
   fbuf = load_file(argv[argnum]);
   
   /* Check to make sure Variables can be found in config file */
   if (strcmp(get_config("VERBOSE",fbuf,'S'),"NULL") == 0) exit(-1);   
   if (strcmp(get_config("RECURSIVE",fbuf,'S'),"NULL") == 0) exit(-1);   
   if (strcmp(get_config("FIND",fbuf,'S'),"NULL") == 0) exit(-1);   
   if (strcmp(get_config("SOURCE",fbuf,'S'),"NULL") == 0) exit(-1);   
   
   /* Set flags with values from the config file */
   VERBOSE = atoi(get_config("VERBOSE",fbuf,'S'));
   NEWONLY = atoi(get_config("NEWONLY",fbuf,'S'));
   
   if ((FIND = atoi(get_config("FIND",fbuf,'S')))) {
      RECURSIVE = 1;
      NEWONLY = 0;
   }
   else
       RECURSIVE = atoi(get_config("RECURSIVE",fbuf,'S'));
   
   if (!FIND && (strcmp(get_config("DEST",fbuf,'S'),"NULL") == 0)) exit(-1);   
   
   SOURCE = get_config("SOURCE",fbuf,'S');
   DEST = get_config("DEST",fbuf,'S');
   TRASHBIN = atoi(get_config("TRASHBIN",fbuf,'S'));
   TRASHBINDIR = get_config("TRASHBINDIR",fbuf,'S');
   if ((MOD_CHECK_SEC = atoi(get_config("SECONDSCHK",fbuf,'S'))) > 0)
       MOD_CHECK = 1;
   INODE_CHECK = atoi(get_config("INODE_CHECK",fbuf,'S'));
   FILESIZE = atoi(get_config("SECONDSCHK",fbuf,'S'));
   
   QUIET = atoi(get_config("QUITE",fbuf,'S'));
   
   SAVEEXCDIR = atoi(get_config("SAVEEXCDIR",fbuf,'S'));
   
   if (TRASHBIN) trash_dir(TRASHBINDIR);
   
   if (RECURSIVE) {
      /* Set the Exclude buffer */
      if ((EXCLUDE = atoi(get_config("EXCLUDEDIR",fbuf,'S'))))
          EXCLUDE_BUF = get_config("EXCLUDEDIR",fbuf,'M');
   }
   
   /* Add the appropriate Source and Destination arguments to filelist */
   filelist = (char **)statmalloc(sizeof(char *));
   filelist[f] = (char *)statmalloc(strlen(SOURCE)+1);
   filelist[f][0] = '\0';
   strcat(filelist[f],SOURCE);
   
   if (!FIND) {
      f++;
      filelist[f] = statmalloc(strlen(DEST)+1);
      filelist[f][0] = '\0';
      strcat(filelist[f],DEST);
   } 
   
   f++;
   filelist[f] = statmalloc(1);
   filelist[f][0] = '\0';

   return(filelist);
}

/* Exclude Dir Command Line Entry */
int
exclude_dir(char *directory)
{
   char *ptr1;
   char *ptr2;
   char *resval=NULL;
   int grow = 1;
   int jump = 0;
   struct stat resfs;
   
   /* arg_num should be the optind for the variable after -e from
    * a command line entry */
   
   ptr1 = directory;
   
   /* Initialize the Exclude Buffer with 1 byte so we can easily
    * realloc later on */
   
   EXCLUDE_BUF = malloc(1);
   EXCLUDE_BUF[0] = '\0';
   
   while (!jump) {
      
      /* Point ptr2 to end of current list of "," delimited dir names. */
      if ((ptr2 = strchr(ptr1,',')) == NULL)
          ptr2 = strchr(ptr1,'\0');
      
      /* Move ptr2 back one and Check if '/' exists. If it doesn't then 
       * move back to original spot. We want to elimite and '/'s from the
       * end of directories */
      ptr2--;      
      if (ptr2[0] != '/')
          ptr2++;
      
      /* Copy the directory name into resval and check if it is a proper
       * directory and only print a warning to stderr */
      resval = statmalloc((ptr2-ptr1)+1);
      resval[0] = '\0';
      strncat(resval,ptr1,ptr2-ptr1);      
      if (lstat(resval,&resfs) == -1) {
         if (!QUIET)
             fprintf(stderr,"%s: lstat: %s: This exclude directory is invalid: %s\n",progname,resval,strerror(errno));
      }
      if (!S_ISDIR(resfs.st_mode)) {
         if (!QUIET)
             fprintf(stderr,"%s: %s: Exclude argument is not a directory.\n",progname,resval);
      }      
      freemalloc(resval);
      
      /* Re size the Exclude Buffer and add the directory to it */
      grow += (ptr2-ptr1)+2;
      EXCLUDE_BUF = realloc(EXCLUDE_BUF,grow);
      strcat(EXCLUDE_BUF,"^");
      strncat(EXCLUDE_BUF,ptr1,ptr2-ptr1);
      strcat(EXCLUDE_BUF,"^");
      
      /* if ptr2[0] or ptr[1] is not equal to a ',' then jump = 1 to
       * end while loop */
      if (ptr2[0] == ',' || ptr2[1] == ',') {
         if (ptr2[1] == ',') ptr1 = ptr2+2;
         else ptr1 = ptr2+1;         
      } else jump = 1;      
      
   }
   
   /* Replace any double '/'s with single '/'s in the Exclude buffer */
   EXCLUDE_BUF = repasubstr("//","/",EXCLUDE_BUF);
   /* Mark EXCLUDE flag */
   EXCLUDE = 1;
   
   return(1);
}

/* File Hole Checker */
int
hole(char *buffer, int bytes)
{
   int res = 1;
   int i = 0;
   
   while (res == 1 && i < bytes) {
      if (buffer[i] != 0x0)
          res = 0;
      i++;
   }
   
   return(res);
}

/* Parse Command Line */

char **
parse_cmdline(int args, char **argv, int optind)
{
   char **filelist = NULL;
   int i;
   int f = 0;
   
   filelist = (char **)realloc(filelist,sizeof(char *)+1);   
   
   for (i = optind; i < args; i++) {
      filelist[f] = statmalloc(strlen(argv[i])+1);
      filelist[f][0] = '\0';
      strcat(filelist[f],argv[i]);
      f++;
   }
   
   filelist[f] = statmalloc(1);
   filelist[f][0] = '\0';
   
   return(filelist);
}

/* return non-zero if symlink filename
 *    does not have the length or contents of symbuf - kji */

int
symlink_not_equal(char *filename,char *symbuf,int linkbytes)
{
   char nsymbuf[MYPATH_MAX+1];
   if ( linkbytes != readlink(filename,nsymbuf,MYPATH_MAX) ) return 1;
   nsymbuf[linkbytes] = '\0';
   
   return strcmp(nsymbuf,symbuf);
} 

/* Copy Recursive files */

int
rec_file_copy(int source_fd, int dest_fd, char *filename)
{
   register char *buffer = NULL;
   int bytes_read = 0;
   int oldf = 0;
   int newf = 0;
   struct stat foldfs;
   char symbuf[MYPATH_MAX+1];
   int linkbytes = 0;
   struct utimbuf newtime;
   unsigned long total_bytes = 0;
   int make_holes = 0;
   
   if (MYfchdir(source_fd,filename,"rec_file_copy 1") == -1) return(-1);
   
   if (lstat(filename,&foldfs) == -1) {
      if (!QUIET)
          fprintf(stderr,"%s: lstat: %s: %s\n",progname,filename,strerror(errno));
      return(-1);      
   }
   
   /* Open file if it is a regular file*/
   
   if (S_ISREG(foldfs.st_mode) && (oldf = open(filename,O_RDONLY)) == -1) {
      if (!QUIET)
          fprintf(stderr,"%s: open: %s: %s\n",progname,filename,strerror(errno));
      return(-1);
   }
   
   /* If a device of some type */
   
   /* If a Character or Block Device */
   
   if (S_ISCHR(foldfs.st_mode) || S_ISBLK(foldfs.st_mode) || S_ISFIFO(foldfs.st_mode) || S_ISSOCK(foldfs.st_mode)) {
      
      if ((UID != 0 && S_ISFIFO(foldfs.st_mode)) || UID == 0) {
         
         if (MYfchdir(dest_fd,filename,"rec_file_copy 2") == -1) return(-1);
         
         remove(filename);
         
         if (mknod(filename, SPC_DEF_MODE, foldfs.st_rdev) == -1) {
            if (!QUIET)
                fprintf(stderr,"%s: mknod: %s: %s\n",progname,filename,strerror(errno));
            return(-1);
         }
         
         if (UID == 0) {
            chown(filename,foldfs.st_uid,foldfs.st_gid);
            SPC_DEF_MODE = foldfs.st_mode;
         } else
             SPC_DEF_MODE = mode_adjust(foldfs.st_mode,&SPC_MODE);
         
         chmod(filename,SPC_DEF_MODE);
         
         /* Set time on file to same as source file */
         newtime.actime = foldfs.st_atime;
         newtime.modtime = foldfs.st_mtime;
         
         if (utime(filename,&newtime) == -1) {
            if (!QUIET)
                fprintf(stderr,"%s: utime: %s: %s\n",progname,filename,strerror(errno));
         }
         
         if (VERBOSE) {
            fprintf(stdout," -> %s\n",filename);
         }
         
         return(0);
      } else {
         if (!QUIET)
             fprintf(stderr,"-> %s: copy only permited by root for this file type.\n",filename);
         return(-1);
      }
      
   }
   
   /* If a symlink, copy and return appropriately */
   if (S_ISLNK(foldfs.st_mode)) {
      
      if ((linkbytes = readlink(filename,symbuf,MYPATH_MAX)) < 0) {
         if (!QUIET)
             fprintf(stderr,"%s: readlink: %s: %s\n",progname,filename,strerror(errno));
         return(-1);
      }
      
      symbuf[linkbytes] = '\0';
      
      if (MYfchdir(dest_fd,filename,"rec_file_copy 3") == -1) {
         return(-1);
      }
      
      /* copy only if not equal already - kji */
      if ( symlink_not_equal(filename,symbuf,linkbytes) ) {
         
         remove(filename);
         
         if (symlink(symbuf,filename) == -1) {
            if (!QUIET)
                fprintf(stderr,"%s: symlink: %s: %s\n",progname,filename,strerror(errno));
            return(-1);
         }
         
         if (UID == 0)
#ifdef FREEBSD_CPBK             
             if (lchown(filename,foldfs.st_uid,foldfs.st_gid) == -1) {
                if (!QUIET)
                    fprintf(stderr,"%s: chown: %s: %s\n",progname,filename,strerror(errno));
             }
#endif
#ifdef BSDOS_CPBK             
         if (lchown(filename,foldfs.st_uid,foldfs.st_gid) == -1) {
            if (!QUIET)
                fprintf(stderr,"%s: chown: %s: %s\n",progname,filename,strerror(errno));
         }
#endif
#ifdef LINUX_CPBK    
         if (chown(filename,foldfs.st_uid,foldfs.st_gid) == -1) {
            if (!QUIET)
                fprintf(stderr,"%s: chown: %s: %s\n",progname,filename,strerror(errno));
         }
#endif         
         
         
         /* no need to do anything else with symlinks:
          *            permissions are ignored by system and
          *            times cannot be set with utimes() */
         
         if (VERBOSE) {
            fprintf(stdout," -> %s\n",filename);
         }
         
      }
      
      return(1);
   }
   
   /* If we get this far and it is not a regular file, then we cannot copy it.
    * It is probably a UNIX Socket. We will get to those soon. */   
   if (!S_ISREG(foldfs.st_mode)) {
      if (!QUIET)
          fprintf(stderr,"%s: %s: Not a Regular File!\n",progname,filename);
      return(-1);
   }   
   
   if (MYfchdir(dest_fd,filename,"rec_file_copy 4") == -1) {
      close(oldf);     
      return(-1);
   }
   
   /* Remove the destination file before write it. Cleaner this way. */
   remove(filename);
   
   if (UID == 0) 
       FILE_DEF_MODE = foldfs.st_mode;
   else 
       FILE_DEF_MODE = mode_adjust(foldfs.st_mode,&FILE_MODE);
   
   /* Open the New file with same perms as source */      
   if ((newf = open(filename,O_WRONLY+O_CREAT,FILE_DEF_MODE)) == -1) {
      if (!QUIET)
          fprintf(stderr,"%s: open: %s: %s\n",progname,filename,strerror(errno));
      close(oldf);
      return(-1);
   }
   

   if ((buffer = statmalloc(BUFSIZE)) == NULL) {
      if (!QUIET)
          fprintf(stderr,"%s: Could not allocate %d bytes of memory for buffer\n",progname,BUFSIZE);
      close(oldf);
      close(newf);
      return(-1);
   }
   
   /* Check if size is what it should be compared to how many blocks
    * that we have. */
   
   if (S_ISREG (foldfs.st_mode) && (size_t) ((foldfs.st_size / foldfs.st_blksize)+1) > (size_t) foldfs.st_blocks) {
      make_holes = 1;
   } 
   
   /* Loop until file is copies */
   while ((bytes_read = read(oldf,buffer,BUFSIZE)) > 0) {
      
      total_bytes += bytes_read;
      
      /* Check if data is a hole */
      if (make_holes == 1 && hole(buffer,bytes_read)) {
         
         /* lseek bytes_read to rebuild the hole */
         if (bytes_read == BUFSIZE) {
            
            if (lseek(newf,bytes_read,SEEK_CUR) == -1) {
               if (!QUIET)
                   fprintf(stderr,"%s: lseek: %s: %s\n",progname,filename,strerror(errno));
               close(oldf);
               close(newf);
               freemalloc(buffer);            
               return(-1);
            }
            
         } else {
            
            /* ftrunct total bytes read to rebuild the hole */
            if (ftruncate(newf,total_bytes) == -1) {
               if (!QUIET)
                   fprintf(stderr,"%s: ftruncate: %s: %s\n",progname,filename,strerror(errno));
               close(oldf);
               close(newf);
               freemalloc(buffer);            
               return(-1);
            }
         }
         
         
      } else if (write(newf,buffer,bytes_read) == -1) {
         if (!QUIET)
             fprintf(stderr,"%s: write: %s: %s\n",progname,filename,strerror(errno));
         close(oldf);
         close(newf);
         freemalloc(buffer);         
         return(-1);
      }
      
   }
   
   /* Error check if read returned an error */
   if (bytes_read == -1) {
      if (!QUIET)
          fprintf(stderr,"%s: read: %s: %s\n",progname,filename,strerror(errno));
      close(oldf);
      close(newf);
      freemalloc(buffer);      
      return(-1);
   }   
   
   close(oldf);
   /* Set perms and ownership for new file matching source file */
   
   if (UID == 0) {
      fchown(newf,foldfs.st_uid,foldfs.st_gid);
      FILE_DEF_MODE = foldfs.st_mode;
   } else
       FILE_DEF_MODE = mode_adjust(foldfs.st_mode,&FILE_MODE);   
   
   fchmod(newf,FILE_DEF_MODE);
   close(newf);
   
   freemalloc(buffer);   
   
   if (MYfchdir(dest_fd,filename,"rec_file_copy 5") == -1) return(-1);
   
   if (VERBOSE) {     
      fprintf(stdout," -> %s\n",filename);
   }   
   
   /* Set time on file to same as source file */
   newtime.actime = foldfs.st_atime;
   newtime.modtime = foldfs.st_mtime;
   
   if (utime(filename,&newtime) == -1) {
      if (!QUIET)
          fprintf(stderr,"%s: utime: %s: %s\n",progname,filename,strerror(errno));
   }
   
   
   return(1);
}


/* Copy regular files - I'm leaving this undocumeted. I'm going to use the
 * rec_file_copy() function to do this same thing. */

int
file_copy(char *oldfile, char *newfile)
{
   register char *buffer = NULL;
   int bytes_read = 0;
   int oldf;
   int newf;
   struct stat foldfs;
   struct stat fnewfs;
   char *p;
   char *temp;
   struct utimbuf newtime;
   unsigned long total_bytes = 0;
   
   if (lstat(oldfile,&foldfs) == -1) {
      if (!QUIET)
          fprintf(stderr,"%s: lstat: %s: %s\n",progname,oldfile,strerror(errno));
      return(-1);
   }
   
   if (S_ISDIR(foldfs.st_mode)) {
      if (!QUIET)
          fprintf(stderr,"%s: %s: omitting directory\n",progname,oldfile);
      return(-1);
   }
   
   lstat(newfile,&fnewfs);
   
   if (fnewfs.st_dev == foldfs.st_dev && fnewfs.st_ino == foldfs.st_ino) {
      if (!QUIET)
          fprintf(stderr,"%s: %s and %s are identical (not copied).\n",progname,oldfile,newfile);
      return(-1);
   }
   
   if (S_ISDIR(fnewfs.st_mode)) {
      temp = statmalloc(strlen(((p = rindex(oldfile,'/')) ? ++p : oldfile))+((newfile[strlen(newfile)-1] == '/') ? 0 : 1)+strlen(newfile)+1);
      temp[0] = '\0';
      strcat(temp,newfile);
      if (newfile[strlen(newfile)-1] != '/')
          strcat(temp,"/");
      strcat(temp,((p = rindex(oldfile,'/')) ? ++p : oldfile));
      
      newfile = statmalloc(strlen(temp)+1);
      newfile[0] = '\0';
      strcat(newfile,temp);
      freemalloc(temp);
   }
   
   /* If a Character or Block Device */
   if (S_ISCHR(foldfs.st_mode) || S_ISBLK(foldfs.st_mode) || S_ISFIFO(foldfs.st_mode) || S_ISSOCK(foldfs.st_mode)) {
      if (mknod(newfile, foldfs.st_mode, foldfs.st_rdev) == -1) {
         if (!QUIET)
             fprintf(stderr,"%s: dev: %s: %s\n",progname,oldfile,strerror(errno));
         return(-1);
      }
      chown(newfile,foldfs.st_uid,foldfs.st_gid);
      chmod(newfile,foldfs.st_mode);
      
      /* Set time on file to same as source file */
      newtime.actime = foldfs.st_atime;
      newtime.modtime = foldfs.st_mtime;
      
      if (utime(newfile,&newtime) == -1) {
         if (!QUIET)
             fprintf(stderr,"%s: utime: %s: %s\n",progname,newfile,strerror(errno));
      }
      
      if (VERBOSE) {
         fprintf(stdout," -> %s\n",newfile);
      }      
      return(0);
   }   
   
   if ((oldf = open(oldfile,O_RDONLY)) == -1) {
      if (!QUIET)
          fprintf(stderr,"%s: open: %s: %s\n",progname,oldfile,strerror(errno));
      return(-1);
   }   
   
   if ((newf = open(newfile,O_WRONLY+O_CREAT,foldfs.st_mode)) == -1) {
      if (!QUIET)
          fprintf(stderr,"%s: open: %s: %s\n",progname,newfile,strerror(errno));
      close(oldf);      
      return(-1);
   }
   
   if ((buffer = statmalloc(BUFSIZE)) == NULL) {
      if (!QUIET)
          fprintf(stderr,"%s: Could not allocate %d bytes of memory for buffer\n",progname,BUFSIZE);
      close(oldf);
      close(newf);      
      return(-1);
   }
   
   while ((bytes_read = read(oldf,buffer,BUFSIZE)) > 0) {
      
      if (bytes_read > 0) 
          total_bytes += bytes_read;
      
      if (hole(buffer,bytes_read)) {
         
         if (bytes_read == BUFSIZE) {
            
            if (lseek(newf,bytes_read,SEEK_CUR) == -1) {
               if (!QUIET)
                   fprintf(stderr,"%s: lseek: %s: %s\n",progname,newfile,strerror(errno));
               close(oldf);
               close(newf);
               return(-1);
            }
         } else {
            
            if (ftruncate(newf,total_bytes) == -1) {
               if (!QUIET)
                   fprintf(stderr,"%s: ftruncate: %s: %s\n",progname,newfile,strerror(errno));
               close(oldf);
               close(newf);
               return(-1);
            }
         }
         
      } else if (write(newf,buffer,bytes_read) == -1) {
         if (!QUIET)
             fprintf(stderr,"%s: write: %s: %s\n",progname,newfile,strerror(errno));
         close(oldf);
         close(newf);
         return(-1);
      }
      
   }
   
   if (bytes_read == -1) {
      if (!QUIET)
          fprintf(stderr,"%s: read: %s: %s\n",progname,oldfile,strerror(errno));
      close(oldf);
      close(newf);      
      return(-1);
   }
   
   close(oldf);
   fchmod(newf,foldfs.st_mode);
   fchown(newf,foldfs.st_uid,foldfs.st_gid);   
   close(newf);
   
   newtime.actime = foldfs.st_atime;
   newtime.modtime = foldfs.st_mtime;
   utime(newfile,&newtime);   
   
   if (VERBOSE) {
      fprintf(stdout,"%s: %s -> %s\n",progname,oldfile,newfile);
   }
   
   if (S_ISDIR(fnewfs.st_mode)) {
      freemalloc(newfile);
   }
   
   freemalloc(buffer);
   
   return(1);
}

/* Get Current Directory */
char *
get_cur_path(dir_item *listptr, char gtype)
{
   
   dir_item *dir_ptr;
   char **dirarray;
   int items=0;
   int i;
   int grow=0;
   int tgrow;
   
   /* gtype options:
    * 
    * V = Verbose, do not include ^'s around dirnames
    * E = Exclude, include ^'s around dirnames as delimiters */
   
   if (cur_dir) {
      free(cur_dir);
   }
   
   dir_ptr = listptr;
   
   while (!dir_ptr->root) {
      dir_ptr=dir_ptr->left;
      items++;
   }
   
   dirarray = calloc(items,sizeof(char *));
   
   dir_ptr=listptr->left;
   
   for (i = items-1; i != -1; i--) {
      dirarray[i] = malloc(strlen(dir_ptr->name)+1);
      dirarray[i][0] = '\0';
      strcat(dirarray[i],dir_ptr->name);
      
      dir_ptr=dir_ptr->left;
   }
   
   if (gtype == 'V') {
      cur_dir = malloc(1);
      cur_dir[0] = '\0';
      grow=1;
   } else {
      grow=2;
      cur_dir = malloc(2);
      cur_dir[0] = '^';
      cur_dir[1] = '\0';
   }
   
   tgrow=grow;
   for (i = 0; i != items; i++) {
      grow+=strlen(dirarray[i])+2;
      if (grow > tgrow) {
         tgrow=grow+64;
         cur_dir = realloc(cur_dir,tgrow);
      }
      strcat(cur_dir,dirarray[i]);
      strcat(cur_dir,"/");
   }
   
   if (gtype == 'E') {
      grow += listptr->name_len;
      cur_dir = realloc(cur_dir,grow+1);
      strcat(cur_dir,listptr->name);
      strcat(cur_dir,"^");
   }
   
   for (i = 0; i != items; i++) {
      free(dirarray[i]);
   }
   
   free(dirarray);
   
   return(cur_dir);
}

/* Delete Files */
int
delete_files(dir_item *listptr, int dest_fd)
{
   dest_item *destptr;
   char *trash_src_dir;
   char *ptr;
   char *tmpdir=NULL;
   
   destptr = listptr->dir_info;
   
   /* Loop Through destptr files and remove any with a the delete flag
    * set to 1 */
   while (destptr != NULL) {
      if (destptr->delete) {
         
         if (destptr->type == 0) {
            
            if (VERBOSE)
                fprintf(stdout,"rmfile: %s\n",destptr->name);
            
            if (TRASHBIN) {
               trash_src_dir = get_cur_path(listptr,'V');
               trashit(destptr->name,dest_fd,TRASHFD,progname,'S',trash_src_dir,TRASH_DIR_DEF_MODE);          
            }
            
            remove(destptr->name);
         }
         else if (destptr->type == 1) {
            
            if (SAVEEXCDIR) {
               get_cur_path(listptr,'E');
               ptr = strrchr(cur_dir,'^');
               *ptr = '\0';
               tmpdir = malloc(strlen(cur_dir)+destptr->name_len+3);
               tmpdir[0] = '\0';
               strcat(tmpdir,cur_dir);
               strcat(tmpdir,"/");          
               strcat(tmpdir,destptr->name);
               strcat(tmpdir,"^");          
            }
            
            if (!SAVEEXCDIR || (SAVEEXCDIR && (strstr(EXCLUDE_BUF,tmpdir) == NULL))) {
               if (VERBOSE)
                   fprintf(stdout,"removing directory: %s\n",destptr->name);            
               if (TRASHBIN) {
                  trash_src_dir = get_cur_path(listptr,'V');
                  trashit(destptr->name,dest_fd,TRASHFD,progname,'D',trash_src_dir,TRASH_DIR_DEF_MODE);
               } else
                   rmrec(destptr->name);
            }
            
            if (SAVEEXCDIR)
                free(tmpdir);
         }
      }
      destptr = destptr->next;
   }
   
   
   return(0);
}

/* Check if File is Newer */

int
file_newer(dir_item *listptr, int source_fd, int dest_fd, char ctype)
{
   struct stat sourcefs;
   struct stat destfs;
   dest_item *destptr;
   char *trash_src_dir;
   int linkbytes;
   char symbuf[MYPATH_MAX+1];
   
   destptr = listptr->left->dir_info;
   
   /* Check if Source file exists in Destination Directory */
   while (destptr != NULL && (strcmp(destptr->name,listptr->name) != 0)) {
      destptr = destptr->next;
   }
   
   /* Return, Source File is newer */
   if (destptr == NULL) {
      return(1);
   }
   
   destptr->delete = 0;
   
   if (MYfchdir(source_fd,"","file_newer 1") == -1) return(-1);
   
   lstat(listptr->name,&sourcefs);
   
   if (S_ISLNK(sourcefs.st_mode)) {
      
      /* if not for verbose printout, always assume here that symlinks
       *         are newer so they will be sent to rec_file_copy.  the link
       *         will actually be copied only if the contents are not equal - kji */
      
      if ( ctype != 'C' ) return(1);
      
      /* from here on is only for verbose printout:
       *         return "newer" if contents are not equal */
      
      if ((linkbytes = readlink(listptr->name,symbuf,MYPATH_MAX)) < 0) {
         if (!QUIET)
             fprintf(stderr,"%s: readlink: %s: %s\n",progname,listptr->name,strerror(errno));
         return(-1);
      }
      symbuf[linkbytes] = '\0';
      
      if (MYfchdir(dest_fd,"","file_newer 2") == -1) return(-1);
      
      return symlink_not_equal(listptr->name,symbuf,linkbytes);
      
   } 
   
   
   if (MYfchdir(dest_fd,"","file_newer 3") == -1) return(-1);
   lstat(destptr->name,&destfs);   
   
   if (INODE_CHECK && sourcefs.st_ctime > destfs.st_ctime) {
      if (TRASHBIN && ctype == 'P') {
         trash_src_dir = get_cur_path(listptr->left,'V');
         trashit(listptr->name,dest_fd,TRASHFD,progname,'S',trash_src_dir,TRASH_DIR_DEF_MODE);
      }
      return(1);
   }
   
   if (FILESIZE && sourcefs.st_size != destfs.st_size) {
      if (TRASHBIN && ctype == 'P') {
         trash_src_dir = get_cur_path(listptr->left,'V');
         trashit(listptr->name,dest_fd,TRASHFD,progname,'S',trash_src_dir,TRASH_DIR_DEF_MODE);
      }
      return(1);      
   }
   
   if (sourcefs.st_mtime != destfs.st_mtime) {
      
      if (MOD_CHECK && (sourcefs.st_mtime - destfs.st_mtime) < MOD_CHECK_SEC) {         
         return(0);
      }
      
      if (TRASHBIN && ctype == 'P') {
         trash_src_dir = get_cur_path(listptr->left,'V');
         trashit(listptr->name,dest_fd,TRASHFD,progname,'S',trash_src_dir,TRASH_DIR_DEF_MODE);
      }
      
      return(1);
   }      
   
   return(0);
}

/* Load Destination Dir Contents into Linked List */

dest_item *
load_dest_dir(int dest_fd, dest_item *destptr)
{
   DIR *recdir;
   struct dirent *filenames = NULL;
   struct stat destfs;
   
   destptr = init_dest_list();
   
   if (MYfchdir(dest_fd,"","load_dest_dir 1") == -1) return(NULL);
   
   /* Open Current Directory */
   if ((recdir = opendir(".")) == NULL) {
      if (!QUIET)
          fprintf(stderr,"%s: Opendir: %s: %s\n",progname,destptr->name,strerror(errno));
      return(NULL);
   }
   
   if ((filenames = readdir(recdir)) == NULL) {
      if (!QUIET)
          fprintf(stderr,"%s: Readdir Error: Load Dest Dir: %s: %s\n",progname,destptr->name,strerror(errno));
      closedir(recdir);
      return(NULL);
   }
   
   /* Move ahead one directory */
   seekdir(recdir,telldir(recdir));
   
   while ((filenames = readdir(recdir)) != NULL) {
      
      /* If at the top of the dir, then move ahead one file */
      if (strcmp(filenames->d_name,"..") == 0) {
         seekdir(recdir,telldir(recdir));
      } else {
         /* Add another item to destptr if not at top of dir */
         destptr = add_next(destptr);
         destptr = destptr->next;
      }
      
      if (strcmp(filenames->d_name,"..") != 0) {
         /* Add filename or Dir to destptr */
         destptr->name = statmalloc(strlen(filenames->d_name)+1);
         destptr->name[0] = '\0';
         strcat(destptr->name,filenames->d_name);
         destptr->name_len = strlen(destptr->name);
         
         if (lstat(filenames->d_name,&destfs) == -1) {
            if (!QUIET)
                fprintf(stderr,"%s: lstat: %s: %s\n",progname,filenames->d_name,strerror(errno));
         }
         
         if (S_ISDIR(destfs.st_mode)) 
             destptr->type=1;
         else 
             destptr->type=0;
         
      } else {
         /* If directory is blank, setup destptr appropriately */
         destptr->name = statmalloc(2);
         destptr->name[0] = ' ';
         destptr->name[1] = '\0';
         destptr->type = 2;
         destptr->name_len = 1;
      }
      
   }   
   
   while (destptr->prev != NULL)
       destptr = destptr->prev;
   
   closedir(recdir);
   
   return(destptr);
}

/* Load Dir Contents into Linked List */

dir_item *
load_list(int source_fd, dir_item *listptr)
{
   DIR *recdir;
   struct dirent *filename = NULL;
   struct stat statres;
   int top=0;
   
   if (MYfchdir(source_fd,listptr->name,"load_dir 1") == -1) return(NULL);
   
   if ((recdir = opendir(".")) == NULL) {
      if (!QUIET)
          fprintf(stderr,"%s: Opendir: %s: %s\n",progname,listptr->name,strerror(errno));
      return(NULL);
   }
   
   while ((filename = readdir(recdir)) != NULL) {
      
      while (!strcmp(filename->d_name,".") || !strcmp(filename->d_name,"..")) {
         top=1;
         seekdir(recdir,telldir(recdir));
         if ((filename = readdir(recdir)) == NULL)
             break;
      }
      if (top) {

         listptr = add_right(listptr);
         listptr = listptr->right;
         
         /* If directory is emtpy then add a space to listptr */
         if (filename == NULL) {
            listptr->name = statmalloc(1);
            listptr->name[0] = '\0';
            listptr->empty = 1;
            listptr->type = 0;
            listptr->root = 0;
            listptr->name_len = 1;
            closedir(recdir);
            return(listptr);
         }
         
         top=0;
      } else {
         listptr = add_down(listptr);
         listptr = listptr->down;
      }
      
      /* Add information from filenames->d_name to listptr */
      listptr->name = statmalloc(strlen(filename->d_name)+1);
      listptr->name[0] = '\0';
      strcat(listptr->name,filename->d_name);
      listptr->name_len = strlen(listptr->name);
      
      if (lstat(filename->d_name,&statres) == -1) {
         if (!QUIET)
             fprintf(stderr,"%s: lstat: %s: %s\n",progname,filename->d_name,strerror(errno));
      }
      
      if (S_ISDIR(statres.st_mode))
          listptr->type=1;
      else
          listptr->type=0;
      
      listptr->root = 0;
      
      seekdir(recdir,telldir(recdir));
   }
   
   closedir(recdir);
   
   return(listptr);
}


/* Fix it when the directory that we are in dies */
int
get_new_dir(dir_item *listptr, int source_fd, int dest_fd)
{
   return(0);
}

/* Recurse Back if at bottom of a directory */

int
recurse_back(dir_item *listptr, int source_fd, int dest_fd)
{
   struct utimbuf newtime;
   struct stat oldfs;
   
   if (MYfchdir(dest_fd,listptr->name,"recurse_copy 5") == -1) {
      close(source_fd);
      close(dest_fd);
      return(-1);
   }   
   
   /* This While will move up a directory until the more items are located
    * in listptr or until we hit the root directory */
   while (listptr->down == NULL && listptr->root == 0) {
      
      listptr = listptr->left;
      if (!FIND) {
         
         /* Delete files or dirs from Dest that have been removed from Source */
         if (NEWONLY) {
            delete_files(listptr,dest_fd);
            
            if (MYfchdir(dest_fd,listptr->name,"recurse_copy 6") == -1) {
               close(source_fd);
               close(dest_fd);
               return(-1);
            }
         }
         
         free_right(listptr);         
         
         if (MYchdir("..","recurse_copy 7") == -1) {
            close(source_fd);
            close(dest_fd);
            return(-1);
         }                 
         
         close(dest_fd);
         
         if ((dest_fd = open(".",O_RDONLY)) == -1) {
            if (!QUIET)
                fprintf(stderr,"%s: open: %s: %s\n",progname,listptr->name,strerror(errno));
         }
         
         if (MYfchdir(source_fd,listptr->name,"recurse_copy 7.3") == -1) {
            close(source_fd);
            close(dest_fd);
            return(-1);
         }
         
         if (MYchdir("..","recurse_copy 7.6") == -1) {
            close(source_fd);
            close(dest_fd);
            return(-1);
         }
         
         close(source_fd);
         if ((source_fd = open(".",O_RDONLY)) == -1) {
            if (!QUIET)
                fprintf(stderr,"%s: open: %s: %s\n",progname,listptr->name,strerror(errno));
         }
         
         if (lstat(listptr->name,&oldfs) == -1) {
            if (!QUIET)
                fprintf(stderr,"%s: lstat: recurse back: %s: %s\n",progname,listptr->name,strerror(errno));
         }
         
         newtime.actime = oldfs.st_atime;
         newtime.modtime = oldfs.st_mtime;
         
         if (MYfchdir(dest_fd,listptr->name,"recurse_copy 6") == -1) {
            close(source_fd);
            close(dest_fd);
            return(-1);
         }         
         
         /* Set time on file to same as source file.
          * This is done here because if we changed the time when
          * we did the mkdir the time will reset when we write to
          * this directory. */         
         
         if (utime(listptr->name,&newtime) == -1) {
            if (!QUIET)
                fprintf(stderr,"%s: utime: %s: %s\n",progname,listptr->name,strerror(errno));
         }
         
      }     
      
   }
   
   if (MYfchdir(source_fd,listptr->name,"recurse_copy 5.2") == -1) {
      close(source_fd);
      close(dest_fd);
      return(-1);
   }            
   
   
   TEMP_FD1 = source_fd;
   TEMP_FD2 = dest_fd;
   TEMP_PTR = listptr;
   
   return(0);
   
}

/* Copy to Directories */

int
recurse_copy(char *olddir, char *newdir)
{
   struct utimbuf newtime;
   struct stat oldfs;
   struct stat newfs;   
   dir_item *listptr=NULL;
   dir_item *tmpptr = NULL;
   int source_fd;
   int dest_fd;
   int load;
   dest_item *destptr=NULL;
   char *cur_path_ptr_E;
   char *cur_path_ptr_V;   
   char *MYcwd;
   
   /* Make sure there is no problems accessing the source directory */
   if (lstat(olddir,&oldfs) == -1) {
      if (!QUIET)
          fprintf(stderr,"%s: lstat: %s: %s\n",progname,olddir,strerror(errno));
      return(-1);
   }
   
   /* Initialize 2D Linked List */
   listptr = init_2D_list();
   listptr->name = statmalloc(strlen(olddir)+((olddir[strlen(olddir)-1] == '/') ? -1 : 0)+1);
   listptr->name[0] = '\0';
   if (olddir[strlen(olddir)-1] == '/')
       strncat(listptr->name,olddir,strlen(olddir)-1);
   else
       strcat(listptr->name,olddir);
   listptr->name_len = strlen(listptr->name);
   listptr->type = 1;
   listptr->root = 1;
   
   /* Open Source Directory */
   if ((source_fd = open(olddir,O_RDONLY)) == -1) {
      if (!QUIET)
          fprintf(stderr,"%s: open: %s: %s\n",progname,olddir,strerror(errno));
      return(-1);
   }
   
   /* Open Destination Directory */
   if ((dest_fd = open(newdir,O_RDONLY)) == -1 && !FIND) {
      
      /* If directory does not exists then make one */
      if (errno == 2) {
         
         if (UID == 0) 
             DIR_DEF_MODE = oldfs.st_mode;
         else
             DIR_DEF_MODE = mode_adjust(oldfs.st_mode,&DIR_MODE);
         
         if (mkdir(newdir,DIR_DEF_MODE) == -1) {
            if (!QUIET)
                fprintf(stderr,"%s: mkdir: %s: %s\n",progname,newdir,strerror(errno));
            close(source_fd);            
            return(-1);
         }
         
         /* Set permissions, ownership and time matching the source dir */
         if (UID == 0) {
            chown(newdir,oldfs.st_uid,oldfs.st_gid);
            DIR_DEF_MODE = oldfs.st_mode;
         } else
             DIR_DEF_MODE = mode_adjust(oldfs.st_mode,&DIR_MODE);   
         
         chmod(newdir,DIR_DEF_MODE);
         
         newtime.actime = oldfs.st_atime;
         newtime.modtime = oldfs.st_mtime;
         if (utime(newdir,&newtime) == -1) {
            if (!QUIET)
                fprintf(stderr,"%s: utime: %s: %s\n",progname,newdir,strerror(errno));
         }
         NEWDIR = 1;
         
         /* Open the new directory */
         if ((dest_fd = open(newdir,O_RDONLY)) == -1) {
            if (!QUIET)
                fprintf(stderr,"%s: open: %s: %s\n",progname,listptr->name,strerror(errno));
            close(source_fd);
            return(-1);
         }
         
      } else {
         if (!QUIET)
             fprintf(stderr,"%s: open: %s: %s\n",progname,newdir,strerror(errno));
         close(source_fd);
         return(-1);
      }
   }   
   
   /* Change CWD to source dir */
   if (MYfchdir(source_fd,newdir,"recurse_copy 1") == -1) {
      close(source_fd);
      close(dest_fd);
      return(-1);
   }
   load = 1;
   
   /* The BIG Recursive Loop! */
   
   while (listptr != NULL) {
      
      /* When load = 1 then we have hit a directory in the Source structure and need
       * to load the directory contents into listptr */
      if (load) {
         
         /* If this directory is in the Exclude Buffer, then skip it, and move to the
          * next item in listptr */
         
         cur_path_ptr_E = get_cur_path(listptr,'E');
         
         if (EXCLUDE && (strstr(EXCLUDE_BUF,cur_path_ptr_E) != NULL)
             && (strcmp(cur_path_ptr_E,"^^") != 0)) {
            
            if (VERBOSE) {
               cur_path_ptr_V = get_cur_path(listptr,'V');
               fprintf(stdout,"%s: %s%s: Excluding directory.\n",progname,cur_path_ptr_V,listptr->name);
            }
            
            tmpptr = listptr;
            listptr = listptr->down;
         } else {
            
            tmpptr = listptr;
            
            
            /* Load directory contents in source dir to listptr */
            listptr = load_list(source_fd,listptr);
            
            if (listptr != NULL) {
               
               /* Moves current listptr to top of current dimension */
               listptr = listptr->left->right;
               
               /* If the destination directory is not new, then load its contents
                * into listptr->left->dir_info. */               
               if (!NEWDIR && NEWONLY) {
                  listptr->left->dir_info = load_dest_dir(dest_fd,listptr->left->dir_info);
               }
               
               
            } else {
               
               /* If listptr returns NULL then ignore directory and move up one dir
                * and go onto the next item in listptr. This is a fix when using
                * smbmounted NT drives, as they currently do not display . or ..
                * directories */
               listptr = tmpptr;
               if (MYchdir("..","recurse_copy 2") == -1) return(-1);
               close(source_fd);
               if ((source_fd = open(".",O_RDONLY)) == -1) {
                  if (!QUIET)
                      fprintf(stderr,"%s: open: %s: %s\n",progname,listptr->name,strerror(errno));
                  return(-1);
               }
               if (MYfchdir(dest_fd,listptr->name,"recurse_copy 3") == -1) return(-1);
               if (MYchdir("..","recurse_copy 4") == -1) return(-1);
               close(dest_fd);
               if ((dest_fd = open(".",O_RDONLY)) == -1) {
                  if (!QUIET)
                      fprintf(stderr,"%s: open: %s: %s\n",progname,listptr->name,strerror(errno));
                  return(-1);
               }
               listptr = listptr->down;
               
            }
            
         }
         
         
      }
      
      
      /* Copy if necessary every file in the current directory up until we hit a directory */
      while (listptr != NULL && listptr->type == 0) {
         
         /* If directory is not empty then copy */
         if (!listptr->empty) {
            
            if (VERBOSE) {
               if (!NEWONLY || (NEWONLY && file_newer(listptr,source_fd,dest_fd,'C'))) {
                  cur_path_ptr_V = get_cur_path(listptr,'V');
                  printf("%s%s",cur_path_ptr_V,listptr->name);
               }
               if (FIND) printf("\n");
            }
            if (!FIND) {
               if (NEWONLY && !NEWDIR) {
                  /* if NEWONLY flag is set and this is not a new directory then copy if
                   * file in source is newer than file in dest */
                  if (file_newer(listptr,source_fd,dest_fd,'P')) {
                     if (rec_file_copy(source_fd,dest_fd,listptr->name) == -1 && VERBOSE) 
                         printf("\n");
                  }
                  
               } else {
                  if (rec_file_copy(source_fd,dest_fd,listptr->name) == -1 && VERBOSE) 
                      printf("\n");
               }
            }
            
         }
         tmpptr = listptr;
         listptr = listptr->down;
      }
      
      /* If no more files in dir (listptr == NULL) Then move up a directory */
      if (listptr == NULL) {
         listptr = tmpptr;         
         
         if (recurse_back(listptr,source_fd,dest_fd) == -1) return(-1);
         source_fd = TEMP_FD1;
         dest_fd = TEMP_FD2;
         listptr = TEMP_PTR;
         
         if (!listptr->root) {
            
            listptr = listptr->down;
            
            while (lstat(listptr->name,&oldfs) == -1) {
               if (!QUIET)
                   fprintf(stderr,"%s: lstat3: %s: Ooops, directory has disappeared!\n",progname,listptr->name);
               if (listptr->down != NULL) listptr = listptr->down;
               if (recurse_back(listptr,source_fd,dest_fd) == -1) return(-1);
               source_fd = TEMP_FD1;
               dest_fd = TEMP_FD2;
               listptr = TEMP_PTR;
            }
            
            load = 0;
         } else {
            freemalloc(listptr->name);
            freemalloc(listptr);            
            listptr = NULL;
         }
         
         
         
      } else if (listptr->type == 1) {
         
         /* If we hit a directory while trying to copy files above, the do the following... */
         
         cur_path_ptr_E = get_cur_path(listptr,'E');
         
         if ((EXCLUDE) && (strstr(EXCLUDE_BUF,cur_path_ptr_E) != NULL)) {
         } else {
            
            /* move source CWD to new directory and close old File Descriptor and open a new one */
            
            if (MYfchdir(source_fd,listptr->name,"recurse_copy 10") == -1) {
               close(source_fd);
               close(dest_fd);
               return(-1);
            }
            
            if (chdir(listptr->name) == -1) {
               close(source_fd);
               close(dest_fd);
               return(-1);
            }
            
            close(source_fd);
            
            if ((source_fd = open(".",O_RDONLY)) == -1) {
               if (!QUIET)
                   fprintf(stderr,"%s: open: %s: %s\n",progname,listptr->name,strerror(errno));
               return(-1);
            }
            
            if (source_fd == 0) {
               if ((source_fd = open(".",O_RDONLY)) == -1) {
                  if (!QUIET)
                      fprintf(stderr,"%s: open: %s: %s\n",progname,listptr->name,strerror(errno));
                  return(-1);
               }
            }
            
            if (lstat(".",&oldfs) == -1) {
               MYcwd = MYgetcwd();
               if (!QUIET)
                   fprintf(stderr,"%s: lstat: %s: %s\n",progname,MYcwd,strerror(errno));
               free(MYcwd);
            }
            
            if (!FIND ) {
               NEWDIR = 0;
               if (MYfchdir(dest_fd,listptr->name,"recurse_copy 11") == -1) {
                  close(source_fd);
                  close(dest_fd);
                  return(-1);
               }
               /* Make a directory in the Destination directory and set 
                * the proper permissions, ownership and times. If directory already
                * exists then mark file as not deleted */
               
               if (UID == 0) 
                   DIR_DEF_MODE = oldfs.st_mode;
               else
                   DIR_DEF_MODE = mode_adjust(oldfs.st_mode,&DIR_MODE);
               
               /* Fix for when files in source change from a file to a directory which
                * fails to match the backup */
               if (lstat(listptr->name,&newfs) == 0) {
                  if (!S_ISDIR(newfs.st_mode))
                      remove(listptr->name);
               }
               
               if (mkdir(listptr->name,DIR_DEF_MODE) == 0) {
                  /*                  chmod(listptr->name,oldfs.st_mode); */
                  if (UID == 0)
                      chown(listptr->name,oldfs.st_uid,oldfs.st_gid);
                  newtime.actime = oldfs.st_atime;
                  newtime.modtime = oldfs.st_mtime;
                  
                  if (utime(listptr->name,&newtime) == -1) {
                     if (!QUIET)
                         fprintf(stderr,"%s: utime: %s: %s\n",progname,listptr->name,strerror(errno));
                  }
                  
                  NEWDIR = 1;
                  if (VERBOSE) {
                     cur_path_ptr_V = get_cur_path(listptr,'V');
                     fprintf(stdout,"%s%s -> %s\n",get_cur_path(listptr,'V'),listptr->name,listptr->name);
                  }
               } else if (NEWONLY) {                  
                  
                  /* Find directory name in listptr->left->dir_info */
                  destptr = listptr->left->dir_info;
                  while (destptr != NULL && strcmp(destptr->name,listptr->name) != 0) {
                     destptr = destptr->next;
                  }
                  
                  /* Mark directory to not delete if dir name was found */
                  if (destptr == NULL) {
                     printf("%s: error! Could not find file to mark as not deleted! This should not happen!\n",progname);
                  } else {
                     destptr->delete = 0;
                  }
                  
               }
               
               /* Close and open new file descriptor for new Destination directory */
               if (MYchdir(listptr->name,"recurse_copy 12") == -1) {
                  MYcwd = MYgetcwd();
                  if (!QUIET)
                      fprintf(stderr,"%s: chdir 12: %s: %s\n",progname,MYcwd,strerror(errno));
                  free(MYcwd);
                  close(source_fd);
                  close(dest_fd);
                  return(-1);
               }
               close(dest_fd);
               if ((dest_fd = open(".",O_RDONLY)) == -1) {
                  if (!QUIET)
                      fprintf(stderr,"%s: open: %s: %s\n",progname,listptr->name,strerror(errno));
               }
               
            }
            
            /* Change CWD to source directory */
            if (MYfchdir(source_fd,listptr->name,"recurse_copy 13") == -1) {
               close(source_fd);
               close(dest_fd);
               return(-1);
            }
         }
         
         load = 1;
         
      }   
      
   }
   
   close(source_fd);
   
   if (!FIND)
       close(dest_fd);
   
   return(1);
}

/* Copy Organizer - Decides how to copy/display the files */

int
copy_files(char **filelist, int args)
{
   struct stat fsdest;
   struct stat fssource;
   int i=0;
   int return_code = 1;
   int destres;
   
   destres = lstat(filelist[args-1],&fsdest);
   
   /* Check if Source argument is a proper file */
   if (lstat(filelist[0],&fssource) == -1) {
      if (!QUIET)
          fprintf(stderr,"%s: lstat: %s: %s\n",progname,filelist[0],strerror(errno));
      return(-1);
   }
   
   /* Check if the last argument (if it exists) is a directory. Incase of
    * multiple file copy */
   if (!S_ISDIR(fsdest.st_mode) && args > 2) {
      if (!QUIET)
          fprintf(stderr,"%s: Copying multiple files, but last arguent (%s) is not a directory.\n",progname,filelist[args-1]);
      return(-1);
   }
   
   if (!RECURSIVE) {
      
      /* Copy every <source> file to <destination> */
      while (i < args-1) {
         return_code = file_copy(filelist[i],filelist[args-1]);
         i++;
      }
      
   } else {
      
      if (args == 2 || FIND) {
         /* Check if source file is a directory. Must be a dir if we are doing a recursive
          * copy or find */
         if (!S_ISDIR(fssource.st_mode)) {
            if (!QUIET)
                fprintf(stderr,"%s: %s: Source argument must be a directory.\n",progname,filelist[0]);
            return(-1);
         }
         
         /* Check if Destination is a directory with only 2 file arguments */
         if (destres != -1 && !S_ISDIR(fsdest.st_mode)) {
            if (!QUIET)
                fprintf(stderr,"%s: %s: Destinatin argument must be a directory.\n",progname,filelist[0]);
            return(-1);
         }
         
         /* Recursively copy specified directory */
         return_code = recurse_copy(filelist[i],filelist[args-1]);
         
      } else if (args > 2) {
         if (!QUIET)
             fprintf(stderr,"%s: too many arguments\n",progname);
         return(-1);
      }
      
   }
   
   return(return_code);
   
}

int
main (int args, char **argv)
{            
   int ch;
   extern char *optarg;
   extern int optind, opterr, optopt;
   char **filelist=NULL;
   int return_code = 1;
   int arg_num = 0;
   char *p;   
   
   /* Grab the progname */
   
   progname = ((p = rindex(argv[0],'/')) ? p+1 : *argv);
   
   /* File Default Mode */
   FILE_MODE.op = '+';
   FILE_MODE.value = FILE_MODE_ADD;
   
   /* Directory Default Mode */
   DIR_MODE.op = '+';
   DIR_MODE.value = DIR_MODE_ADD;
   
   /* Special File Default Mode */
   SPC_MODE.op = '+';
   SPC_MODE.value = SPC_MODE_ADD;
   
   UID = getuid();
   
   if (args < 2) usage();   
   
   while ((ch = getopt(args,argv,"vct:rfqE:e:ns:iV")) != EOF) {
      switch(ch) {
      case 'v':
         VERBOSE = 1;
         break;
         
      case 'r':
         RECURSIVE = 1;
         break;   
         
      case 'n':
         NEWONLY = 1;
         break;
         
      case 'f':
         FIND = 1;
         RECURSIVE = 1;
         break;
         
      case 'c':
         CONFIG = 1;
         arg_num = optind;
         break;
         
      case 'e':
         exclude_dir(optarg);
         break;
         
      case 'E':
         exclude_dir(optarg);
         SAVEEXCDIR=1;
         break;
         
      case 't':
         TRASHBIN = 1;
         NEWONLY = 1;
         trash_dir(optarg);
         break;
         
      case 's':
         MOD_CHECK = 1;
         MOD_CHECK_SEC = atoi(optarg);
         break;
         
      case 'i':         
         INODE_CHECK = 0;
         break;
         
      case 'z':
         FILESIZE=0;
         break;
      case 'q':
         QUIET=1;
         break;
         
      case 'V':
         fprintf(stdout,"%s",VERSION);
         exit(0);
         break;
         
         
      default:
         usage();
         break;
      }      
   }
   
   /* If a Trash bin is used, we must exclude that directory from our backup
    * so that we don't backup our old backups. :) */
   if (TRASHBIN) {
      if (!EXCLUDE) {
         EXCLUDE_BUF = malloc(strlen(TRASHBINDIR)+3);
         EXCLUDE_BUF[0] = '\0';
      }
      else
          EXCLUDE_BUF = realloc(EXCLUDE_BUF,strlen(EXCLUDE_BUF)+strlen(TRASHBINDIR)+3);
      
      strcat(EXCLUDE_BUF,"^");
      strcat(EXCLUDE_BUF,TRASHBINDIR);
      strcat(EXCLUDE_BUF,"^");
      
      EXCLUDE = 1;
   }
   
   /* Set proper Flags */
   if (FIND)
       NEWONLY = 0;
   
   /* Grab the approprite source and destination arguments */
   if (CONFIG) 
       filelist = parse_config(argv,arg_num);
   else
       filelist = parse_cmdline(args,argv,optind);
   
   /* Change variable args to the number of arguments in filelist[] */
   for (args = 0; filelist[args][0] != '\0'; args++);
   
   /* If there are enough proper arguments or FIND and RECURSIVE are 
    * set while args is > 0 then copy files */
   
   if ((args) > 1 || (FIND && RECURSIVE && args > 0)) {
      
      umask(0);
      return_code = copy_files(filelist,args);
      
   } else {
      if (args == 1){
         if (!QUIET)
             fprintf(stderr,"%s: missing destination argument.\n",progname);
      }
      if (args == 0) {
         if (!QUIET)
             fprintf(stderr,"%s: missing source and destination arguments.\n",progname);    
      }
   }
   
   /* printf("Backup Copy Complete: %d\n",return_code); */
   
   return(return_code);
} 



syntax highlighted by Code2HTML, v. 0.9.1