/* dares.c: searches for files on a filesystem image (backend) */
/* works only for ISO9660 */

/* DAta REScue (C) 2002 Oliver Diedrich, odi@ct.heise.de */
/* (C) 2005 c't magazine for computer technology, Heise Zeitschriften Verlag */
/* This file may be redistributed under the terms of the */
/* GNU Public License (GPL), see www.gnu.org */

/* Basic assumption: the data blocks of one file follow each other
   directly on the filesystem */

/* BUGS / TODO:

  - binary file's end is less than END_NULL_BIN bytes before the end of the
    block: garbage will be appended that might belong to another file which
    then will not be recognized. Is there any way to handle this?

  - missing: scrolling in ncurses file display

  - missing: grep file contents for strings in file list display, as a filter
    setting on the command line, maybe in file type list display?
*/



#define _LARGEFILE_SOURCE
#define _LARGEFILE64_SOURCE
#define _FILE_OFFSET_BITS 64

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
#include <magic.h>

#include "../include/dares.h"
#include "../include/global.h"

/* global variables */

char image[256];                     // name of image to work on
char save_path[256];                 // path name where to save recovered files
char h2iname[256];                   // h2i file name
FILE *log_file = 0;                  // log file
char logname[256];                   // name of log file (-l); default: LOG_FILE
int autosave = 0;                    // 1: save all files
int use_mime = 0;                    // 1: use mime types
unsigned long block_nr = 0;          // # of read blocks
unsigned long block_size = 2048;     // 2 kByte
unsigned long max_blocks = 10000000; // # blocks; is adapted in open_image()
volatile int got_signal = 0;         // if > 0: program interrupted
struct one_type all_types[MAXMAGIS]; // all file types
int n_types;                         // # different file types


/* internal global data */

magic_t global_magic_cookie = 0;     // magic cookie




void sighandler(int sig)
/*
  catches signals SIGHUP, SIGINT, SIGTERM
  sets only global variable got_signal which is checked in main()
*/
{
  got_signal = sig;
}



int eval_args(int argc, char *argv[])
/*
  evaluates command line arguments. Sets some global variables.
  0: error, 1: ok
*/
{
  int i;
  char *c;

  memset(image, 0, 256);
  memset(save_path, 0, 256);
  memset(logname, 0, 256);
  memset(h2iname, 0, 256);
  strncpy(logname, LOG_FILE, 255);

  for (i=1; i<argc; i++)
  {
    c = argv[i];
    if (*c != '-') return 0; else c++ ;
    switch (*c)
    {
      case 'i' :
                 if (i<argc-1) i++; else return 0;
                 strncpy(image, argv[i], 255);
                 break;
      case 's' :
                 if (i<argc-1) i++; else return 0;
                 strncpy(save_path, argv[i], 255);
                 break;
      case 'h':
                 c++; if (*c != '2') return 0;
                 c++; if (*c != 'i') return 0;
                 if (i<argc-1) i++; else return 0;
                 strncpy(h2iname, argv[i], 255);
                 break;
      case 'l' :
                 if (i<argc-1) i++; else return 0;
                 strncpy(logname, argv[i], 255);
                 break;
      case 'm' :
                 use_mime = 1;
                 break;
      case 'a':
                 autosave = 1;
                 break;
      default :
                 printf("\nargument '%s' invalid\n\n", argv[i]);
                 return 0;
    }   /* switch */
  }   /* for i */
  if ('\0' == image[0]) return 0;
  if ('\0' == save_path[0]) return 0;
  return 1;
}   /* eval_args() */




void usage(char *myname)
/*
  prints usage information
*/
{
  printf("DAta REScue %s\n", VERSION);
  printf("Synopsis: %s -i image -s path [-h2i h2i_file] [-a] [-m] [-l logfile]\n",
         myname);
  printf("-i:   name of CDROM/DVD image or device\n");
  printf("-s:   a directory you have write perimissions with enough room for your data\n");
  printf("-h2i: name of h2i file for image (as written by H2cdimage)\n");
  printf("-a:   save all files in image without user interaction\n");
  printf("-m:   use short mime types instead of detailed file types\n");
  printf("-l:   name of log file (default: dares.log)\n");
  printf("e.g. %s -i cdrom.image -s /tmp\n", myname);
} /* usage() */




