/*

    File: photorec.c

    Copyright (C) 1998-2007 Christophe GRENIER <grenier@cgsecurity.org>
  
    This software 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 of the License, 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 the Free Software Foundation, Inc., 51
    Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#include <stdarg.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>	/* unlink, ftruncate */
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#ifdef HAVE_TIME_H
#include <time.h>
#endif
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
#include <ctype.h>      /* tolower */
#ifdef HAVE_LOCALE_H
#include <locale.h>	/* setlocale */
#endif
#ifdef HAVE_SIGNAL_H
#include <signal.h>
#endif
#ifdef HAVE_SYS_WAIT_H
#include <sys/wait.h>
#endif
#include <errno.h>
#include "types.h"
#include "common.h"
#include "intrf.h"
#include "godmode.h"
#include "fnctdsk.h"
#include "intrfn.h"
#include "dir.h"
#include "filegen.h"
#include "photorec.h"
#include "fat.h"
#include "hdcache.h"
#include "fatp.h"
#include "ntfsp.h"
#include "ewf.h"
#include "log.h"
#include "phrecn.h"

/* #define DEBUG_FILE_FINISH */
/* #define DEBUG_UPDATE_SEARCH_SPACE */
/* #define DEBUG_FREE */

extern const arch_fnct_t arch_i386;
extern const arch_fnct_t arch_mac;
extern const arch_fnct_t arch_none;
extern const arch_fnct_t arch_sun;
extern const arch_fnct_t arch_xbox;

extern const file_hint_t file_hint_7z;
extern const file_hint_t file_hint_a;
extern const file_hint_t file_hint_accdb;
extern const file_hint_t file_hint_ace;
extern const file_hint_t file_hint_addressbook;
extern const file_hint_t file_hint_aif;
extern const file_hint_t file_hint_all;
extern const file_hint_t file_hint_asf;
extern const file_hint_t file_hint_au;
extern const file_hint_t file_hint_bkf;
extern const file_hint_t file_hint_blend;
extern const file_hint_t file_hint_bmp;
extern const file_hint_t file_hint_bz2;
extern const file_hint_t file_hint_cab;
extern const file_hint_t file_hint_cam;
extern const file_hint_t file_hint_crw;
extern const file_hint_t file_hint_ctg;
extern const file_hint_t file_hint_cwk;
extern const file_hint_t file_hint_dat;
extern const file_hint_t file_hint_dbf;
extern const file_hint_t file_hint_dir;
extern const file_hint_t file_hint_djv;
extern const file_hint_t file_hint_doc;
extern const file_hint_t file_hint_dsc;
extern const file_hint_t file_hint_dta;
extern const file_hint_t file_hint_dump;
extern const file_hint_t file_hint_dv;
extern const file_hint_t file_hint_elf;
extern const file_hint_t file_hint_evt;
extern const file_hint_t file_hint_exe;
extern const file_hint_t file_hint_ext2_sb;
extern const file_hint_t file_hint_fh10;
extern const file_hint_t file_hint_fh5;
extern const file_hint_t file_hint_flac;
extern const file_hint_t file_hint_flv;
extern const file_hint_t file_hint_fs;
extern const file_hint_t file_hint_gif;
extern const file_hint_t file_hint_gz;
extern const file_hint_t file_hint_imb;
extern const file_hint_t file_hint_indd;
extern const file_hint_t file_hint_itunes;
extern const file_hint_t file_hint_jpg;
extern const file_hint_t file_hint_max;
extern const file_hint_t file_hint_mdb;
extern const file_hint_t file_hint_mdf;
extern const file_hint_t file_hint_mid;
extern const file_hint_t file_hint_mkv;
extern const file_hint_t file_hint_mov;
extern const file_hint_t file_hint_mp3;
extern const file_hint_t file_hint_mpg;
extern const file_hint_t file_hint_mrw;
extern const file_hint_t file_hint_mus;
extern const file_hint_t file_hint_mysql;
extern const file_hint_t file_hint_njx;
extern const file_hint_t file_hint_ogg;
extern const file_hint_t file_hint_orf;
extern const file_hint_t file_hint_pap;
extern const file_hint_t file_hint_pcx;
extern const file_hint_t file_hint_pdf;
extern const file_hint_t file_hint_png;
extern const file_hint_t file_hint_prc;
extern const file_hint_t file_hint_ps;
extern const file_hint_t file_hint_psd;
extern const file_hint_t file_hint_pst;
extern const file_hint_t file_hint_qbb;
extern const file_hint_t file_hint_qdf;
extern const file_hint_t file_hint_qxd;
extern const file_hint_t file_hint_ra;
extern const file_hint_t file_hint_raf;
extern const file_hint_t file_hint_rar;
extern const file_hint_t file_hint_raw;
extern const file_hint_t file_hint_rdc;
extern const file_hint_t file_hint_reg;
extern const file_hint_t file_hint_riff;
extern const file_hint_t file_hint_rm;
extern const file_hint_t file_hint_rns;
extern const file_hint_t file_hint_rpm;
extern const file_hint_t file_hint_sit;
extern const file_hint_t file_hint_spss;
extern const file_hint_t file_hint_stuffit;
extern const file_hint_t file_hint_swf;
extern const file_hint_t file_hint_tar;
extern const file_hint_t file_hint_tiff;
extern const file_hint_t file_hint_fasttxt;
extern const file_hint_t file_hint_txt;
extern const file_hint_t file_hint_wmf;
extern const file_hint_t file_hint_x3f;
extern const file_hint_t file_hint_xcf;
extern const file_hint_t file_hint_xm;
extern const file_hint_t file_hint_zip;

#ifdef DEAD_CODE
static void get_prev_sector(alloc_data_t *list_search_space, alloc_data_t **current_search_space, uint64_t *offset, const unsigned int blocksize);
static void get_prev_header(alloc_data_t *list_search_space, alloc_data_t **current_search_space, uint64_t *offset);
#endif

static alloc_data_t *update_search_space(const file_recovery_t *file_recovery, alloc_data_t *list_search_space, alloc_data_t **new_current_search_space, uint64_t *offset, const unsigned int blocksize);
static alloc_data_t *update_search_space_aux(alloc_data_t *list_search_space, uint64_t start, uint64_t end, alloc_data_t **new_current_search_space, uint64_t *offset);

