/* -*- c -*- */
#include <sys/types.h>
#include <sys/exec.h>
#include <sys/core.h>
#include <sys/stat.h>
#include <a.out.h>
#include <vfont.h>
#include <strings.h>
#include <stdio.h>
#include <sys/file.h>
#include <regex.h>

#ifdef sun
#include <rasterfile.h>
#endif /* sun */

/*
 * FILE.C    ---    Heuristically determine the type
 *		    of a file.  Relies on Magic Numbers
 *		    for binaries, and dirty hacks for
 *		    nroff etc, lisp, pascal, c, fortran,
 *		    and so on.  Knows rather more than it
 *		    probably ought to about SUN/3 files.
 *		    
 *		    This is intended to be submitted to
 *		    the GNU project, but is also likely
 *		    to be handy on SUNs.
 *
 * AUTHOR	    Ian G. Batten, University of Birmingham.
 * DATE		    1-Nov-1986
 * 
 * NOTICE -----------------------------------------------------
 *
 * No proprietry software's source code was consulted during
 * the writing of this program.  SUN include files are
 * referenced, however.  This software may be freely duplicated
 * provided that (i) no commercial advantage is taken of it and
 * (ii) this notice is attached to all copies so made.
 *
 * ------------------------------------------------------------
 *
 * Batten@MULTICS.BHAM.AC.UK
 *
 */

#ifndef CONFIG_FILE
#define CONFIG_FILE "/usr/local/filetypes"
#endif CONFIG_FILE
#define BUFFER_SIZE 256
#define NOFILE (FILE *) NULL
#define BLOCK_SIZE 512			     /* The number of characters to consider */
#define FIELD_BOUNDARY ':'		     /* Spliter in the config */
#define BYTEWIDTH 8

static void type_determine ();
static void use_config_file ();
char *getenv ();

struct regex_link
{
  struct re_pattern_buffer re_comp_buf;
  char regexp[BUFFER_SIZE];
  char message[BUFFER_SIZE];		     /* Description of what matches this regex */
  struct regex_link *next_link;		     /* And the next... */
};

static struct regex_link *regex_list = (struct regex_link *)NULL;
static struct regex_link *last_link;	     /* For appending to list */

main (argc, argv)
     int argc;
     char **argv;
{
  int target,				     /* File descriptor */
  file_count,
  byte_count,
  mode_bits;
  char first_block[BLOCK_SIZE + 1];	     /* Allow space for nul terminator */
  struct stat statbuf;

  for (file_count = 1; file_count < argc; file_count++)
    {
      printf ("%s:\t", argv[file_count]);
      fflush (stdout);			     /* In case we perror, ensure file name */
					     /* which took the fault is printed now. */

      if (lstat (argv[file_count], &statbuf) == -1) /* I want to know about symlinks. */
					     /* There is no 'flstat' to take an fd and get link */
					     /* And you can't open a socket. */
	{
	  perror (argv[file_count]);
	  continue;
	}

      if ((mode_bits = statbuf.st_mode & S_IFMT) != S_IFREG)
	switch (mode_bits)
	  {
	  case S_IFDIR:
	    printf (" directory\n");
	    continue;
	  case S_IFCHR:
	    printf (" character special (%d/%d)\n",
		    major (statbuf.st_rdev), minor (statbuf.st_rdev));
	    continue;
	  case S_IFBLK:
	    printf (" block special (%d/%d)\n",
		    major (statbuf.st_rdev), minor (statbuf.st_rdev));
	    continue;
	  case S_IFLNK:
	    printf (" symbolic link\n");
	    continue;
	  case S_IFSOCK:
	    printf (" socket\n");
	    continue;
	  default:
	    printf (" unknown non-file\n");
	  } 

      target = open (argv[file_count], O_RDONLY, 0);
      if (target == -1) 
	{
	  perror (" open");
	  continue;
	}

      byte_count = read (target, first_block, BLOCK_SIZE);
      if (byte_count == -1)
	perror (" read");	     
      else
	{
	  first_block[byte_count] = '\0';
	  type_determine (first_block, byte_count);
	  
	  if ((int) statbuf.st_mode & S_ISUID)
	    printf (" (setuid)");
	  if ((int) statbuf.st_mode & S_ISGID)
	    printf (" (setgid)");
	  if ((int) statbuf.st_mode & S_ISVTX)
	    printf (" (sticky bit)");
	}

      putchar ('\n');
      close (target);
    }
}

#ifdef sun


static void
raster_type (code)
     int code;
{
  switch (code)
    {
    case RT_OLD:
      printf (" Old raw pixrect image, 68000 byte order");
      break;
    case RT_STANDARD:
      printf (" Standard raw pixrect image, 68000 byte order");
      break;
    case RT_BYTE_ENCODED:
      printf (" Run-length compression of bytes");
      break;
    case RT_EXPERIMENTAL:
      printf (" Type reserved for testing");
      break;
    }
}

static void
raster_maptype (code)
     int code;
{
  switch (code)
    {
    case RMT_RAW:
      printf (" Sun RMT_RAW");
      break;
    case RMT_NONE:
      printf (" Standard RMT_NONE");
      break;
    case RMT_EQUAL_RGB:
      printf (" Standard RMT_EQUAL_RGB");
    }
  printf (" colormap");
}

static void
machine_type (code)
     unsigned short code;
{
  switch (code)
    {
    case M_OLDSUN2:
      printf (" old sun/2");
      break;
    case M_68010:
      printf (" mc68010");
      break;
    case M_68020:
      printf (" mc68020");
      break;
    }
}

