/* 

        Copyright (C) 1995
        Free Software Foundation, Inc.

   This file is part of GNU cfengine - written and maintained 
   by Mark Burgess, Dept of Computing and Engineering, Oslo College,
   Dept. of Theoretical physics, University of Oslo
 
   This program is free software; you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by the
   Free Software Foundation; either version 2, or (at your option) any
   later version.
 
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.
 
  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA

*/


/*******************************************************************/
/*                                                                 */
/* File Image copying                                              */
/*                                                                 */
/*******************************************************************/

#define INET 1

#include "cf.defs.h"
#include "cf.extern.h"

/*********************************************************************/
/* Level 1                                                           */
/*********************************************************************/

RecursiveImage(ip,from,to,maxrecurse)

struct Image *ip;
char *from, *to;
int maxrecurse;

{ struct stat statbuf, deststatbuf;
  CFDIR *dirh;
  struct cfdirent *dirp;
  char newfrom[bufsize];
  char newto[bufsize];
  void *bug_check;

if (maxrecurse == -1)  /* reached depth limit */
   {
   Debug2("MAXRECURSE ran out, quitting at level %s with endlist = %d\n",from,ip->next);
   return;
   }

Debug2("RecursiveImage(%s,lev=%d,next=%d)\n",from,maxrecurse,ip->next);

if (IgnoreFile(from,""))
   {
   Verbose("cfengine: Ignoring directory %s\n",from);
   return;
   }

if (strlen(from) == 0)     /* Check for root dir */
   {
   from = "/";
   }

bug_check = ip->next;

  /* Check that dest dir exists before starting */

strcpy(newto,to);
AddSlash(newto);

if (! MakeDirectoriesFor(newto))
   {
   printf("cfengine:%s: Unable to make directory %s\n",VPREFIX,from);
   return;
   }

if ((dirh = cfopendir(from,ip)) == NULL)
   {
   printf("cfengine: RecursiveImage(): Can't open directory [%s]\n",from);
   return;
   }

if (ip->next != bug_check)
   {
   printf("cfengine:%s: solaris BSD compat bug: opendir wrecked the heap memory!!",VPREFIX);
   printf("cfengine:%s: in copy to %s, using workaround...\n",VPREFIX,from);
   ip->next = bug_check;
   }

for (dirp = cfreaddir(dirh,ip); dirp != NULL; dirp = cfreaddir(dirh,ip))
   {
   if (!SensibleFile(dirp->d_name,from))
      {
      continue;
      }

   if (IgnoreFile(from,dirp->d_name))
      {
      continue;
      }

   if (IgnoredOrExcluded(dirp->d_name,ip->inclusions,ip->exclusions))
      {
      continue;
      }

   strcpy(newfrom,from);                                   /* Assemble pathname */
   AddSlash(newfrom);
   strcpy(newto,to);
   AddSlash(newto);

   if (BufferOverflow(newfrom,dirp->d_name))
      {
      printf(" culprit: RecursiveImage\n");
      cfclosedir(dirh);
      return;
      }

   strcat(newfrom,dirp->d_name);

   if (BufferOverflow(newto,dirp->d_name))
      {
      printf(" culprit: RecursiveImage\n");
      cfclosedir(dirh);
      return;
      }

   strcat(newto,dirp->d_name);

   if (TRAVLINKS)
      {
      if (cfstat(newfrom,&statbuf,ip) == -1)
         {
         Verbose("cfengine: RecursiveImage(): Can't stat %s\n",newfrom);
         continue;
         }
      }
   else
      {
      if (cflstat(newfrom,&statbuf,ip) == -1)
         {
         Verbose("cfengine: RecursiveImage(): Can't stat %s\n",newfrom);
         if (cfreadlink(newfrom,VBUFF,bufsize,ip) != -1)
            {
            Verbose("          File is link to -> %s\n",VBUFF);
            }
         continue;
         }
      }

   if (S_ISDIR(statbuf.st_mode))
      {
      if ((ip->uid)->uid == -1)          /* Preserve uid and gid  */
	 {
	 (ip->uid)->uid = statbuf.st_uid;
	 }
      
      if ((ip->gid)->gid == -1)
	 {
	 (ip->gid)->gid = statbuf.st_gid;
	 }

      if (stat(newto,&statbuf) == -1)
	 {
	 mkdir(newto,statbuf.st_mode);
	 bzero(&statbuf,sizeof(statbuf));
	 }

      CheckExistingFile(newto,ip->plus,ip->minus,fixall,ip->uid,ip->gid,&statbuf);
      
      RecursiveImage(ip,newfrom,newto,maxrecurse-1);
      }
   else
      {
      CheckImage(newfrom,newto,ip);
      }
   }

DeleteCompressedArray(ip->inode_cache);

ip->inode_cache = NULL;

cfclosedir(dirh);
}