static void list_free_add(const file_recovery_t *file_recovery, alloc_data_t *list_search_space);

#ifdef HAVE_SIGACTION
void sighup_hdlr(int shup)
{
  log_critical("SIGHUP detected! PhotoRec has been killed.\n");
  log_close();
  exit(1);
}
#endif

void list_space_used(const file_recovery_t *file_recovery, const unsigned int sector_size)
{
  const alloc_list_t *element;
  uint64_t file_size=0;
  uint64_t file_size_on_disk=0;
  if(file_recovery->filename==NULL)
    return;
  log_info("%s\t",file_recovery->filename);
  for(element=&file_recovery->location;element!=NULL;element=element->next)
  {
    file_size_on_disk+=(element->end-element->start+1);
    if(element->data>0)
    {
      // log_trace(" %llu-%llu", (unsigned long long)(element->start), (unsigned long long)(element->end));
      log_info(" %lu-%lu", (unsigned long)(element->start/sector_size), (unsigned long)(element->end/sector_size));
      file_size+=(element->end-element->start+1);
    }
    else
    {
      log_info(" (%lu-%lu)", (unsigned long)(element->start/sector_size), (unsigned long)(element->end/sector_size));
    }
  }
  log_info("\n");
  /*
  log_trace("list file_size %lu, file_size_on_disk %lu\n",
      (unsigned long)file_size, (unsigned long)file_size_on_disk);
  log_trace("file_size %lu, file_size_on_disk %lu\n",
      (unsigned long)file_recovery->file_size, (unsigned long)file_recovery->file_size_on_disk);
   */
}

static void list_free_add(const file_recovery_t *file_recovery, alloc_data_t *list_search_space)
{
  struct list_head *search_walker = NULL;
#ifdef DEBUG_FREE
  log_trace("list_free_add %lu\n",(long unsigned)(file_recovery->location.start/512));
#endif
  list_for_each(search_walker, &list_search_space->list)
  {
    alloc_data_t *current_search_space;
    current_search_space=list_entry(search_walker, alloc_data_t, list);
    if(current_search_space->start < file_recovery->location.start && file_recovery->location.start < current_search_space->end)
    {
      alloc_data_t *new_free_space;
      new_free_space=(alloc_data_t*)MALLOC(sizeof(*new_free_space));
      new_free_space->start=file_recovery->location.start;
      new_free_space->end=current_search_space->end;
      new_free_space->file_stat=NULL;
      current_search_space->end=file_recovery->location.start-1;
      list_add(&new_free_space->list, search_walker);
    }
    if(current_search_space->start==file_recovery->location.start)
    {
      current_search_space->file_stat=file_recovery->file_stat;
      return ;
    }
  }
}

static alloc_data_t *update_search_space(const file_recovery_t *file_recovery, alloc_data_t *list_search_space, alloc_data_t **new_current_search_space, uint64_t *offset, const unsigned int blocksize)
{
  const alloc_list_t *element;
  struct list_head *search_walker = NULL;
#ifdef DEBUG_UPDATE_SEARCH_SPACE
  log_trace("update_search_space\n");
  list_for_each(search_walker, &list_search_space->list)
  {
    alloc_data_t *cur_free_space;
    cur_free_space=list_entry(search_walker, alloc_data_t, list);
    log_trace(" %llu-%llu",(long long unsigned)(cur_free_space->start/512),
	(long long unsigned)(cur_free_space->end/512));
  }
  log_trace("\n");
#endif
  list_for_each(search_walker, &list_search_space->list)
  {
    alloc_data_t *current_search_space;
    current_search_space=list_entry(search_walker, alloc_data_t, list);
    if(current_search_space->start <= file_recovery->location.start &&
	file_recovery->location.start <= current_search_space->end)
    {
      *offset=file_recovery->location.start;
      *new_current_search_space=current_search_space;
      for(element=&file_recovery->location;element!=NULL;element=element->next)
      {
	uint64_t end=(element->end-(element->start%blocksize)+blocksize-1+1)/blocksize*blocksize+(element->start%blocksize)-1;
	list_search_space=update_search_space_aux(list_search_space, element->start, end, new_current_search_space, offset);
      }
      return list_search_space;
    }
  }
  return list_search_space;
}

alloc_data_t *del_search_space(alloc_data_t *list_search_space, uint64_t start, uint64_t end)
{
  return update_search_space_aux(list_search_space, start, end, NULL, NULL);
}

