/* Sparsify.c 
   J.H. Davenport 21.5.89
   usage: "sparsify filename"
   The file-name had better not be a symbolic link,
   but this isn't checked for.
   Modification: JHD 25.7.89: set owner (where possible)
   modification times, permission status etc. 
   Modification: JHD 16.10.89: fix a SUNos 4 feature for files whose
   last block is precisely null
*/

#define BSIZE 8192
#ifdef hpux
#include <fcntl.h>
/* This will give us O_RDONLY and friends on HPUX */
#include <utime.h>
struct utimbuf hpux_utime;
#endif
#include <sys/file.h>
#include <sys/types.h>
#include <sys/stat.h>
long lseek();
char *malloc();
/* Really should look it up: could be something different, but it
   doesn't really matter */
int buf[BSIZE/4]; /* So can use word operations */
main(argc,argv)
int argc;
char **argv;
{ int ifile,ofile,ilen,ans,seeking;
  long lans;
  char *oname;
  struct stat statbuf;
  if (argc != 2)
    { printf("Usage: sparsify filename\n");
      exit(-1);
    }
  ifile=open(argv[1],O_RDONLY);
  if (ifile<0)
    { perror("File couldn't be opened\n");
      exit(ifile);
    }
  oname=malloc((ilen=strlen(argv[1]))+3);
  strcpy(oname,argv[1]);
  oname[ilen]='.';
  oname[ilen+1]='z';
  oname[ilen+2]='\0';
  ofile=open(oname,O_WRONLY|O_CREAT|O_EXCL,0755);
  if (ofile<0)
    { perror("Output file couldn't be opened\n");
      exit(ifile);
    }
  seeking=0;
  while ((ilen=read(ifile,(char *)buf,BSIZE))==BSIZE)
    if (allzeros())
      { lans=lseek(ofile,(long)BSIZE,L_INCR);
        if(lans<0)
          { perror("Lseek failed\n");
            tidyup(oname);
          }
        seeking=1;
      }
    else
      { ans=write(ofile,(char *)buf,BSIZE);
        if (ans != BSIZE)
          { perror("Write failed\n");
            tidyup(oname);
          }
        seeking=0;
      }
  if (ilen > 0)
    { ans=write(ofile,(char *)buf,ilen);
      if (ans != ilen)
        { perror("Write failed\n");
          tidyup(oname);
        }
    }
  else if (seeking)
    { lans=lseek(ofile,-(long)BSIZE,L_INCR);
      if(lans<0)
        { perror("Lseek failed\n");
                      tidyup(oname);
        }
      ans=write(ofile,(char *)buf,BSIZE);
      if (ans != BSIZE)
        { perror("Write failed\n");
          tidyup(oname);
          }
    }
  ilen=fstat(ifile,&statbuf);
  if (ilen < 0)
    { perror("Fstat failed\n");
      tidyup(oname);
    }
  ilen=fchmod(ofile,statbuf.st_mode);
  if (ilen < 0)
    { perror("Attempt to chmod failed\n");
      tidyup(oname);
    }
  if (geteuid())
    { if (statbuf.st_uid != geteuid())
        printf("Can't reset owner/group\n");
    }
  else {
    ilen=fchown(ofile,statbuf.st_uid,statbuf.st_gid);
    if (ilen < 0)
      { perror("Chown failed\n");
        tidyup(oname);
      }
  }
#ifdef hpux
  /* So it's hpux  */
  hpux_utime.actime=statbuf.st_atime;
  hpux_utime.modtime=statbuf.st_mtime;
  ilen=utime(oname,&hpux_utime);
#else
  /* so it's a sun, or maybe an apollo */
  ilen=utimes(oname,&statbuf.st_atime);
#endif
  if (ilen < 0)
    { perror("Attempt to set times failed\n");
            tidyup(oname);
    }
  ilen=unlink(argv[1]);
  if (ilen<0)
    { perror("Unlink for old file failed\n");
      printf("Tidy up by hand!\n");
      exit(-1);
    }
  ilen=link(oname,argv[1]);
  if (ilen!= 0)
    { perror("Rename failed\n");
      printf("Tidy up by hand!\n");
      exit(-1);
    }
  ilen=unlink(oname);
  if (ilen<0)
    { perror("Unlink for temporary file failed\n");
      printf("Tidy up by hand!\n");
      exit(-1);
    }
  exit(0);
}

tidyup(oname)
char *oname;
{ int ilen;
  ilen=unlink(oname);
  if (ilen<0)
    { perror("Unlink for temporary file failedwhile tidying up\n");
      printf("Tidy up by hand!\n");
      exit(-1);
    }
}

allzeros()
{ int i;
  for(i=0;i<BSIZE/4;i++)
    if(buf[i])
      return 0;
  return 1;
}