/*********************************************************************/

CheckHomeImages(ip)

struct Image *ip;

{ CFDIR *dirh, *dirh2;
  struct cfdirent *dirp, *dirp2;
  char *ReadLastNode(), username[maxvarsize];
  char homedir[bufsize],dest[bufsize];
  struct passwd *pw;
  struct stat statbuf;
  struct Item *itp;
  int request_uid = ip->uid->uid;  /* save if -1 */

if (!MountPathDefined())
   {
   printf("cfengine:%s:  mountpattern is undefined\n",VPREFIX);
   return;
   }

if (cfstat(ip->path,&statbuf,ip))
   {
   printf("cfengine:%s: Master file %s doesn't exist for copying\n",VPREFIX,ip->path);
   return;
   }

for (itp = VMOUNTLIST; itp != NULL; itp=itp->next)
   {
   if (IsExcluded(itp->classes))
      {
      continue;
      }
   
   if ((dirh = cfopendir(itp->name,ip)) == NULL)
      {
      printf("cfengine:%s: Can't open directory %s\n",VPREFIX,itp->name);
      perror("cfopendir");
      return;
      }

   for (dirp = cfreaddir(dirh,ip); dirp != NULL; dirp = cfreaddir(dirh,ip))
      {
      if (!SensibleFile(dirp->d_name,itp->name))
         {
         continue;
         }

      strcpy(homedir,itp->name);
      AddSlash(homedir);
      strcat(homedir,dirp->d_name);

      if (! IsHomeDir(homedir))
         {
         continue;
         }

      if ((dirh2 = cfopendir(homedir,ip)) == NULL)
         {
         printf("cfengine:%s: Can't open directory %s\n",VPREFIX,homedir);
         perror("cfopendir");
         return;
         }

      for (dirp2 = cfreaddir(dirh2,ip); dirp2 != NULL; dirp2 = cfreaddir(dirh2,ip))
         {
         if (!SensibleFile(dirp2->d_name,homedir))
            {
            continue;
            }

         strcpy(username,dirp2->d_name);
         strcpy(dest,homedir);
         AddSlash(dest);
         strcat(dest,dirp2->d_name);

         if (strlen(ip->destination) > 4)
            {
            AddSlash(dest);
            strcat(dest,ReadLastNode(ip->destination));
            }

         if (request_uid == -1)
            {
            if ((pw = getpwnam(username)) == NULL)
               {
               Debug2("cfengine: directory corresponds to no user %s - ignoring\n",username);
               continue;
               }
            else
               {
               Debug2("(Setting user id to %s)\n",username);
               }

            ip->uid->uid = pw->pw_uid;
            }

         CheckImage(ip->path,dest,ip);
         }
      cfclosedir(dirh2);
      }
   cfclosedir(dirh);
   }
}

/*********************************************************************/
/* Level 2                                                           */
/*********************************************************************/

CheckImage(source,destination,ip)

char *source;
char *destination;
struct Image *ip;