static alloc_data_t *update_search_space_aux(alloc_data_t *list_search_space, uint64_t start, uint64_t end, alloc_data_t **new_current_search_space, uint64_t *offset)
{
  struct list_head *search_walker = NULL;
#ifdef DEBUG_UPDATE_SEARCH_SPACE
    log_trace("update_search_space_aux offset=%llu remove [%llu-%llu]\n",
	(long long unsigned)((*offset)/512),
	(unsigned long long)(start/512),
	(unsigned long long)(end/512));
#endif
  if(start >= end)
    return list_search_space;
  list_for_each(search_walker, &list_search_space->list)
  {
    alloc_data_t *current_search_space;
    current_search_space=list_entry(search_walker, alloc_data_t, list);
#ifdef DEBUG_UPDATE_SEARCH_SPACE
    log_trace("update_search_space_aux offset=%llu remove [%llu-%llu] in [%llu-%llu]\n",
	(long long unsigned)((*offset)/512),
	(unsigned long long)(start/512),
	(unsigned long long)(end/512),
	(unsigned long long)(current_search_space->start/512),
	(unsigned long long)(current_search_space->end/512));
#endif
    if(current_search_space->start==start)
    {
      if(end<current_search_space->end)
      { /* current_search_space->start==start end<current_search_space->end */
	if(offset!=NULL && new_current_search_space!=NULL &&
	    current_search_space->start<=*offset && *offset<=end)
	{
	  *new_current_search_space=current_search_space;
	  *offset=end+1;
	}
	current_search_space->start=end+1;
	return list_search_space;
      }
      /* current_search_space->start==start current_search_space->end<=end */
      start=current_search_space->end+1;
      if(list_search_space==current_search_space)
	list_search_space=list_entry(current_search_space->list.next, alloc_data_t, list);
      if(offset!=NULL && new_current_search_space!=NULL &&
	  current_search_space->start<=*offset && *offset<=current_search_space->end)
      {
	*new_current_search_space=list_entry(current_search_space->list.next, alloc_data_t, list);
	*offset=(*new_current_search_space)->start;
      }
      list_del(search_walker);
      free(current_search_space);
      return update_search_space_aux(list_search_space, start,end, new_current_search_space, offset);
    }
    if(current_search_space->end==end)
    {
#ifdef DEBUG_UPDATE_SEARCH_SPACE
      log_trace("current_search_space->end==end\n");
#endif
      if(current_search_space->start<start)
      { /* current_search_space->start<start current_search_space->end==end */
	if(offset!=NULL && new_current_search_space!=NULL &&
	  start<=*offset && *offset<=current_search_space->end)
	{
	  *new_current_search_space=list_entry(current_search_space->list.next, alloc_data_t, list);
	  *offset=(*new_current_search_space)->start;
	}
	current_search_space->end=start-1;
	return list_search_space;
      }
      /* start<=current_search_space->start current_search_space->end==end */
      end=current_search_space->start-1;
      if(list_search_space==current_search_space)
	list_search_space=list_entry(current_search_space->list.next, alloc_data_t, list);
      if(offset!=NULL && new_current_search_space!=NULL &&
	  current_search_space->start<=*offset && *offset<=current_search_space->end)
      {
	*new_current_search_space=list_entry(current_search_space->list.next, alloc_data_t, list);
	*offset=(*new_current_search_space)->start;
      }
      list_del(search_walker);
      free(current_search_space);
      return update_search_space_aux(list_search_space, start,end, new_current_search_space, offset);
    }
    if(start < current_search_space->start && current_search_space->start <= end)
    {
      list_search_space=update_search_space_aux(list_search_space,
	  start, current_search_space->start-1,  new_current_search_space, offset);
      return update_search_space_aux(list_search_space, current_search_space->start, end, new_current_search_space, offset);
    }
    if(start <= current_search_space->end && current_search_space->end < end)
    {
      list_search_space=update_search_space_aux(list_search_space, start, current_search_space->end, new_current_search_space, offset);
      return update_search_space_aux(list_search_space, current_search_space->end+1, end, new_current_search_space, offset);
    }
    if(current_search_space->start < start && end < current_search_space->end)
    {
      alloc_data_t *new_free_space;
      new_free_space=(alloc_data_t*)MALLOC(sizeof(*new_free_space));
      new_free_space->start=start;
      new_free_space->end=current_search_space->end;
      new_free_space->file_stat=NULL;
      current_search_space->end=start-1;
      list_add(&new_free_space->list,search_walker);
      if(offset!=NULL && new_current_search_space!=NULL &&
	  new_free_space->start<=*offset && *offset<=new_free_space->end)
      {
	*new_current_search_space=new_free_space;
      }
      return update_search_space_aux(list_search_space, start, end, new_current_search_space, offset);
    }
  }
  return list_search_space;
}

alloc_data_t *init_search_space(const partition_t *partition, const disk_t *disk_car)
{
  alloc_data_t *list_search_space;
  list_search_space=(alloc_data_t*)MALLOC(sizeof(*list_search_space));
  list_search_space->start=partition->part_offset;
  list_search_space->end=partition->part_offset+partition->part_size-1;
  if(list_search_space->end > disk_car->disk_size-1)
    list_search_space->end = disk_car->disk_size-1;
  if(list_search_space->end > disk_car->disk_real_size-1)
    list_search_space->end = disk_car->disk_real_size-1;
  list_search_space->file_stat=NULL;
  list_search_space->list.prev=&list_search_space->list;
  list_search_space->list.next=&list_search_space->list;
  return list_search_space;
}

void free_list_search_space(alloc_data_t *list_search_space)
{
  struct list_head *search_walker = NULL;
  struct list_head *search_walker_next = NULL;
  list_for_each_safe(search_walker,search_walker_next,&list_search_space->list)
  {
    alloc_data_t *current_search_space;
    current_search_space=list_entry(search_walker, alloc_data_t, list);
    list_del(search_walker);
    free(current_search_space);
  }
}

void reset_file_recovery(file_recovery_t *file_recovery)
{
  file_recovery->filename[0]='\0';
  file_recovery->file_stat=NULL;
  file_recovery->handle=NULL;
  file_recovery->file_size=0;
  file_recovery->file_size_on_disk=0;
  file_recovery->location.prev=NULL;
  file_recovery->location.next=NULL;
  file_recovery->location.start=0;
  file_recovery->location.end=0;
  file_recovery->location.data=0;
  file_recovery->extension=NULL;
  file_recovery->min_filesize=0;
  file_recovery->calculated_file_size=0;
  file_recovery->data_check=NULL;
  file_recovery->file_check=NULL;
  file_recovery->offset_error=0;
}

unsigned int photorec_mkdir(const char *recup_dir, const unsigned int initial_dir_num)
{
  char working_recup_dir[2048];
  int dir_ok=0;
  int dir_num=initial_dir_num;
#ifdef DJGPP
  int i=0;
#endif
  do
  {
    snprintf(working_recup_dir,sizeof(working_recup_dir)-1,"%s.%d",recup_dir,dir_num);
#ifdef HAVE_MKDIR
#ifdef __MINGW32__
    if(mkdir(working_recup_dir)!=0 && errno==EEXIST)
#else
      if(mkdir(working_recup_dir, 0700)!=0 && errno==EEXIST)
#endif
#else
#error You need a mkdir function!
#endif
      {
	dir_num++;
      }
      else
      {
	dir_ok=1;
      }
#ifdef DJGPP
  /* Avoid endless loop in Dos version of Photorec after 999 directories if working with short name */
    i++;
    if(dir_ok==0 && i==1000)
    {
      dir_num=initial_dir_num;
      dir_ok=1;
    }
#endif
  } while(dir_ok==0);
  return dir_num;
}

