/* This program issues add-single-device commands to /proc/scsi/scsi
   for all SCSI hosts found in /proc/scsi/sbp2*?/<hostnumber>.

   It can be used as a stand-alone binary, to be run in initrd, or
   compiled into other programs (in compliance with the GNU GPL) that
   may call rescan_scsi_bus(chmax, idmax, lunmax) to scan channels
   from 0 to chmax, IDs from 0 to idmax, Luns from 0 to lunmax, in all
   SCSI hosts found in the current directory (that is expected to be
   /proc/scsi).

   Copyright (C) 2003 Alexandre Oliva <aoliva@redhat.com>
   based on the rescan-scsi-bus.sh script by Kurt Garloff <garloff@suse.de>

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; see the file COPYING.  If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.  */

#include <dirent.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

/* Variables to determine the ranges to be scanned for each SCSI host.
   Set from the arguments passed to rescan_scsi_bus(int, int, int).  */
static int channel_max, id_max, lun_max;

/* Tell /proc/scsi/scsi to attempt to add a device for all
   combinations of channel, id and lun in the given SCSI host.  */
static void
add_devices_for_host (int host)
{
  int channel, id, lun;

  for (channel = 0; channel <= channel_max; channel++)
    for (id = 0; id <= id_max; id++)
      for (lun = 0; lun <= lun_max; lun++)
	{
	  /* This assumes CWD is /proc/scsi.  */
	  FILE *proc_scsi_scsi = fopen ("scsi", "w");

	  if (! proc_scsi_scsi)
	    return;

	  fprintf (proc_scsi_scsi, "scsi add-single-device %i %i %i %i\n",
		   host, channel, id, lun);
	  fclose (proc_scsi_scsi);
	}
}

/* Look for a files whose names are decimal numbers, take them as a
   SCSI host numbers and scan them.  */
static int
scan_driver_dir (const struct dirent *entry)
{
  char *endptr;
  int host;

  host = strtol (entry->d_name, &endptr, 10);
  if (*endptr)
    {
#if 0
      if (entry->d_name[0] == '.'
	  || strcmp (entry->d_name, "add_map") == 0
	  || strcmp (entry->d_name, "map") == 0
	  || strcmp (entry->d_name, "mod_parm") == 0)
	return 0;

      if (strcmp (entry->d_name, "status") == 0)
	{
	  /* FIXME: extract host from a line that looks like:
	     SCSI host number: <number>  

	     This file AFAIK is only present in kernel 2.6, that AFAIK
	     doesn't really need rescanning.  */
	  host = ...;
	}
      else
#endif
	return 0;
    }
  add_devices_for_host (host);
  return 0;
}

/* Skipping drivers that make no sense to scan, scan scsi hosts.  */
static int
rescan_driver_bus (const struct dirent *entry)
{
  struct dirent **namelist;

#if 1
  /* We only want to rescan sbp2 buses for now.  */
  if (strncmp (entry->d_name, "sbp2", 4) != 0)
    return 0;
#else
  if (entry->d_name[0] == '.'
      || strcmp (entry->d_name, "scsi") == 0
      || strcmp (entry->d_name, "sg") == 0
      || strcmp (entry->d_name, "dummy") == 0)
    return 0;
#endif

  scandir (entry->d_name, &namelist, scan_driver_dir, NULL);

  return 0;
}

/* Main entry point.  CWD must be /proc/scsi.  CHMAX, IDMAX and LUNMAX
   are the maximum numbers to scan in each SCSI host.  */
void
rescan_scsi_bus(int chmax, int idmax, int lunmax)
{
  struct dirent **namelist;

  channel_max = chmax;
  id_max = idmax;
  lun_max = lunmax;

  scandir (".", &namelist, rescan_driver_bus, NULL);
}

#ifdef MAIN
int
main()
{
  if (chdir ("/proc/scsi"))
    return 0;
  rescan_scsi_bus (0, 7, 0);
  return 0;
}
#endif