{ CFDIR *dirh;
  char sourcefile[bufsize];
  char sourcedir[bufsize];
  char destdir[bufsize];
  char destfile[bufsize];
  struct stat sourcestatbuf, deststatbuf;
  struct cfdirent *dirp;

Debug2("CheckImage (source=%s destination=%s)\n",source,destination);

if (cflstat(source,&sourcestatbuf,ip) == -1)
   {
   printf("cfengine:%s: Can't stat %s\n",VPREFIX,source);
   FlushClientCache(ip);
   return;
   }

if (sourcestatbuf.st_nlink > 1)    /* Preserve hard link structure when copying */
   {
   RegisterHardLink(sourcestatbuf.st_ino,destination,ip);
   }

if ((ip->uid)->uid == -1)          /* Preserve uid and gid  */
   {
   (ip->uid)->uid = sourcestatbuf.st_uid;
   }

if ((ip->gid)->gid == -1)
   {
   (ip->gid)->gid = sourcestatbuf.st_gid;
   }

if (S_ISDIR(sourcestatbuf.st_mode))
   {
   strcpy(sourcedir,source);
   AddSlash(sourcedir);
   strcpy(destdir,destination);
   AddSlash(destdir);

   if ((dirh = cfopendir(sourcedir,ip)) == NULL)
      {
      printf("cfengine:%s: Can't open directory %s\n",VPREFIX,sourcedir);
      perror("opendir");
      FlushClientCache(ip);
      return;
      }

   if (chmod (destdir,sourcestatbuf.st_mode  & 07777) == -1)
      {
      perror("chmod");
      }

   stat(destdir,&deststatbuf);

   CheckExistingFile(destdir,ip->plus,ip->minus,fixall,ip->uid,ip->gid,&deststatbuf);
   
   for (dirp = cfreaddir(dirh,ip); dirp != NULL; dirp = cfreaddir(dirh,ip))
      {
      if (!SensibleFile(dirp->d_name,sourcedir))
         {
         continue;
         }

      strcpy(sourcefile, sourcedir);
      
      if (BufferOverflow(sourcefile,dirp->d_name))
	 {
	 FatalError("Culprit: CheckImage");
	 }
  
      strcat(sourcefile, dirp->d_name);
      strcpy(destfile, destdir);
      
      if (BufferOverflow(destfile,dirp->d_name))
	 {
	 FatalError("Culprit: CheckImage");
	 }
      strcat(destfile, dirp->d_name);
 
      if (cflstat(sourcefile,&sourcestatbuf,ip) == -1)
         {
         printf("cfengine: Can't stat %s\n",VPREFIX,sourcefile);
	 FlushClientCache(ip);
         return;
	 }

      ImageCopy(sourcefile,destfile,sourcestatbuf,ip);
      }

   cfclosedir(dirh);
   FlushClientCache(ip);
   return;
   }

strcpy(sourcefile,source);
strcpy(destfile,destination);

ImageCopy(sourcefile,destfile,sourcestatbuf,ip);
FlushClientCache(ip);
}


/*********************************************************************/
/* Level 3                                                           */
/*********************************************************************/

ImageCopy(sourcefile,destfile,sourcestatbuf,ip)

char *sourcefile;
char *destfile;
struct stat sourcestatbuf;
struct Image *ip;