#ifdef DEAD_CODE
static void get_prev_sector(alloc_data_t *list_search_space, alloc_data_t **current_search_space, uint64_t *offset, const unsigned int blocksize)
{
  /*
  log_trace("get_prev_sector %lu (%lu-%lu)\n", (long unsigned int)((*offset)/blocksize),
      (unsigned long int)((*current_search_space)->start/blocksize),
      (unsigned long int)((*current_search_space)->end/blocksize));
  */
  if((*current_search_space) == list_search_space)
    return ;
  if(! ((*current_search_space)->start <= *offset && (*offset)<=(*current_search_space)->end))
  {
    log_critical("BUG: get_prev_sector stop everything\n");
    log_close();
    exit(1);
  }
  if((*offset)-blocksize >= (*current_search_space)->start)
    *offset-=blocksize;
  else
    get_prev_header(list_search_space, current_search_space, offset);
}

static void get_prev_header(alloc_data_t *list_search_space, alloc_data_t **current_search_space, uint64_t *offset)
{
  if((*current_search_space) != list_search_space)
    *current_search_space=list_entry((*current_search_space)->list.prev, alloc_data_t, list);
  *offset=(*current_search_space)->start;
}
#endif

int get_prev_file_header(alloc_data_t *list_search_space, alloc_data_t **current_search_space, uint64_t *offset)
{
  int nbr;
  alloc_data_t *file_space=*current_search_space;
  for(nbr=0;nbr<5;nbr++)
  {
    file_space=list_entry(file_space->list.prev, alloc_data_t, list);
    if(file_space==list_search_space)
      return -1;
    if(file_space->file_stat!=NULL)
    {
      *current_search_space=file_space;
      *offset=file_space->start;
      return 0;
    }
  }
  return -1;
}

void forget(alloc_data_t *list_search_space, alloc_data_t *current_search_space)
{
  struct list_head *search_walker = NULL;
  struct list_head *prev= NULL;
  int nbr=0;
  if(current_search_space==list_search_space)
    return ;
  for(search_walker=&current_search_space->list;
      search_walker!=&list_search_space->list;
      search_walker=prev)
  {
    prev=search_walker->prev;
    if(nbr>10000)
    {
      alloc_data_t *tmp;
      tmp=list_entry(search_walker, alloc_data_t, list);
      list_del(&tmp->list);
      free(tmp);
    }
    else
      nbr++;
  }
}

void list_cluster_free(list_cluster_t *list_cluster)
{
  struct list_head *dir_walker = NULL;
  struct list_head *dir_walker_next = NULL;
  list_for_each_safe(dir_walker,dir_walker_next,&list_cluster->list)
  {
    list_cluster_t *info;
    info=list_entry(dir_walker, list_cluster_t, list);
    delete_list_file(info->dir_list);
    list_del(dir_walker);
    free(info);
  }
}

unsigned int remove_used_space(disk_t *disk_car, const partition_t *partition, alloc_data_t *list_search_space)
{
  if(partition->upart_type==UP_FAT16 || partition->upart_type==UP_FAT32)
    return fat_remove_used_space(disk_car, partition, list_search_space);
#ifdef HAVE_LIBNTFS
  else if(partition->upart_type==UP_NTFS)
    return ntfs_remove_used_space(disk_car, partition, list_search_space);
#endif
  return 0;
}

void update_stats(file_stat_t *file_stats, alloc_data_t *list_search_space)
{
  struct list_head *search_walker = NULL;
  int i;
  /* Reset */
  for(i=0;file_stats[i].file_hint!=NULL;i++)
    file_stats[i].not_recovered=0;
  /* Update */
  list_for_each(search_walker, &list_search_space->list)
  {
    alloc_data_t *current_search_space;
    current_search_space=list_entry(search_walker, alloc_data_t, list);
    if(current_search_space->file_stat!=NULL)
    {
      current_search_space->file_stat->not_recovered++;
    }
  }
}

void write_stats_log(const file_stat_t *file_stats)
{
  unsigned int file_nbr=0;
  unsigned int i;
  unsigned int nbr;
  file_stat_t *new_file_stats;
  for(i=0;file_stats[i].file_hint!=NULL;i++);
  if(i==0)
    return ;
  nbr=i;
  new_file_stats=(file_stat_t*)MALLOC(nbr*sizeof(file_stat_t));
  memcpy(new_file_stats, file_stats, nbr*sizeof(file_stat_t));
  qsort(new_file_stats, nbr, sizeof(file_stat_t), sorfile_stat_ts);
  for(i=0;i<nbr;i++)
  {
    if(new_file_stats[i].recovered+new_file_stats[i].not_recovered>0)
    {
      file_nbr+=new_file_stats[i].recovered;
      log_info("%s: %u/%u recovered\n",
	  (new_file_stats[i].file_hint->extension!=NULL?
	   new_file_stats[i].file_hint->extension:""),
	  new_file_stats[i].recovered, new_file_stats[i].recovered+new_file_stats[i].not_recovered);
    }
  }
  free(new_file_stats);
  if(file_nbr>1)
  {
    log_info("Total: %u files found\n\n",file_nbr);
  }
  else
  {
    log_info("Total: %u file found\n\n",file_nbr);
  }
}

int sorfile_stat_ts(const void *p1, const void *p2)
{
  const file_stat_t *f1=(const file_stat_t *)p1;
  const file_stat_t *f2=(const file_stat_t *)p2;
  /* bigest to lowest */
  if(f1->recovered < f2->recovered)
    return 1;
  if(f1->recovered > f2->recovered)
    return -1;
  return 0;
}

void write_stats_stdout(const file_stat_t *file_stats)
{
  int i;
  unsigned int file_nbr=0;
  for(i=0;file_stats[i].file_hint!=NULL;i++)
  {
    if(file_stats[i].recovered+file_stats[i].not_recovered>0)
    {
      file_nbr+=file_stats[i].recovered;
      printf("%s: %u/%u recovered\n",
	  (file_stats[i].file_hint->extension!=NULL?
	   file_stats[i].file_hint->extension:""),
	  file_stats[i].recovered, file_stats[i].recovered+file_stats[i].not_recovered);
    }
  }
  if(file_nbr>1)
  {
    printf("Total: %u files found\n\n",file_nbr);
  }
  else
  {
    printf("Total: %u file found\n\n",file_nbr);
  }
}