int open_image(const char *name)
/*
  opens image file name
  computes global variable max_blocks
  opens and reads 
  0: error; 1: file handle
*/
{
  int f;
  time_t t;
  struct stat buf;

  t = time(NULL);
  printf("%s started at %s", VERSION, ctime(&t));
  if (log_file)
  {
    fprintf(log_file, "%s started at %s", VERSION,
         ctime(&t));
    fprintf(log_file, "scanning %s, saving recovered files in %s\n",
                  image, save_path);
  }

  printf("opening image %s: ", name);
  if (-1 == (f = open(name, O_RDONLY|O_BINARY)))
  {
    printf("error: %1d (%s)\n", errno, strerror(errno));
    return 0;
  }
  else printf("ok\n");

  if (-1 == fstat(f, &buf))
  {
    printf("fstat(%s) error: %1d (%s)\n", name, errno, strerror(errno));
    return 0;
  }
  if ( S_ISREG(buf.st_mode) && (buf.st_size > 0) )     // regular file >0 Byte?
  {
    max_blocks = buf.st_size / block_size;
    printf("%s contains %lu blocks of %lu byte size\n", name, max_blocks,
           block_size);
    if (log_file)
    {
      fprintf(log_file, "%s contains %lu blocks of %lu byte size\n\n", name,
              max_blocks, block_size);
      fflush(log_file);
    }
  }
  return f;
}   /* open_image() */





char *open_h2i(const char *name, int *h2isize)
/*
  opens h2i file name, read it into a char array, and returns a pointer to it
  h2isize* gets the number of bytes the returned *char points to
  NULL: error, else: h2i data
*/
{
  int f;            // h2i file
  char *c;          // its content
  struct stat buf;

  printf("opening h2i file %s: ", name);
  if (-1 == (f = open(name, O_RDONLY|O_BINARY)))
  {
    printf("error: %1d (%s)\n", errno, strerror(errno));
    return NULL;
  }
  else printf("ok\n");

  if (-1 == fstat(f, &buf))
  {
    printf("fstat error: %1d (%s)\n", errno, strerror(errno));
    close(f);
    return NULL;
  }

  if (S_ISREG(buf.st_mode))
  {
    c = malloc(buf.st_size);
    if (!c)
    {
      printf("malloc error: %1d (%s)\n", errno, strerror(errno));
      close(f);
      return NULL;
    }
    memset(c, 0, buf.st_size);
    *h2isize = (int) read(f, c, buf.st_size);
    if (-1 == *h2isize)
    {
      printf("read error: %1d (%s)\n", errno, strerror(errno));
      close(f);
      return NULL;
    }
    else
    {
      close(f);
      if ( ('0' == c[0]) || ('_' == c[0]) ) return c;
      else
      {
        printf("error: %s is not a valid h2i file\n", name);
        return NULL;
      }
    }
  }

  else
  {
    printf("error: %s is not a valid h2i file\n", name);
    close(f);
    return NULL;
  }
} /* open_h2i() */




int is_text(unsigned char *buf, int bufsize)
/*
  checks for text data in buf
  taken with minor changes from ascmagic.c
  0: no text, 1: text
*/
{
#define F 0   /* character never appears in text */
#define T 1   /* character appears in plain ASCII text */
#define I 2   /* character appears in ISO-8859 text */
#define X 3   /* character appears in non-ISO extended ASCII (Mac, IBM PC) */

  int i;
  unsigned char *c;
  unsigned int t;
  
  static unsigned int text_chars[256] = {
	/*                  BEL BS HT LF    FF CR    */
	F, F, F, F, F, F, F, T, T, T, T, F, T, T, F, F,  /* 0x0X */
        /*                              ESC          */
	F, F, F, F, F, F, F, F, F, F, F, T, F, F, F, F,  /* 0x1X */
	T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,  /* 0x2X */
	T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,  /* 0x3X */
	T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,  /* 0x4X */
	T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,  /* 0x5X */
	T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,  /* 0x6X */
	T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, F,  /* 0x7X */
	/*            NEL                            */
	X, X, X, X, X, T, X, X, X, X, X, X, X, X, X, X,  /* 0x8X */
	X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X,  /* 0x9X */
	I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I,  /* 0xaX */
	I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I,  /* 0xbX */
	I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I,  /* 0xcX */
	I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I,  /* 0xdX */
	I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I,  /* 0xeX */
	I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I   /* 0xfX */
  };

  while ((bufsize > 0) && (0 == buf[bufsize-1]))
   bufsize--;  /* Nullen am Ende weg */
  if (0 == bufsize) return 0;           /* nur Nullen drin */
  c = buf;
  for (i=0; i<bufsize; i++, c++)
  {
    t = text_chars[*c];
    if (t == F) return 0;
  }   /* for */
  return 1;
}   /* is_text() */




