/*
 * @(#) eraser.c - Program for file and directory unrecoverable deletion.
 * (c) 1996 Ivan Maidanski <ivmai@chat.ru> http://ivmai.chat.ru
 * Freeware program source. All rights reserved.
 **
 * Language: ANSI C
 * Tested with: Watcom C16 v10.0
 * Last modified: 1996-07-18 14:20:00 GMT+04:00
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <direct.h> /* <dirent.h> */

#define DRIVESPEC ':' /* Drive letter delimiter in MS-DOS */
#define DELIMITER '\\' /* MS-DOS style of directory names delimiter */

#ifndef PATH_MAX
#define PATH_MAX FILENAME_MAX /* MS-DOS style of path name length */
#endif

#define FILE_EXT 3 /* size of file name extension */
#define SPECIALCH '.' /* for '.' and '..' files */

#define BLANKCH '~'

#define BUFSIZEMAX 16384 /* <= INT_MAX */
#define BUFBLOCKSIZE 1024

void printerror(char *errmsg,char *path)
{
 fputs("Error ",stderr);
 fputs(errmsg,stderr);
 fputs(": ",stderr);
 fputs(path,stderr);
 fputs("\n",stderr);
}

int getfilestat(char *path)
{
 struct stat info;
 if (!stat(path,&info))
  if (S_ISDIR(info.st_mode))
   return (1);
   else if (S_ISREG(info.st_mode))
    return (0);
 return (-1);
}

int testpathname(char *path)
{
 char *pc=strchr(path,'\0')-1;
 if (*pc==DELIMITER)
  if (path==pc || *(pc-1)==DRIVESPEC)
   return (3);
   else *(pc--)='\0';
  else if (*pc==DRIVESPEC)
  {
   *(++pc)=SPECIALCH;
   *(pc+1)='\0';
  }
 if (*pc==SPECIALCH && (path==pc ||
      *(pc-1)==DELIMITER || *(pc-1)==DRIVESPEC ||
      *(pc-1)==SPECIALCH && (path==pc-1 ||
       *(pc-2)==DELIMITER || *(pc-2)==DRIVESPEC)))
  return (2);
 return (getfilestat(path));
}

int readdirectoryentry(DIR *dirp,char *path,int isdir)
{
 struct dirent *entry;
 do entry=readdir(dirp);
  while (entry && (entry->d_name[0]==SPECIALCH &&
         (!entry->d_name[1] || entry->d_name[1]==SPECIALCH) ||
         (strcpy(strrchr(path,DELIMITER)+1,&entry->d_name[0]),
          getfilestat(path)!=isdir)));
 return (!!entry);
}

int blankfile(char *path,char *buf,size_t bufsize)
{
 int fid;
 off_t left;
 unsigned len;
 if ((fid=open(path,O_RDWR))<0)
 {
  printerror("opening file",path);
  return (1);
 }
 if ((left=lseek(fid,0,SEEK_END))<0)
 {
  close(fid);
  printerror("seeking file",path);
  return (1);
 }
 if (lseek(fid,0,SEEK_SET)<0)
 {
  close(fid);
  printerror("preparing to blank file",path);
  return (1);
 }
 if (!left)
  left++;
 left=(left+BUFBLOCKSIZE-1)/BUFBLOCKSIZE*BUFBLOCKSIZE;
 for (;left>0;left-=len)
  if (write(fid,buf,(len=left<bufsize?left:bufsize))<len)
  {
   close(fid);
   printerror("blanking file",path);
   return (1);
  }
 close(fid);
 if ((fid=open(path,O_RDWR | O_TRUNC))<0)
 {
  printerror("truncating file",path);
  return (1);
 }
 close(fid);
 return (0);
}

int delfileentry(char *path,char *blankname)
{
 static char newname[PATH_MAX+NAME_MAX+2];
 char *pc=strrchr(strcpy(&newname[0],path),DELIMITER);
 strcpy(pc? pc+1 : &newname[0],blankname);
 newname[PATH_MAX]='\0';
 if (strcmp(path,&newname[0]) && rename(path,&newname[0])<0)
 {
  printerror("renaming file",path);
  return (1);
 }
 if (remove(&newname[0])<0)
 {
  if (strcmp(path,&newname[0]) && rename(&newname[0],path)<0)
   printerror("removing renamed file",&newname[0]);
   else printerror("removing file",path);
  return (1);
 }
 return (0);
}

int deldirectoryentry(char *path)
{
 int fid;
 if (rmdir(path)<0)
 {
  printerror("removing directory",path);
  return (1);
 }
 if ((fid=open(path,O_RDWR | O_CREAT,S_IRUSR | S_IWUSR))<0)
 {
  printerror("creating file",path);
  return (1);
 }
 close(fid);
 return (0);
}