partition_t *new_whole_disk(const disk_t *disk_car)
{
  partition_t *fake_partition;
  fake_partition=partition_new();
  fake_partition->part_offset=0;
  fake_partition->part_size=disk_car->disk_size;
  strncpy(fake_partition->name,"Whole disk",sizeof(fake_partition->name)-1);
  return fake_partition;
}


typedef struct info_cluster_offset cluster_offset_t;

struct info_cluster_offset
{
  unsigned int cluster_size;
  unsigned long int offset;
  unsigned int nbr;
};

unsigned int find_blocksize_cluster(list_cluster_t *list_cluster, const unsigned int default_blocksize, uint64_t *offset)
{
  unsigned int cluster_size;
  unsigned int cluster_size_best=default_blocksize;
  unsigned int nbr_max=0;
  for(cluster_size=default_blocksize;cluster_size<=128*512;cluster_size*=2)
  {
    struct list_head *dir_walker = NULL;
    cluster_offset_t cluster_offset[1000];
    unsigned int nbr_sol=0;
    list_for_each(dir_walker,&list_cluster->list)
    {
      list_cluster_t *info;
      info=list_entry(dir_walker, list_cluster_t, list);
      if(info->cluster>=2 && (info->cluster-2)*cluster_size<info->offset)
      {
	unsigned int sol_cur;
	unsigned int found=0;
	unsigned int offset_tmp;
	offset_tmp=info->offset-(info->cluster-2)*cluster_size;
	for(sol_cur=0;sol_cur<nbr_sol && !found;sol_cur++)
	{
	  if(cluster_offset[sol_cur].offset==offset_tmp)
	  {
	    cluster_offset[sol_cur].nbr++;
	    found=1;
	  }
	}
	if(!found && nbr_sol<1000)
	{
	  cluster_offset[nbr_sol].offset=offset_tmp;
	  cluster_offset[nbr_sol].nbr=1;
	  nbr_sol++;
	}
      }
    }
    {
      unsigned int sol_cur;
      for(sol_cur=0;sol_cur<nbr_sol;sol_cur++)
	if(nbr_max<cluster_offset[sol_cur].nbr)
	{
	  nbr_max=cluster_offset[sol_cur].nbr;
	  cluster_size_best=cluster_size;
	  *offset=cluster_offset[sol_cur].offset;
	}
    }
  }
  return cluster_size_best;
}

unsigned int find_blocksize(alloc_data_t *list_file, const unsigned int default_blocksize, uint64_t *offset)
{
  int blocksize_ok=0;
  unsigned int blocksize;
  *offset=0;
  if(list_empty(&list_file->list))
    return default_blocksize;
  for(blocksize=128*512;blocksize>=default_blocksize && blocksize_ok==0;blocksize=blocksize>>1)
  {
    struct list_head *search_walker = NULL;
    blocksize_ok=1;
    {
      alloc_data_t *tmp;
      tmp=list_entry(list_file->list.next, alloc_data_t, list);
      *offset=tmp->start%blocksize;
    }
    for(search_walker=list_file->list.next;
      search_walker!=&list_file->list && blocksize_ok!=0;
      search_walker=search_walker->next)
    {
      alloc_data_t *current_file;
      current_file=list_entry(search_walker, alloc_data_t, list);
      if(current_file->start%blocksize!=*offset)
	blocksize_ok=0;
    }
  }
  blocksize=blocksize<<1;
  return blocksize;
}

alloc_data_t * update_blocksize(unsigned int blocksize, alloc_data_t *list_search_space, const uint64_t offset)
{
  struct list_head *search_walker = NULL;
  struct list_head *search_walker_next = NULL;
  list_for_each_safe(search_walker,search_walker_next,&list_search_space->list)
  {
    alloc_data_t *current_search_space;
    current_search_space=list_entry(search_walker, alloc_data_t, list);
    current_search_space->start=(current_search_space->start-offset%blocksize+blocksize-1)/blocksize*blocksize+offset%blocksize;
    if(current_search_space->start>current_search_space->end)
    {
      list_del(search_walker);
      if(list_search_space==current_search_space)
	list_search_space=list_entry(search_walker_next, alloc_data_t, list);
      free(current_search_space);
    }
  }
  return list_search_space;
}