int get_filetype(unsigned char *buf,   // data block
                 int bufsize,          // size of data block
                 magic_t magic,        // magic cookie pointer
                 char *type)           // file type as string
/*
  determines file type and prints it to type*
  -1: error, 0: no file type type found, else: ASC or BIN
*/
{
  char *tmp;
  int f = 0;

assert('\0' == type[0]);

  memset(type, 0, MAX_TYPE);
  tmp = (unsigned char*) malloc(bufsize);
  if (NULL == tmp)
  {
    printf("get_filetype(): out of memory\n");
    return -1;
  }

  if (is_text(buf, bufsize)) f = ASC;
  else f = BIN;

  tmp = (char *) magic_buffer(magic, buf, bufsize);
  if (tmp)
  {
    if (tmp[0]) strncpy(type, tmp, MAX_TYPE-1);
    else strncpy(type, "file/unknown", MAX_TYPE-1);
    return f;
  }
  else return 0;
}   /* get_filetype() */




int is_end(int image,                   // filesystem
           unsigned char *buf,          // data block
           const int bufsize,           // size of buf
           const struct one_file *f)    // recent file
/*
  checks if data is the end of a file
  returns bufsize if no end, else number of bytes before end; -1: error
*/
{
  int n = 0;                  // # zeros at end of data
  int end_null = 0;           // max # of allowed zeros
  unsigned char *c;
  unsigned char tmp;          // to read start of next block
  int nr;

assert( (f->filetype == ASC) || (f->filetype == BIN) );

  if (BIN == f->filetype) end_null = END_NULL_BIN;
  else end_null = END_NULL_ASC;
  c = &buf[bufsize-1];
  while ('\0' == *c)
  {
    c--;
    n++;
    if (n == bufsize) break;       // only zeros
  }

  if (n > end_null)                // large enough # of zeros -> end
  {
    if (BIN == f->filetype)
    {                                   // binary file's end: does next block
      nr = read(image, &tmp, 1);        // start with zeros?
      if (nr < 1) return (bufsize-n);   // don't care about read errors
      else
      {
        if (-1 == lseek(image, -1, SEEK_CUR))
        {
          printf("is_end(): lseek(): %1d (%s)\n", errno, strerror(errno));
          return -1;
        }
        if ('\0' == tmp) return bufsize;     // binary file seems to continue
        else return (bufsize-n);             // seems to be the "real end"
      }
    }
    return (bufsize-n);
  }
  else return bufsize;             // no end
}   /* is_end() */




void close_file(struct one_file *f,          // recent file
                char *type,                  // file type
                unsigned long last_block,    // # of last block; 0: last extent
                                             // of f already complete
                char *add)                   // additional text