{ char linkbuf[bufsize], *lastnode;
  struct stat deststatbuf;
  int i, silent = false;
  mode_t srcmode = sourcestatbuf.st_mode;
  int ok_to_copy;
  
Debug2("ImageCopy(%s,%s,+%o,-%o)\n",sourcefile,destfile,ip->plus,ip->minus);

if (IgnoredOrExcluded(sourcefile,ip->inclusions,ip->exclusions))
   {
      printf("Ignoring: %s\n",sourcefile);

   return;
   }

if (IsWildItemIn(VLINKCOPIES,lastnode) || IsWildItemIn(ip->symlink,lastnode))
   {
   Verbose("cfengine: copy item %s marked for linking instead\n",sourcefile);
   switch (ip->linktype)
      {
      case 's':
                LinkFiles(destfile,sourcefile,NULL,NULL,NULL);
                break;
      case 'r':
                RelativeLink(destfile,sourcefile,NULL,NULL,NULL);
	        break;
      case 'a':
                AbsoluteLink(destfile,sourcefile,NULL,NULL,NULL);
                break;
      default:
                printf("cfengine:%s: internal error, link type was [%c] in ImageCopy\n",VPREFIX,ip->linktype);
                return;
      }
   return;
   }

if (strcmp(ip->action,"silent") == 0)
   {
   silent = true;
   }

bzero(linkbuf,bufsize);

if (lstat(destfile,&deststatbuf) == -1)
   {
   if (strcmp(ip->action,"warn") == 0)
      {
      Silent("cfengine:%s: image file %s is non-existent\n",VPREFIX,destfile);
      Silent("cfengine:%s: (should be copy of %s)\n",VPREFIX, sourcefile);
      return;
      }

   if (S_ISREG(srcmode))
      {
      Verbose("Image doesn't exist: copying %s:%s to %s\n",ip->server,sourcefile,destfile);      

      if (CopyReg(sourcefile,destfile,sourcestatbuf,deststatbuf,ip))
         {
         chmod(destfile,sourcestatbuf.st_mode & 07777);
         stat(destfile,&deststatbuf);
         CheckExistingFile(destfile,ip->plus,ip->minus,fixall,ip->uid,ip->gid,&deststatbuf);
	 AddCopyClasses(ip);
         }

      Debug2("Leaving ImageCopy\n");
      return;
      }

   if (S_ISFIFO (srcmode))
      {
#ifdef HAVE_MKFIFO
      if (DONTDO)
         {
         printf("cfengine: Make FIFO %s\n",destfile);
         }
      else if (mkfifo (destfile,srcmode))
         {
         printf ("cfengine:%s: cannot create fifo `%s'",VPREFIX, destfile);
	 Debug2("Leaving ImageCopy\n");
         return;
         }
      
      AddCopyClasses(ip);
#endif
      }
   else
      {
      if (S_ISBLK (srcmode) || S_ISCHR (srcmode) || S_ISSOCK (srcmode))
         {
         if (DONTDO)
            {
            printf("cfengine:%s: Make BLK/CHR/SOCK %s\n",VPREFIX,destfile);
            }
         else if (mknod (destfile, srcmode, sourcestatbuf.st_rdev))
            {
            printf ("cfengine:%s: cannot create special file `%s'",VPREFIX, destfile);
	    Debug2("Leaving ImageCopy\n");
            return;
            }
	 
	 AddCopyClasses(ip);
         }
      }

   if (S_ISLNK(srcmode))
      {
      if (cfreadlink(sourcefile,linkbuf,bufsize,ip) == -1)
         {
         printf("cfengine:%s: Can't readlink %s\n",VPREFIX,sourcefile);
	 Debug2("Leaving ImageCopy\n");
         return;
         }

      Verbose("cfengine: imaging link from %s to %s\n",destfile,linkbuf);

      if (ip->linktype == 'a' && linkbuf[0] != '/')      /* Not absolute path - must fix */
         {
         strcpy(VBUFF,sourcefile);
         ChopLastNode(VBUFF);
         AddSlash(VBUFF);
         strcat(VBUFF,linkbuf);
         strcpy(linkbuf,VBUFF);
         }
      
      switch (ip->linktype)
         {
         case 's':
	           if (*linkbuf == '.')
		      {
		      RelativeLink(destfile,linkbuf,NULL,NULL,NULL);
		      }
		   else
		      {
                      LinkFiles(destfile,linkbuf,NULL,NULL,NULL);
		      }
                   break;
         case 'r':
                   RelativeLink(destfile,linkbuf,NULL,NULL,NULL);
	           break;
         case 'a':
                   AbsoluteLink(destfile,linkbuf,NULL,NULL,NULL);
                   break;
         default:
                   printf("cfengine: internal error, link type was [%c] in ImageCopy\n",ip->linktype);
                   return;
	 }

      AddCopyClasses(ip);
      }
   }
else
   {
   if (! ip->force)
      {
      if (ip->size != cfnosize)
         {
         switch (ip->comp)
   	       {
               case '<':  if (sourcestatbuf.st_size > ip->size)
	                     {
	   	             Silent("cfengine:%s: source file %s is > %d bytes in copy\n",VPREFIX,sourcefile,ip->size);
		             return;
	                     }
	                  break;
		    
   	       case '=':  if (sourcestatbuf.st_size != ip->size)
	                     {
		             Silent("cfengine:%s: source file %s is not %d bytes in copy\n",VPREFIX,sourcefile,ip->size);
		             return;
	                     }
	                  break;
		       
	       default:  if (sourcestatbuf.st_size < ip->size)
	                    {
		            Silent("cfengine:%s: source file %s is < %d bytes in copy\n",VPREFIX,sourcefile,ip->size);
		            return;
	                    }
	                  break;;
	       }
         }
   
      switch (ip->type)
         {
         case 'c': if (S_ISREG(deststatbuf.st_mode) && S_ISREG(srcmode))
		      {
	              ok_to_copy = CompareMD5CheckSums(sourcefile,destfile,ip,&sourcestatbuf,&deststatbuf);
		      }	     
	           else
	              {
		      Verbose("cfengine: checksum comparison replaced by ctime: files not regular\n");
		      Verbose("          %s -> %s\n",sourcefile,destfile);
		      ok_to_copy = (deststatbuf.st_ctime < sourcestatbuf.st_ctime);
	              }

                   if (ok_to_copy && strcmp(ip->action,"warn") == 0)
                      { 
                      Verbose("cfengine: image file %s has a wrong MD5 checksum\n",destfile);
                      Verbose("          (should be copy of %s)\n", sourcefile);
                      return;
                      }
	           break;

	 case 'b': if (S_ISREG(deststatbuf.st_mode) && S_ISREG(srcmode))
		      {
	              ok_to_copy = CompareBinarySums(sourcefile,destfile,ip,&sourcestatbuf,&deststatbuf);
		      }	     
	           else
	              {
		      Verbose("cfengine: checksum comparison replaced by ctime: files not regular\n");
		      Verbose("          %s -> %s\n",sourcefile,destfile);
		      ok_to_copy = (deststatbuf.st_ctime < sourcestatbuf.st_ctime);
	              }

                   if (ok_to_copy && strcmp(ip->action,"warn") == 0)
                      { 
                      Verbose("cfengine: image file %s has a wrong binary checksum\n",destfile);
                      Verbose("          (should be copy of %s)\n", sourcefile);
                      return;
                      }
	           break;
		   
         default:  ok_to_copy = (deststatbuf.st_ctime < sourcestatbuf.st_ctime);
	     
                   if (ok_to_copy && strcmp(ip->action,"warn") == 0)
                      { 
                      Verbose("cfengine: image file %s out of date\n",destfile);
                      Verbose("          (should be copy of %s)\n", sourcefile);
                      return;
                      }
	           break;
         }
      }



   if ((ip->force) || ok_to_copy)
      {
      if (S_ISREG(srcmode))
         {
         Verbose("cfengine: updating image %s from master %s\n",destfile,sourcefile);

	 AddCopyClasses(ip);

         if (CopyReg(sourcefile,destfile,sourcestatbuf,deststatbuf,ip))
            {
            chmod(destfile,srcmode & 07777);
            stat(destfile,&deststatbuf);
            CheckExistingFile(destfile,ip->plus,ip->minus,fixall,ip->uid,ip->gid,&deststatbuf);
            }

         return;
         }

      if (S_ISLNK(sourcestatbuf.st_mode))
         {
         if (cfreadlink(sourcefile,linkbuf,bufsize,ip) == -1)
            {
            printf("cfengine:%s:Can't readlink %s\n",VPREFIX,sourcefile);
	    Debug2("Leaving ImageCopy\n");
            return;
            }

         Verbose("cfengine: imaging link from %s to %s\n",destfile,linkbuf);

         LinkFiles(destfile,linkbuf,NULL,NULL,NULL);
         }
      }
   else
      {
      if ((S_ISDIR(deststatbuf.st_mode)  && ! S_ISDIR(sourcestatbuf.st_mode))  ||
          (S_ISREG(deststatbuf.st_mode)  && ! S_ISREG(sourcestatbuf.st_mode))  ||
          (S_ISBLK(deststatbuf.st_mode)  && ! S_ISBLK(sourcestatbuf.st_mode))  ||
          (S_ISCHR(deststatbuf.st_mode)  && ! S_ISCHR(sourcestatbuf.st_mode))  ||
          (S_ISSOCK(deststatbuf.st_mode) && ! S_ISSOCK(sourcestatbuf.st_mode)) ||
          (S_ISFIFO(deststatbuf.st_mode) && ! S_ISFIFO(sourcestatbuf.st_mode)) ||
          (S_ISLNK(deststatbuf.st_mode)  && ! S_ISLNK(sourcestatbuf.st_mode)))

         {
         printf("cfengine:%s: image exists but destination type is silly (file/dir/link doesn't match)\n",VPREFIX);
         printf("cfengine:%s: source=%s, dest=%s\n",VPREFIX,sourcefile,destfile);
         return;
         }

      Debug2("cfengine: image file is up to date: %s\n",destfile);
      }
   }
}