int main( int argc, char **argv )
{
  int i;
  int help=0, create_log=0, debug=0;
  int run_setlocale=1;
  int testdisk_mode=TESTDISK_O_RDONLY|TESTDISK_O_READAHEAD_32K;
  const char *recup_dir=NULL;
  list_disk_t *list_disk=NULL;
  list_disk_t *element_disk;
  char *cmd_device=NULL;
  char *cmd_run=NULL;
#ifdef TARGET_SOLARIS
  const arch_fnct_t *arch=&arch_sun;
#elif defined __APPLE__
  const arch_fnct_t *arch=&arch_mac;
#else
  const arch_fnct_t *arch=&arch_i386;
#endif
#ifdef HAVE_SIGACTION
  struct sigaction action, old_action;
#endif
  file_enable_t list_file_enable[]=
  {
    { .enable=1, .file_hint=&file_hint_7z   },
    { .enable=1, .file_hint=&file_hint_a    },
    { .enable=1, .file_hint=&file_hint_accdb},
    { .enable=1, .file_hint=&file_hint_ace  },
    { .enable=1, .file_hint=&file_hint_addressbook},
    { .enable=1, .file_hint=&file_hint_aif  },
    { .enable=1, .file_hint=&file_hint_all  },
    { .enable=1, .file_hint=&file_hint_asf  },
    { .enable=1, .file_hint=&file_hint_au   },
    { .enable=1, .file_hint=&file_hint_bkf  },
    { .enable=1, .file_hint=&file_hint_blend },
    { .enable=1, .file_hint=&file_hint_bmp  },
    { .enable=1, .file_hint=&file_hint_bz2  },
    { .enable=1, .file_hint=&file_hint_cab  },
    { .enable=1, .file_hint=&file_hint_cam  },
    { .enable=1, .file_hint=&file_hint_crw  },
    { .enable=1, .file_hint=&file_hint_ctg  },
    { .enable=1, .file_hint=&file_hint_cwk  },
    { .enable=1, .file_hint=&file_hint_dat  },
    { .enable=0, .file_hint=&file_hint_dbf  },
    { .enable=1, .file_hint=&file_hint_dir  },
    { .enable=1, .file_hint=&file_hint_djv  },
    { .enable=1, .file_hint=&file_hint_doc  },
    { .enable=1, .file_hint=&file_hint_dsc  },
    { .enable=0, .file_hint=&file_hint_dta  },
    { .enable=1, .file_hint=&file_hint_dump },
    { .enable=1, .file_hint=&file_hint_dv   },
    { .enable=1, .file_hint=&file_hint_elf  },
    { .enable=1, .file_hint=&file_hint_evt  },
    { .enable=1, .file_hint=&file_hint_exe  },
    { .enable=1, .file_hint=&file_hint_ext2_sb },
    { .enable=1, .file_hint=&file_hint_fh10  },
    { .enable=1, .file_hint=&file_hint_fh5  },
    { .enable=1, .file_hint=&file_hint_flac },
    { .enable=1, .file_hint=&file_hint_flv  },
    { .enable=1, .file_hint=&file_hint_fs   },
    { .enable=1, .file_hint=&file_hint_gif  },
    { .enable=1, .file_hint=&file_hint_gz   },
    { .enable=1, .file_hint=&file_hint_imb  },
    { .enable=1, .file_hint=&file_hint_indd  },
    { .enable=1, .file_hint=&file_hint_itunes  },
    { .enable=1, .file_hint=&file_hint_jpg  },
    { .enable=1, .file_hint=&file_hint_max  },
    { .enable=1, .file_hint=&file_hint_mdb  },
    { .enable=1, .file_hint=&file_hint_mdf  },
    { .enable=1, .file_hint=&file_hint_mid  },
    { .enable=1, .file_hint=&file_hint_mkv  },
    { .enable=1, .file_hint=&file_hint_mov  },
    { .enable=1, .file_hint=&file_hint_mp3  },
    { .enable=1, .file_hint=&file_hint_mpg  },
    { .enable=1, .file_hint=&file_hint_mrw  },
    { .enable=1, .file_hint=&file_hint_mus  },
    { .enable=1, .file_hint=&file_hint_mysql },
    { .enable=1, .file_hint=&file_hint_njx  },
    { .enable=1, .file_hint=&file_hint_ogg  },
    { .enable=1, .file_hint=&file_hint_orf  },
    { .enable=1, .file_hint=&file_hint_pap  },
    { .enable=1, .file_hint=&file_hint_pcx  },
    { .enable=1, .file_hint=&file_hint_pdf  },
    { .enable=1, .file_hint=&file_hint_png  },
    { .enable=1, .file_hint=&file_hint_prc  },
    { .enable=1, .file_hint=&file_hint_ps   },
    { .enable=1, .file_hint=&file_hint_psd  },
    { .enable=1, .file_hint=&file_hint_pst  },
    { .enable=1, .file_hint=&file_hint_qbb  },
    { .enable=1, .file_hint=&file_hint_qdf  },
    { .enable=1, .file_hint=&file_hint_qxd  },
    { .enable=1, .file_hint=&file_hint_ra  },
    { .enable=1, .file_hint=&file_hint_raf  },
    { .enable=1, .file_hint=&file_hint_rar  },
    { .enable=1, .file_hint=&file_hint_raw  },
    { .enable=1, .file_hint=&file_hint_rdc  },
    { .enable=1, .file_hint=&file_hint_reg  },
    { .enable=1, .file_hint=&file_hint_riff },
    { .enable=1, .file_hint=&file_hint_rm   },
    { .enable=1, .file_hint=&file_hint_rns  },
    { .enable=1, .file_hint=&file_hint_rpm  },
    { .enable=1, .file_hint=&file_hint_sit  },
    { .enable=1, .file_hint=&file_hint_spss },
    { .enable=1, .file_hint=&file_hint_stuffit  },
    { .enable=1, .file_hint=&file_hint_swf  },
    { .enable=1, .file_hint=&file_hint_tar  },
    { .enable=1, .file_hint=&file_hint_tiff },
    { .enable=1, .file_hint=&file_hint_fasttxt  },
    { .enable=1, .file_hint=&file_hint_txt  },
    { .enable=1, .file_hint=&file_hint_x3f  },
    { .enable=1, .file_hint=&file_hint_xcf  },
    { .enable=1, .file_hint=&file_hint_xm   },
    { .enable=1, .file_hint=&file_hint_wmf  },
    { .enable=1, .file_hint=&file_hint_zip  },
    { .enable=0, .file_hint=NULL }
  };

#ifdef TESTING
  srand(1);
#endif
#ifdef HAVE_SIGACTION
  /* set up the signal handler for SIGHUP */
  action.sa_handler  = sighup_hdlr;
  action.sa_flags = 0;
  if(sigaction(SIGHUP, &action, &old_action)==-1)
  {
    printf("Error on SIGACTION call\n");
    return -1;
  }
#endif
  printf("PhotoRec %s, Data Recovery Utility, %s\nChristophe GRENIER <grenier@cgsecurity.org>\nhttp://www.cgsecurity.org\n",VERSION,TESTDISKDATE);
  for(i=1;i<argc;i++)
  {
    if((strcmp(argv[i],"/log")==0) ||(strcmp(argv[i],"-log")==0))
      create_log=1;
    else if((strcmp(argv[i],"/debug")==0) || (strcmp(argv[i],"-debug")==0))
    {
      debug++;
      create_log=1;
    }
    else if(((strcmp(argv[i],"/d")==0)||(strcmp(argv[i],"-d")==0)) &&(i+1<argc))
    {
      int len=strlen(argv[i+1]);
      if(argv[i+1][len-1]=='\\' || argv[i+1][len-1]=='/')
      {
	char *new_recup_dir=MALLOC(len+strlen(DEFAULT_RECUP_DIR)+1);
	strcpy(new_recup_dir,argv[i+1]);
	strcat(new_recup_dir,DEFAULT_RECUP_DIR);
	recup_dir=new_recup_dir;	/* small memory leak */
      }
      else
	recup_dir=argv[i+1];
      i++;
    }
    else if((strcmp(argv[i],"/all")==0) || (strcmp(argv[i],"-all")==0))
      testdisk_mode|=TESTDISK_O_ALL;
    else if((strcmp(argv[i],"/direct")==0) || (strcmp(argv[i],"-direct")==0))
      testdisk_mode|=TESTDISK_O_DIRECT;
    else if((strcmp(argv[i],"/help")==0) || (strcmp(argv[i],"-help")==0) || (strcmp(argv[i],"--help")==0) ||
      (strcmp(argv[i],"/h")==0) || (strcmp(argv[i],"-h")==0))
      help=1;
    else if((strcmp(argv[i],"/nosetlocale")==0) || (strcmp(argv[i],"-nosetlocale")==0))
      run_setlocale=0;
    else if(strcmp(argv[i],"/cmd")==0)
    {
      if(i+2>=argc)
	help=1;
      else
      {
	disk_t *disk_car;
	cmd_device=argv[++i];
	cmd_run=argv[++i];
	/* There is no log currently */
	disk_car=file_test_availability(cmd_device,debug,arch,testdisk_mode);
	if(disk_car==NULL)
	{
	  printf("\nUnable to open file or device %s\n",cmd_device);
	  help=1;
	}
	else
	  list_disk=insert_new_disk(list_disk,disk_car);
      }
    }
    else
    {
      disk_t *disk_car=file_test_availability(argv[i],debug,arch,testdisk_mode);
      if(disk_car==NULL)
      {
	printf("\nUnable to open file or device %s\n",argv[i]);
	help=1;
      }
      else
	list_disk=insert_new_disk(list_disk,disk_car);
    }
  }
  if(help!=0)
  {
    printf("\nUsage: photorec [/log] [/debug] [/d recup_dir] [file or device]\n"\
	    "\n" \
	    "/log          : create a photorec.log file\n" \
	    "/debug        : add debug information\n" \
	    "\n" \
	    "PhotoRec searches various file formats (JPEG, Office...), it stores them\n" \
	    "in recup_dir directory.\n" \
	    "\n" \
	    "If you have problems with PhotoRec or bug reports, please contact me.\n");
    return 0;
  }
  if(create_log>0)
    log_open("photorec.log",(create_log==1?"a":"w"),"PhotoRec",argc,argv);
  log_info("PhotoRec %s, Data Recovery Utility, %s\nChristophe GRENIER <grenier@cgsecurity.org>\nhttp://www.cgsecurity.org\n",VERSION,TESTDISKDATE);
  log_info(TESTDISK_OS);
  log_info(" (ewf lib: %s)\n", td_ewf_version());
#if 0
#if defined(HAVE_LIBJPEG) && defined(JPEG_LIB_VERSION)
  log_info(", libjpeg: %u)\n", JPEG_LIB_VERSION);
#else
  log_info(", libjpeg: none)\n");
#endif
#endif
  log_info("\n");
  printf("Please wait...\n");
#ifdef HAVE_SETLOCALE
  if(run_setlocale>0)
  {
    const char *locale;
    locale = setlocale (LC_ALL, "");
    if (locale==NULL) {
      locale = setlocale (LC_ALL, NULL);
      log_error("Failed to set locale, using default '%s'.\n", locale);
    } else {
      log_info("Using locale '%s'.\n", locale);
    }
  }
#endif
  aff_buffer(BUFFER_RESET,"Q");
  /* Scan for available device only if no device or image has been supplied in parameter */
  if(list_disk==NULL)
    list_disk=hd_parse(list_disk,debug,arch,testdisk_mode);
#ifdef DJGPP
  for(element_disk=list_disk;element_disk!=NULL;element_disk=element_disk->next)
  {
    printf("%s\n",element_disk->disk->description(element_disk->disk));
  }
#endif
  hd_update_all_geometry(list_disk,0,debug);
  /* Activate the cache, even if photorec has its own */
  for(element_disk=list_disk;element_disk!=NULL;element_disk=element_disk->next)
    element_disk->disk=new_diskcache(element_disk->disk,testdisk_mode);
  /* save disk parameters to rapport */
  log_info("Hard disk list\n");
  for(element_disk=list_disk;element_disk!=NULL;element_disk=element_disk->next)
  {
    printf("%s, sector size=%u\n",element_disk->disk->description(element_disk->disk),element_disk->disk->sector_size);
    log_info("%s, sector size=%u\n",element_disk->disk->description(element_disk->disk),element_disk->disk->sector_size);
  }
  printf("\n");
  log_info("\n");
  if(start_ncurses("PhotoRec", argv[0])==0)
  {
    do_curses_photorec(debug,recup_dir,list_disk,list_file_enable,cmd_device,cmd_run);
    end_ncurses();
  }
  delete_list_disk(list_disk);
  log_info("PhotoRec exited normally.\n");
  if(log_close()!=0)
  {
    printf("PhotoRec: Log file corrupted!\n");
  }
  else
  {
    printf("PhotoRec exited normally.\n");
  }
  return 0;
}