/*
  writes file information to log file
  if add != NULL, this text is written to log file and copied to f->trunc
  adds the freshly recovered file to all-types[] and adjusts n_types
  puts last_block into last extent of f
*/
{
  int i;
  unsigned int hash;
  struct extent *e;

assert(f != NULL);
assert(type[0] != '\0');
assert(f->ext != NULL);

  if (last_block)
  {
    e = f->ext;                    // 1st extent of block numbers
    while(e->next) e = e->next;    // not last extent
assert(0 == e->end);               // last extent: must not have an end
    e->end = last_block;
  }

  if (log_file)
  {
    fprintf(log_file, "File %6ld, block %6ld (%s): %8ld", f->count,
                 f->ext->start, type, f->size);
    if (add) fprintf(log_file, " (%s)\n", add);
    else fprintf(log_file, "\n");
    fflush(log_file);
  }

  if (add)
  {
    if (f->trunc) free(f->trunc);
    f->trunc = malloc(strlen(add)+1);
    strcpy(f->trunc, add);
  }

  if (strlen(type) > 3) hash = (unsigned int) type[strlen(type) - 4];
  else hash = 0;                   // this should not happen, but who knows ...
  for (i=0; i<n_types; i++)        // do we already know this file type?
  {
    if (all_types[i].hash == hash)      // we know the hash value ...
    {
      if (0 == strcmp(all_types[i].name, type))  // ... and the file type matches
      {
        all_types[i].last->next = f;
        all_types[i].last = f;
        if (add)
        {
          if (add) all_types[i].trunc++;
        }
        i = 0;
        break;
      }
    }
  } // for i

  if (n_types == i)                     // new file type
  {
    all_types[n_types].hash = hash;
    all_types[n_types].first = f;
    all_types[n_types].last = f;
    all_types[n_types].name = malloc(strlen(type)+1);
    strcpy(all_types[n_types].name, type);
    if (add) all_types[i].trunc = 1;
    else all_types[i].trunc = 0;
    n_types++;
    if (MAXMAGIS == n_types) n_types--;      // all_types[] is full now ...
  }
  memset(type, 0, MAX_TYPE);
}   /* close_file */




int comp_type(const struct one_type *t1, const struct one_type *t2)
/* sorts file types according to value in field hash which contains the */
/* number of files of this type */
{
  if (t1->hash == t2->hash) return 0;
  else
  {
    if (t1->hash < t2->hash) return 1;
    else return -1;
  }
}   /* comp_type() */




int save_file(struct one_file *f, char *type, char *name)
/* saves file f of type to disk. Constructs file name from type */
/* Returns sanitized file name in fname which must point to a large enough */
/* character array (> 255 byte) */
/* 0: ok, <0: error (see constants in dares.h)  */
{
  int out;                    // new file
  int fs;                     // image file
  unsigned char buf[4096];    // 1 block buffer
  int toread = 0;             // bytes to read / write
  struct extent *e;           // to handle extent list of f
  unsigned long written = 0;  // bytes yet written at all
  unsigned long bk = 0;       // block number
  char *c;
  int i;


/* some sanity checks */
assert(f != NULL);
assert(type != NULL);
assert(type[0] != '\0');

  sprintf(name, NAME_FORMAT, save_path, f->ext->start, type, 
          BIN == f->filetype ? "bin" : "asc");
  c = name;
  while (*c > '\0') c++;      // end of string
  c--;
  for (i=0; i < (int) strlen(type); i++, c--)
  {                           // build a sane file name without special chars
    if (*c < '-') *c = '_';
    if ('/' == *c) *c = '_';
    if ( (*c > '9') && (*c < 'A') ) *c = '_';
    if (*c > 'z') *c = '_';
  }
  memset(buf, 0, 4096);

  if (-1 == (fs = open(image, O_RDONLY|O_BINARY)))
   return SAVE_FILE_CANNOT_OPEN_IMAGE;

  if (-1 == (out = open(name, O_WRONLY|O_CREAT|O_BINARY, S_IRWXU)))
  {
    close(fs);
    return SAVE_FILE_CANNOT_OPEN_OUTFILE;
  }

  e = f->ext;
  while (e)
  {
    bk = e->start;
    if (-1 == lseek(fs, (off_t) bk * (off_t) block_size, SEEK_SET))
    {
      close(fs);
      close(out);
      return SAVE_FILE_SEEK_ERROR;
    }
    while (bk <= e->end)
    {
      toread = (f->size - written) > 4096 ? 4096 : f->size - written;
      if (-1 == read(fs, buf, toread))
      {
        close(fs);
        close(out);
        return SAVE_FILE_READ_ERROR;
      }
      if (-1 == write(out, buf, toread))
      {
        close(fs);
        close(out);
        return SAVE_FILE_WRITE_ERROR;
      }
      written += toread;
      bk++;
    } // while (bk)
    e = e->next;
  } // while (e)

  close(fs);
  close(out);
  return SAVE_FILE_OK;
} /* save_file() */




