/* files.c
 *
 * This is a work of the US Government. In accordance with 17 USC 105, 
 * copyright protection is not available for any work of the US Government.
 *
 * 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.
 *
 */

#include "foremost.h"

/* Audit File
   The audit file is opened before we do the digging. 
   This file will be closed either at the end of the program,
   or by a signal_handler if the user sends a SIGINT or SIGTERM...
*/



/* Returns TRUE if the directory exists and is empty. 
   If the directory does not exist, we attempt to create it.
   On error, returns FALSE */
int outputDirectoryOK(char *dir) {

  DIR *temp;
  struct dirent *entry;
  int i;
  mode_t newDirectoryMode;


  if ((temp = opendir(dir)) == NULL) {

    /* If the directory doesn't exist (ENOENT), we will create it */
    if (errno == ENOENT) {
      
      /* The directory mode values come from the chmod(2) man page */
#ifdef  __MINGW32__
      newDirectoryMode = 0;
	  if(mkdir(dir)){
#else
      newDirectoryMode = (S_IRUSR | S_IWUSR | S_IXUSR |
			    S_IRGRP | S_IWGRP | S_IXGRP |
			    S_IROTH | S_IWOTH);
	  if (mkdir(dir,newDirectoryMode)) {
#endif	
      

	fprintf (stderr,"An error occured while trying to create %s - %s\n",
		dir,strerror(errno));
	return FALSE;
      }

      /* So now that we've created the directory we should 
	 be able to open it. Let's try again */
      if ((temp = opendir(dir)) == NULL) {
	
	fprintf (stderr,"An error occured while trying to open %s - %s\n",
		dir,strerror(errno));
	return FALSE;
      }
    }

    /* This is going back to the first time we tried to open the directory
       and failed for a reason other than "file does not exist." */
    else {

      fprintf (stderr,"An error occured while trying to open %s - %s\n",
	       dir,strerror(errno));
      return FALSE;
    }
  }

  /* The directory is open. Let's verify that it's empty. There should
     be only two entries "." and ".." */
  i=0;
  while((entry = readdir(temp))) {
    if(i>1){
      fprintf (stderr, NONEMPTYDIR_ERROR_MSG);
      return FALSE;
    }
    i++;
  }
  closedir(temp);
  return TRUE;
}


int openAuditFile(struct foremostState* state){

  time_t now = time(NULL);
  char* timestring = ctime(&now);
  char fn[MAX_STRING_LENGTH];

  if (!outputDirectoryOK(state->outputdirectory)) 
    return FOREMOST_ERROR_FILE_OPEN;

  snprintf(fn,MAX_STRING_LENGTH,"%s/audit.txt",
	   state->outputdirectory);
  
  if(!(state->auditFile = fopen(fn,"w"))) {    
    fprintf(stderr,"Couldn't open %s -- %s\n",fn,strerror(errno));
    return FOREMOST_ERROR_FILE_OPEN;
  }

  fprintf (state->auditFile,
	   "\nForemost version %s audit file\n"
	   "Started at %sCommand line:\n%s\n\n"
	   "Output directory: %s\n"
	   "Configuration file: %s\n",
	   FOREMOST_VERSION, timestring, state->invocation,
	   state->outputdirectory,state->conffile);
  
  if (state->modeQuick){
    fprintf(state->auditFile,"Quick mode enabled.\n");
  }
  
  return FOREMOST_OK;
}



int closeFile(FILE* f){

  time_t now = time(NULL);
  char* timestring = ctime(&now);

  fprintf (f, "\n\nCompleted at %s", timestring);

  if(fclose(f)){
    return FOREMOST_ERROR_FILE_CLOSE;
  }

  return FOREMOST_OK;
}



/* writeToDisk(char*                 suffix of file to be written,
               struct foremostState* program state
               struct CharBucket*    text to be written)
	       unsigned long long    offset in the image file
	                               from we should start writing
   
   Writes text, a file of format suffix, to the disk, in order.
   Returns FOREMOST_OK on success.                             */

int writeToDisk(char* suffix, 
		int length,
		struct foremostState* state, 
		struct CharBucket* text,
		unsigned long long offset){

  char  fn[MAX_STRING_LENGTH], sectorHeader, chopped;
  FILE* f;
  long  byteswritten = 0;
  int   noSuffixThisTime = FALSE;


  /* RBF - This is a kludgy way to check if we have to ignore
     RBF - the suffix. This MUST be fixed. */
  if (state->modeNoSuffix || suffix[0] == FOREMOST_NOEXTENSION) {
    noSuffixThisTime = TRUE;
    snprintf(fn,MAX_STRING_LENGTH,"%s/%08d",
             state->outputdirectory,state->fileswritten);
  } else {
    snprintf(fn,MAX_STRING_LENGTH,"%s/%08d.%s",
             state->outputdirectory,state->fileswritten,suffix);
  }


  if (offset % FOREMOST_BLOCK_SIZE != 0) 
    sectorHeader = 'X';
  else 
    sectorHeader = ' ';

  /* RBF - The CHOP field must be documented! */
  if(text->length == length)
    chopped = 'X';
  else
    chopped = ' ';


  /* Originally we wrote the full path to the audit file, but that leads
     to problems when the path is long. Because the path is printed at 
     the top of the audit file, we don't have to repeat it every time. (JK) */

  if (noSuffixThisTime) {
    fprintf(state->auditFile,
            "%08d       %14Ld   %c    %c    %7ld     %s\n",
            state->fileswritten,
            offset,sectorHeader,chopped,
            text->length,basename(state->imagefile));
  }else{
    fprintf(state->auditFile,"%08d.%s    ",state->fileswritten,suffix);
    fprintf(state->auditFile,"%13Ld   ",offset);
    fprintf(state->auditFile,"%c    ",sectorHeader);
    fprintf(state->auditFile,"%c    ",chopped);
    fprintf(state->auditFile,"%7ld     ",text->length);
    fprintf(state->auditFile,"%s\n",basename(state->imagefile));
  }
  if(!(f = fopen(fn,"w"))){
    fprintf (stderr,           "Error opening file: %s -- %s\n", 
	     fn, strerror(errno));
    fprintf (state->auditFile, "Error opening file: %s -- %s\n", 
	     fn, strerror(errno));
    return FOREMOST_ERROR_FILE_OPEN;
  }
  
  if ((byteswritten = fwrite(text->str,
			     sizeof(char),
			     text->length,f)) != text->length) {

    fprintf(stderr,"Error writing to file: %s -- %s\n",
	    fn, strerror(ferror(f)));
    fprintf(state->auditFile,"Error writing to file: %s -- %s\n",
	    fn, strerror(ferror(f)));
    return FOREMOST_ERROR_FILE_WRITE;
  }
  if(fclose(f)){
    fprintf(stderr,           "Error closing file: %s -- %s\n\n",
	    fn,strerror(ferror(f)));
    fprintf(state->auditFile, "Error closing file: %s -- %s\n\n",
	    fn,strerror(ferror(f)));
    return FOREMOST_ERROR_FILE_WRITE;
  }
    
  if (state->modeVerbose) {
    fprintf (stdout,"Wrote file %s -- Success\n", basename(fn));
  }


  /* We only say that we wrote the file if we were successful. This 
     statement was originally immediately after the snprintf for the
     filename. Because we use the variable fileswritten elsewhere in
     this function I've moved it down here.   (JK) */
  state->fileswritten++;
  return FOREMOST_OK;
}



/* Return the size, in bytes of an open file stream. On error, return -1 */
unsigned long long measureOpenFile(FILE *f){
  
  /* If numsectors and total are not initialized, you will get wildly
     innacurate results at run time */
  unsigned long long total = 0, original = ftello(f);

#ifdef __LINUX
  int descriptor = 0;
  struct stat *info;
  unsigned long long numsectors = 0;
#endif
    
  if ((fseeko(f,0,SEEK_END)))
    return -1;
  total = ftello(f);
  if ((fseeko(f,original,SEEK_SET)))
    return -1;

#ifdef __LINUX

  /* Block devices, like /dev/hda, don't return a normal filesize.
     If we are working with a block device, we have to ask the operating
     system to tell us the true size of the device.

     The following only works on Linux as far as I know. If you know
     how to port this code to another operating system, please contact
     the current maintainer of this program! */

  descriptor = fileno(f);
  info = (struct stat*)malloc(sizeof(struct stat));

  /* I'd prefer not to use fstat as it will follow symbolic links. We don't
     follow symbolic links. That being said, all symbolic links *should*
     have been caught before we got here. */

  fstat(descriptor,info);
  if (S_ISBLK(info->st_mode)) {
    if (ioctl(descriptor, BLKGETSIZE, &numsectors)){
#if defined(__DEBUG)
      perror("BLKGETSIZE failed");
#endif
    } else {

      /* We're going to assume that this device has 512 byte sectors.
         Eventually we should add a way to get the real number of sectors
         from the device. */
      total = numsectors * 512;
    }
  }

  free(info);

#endif   /* #ifdef __LINUX */

  return (total - original);
}