void file_search_footer(file_recovery_t *file_recovery, const unsigned char*footer, const unsigned int footer_length)
{
  const unsigned int read_size=4096;
  unsigned char*buffer;
  int64_t file_size;
  if(footer_length==0)
    return ;
  buffer=(unsigned char*)MALLOC(read_size+footer_length-1);
  file_size=file_recovery->file_size;
  memset(buffer+read_size,0,footer_length-1);
  do
  {
    int i;
    int taille;
    if(file_size%read_size!=0)
      file_size=file_size-(file_size%read_size);
    else
      file_size-=read_size;
    if(fseek(file_recovery->handle,file_size,SEEK_SET)<0)
      return;
    taille=fread(buffer,1,read_size,file_recovery->handle);
    for(i=taille-1;i>=0;i--)
    {
      if(buffer[i]==footer[0] && memcmp(buffer+i,footer,footer_length)==0)
      {
	file_recovery->file_size=file_size+i+footer_length;
	free(buffer);
	return;
      }
    }
    memcpy(buffer+read_size,buffer,footer_length-1);
  } while(file_size>0);
  file_recovery->file_size=0;
  free(buffer);
}

void file_search_lc_footer(file_recovery_t *file_recovery, const unsigned char*footer, const unsigned int footer_length)
{
  const unsigned int read_size=4096;
  unsigned char*buffer;
  int64_t file_size;
  if(footer_length==0)
    return ;
  buffer=(unsigned char*)MALLOC(read_size+footer_length-1);
  file_size=file_recovery->file_size;
  memset(buffer+read_size,0,footer_length-1);
  do
  {
    int i;
    int taille;
    if(file_size%read_size!=0)
      file_size=file_size-(file_size%read_size);
    else
      file_size-=read_size;
    if(fseek(file_recovery->handle,file_size,SEEK_SET)<0)
      return;
    taille=fread(buffer,1,read_size,file_recovery->handle);
    for(i=0;i<taille;i++)
      buffer[i]=tolower(buffer[i]);
    for(i=taille-1;i>=0;i--)
    {
      if(buffer[i]==footer[0] && memcmp(buffer+i,footer,footer_length)==0)
      {
	file_recovery->file_size=file_size+i+footer_length;
	free(buffer);
	return;
      }
    }
    memcpy(buffer+read_size,buffer,footer_length-1);
  } while(file_size>0);
  file_recovery->file_size=0;
  free(buffer);
}