/*********************************************************************/

cfstat(file,buf,ip)

 /* wrapper for network access */

char *file;
struct stat *buf;
struct Image *ip;

{ int res;

if (strcmp(ip->server,"localhost") == 0)
   {
   res = stat(file,buf);
   CheckForHoles(buf,ip);
   return res;
   }
else
   {
   return cf_rstat(file,buf,ip,"file");
   }
}

/*********************************************************************/

cflstat(file,buf,ip)

char *file;
struct stat *buf;
struct Image *ip;

 /* wrapper for network access */

{ int res;

if (strcmp(ip->server,"localhost") == 0)
   {
   res = lstat(file,buf);
   CheckForHoles(buf,ip);
   return res;
   }
else
   {
   /* read cache if possible */
   return cf_rstat(file,buf,ip,"link");
   }
}

/*********************************************************************/

cfreadlink(sourcefile,linkbuf,buffsize,ip)

char *sourcefile, *linkbuf;
int buffsize;
struct Image *ip;

 /* wrapper for network access */

{ struct cfstat *sp;

if (strcmp(ip->server,"localhost") == 0)
   {
   return readlink(sourcefile,linkbuf,buffsize);
   }

for (sp = ip->cache; sp != NULL; sp=sp->next)
   {
   if ((strcmp(ip->server,sp->cf_server) == 0) && (strcmp(sourcefile,sp->cf_filename) == 0))
      {
      if (strlen(sp->cf_readlink)+1 > buffsize)
	 {
	 printf("cfengine:%s: readlink value is too large in cfreadlink\n",VPREFIX);
	 printf("cfengine:%s: [%s]]n",VPREFIX,sp->cf_readlink);
	 return -1;
	 }
      else
	 {
         Debug("cfreadlink returning %s\n",sp->cf_readlink);
	 bzero(linkbuf,buffsize);
	 strcpy(linkbuf,sp->cf_readlink);
	 return 0;
	 }
      }
   }

return -1;
}