#endif /* sun */

static void purity_type (code)
     unsigned code;
{
  switch (code)
    {
    case OMAGIC:
      printf (" impure");
      break;
    case NMAGIC:
      printf (" read-only text");
      break;
    case ZMAGIC:
      printf (" demand paged");
      break;
    }
}


static void
type_determine (block, length)
     int length;
     char block[BLOCK_SIZE];
{

/* Test for executable */

  {
    struct exec *exec_header;
    exec_header = (struct exec *)block;
    if (!(exec_header -> a_magic != OMAGIC && exec_header -> a_magic != NMAGIC
	&& exec_header -> a_magic != ZMAGIC))
      {
#ifdef sun
	machine_type (exec_header -> a_machtype);
#endif /* sun */
	purity_type (exec_header -> a_magic);
	if (exec_header -> a_syms == 0)
	  printf (" stripped");
	printf (" executable");
	return;
      }
  }

/* Test for coredump */

  {
    struct core *core_header;
    core_header = (struct core *)block;

    if (core_header -> c_magic == CORE_MAGIC)
      {
	printf (" core dump from %s", (char *)(core_header -> c_cmdname));
	return;
      }
  }

#ifdef sun

/* Test for rasterfile */

  {
    struct rasterfile *raster_header;
    raster_header = (struct rasterfile *)block;

    if (raster_header -> ras_magic == RAS_MAGIC)
      {
	printf (" raster image %d by %d by %d", raster_header -> ras_width, 
		raster_header -> ras_height, raster_header -> ras_depth);

	raster_type (raster_header -> ras_type);
	raster_maptype (raster_header -> ras_maptype);

	return;
      }
  }

#endif /* sun */

/* Test for vfont */

  {
    struct header /* CAREFUL!  Dodgy nameing in vfont.h */ *vfont_header;
    vfont_header = (struct header *) block;

    if (vfont_header -> magic == VFONT_MAGIC)
      {
	printf (" vfont file, size %d by %d", vfont_header -> maxx, vfont_header -> maxy);
	return;
      }
  }

  use_config_file (block, length);
}

static void 
read_config_file (name, barf)
     char *name;
     int barf;				     /* 1 = fail on no open */
{
  char *buffer;
  FILE *types;
  struct regex_link *this_link;
  char *regexp, *error;

  buffer = (char *) malloc (BUFFER_SIZE);

  types = fopen (name, "r");
  if (types == NOFILE)
    if (barf)
      {
	perror (name);
	exit (1);
      }
  else
    return;

  while ((buffer = fgets (buffer, BUFFER_SIZE, types)) != NULL)
    {
      if (buffer[0] == '#') continue;	     /* Comment */
      if (buffer[0] == '\0') continue;	     /* Blank line */
      
      if (!(regexp = index (buffer, FIELD_BOUNDARY)))
	{
	  fprintf (stderr, "%s: Bad config line (only one field): \n%s\n", name, buffer);
	  exit (1);
	}
	  
      *regexp++ = '\0';	     /* Partition the buffer */
      regexp[strlen(regexp) - 1] = '\0'; /* Stamp on the CR */
      error = (char *) NULL;
      
      this_link = (struct regex_link *) malloc (sizeof (struct regex_link));
      
      if (!(this_link->re_comp_buf.buffer = (char *) malloc (200)))
	error = "Memory exhausted";
      else
	{
	  this_link->re_comp_buf.allocated = 200;
	  if (!(this_link->re_comp_buf.fastmap = (char *) malloc (1 << BYTEWIDTH)))
	    error = "Memory exhausted";
	  else
	    error = re_compile_pattern (regexp, strlen (regexp), &this_link->re_comp_buf);
	}
      
      if (error != (char *) NULL)
	{
	  fprintf (stderr, "%s: Bad config line (%s): \n%s:%s\n", name, error, buffer, regexp);
	  exit (1);
	}
      
      strcpy (this_link->message, buffer);
      strcpy (this_link->regexp, regexp);
      if (regex_list == NULL)
	regex_list = last_link = this_link;
      else
	{
	  last_link->next_link = this_link;
	  last_link = this_link;
	}
      this_link->next_link = NULL;
    }
  fclose (types);
  free (buffer);
}

/* Examine the contents of BLOCK (whose length is BLOCK_LENGTH chars)
   by searching for the regexps specified in the config file.
   Try regexps one by one in the order specified in that file.
   When a regexp is found, print corresponding message and return.

   The config file data is read on the first call to this function.  */

static void
use_config_file (block, block_length)
     char *block;
     int block_length;
{
  struct regex_link *this_link;

  /* Read config files if not already done.  */

  if (regex_list == NULL)
    {
      char *homedir;

      if ((homedir = getenv ("HOME")) != (char *) NULL)
	{
	  if (homedir[1] == '\0')	     /* Must be root */
	    read_config_file ("/.filetypes", 0);
	  else
	    {
	      char *peruser = (char *) malloc (strlen (homedir)+20);

	      strcpy (peruser, homedir);
	      strcat (peruser, "/.filetypes");
	      read_config_file (peruser, 0);
	      free (peruser);
	    }
	}
      read_config_file (CONFIG_FILE, 1);
    }

  /* Now search for the regexps.  */

  for (this_link = regex_list;
       this_link != NULL;
       this_link = this_link->next_link)
    if (0 <= re_search (&this_link->re_comp_buf, block, block_length, 0, block_length, 0))
      {
	printf (" %s", this_link->message);
	return;
      }
}