int data_check_size(const unsigned char *buffer, const unsigned int buffer_size, file_recovery_t *file_recovery)
{
  if(file_recovery->file_size>=file_recovery->calculated_file_size)
  {
    file_recovery->file_size=file_recovery->calculated_file_size;
    return 2;
  }
  return 1;
}

void file_check_size(file_recovery_t *file_recovery)
{
  if(file_recovery->file_size<file_recovery->calculated_file_size)
    file_recovery->file_size=0;
  else
    file_recovery->file_size=file_recovery->calculated_file_size;
}

/* file_finish() returns
   -1: file not recovered, file_size=0 offset_error!=0
    0: file not recovered
    1: file recovered	   
 */
int file_finish(file_recovery_t *file_recovery, const char *recup_dir, const int paranoid, unsigned int *file_nbr,
    const unsigned int blocksize, alloc_data_t *list_search_space, alloc_data_t **current_search_space, uint64_t *offset,
    unsigned int *dir_num, const photorec_status_t status, const unsigned int sector_size, const disk_t *disk_car)
{
  int file_recovered=0;
#ifdef DEBUG_FILE_FINISH
  log_debug("file_finish start %lu (%lu-%lu)\n", (long unsigned int)((*offset)/blocksize),
      (unsigned long int)((*current_search_space)->start/blocksize),
      (unsigned long int)((*current_search_space)->end/blocksize));
  log_debug("file_recovery->offset_error=%llu\n", (long long unsigned)file_recovery->offset_error);
  log_debug("file_recovery->handle %s NULL\n", (file_recovery->handle!=NULL?"!=":"=="));
#endif
  if(file_recovery->handle)
  {
    if(status!=STATUS_EXT2_ON_SAVE_EVERYTHING && status!=STATUS_EXT2_OFF_SAVE_EVERYTHING)
    {
      if(file_recovery->file_stat!=NULL && file_recovery->file_check!=NULL && paranoid>0)
      { /* Check if recovered file is valid */
	file_recovery->file_check(file_recovery);
      }
      if(file_recovery->file_stat!=NULL && file_recovery->file_size> 0 &&
	  file_recovery->file_size < file_recovery->min_filesize)
      { 
	log_info("File too small ( %llu < %llu), reject it\n",
	    (long long unsigned) file_recovery->file_size,
	    (long long unsigned) file_recovery->min_filesize);
	file_recovery->file_size=0;
	file_recovery->file_size_on_disk=0;
      }
      /* FIXME: need to adapt read_size to volume size to avoid this */
      if(file_recovery->file_size > disk_car->disk_size)
	file_recovery->file_size = disk_car->disk_size;
      if(file_recovery->file_size > disk_car->disk_real_size)
	file_recovery->file_size = disk_car->disk_real_size;
#ifdef HAVE_FTRUNCATE
      fflush(file_recovery->handle);
      if(ftruncate(fileno(file_recovery->handle), file_recovery->file_size)<0)
      {
	log_critical("ftruncate failed.\n");
      }
#endif
    }
    fclose(file_recovery->handle);
    file_recovery->handle=NULL;
//    log_debug("%s %llu\n",file_recovery->filename,(long long unsigned)file_recovery->file_size);
    if(file_recovery->file_size>0)
    {
      if((++(*file_nbr))%MAX_FILES_PER_DIR==0)
      {
	*dir_num=photorec_mkdir(recup_dir,*dir_num+1);
      }
      if(paranoid==0 || (status!=STATUS_EXT2_ON_SAVE_EVERYTHING && status!=STATUS_EXT2_OFF_SAVE_EVERYTHING))
	file_recovery->file_stat->recovered++;
    }
    else
    {
      unlink(file_recovery->filename);
    }
  }
  if(file_recovery->file_stat!=NULL)
  {
    list_truncate(&file_recovery->location,file_recovery->file_size);
    if(status!=STATUS_FIND_OFFSET && file_recovery->file_size>0)
      list_space_used(file_recovery,sector_size);
    if(file_recovery->file_size==0)
    {
      /* File hasn't been sucessfully recovered, remember where it begins */
      list_free_add(file_recovery, list_search_space);
      if((*current_search_space)!=list_search_space &&
	  !((*current_search_space)->start <= *offset && *offset <= (*current_search_space)->end))
	*current_search_space=list_entry((*current_search_space)->list.next, alloc_data_t, list);
    }
    else if(status!=STATUS_EXT2_ON_SAVE_EVERYTHING && status!=STATUS_EXT2_OFF_SAVE_EVERYTHING && status!=STATUS_FIND_OFFSET)
    {
      list_search_space=update_search_space(file_recovery,list_search_space,current_search_space,offset,blocksize);
      file_recovered=1;
    }
    list_delete(file_recovery->location.next);
    file_recovery->location.next=NULL;
  }
  if(file_recovery->file_size==0 && file_recovery->offset_error!=0)
    file_recovered=-1;
  else
    reset_file_recovery(file_recovery);
#ifdef DEBUG_FILE_FINISH
  log_debug("file_finish end %lu (%lu-%lu)\n\n", (long unsigned int)((*offset)/blocksize),
      (unsigned long int)((*current_search_space)->start/blocksize),
      (unsigned long int)((*current_search_space)->end/blocksize));
#endif
  return file_recovered;
}


syntax highlighted by Code2HTML, v. 0.9.1