/*********************************************************************/

CFDIR *cfopendir(name,ip)

char *name;
struct Image *ip;

{ CFDIR *cf_ropendir(),*returnval;

if (strcmp(ip->server,"localhost") == 0)
   {
   if ((returnval = (CFDIR *)malloc(sizeof(CFDIR))) == NULL)
      {
      FatalError("Can't allocate memory in cfopendir()\n");
      }
   
   returnval->cf_list = NULL;
   returnval->cf_listpos = NULL;
   returnval->cf_dirh = opendir(name);
   return returnval;
   }
else
   { 
   return cf_ropendir(name,ip);
   }
}

/*********************************************************************/

struct cfdirent *cfreaddir(cfdirh,ip)

CFDIR *cfdirh;
struct Image *ip;

  /* We need this cfdirent type to handle the weird hack */
  /* used in SVR4/solaris dirent structures              */

{ static struct cfdirent dir;
  struct dirent *dirp;

bzero(dir.d_name,bufsize);

if (strcmp(ip->server,"localhost") == 0)
   {
   dirp = readdir(cfdirh->cf_dirh);

   if (dirp == NULL)
      {
      return NULL;
      }
   strncpy(dir.d_name,dirp->d_name,bufsize);
   return &dir;
   }
else
   {
   if (cfdirh->cf_listpos != NULL)
      {
      strncpy(dir.d_name,(cfdirh->cf_listpos)->name,bufsize);
      cfdirh->cf_listpos = cfdirh->cf_listpos->next;
      return &dir;
      }
   else
      {
      return NULL;
      }
   }
}
 