int scandirectory(char *path,char *buf,size_t bufsize,char *blankname)
{
 int err=0;
 char *pc=strchr(path,'\0');
 int rootdir=*(pc-1)==DELIMITER;
 DIR *dirp=opendir(path);
 *(pc+1)='\0';
 if (dirp)
 {
  *(pc-rootdir)=DELIMITER;
  while (readdirectoryentry(dirp,path,1))
   err+=deldirectoryentry(path);
  closedir(dirp);
  *pc='\0';
  dirp=opendir(path);
 }
 if (dirp)
 {
  *(pc-rootdir)=DELIMITER;
  while (readdirectoryentry(dirp,path,0))
   err+=blankfile(path,buf,bufsize);
  closedir(dirp);
  *pc='\0';
  dirp=opendir(path);
 }
 if (dirp)
 {
  *(pc-rootdir)=DELIMITER;
  while (readdirectoryentry(dirp,path,0))
   err+=delfileentry(path,blankname);
  closedir(dirp);
  *pc='\0';
 }
  else
  {
   printerror("reading directory",path);
   err++;
  }
 return (err);
}

int searchdirectory(char *path,int *subdir)
{
 char *name=*subdir? strchr(path,'\0') : strrchr(path,DELIMITER);
 DIR *dirp;
 struct dirent *entry;
 *name='\0';
 if (!(dirp=opendir(path)))
 {
  printerror("opening directory",path);
  *subdir=0;
  return (1);
 }
 *name=DELIMITER;
 if (!*subdir)
 {
  do entry=readdir(dirp);
   while (entry && strcmp(name+1,&entry->d_name[0]));
  if (!entry)
  {
   closedir(dirp);
   printerror("finding",path);
   *name='\0';
   if (!(dirp=opendir(path)))
   {
    printerror("rewinding directory",path);
    *subdir=0;
    return (2);
   }
   *name=DELIMITER;
   *subdir=readdirectoryentry(dirp,path,1);
   closedir(dirp);
   if (!*subdir)
    *name='\0';
   return (1);
  }
 }
  else *(name+1)='\0';
 *subdir=readdirectoryentry(dirp,path,1);
 closedir(dirp);
 if (!*subdir)
  *name='\0';
 return (0);
}

int deletesubdir(char *path,char *buf,size_t bufsize,char *blankname)
{
 int err=0;
 int level=1;
 int subdir=1;
 do
 {
  err+=searchdirectory(path,&subdir);
  if (!subdir)
   err+=scandirectory(path,buf,bufsize,blankname);
  subdir? level++ : level--;
 } while (level);
 return (err);
}

int deleterootdir(char *path,char *buf,size_t bufsize,char *blankname)
{
 int err=0;
 DIR *dirp=opendir(path);
 if (!dirp)
 {
  printerror("opening root directory",path);
  return (1);
 }
 while (readdirectoryentry(dirp,path,1))
  err+=deletesubdir(path,buf,bufsize,blankname);
 closedir(dirp);
 *(strrchr(path,DELIMITER)+1)='\0';
 err+=scandirectory(path,buf,bufsize,blankname);
 return (err);
}

int main(int argc,char *argv[])
{
 int err=0;
 int cnt;
 char *buf;
 size_t bufsize=BUFSIZEMAX;
 static char path[PATH_MAX+NAME_MAX+2];
 static char blankname[NAME_MAX+1];
 if (argc<2)
 {
  puts("Files and Directories Complete Deletion Utility.\n"
       "Usage:\n"
       " ERASER pathname [...]");
  return (2);
 }
 while (bufsize>0 && !(buf=(char *)calloc(bufsize,sizeof(char))))
  bufsize-=BUFBLOCKSIZE;
 if (!buf)
 {
  fputs("Error: Not enough memory!\n",stderr);
  return (3);
 }
 for (cnt=0;cnt<NAME_MAX;cnt++)
  blankname[cnt]=BLANKCH;
#if FILE_EXT
 blankname[NAME_MAX-FILE_EXT-1]=SPECIALCH;
#endif
 blankname[NAME_MAX]='\0';
 for (cnt=1;cnt<argc;cnt++)
  if (strlen(argv[cnt])<=PATH_MAX)
   switch (testpathname(strcpy(&path[0],argv[cnt])))
   {
    case 3:
     err+=deleterootdir(&path[0],buf,bufsize,blankname);
     break;
    case 2:
     err+=deletesubdir(&path[0],buf,bufsize,blankname);
     break;
    case 1:
     err+=deletesubdir(&path[0],buf,bufsize,blankname);
     err+=deldirectoryentry(&path[0]);
     if (getfilestat(&path[0]))
      break;
    case 0:
     err+=blankfile(&path[0],buf,bufsize);
     err+=delfileentry(&path[0],blankname);
     break;
    default:
     printerror("(Invalid path name)",&path[0]);
     err++;
   }
   else
   {
    printerror("(Path name too long)",argv[cnt]);
    err++;
   }
 free((void *)buf);
 return (!!err);
}
