/* 
   Copyright (C) 1994 Free Software Foundation

   This program 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, 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 to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */

#include <hurd.h>
#include <device/device.h>
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <hurd/ports.h>

#include "dev.h"

/* Add a new device to the DEVICES array.  Initialize fields with
   NAME, TYPE, RAW, and BLKSIZE. */
void
adddev (char *name, enum device_type type, int raw, int blksize,
	mach_port_t port)
{
  if (nextdev >= ndevices)
    devices = realloc (devices, (ndevices *= 2) * sizeof (struct device));

  devices[nextdev].name = malloc (strlen (name) + 1);
  strcpy (devices[nextdev].name, name);
  devices[nextdev].type = type;
  devices[nextdev].raw = raw;
  bzero(&devices[nextdev].st, sizeof (struct stat));
  devices[nextdev].st.st_blksize = blksize;
  devices[nextdev].port = port;
  fshelp_lock_init (&devices[nextdev].flock);
  nextdev++;
}

/* Try and open the device named NAME.  If the device exists, close it
   right back, create appropriate entries in DEVICES, and return 1.
   Otherwise, return 0.  The struct mach_dev for this devices is
   MACHDEV.  */
int
try_open (char *name, struct mach_dev *machdev)
{
  error_t err;
  device_t dev;
  int sizes[DEV_GET_SIZE_COUNT], sizescnt;
  int iosize;

  printf ("%s\n", name);
  err = device_open (master_device, D_NODELAY|D_READ, name, &dev);
  if (err == D_NO_SUCH_DEVICE)
    return 0;
  
  if (!err)
    {
      /* Optimal block size is the device's record size rounded up to
	 an integral number of pages. */
      sizescnt = DEV_GET_SIZE_COUNT;
      err = device_get_status (dev, DEV_GET_SIZE, sizes, &sizescnt);
      if (!err && sizescnt == DEV_GET_SIZE_COUNT)
	iosize = round_page (sizes[DEV_GET_SIZE_RECORD_SIZE]);
      else
	iosize = vm_page_size;
      device_close (dev);
      mach_port_deallocate (mach_task_self (), dev);
    }
  else
    iosize = vm_page_size;
  
  switch (machdev->type)
    {
    case DEV_TYPE_DISK:
    case DEV_TYPE_TTY:
    case DEV_TYPE_TAPE:
      /* Create raw device */
      adddev (name, machdev->type, 1, iosize, MACH_PORT_NULL);
      /* Fall through ... */

    default:
      adddev (name, machdev->type, 0, iosize, MACH_PORT_NULL);
      break;
    }
  
  return 1;
}

/* Initialize the DEVICES array from mach_dev_list by trying 
   to open things. */
void
init_devices (void)
{
  struct mach_dev *dev;
  int unit, partition;

  ndevices = 100;
  nextdev = 0;
  devices = malloc (sizeof (struct device) * 100);
  
  for (dev = mach_dev_list; dev - mach_dev_list < mach_dev_list_len; dev++)
    {
      /* Try and open the device, as many partitions as possible;
	 as many unit numbers as possible.  For each successful
	 open, add the device to DEVICES. */
      if (!dev->minordevices)
	try_open (dev->name, dev);
      else if (!dev->npartitions)
	{
	  char name[100];
	  unit = 0;
	  do
	    sprintf (name, "%s%d", dev->name, unit++);
	  while (try_open (name, dev));
	}
      else
	{
	  char name[100];
	  unit = 0;
	  partition = 0;
	  do
	    {
	      sprintf (name, "%s%d%c", dev->name, unit, partition + 'a');
	      if (partition++ >= dev->npartitions
		  || partition > 26)
		{
		  partition = 0;
		  unit++;
		}
	    }
	  while (try_open (name, dev));
	}
    }
}

/* Add entries for standard special files that always exist. */
void
add_standard_devices ()
{
  adddev ("null", DEV_TYPE_NULL, 0, vm_page_size, MACH_PORT_NULL);
  adddev ("zero", DEV_TYPE_ZERO, 0, vm_page_size, MACH_PORT_NULL);
  adddev ("tty", DEV_TYPE_CTTY, 0, vm_page_size, MACH_PORT_NULL);
  adddev ("fd", DEV_TYPE_FDDIR, 0, vm_page_size, MACH_PORT_NULL);
  adddev ("stdin", DEV_TYPE_FD, 0, vm_page_size, 0);
  adddev ("stdout", DEV_TYPE_FD, 0, vm_page_size, 1);
  adddev ("stderr", DEV_TYPE_FD, 0, vm_page_size, 2);
}

int
main (int argc, char **argv)
{
  file_t under_file;
  FILE *config;

  if (argc != 2)
    {
      fprintf (stderr, "Usage: %s device-dir\n", argv[0]);
      exit (1);
    }

  get_privileged_ports (&master_host, &master_device);

  init_devices ();
  add_standard_devices ();

  /* DEVICES array is now fixed in size. */
  ndevices = nextdev;
  
  _libports_initialize ();	/* XXX */

  /* Attach ourselves to the device directory as its
     translator */
  under_file = path_lookup (argv[1], O_CREAT|O_NOTRANS, 0666);
  if (under_file == MACH_PORT_NULL)
    {
      perror (argv[1]);
      exit (1);
    }
  
  fs_control = ports_allocate_port (sizeof (struct port_info),
				    PT_CNTL);
  
  errno = file_set_translator (under_file, FS_TRANS_EXCL, 0, 0, 0,
			     ports_get_right (fs_control),
			     MACH_MSG_TYPE_MAKE_SEND);
  if (errno)
    {
      perror ("Setting translator");
      exit (1);
    }

  /* Now look at the underlying node.  If it's a directory, then
     try and find the config file in `devlist' inside that directory.
     Otherwise, use the underlying node itself.  */
  errno = hurd_path_lookup (MACH_PORT_NULL, under_file, "devlist",
			    O_READ|O_WRITE|O_CREAT, 0644, &config_file);
  if (errno && errno != ENOTDIR)
    {
      perror ("Opening config file");
      exit (1);
    }

  if (errno == ENOTDIR)
    config_file = under_file;
  
  /* Read the config file */
  mach_port_mod_refs (mach_task_self (), config_file,
		      MACH_PORT_RIGHT_SEND, 1);
  /* We should use fopenport here, but that's currently broken. */
  config = fdopen (openport (config_file, O_READ), "r");
  read_config_file (config);
  fclose (config);

  

  exit(0);
}

  