/*********************************************************************/

cfclosedir(dirh)

CFDIR *dirh;

{
Debug("cfclosedir()\n");
DeleteItemList(dirh->cf_list);
}

/*********************************************************************/
/* Level 2                                                           */
/*********************************************************************/

CopyReg (source,dest,sstat,dstat,ip)

char *source, *dest;
struct stat sstat, dstat;
struct Image *ip;

{ char backup[bufsize];
  char new[bufsize], *linkable;
  int remote = false, silent;

Debug2("CopyReg(%s,%s)\n",source,dest);

if (DONTDO)
   {
   printf("cfengine:%s: copy from %s to %s\n",VPREFIX,source,dest);
   return false;
   }

 /* Make an assoc array of inodes used to preserve hard links */

linkable = CompressedArrayValue(ip->inode_cache,sstat.st_ino);

if (sstat.st_nlink > 1)  /* Preserve hard links, if possible */
   {
   if ((strcmp(dest,linkable) != 0) && CompressedArrayElementExists(ip->inode_cache,sstat.st_ino))
      {
      unlink(dest);

      silent = SILENT;
      SILENT = true;
      
      DoHardLink(dest,linkable);
      
      SILENT = silent;
      return true;
      }
   }

if (strcmp(ip->server,"localhost") != 0)
   {
   Debug("This is a remote copy from server: %s\n",ip->server);
   remote = true;
   }

strcpy(new,dest);
strcat(new,CF_NEW);

if (remote)
   {
   if (!CopyRegNet(source,new,ip))
      {
      return false;
      }
   }
else
   {
   if (!CopyRegDisk(source,new,ip))
      {
      return false;
      }
   }

Debug("CopyReg succeeded in copying to .new\n");

if (IMAGEBACKUP)
   {
   strcpy(backup,dest);
   strcat(backup,CF_SAVED);

   if (IsItemIn(VREPOSLIST,backup))
      {
      return true;
      }

   if (rename(dest,backup) == -1)
      {
      /* ignore */
      }
   }

stat(new,&dstat);

if (dstat.st_size == 0)
   {
   Verbose("cfengine:%s: WARNING: new file was empty %s\n",VPREFIX,new);
   }

if (rename(new,dest) == -1)
   {
   printf("cfengine:%s: SERIOUS PROBLEM! Could not install copy file as %s\n",VPREFIX,dest);
   perror("rename");
   rename(backup,dest);
   return false;
   }

if (Repository(backup))
   {
   unlink(backup);
   }

return true;
}


/*********************************************************************/

AddCopyClasses(ip)

struct Image *ip;

{ char *sp;
  char currentitem[maxvarsize];

Debug("Entering AddCopyClasses(%s)\n",ip->defines);

for (sp = ip->defines; *sp != '\0'; sp++)
   {
   currentitem[0] = '\0';

   sscanf(sp,"%[^,:.]",currentitem);

   sp += strlen(currentitem);

   AddClassToHeap(currentitem);
   }
}

/*********************************************************************/
/* Level 3                                                           */
/*********************************************************************/

CheckForHoles(sstat,ip)