int daresOpen(int argc, char *argv[])
/*
  Initializing the library according to command line arguments.
  -1: error, 0: no files found, else: number of found files
  exits if (autosave)
*/
{
  int img;                         // file handle of image file
  unsigned char *data;             // read buffer, contains recent block
  int nread;                       // # bytes read into data
  struct one_file *found;          // recent file
  int n_found;                     // # found files
  char type[MAX_TYPE];             // file type determined by get_filetype()
  char *h2i = NULL;                // h2i data if h2iname[0] is set
  unsigned int h2isize = 0;        // size of h2i[]
  char buf[1024];
  int i, valid, n_end;


/****************************************************/
/* evaluate command line arguments, init everything */
/****************************************************/

  if (0 == eval_args(argc, argv))
  {
    usage(argv[0]);
    return DARES_OPEN_INVALID_PARAM;
  }

  log_file = fopen(logname, "w");       // open log file
  if (!log_file) printf("can't open log file %s\n", logname);

  img = open_image(image);              // open image for reading
  if (!img) return DARES_OPEN_CANNOT_OPEN_IMAGE;

  data = (unsigned char *) malloc(block_size);    // set up read buffer and
  if (!data)
  {
    printf("Error while allocating memory\n");
    close(img);
    if (log_file) fclose(log_file);
    return DARES_OPEN_CANNOT_MALLOC;
  }
  memset(data, 0, block_size);
  nread = 0;

  if (h2iname[0]) h2i = open_h2i(h2iname, &h2isize);   // open h2i file

  if (use_mime) global_magic_cookie = magic_open(MAGIC_MIME); // set up libmagic
  else global_magic_cookie = magic_open(MAGIC_NONE);
  if (! global_magic_cookie )
  {
    printf("Error while opening file magic database\n");
    close(img);
    if (log_file) fclose(log_file);
    return DARES_OPEN_CANNOT_OPEN_MAGIC;
  }

  if (-1 == magic_load(global_magic_cookie, MAGIC_FILE))    // load magic file
  {
    printf("Could not load file magic database file 'magic%s', using default database\n",
            use_mime ? ".mime" : "");
    magic_load(global_magic_cookie, NULL);        // use default magic database
  }

  for (i=0; i<MAXMAGIS; i++)            // initialize file type array
  {
    all_types[i].first = NULL;
    all_types[i].last = NULL;
  }
  n_types = 0;
  n_found = 0;
  found = NULL;
  memset(type, 0, MAX_TYPE);

  signal(SIGHUP, sighandler);           // set up signal handlers
  signal(SIGINT, sighandler);
  signal(SIGTERM, sighandler);



/*******************************/
/* read all blocks of the disk */
/*******************************/

  while (block_nr < max_blocks)
  {
    if (0 == block_nr % 1000)           // display status information
    {
      printf("\rblock %8lu (%d files)", block_nr, n_found);
      fflush(stdout);
    }

    nread = read(img, data, block_size);
    if (nread <= 0)
    {
      if (-1 == nread)
      {
        printf("\nblock %lu: read(): %1d (%s)\n", block_nr, errno,
               strerror(errno));
        goto ERROR;
      }
      else goto AFTER_SCAN;   /* ready: all blocks are read */
    }

/* working on a file? */

    if (found)       // working on a file
    {

/*********************/
/* working on a file */
/*********************/

assert(found->size > 0);
assert(found->filetype > 0);
assert(found->ext != NULL);
assert(NULL == found->next);
assert('\0' != type[0]);

      if (h2i && block_nr < h2isize)    // h2i file was read and contains block
      {
        if ('_' == h2i[block_nr])       // block with read error
        {
          if (!found->trunc)            // not marked yet
          {
            found->trunc = malloc(16);
            sprintf(found->trunc, "read error");
          }
        }
      }

/* end of file? */

      n_end = is_end(img, data, nread, found);
      if (-1 == n_end) goto ERROR;
      found->size += n_end;
      if (n_end < nread)                // file's regular end
      {
        close_file(found, type, block_nr, NULL);
        found = NULL;
      }
      goto NEXT_BLOCK;

    }   /* if (found.filetype) */


/*************************/
/* not working on a file */
/*************************/

assert(NULL == found);

    valid = get_filetype(data, nread, global_magic_cookie, type);
    if (-1 == valid) goto ERROR;
    if (valid)                // valid file type
    {

/************/
/* new file */
/************/

      found = malloc(sizeof(struct one_file));
      if (NULL == found)
      {
        printf("\nError while allocating memory\n");
        goto ERROR;
      }

      found->count = n_found;
      found->filetype = valid;
      found->trunc = NULL;
      found->next = NULL;
      found->ext = malloc(sizeof(struct extent));
      memset(found->ext, 0, sizeof(struct extent));
      found->ext->start = block_nr;

      n_end = is_end(img, data, nread, found);
      if (-1 == n_end) goto ERROR;
      if (0 == n_end)         // block contains only zeros
      {
        free(found->ext);
        free(found);
        found = NULL;
        memset(type, 0, MAX_TYPE);
        goto NEXT_BLOCK;
      }
      n_found++;

      if (h2i && block_nr < h2isize)    // h2i file read?
      {
        if ('_' == h2i[block_nr])       // block with read error?
        {
          if (!found->trunc)            // not marked yet?
          {
            found->trunc = malloc(16);
            sprintf(found->trunc, "read error");
          }
        }
      }

      found->size = n_end;
      if (n_end < nread)      // file's end, less than one block
      {
        close_file(found, type, block_nr, NULL);
        found = NULL;
      }
    }   /* if (valid) */

NEXT_BLOCK:
    if (got_signal)      // scan aborted by user
    {
ERROR:
/* jumped to directly in case of error with got_signal==0 */
      printf("\n%s: scanned %ld %ldk blocks, found %1d files\n",
             got_signal ? "aborted" : "error", block_nr,
             block_size / 1024, n_found);
      if (found) close_file(found, type, block_nr-1,
                            got_signal ? "truncated: aborted"
                                       : "truncated: error");
      if (log_file)
       fprintf(log_file, "\n%s: scanned %ld %ldk blocks, found %1d files\n",
                    got_signal ? "aborted" : "error", block_nr,
                    block_size / 1024, n_found);
      close(img);
      got_signal = 0;
      goto DISPLAY_LIST;
    }   /* if (got_signal) */

    memset(data, 0, block_size);
    block_nr++;
  }   /* while read() */

AFTER_SCAN:
  close(img);
  if (found) close_file(found, type, block_nr-1, "scan finished");
  if (got_signal) got_signal = 0;
  printf("\rfinished: scanned %ld %ldK blocks, found %1d files\n",
         block_nr, block_size / 1024, n_found);
  if (log_file)
    fprintf(log_file, "\nfinished: scanned %ld %ldK blocks, found %1d files\n",
                   block_nr, block_size / 1024, n_found);
  if (!n_found) return DARES_OPEN_OK;

/*************************/
/* display the file list */
/*************************/

DISPLAY_LIST:

  if (autosave){
     printf("Saving %d files to %s : ", n_found, save_path);
     fflush( stdout );
  }

  for (i=0; i<n_types; i++)
  {
    all_types[i].hash = 1;
    found = all_types[i].first;
    if (autosave)
    {
      printf(".");
      fflush( stdout );
      if (SAVE_FILE_OK != save_file(found, all_types[i].name, buf))
       printf("\nerror while saving file %s\n", buf);
    }
    while (found->next)
    {
      if (autosave)
      {
        printf(".");
        fflush( stdout );
        if (SAVE_FILE_OK != save_file(found->next, all_types[i].name, buf))
         printf("\nerror while saving file %s\n", buf);
      }
      all_types[i].hash++;
      found = found->next;
    }
  }
  if (autosave)
  {
    printf(" finished.\n");
    exit(1);
  }

  qsort(all_types, n_types, sizeof(struct one_type), (void*) comp_type);
  if (log_file)
  {
    fprintf(log_file,
      "\nfound %d files of %d different types:\n", n_found, n_types);
    for (i=0; i<n_types; i++)
      fprintf(log_file, "%s \t(%d)\n", all_types[i].name, all_types[i].hash);
    fclose(log_file);
  }

  return n_found;
} /* daresOpen() */


int daresClose( void )
{
  if (global_magic_cookie) magic_close( global_magic_cookie );
  return 0;
}