/* Need a transparent way of getting this into CopyReg() */
/* Use a public member in struct Image                   */

struct stat *sstat;
struct Image *ip;

{
#ifndef IRIX
if (sstat->st_size > sstat->st_blocks * DEV_BSIZE)
#else
# ifdef HAVE_ST_BLOCKS
if (sstat->st_size > sstat->st_blocks * DEV_BSIZE)
# else
if (sstat->st_size > ST_NBLOCKS((*sstat)) * DEV_BSIZE)
# endif
#endif
   {
   ip->makeholes = 1;   /* must have a hole to get checksum right */
   }

ip->makeholes = 0;
}

/*********************************************************************/

CopyRegDisk(source,new,ip)

char *source, *new;
struct Image *ip;

{ int sd, dd, buf_size;
  char *buf, *cp;
  int n_read, *intp, remote=false;
  long n_read_total = 0;
  int last_write_made_hole = 0;
  
if ((sd = open(source,O_RDONLY)) == -1)
   {
   printf("cfengine: can't copy %s!\n",source);
   perror("open");
   unlink(new);
   return false;
   }

if ((dd = open(new,O_WRONLY | O_CREAT | O_TRUNC, 0600)) == -1)
   {
   printf("cfengine:%s: can't copy to destination %s!\n",VPREFIX,new);
   perror("open");
   unlink(new);
   return false;
   }

buf_size = ST_BLKSIZE(dstat);
buf = (char *) malloc(buf_size + sizeof(int));

while (true)
   {
   if ((n_read = read (sd, buf, buf_size)) == -1)
      {
      if (errno == EINTR) 
         {
         continue;
         }

      close(sd);
      close(dd);
      free(buf);
      return false;
      }

   if (n_read == 0)
      {
      break;
      }

   n_read_total += n_read;

   intp = 0;

   if (ip->makeholes)
      {
      buf[n_read] = 1;	                   /* Sentinel to stop loop.  */

      /* Find first non-zero *word*, or the word with the sentinel.  */

      intp = (int *) buf;

      while (*intp++ == 0)
         {
         }

      /* Find the first non-zero *byte*, or the sentinel.  */

      cp = (char *) (intp - 1);

      while (*cp++ == 0)
         {
         }

      /* If we found the sentinel, the whole input block was zero,
         and we can make a hole.  */

      if (cp > buf + n_read)
         {
         /* Make a hole.  */
         if (lseek (dd, (off_t) n_read, SEEK_CUR) < 0L)
            {
            printf ("cfengine: lseek in CopyReg, dest=%s\n", new);
            perror("lseek");
            free(buf);
	    unlink(new);
            return false;
            }
         last_write_made_hole = 1;
         }
      else
         {
         /* Clear to indicate that a normal write is needed. */
         intp = 0;
         }
      }

   if (intp == 0)
      {
      if (cf_full_write (dd, buf, n_read) < 0)
         {
         printf ("cfengine: full_write failed in CopyReg()\n");
         close(sd);
         close(dd);
         free(buf);
	 unlink(new);
         return false;
         }
      last_write_made_hole = 0;
      }
   }

  /* If the file ends with a `hole', something needs to be written at
     the end.  Otherwise the kernel would truncate the file at the end
     of the last write operation.  */

  if (last_write_made_hole)
    {
    /* Write a null character and truncate it again.  */

    if (cf_full_write (dd, "", 1) < 0 || ftruncate (dd, n_read_total) < 0)
       {
       printf("cfengine: full_write or ftruncate error in CopyReg\n");
       free(buf);
       unlink(new);
       return false;
       }
    }

close(sd);
close(dd);

free(buf);
return true;
}

/*********************************************************************/

RegisterHardLink(i,value,ip)

int i;
char *value;
struct Image *ip;

{
if (!FixCompressedArrayValue(i,value,&(ip->inode_cache)))
   {
    /* Not root hard link, remove to preserve consistency */
   Verbose("Removing old hard link %s to preserve structure..\n",value);
   unlink(value);
   }
}
