/*
* SCSI_media.c -
*
* Written by Eryk Vershen
*/

/*
* Copyright 1997,1998 by Apple Computer, Inc.
*              All Rights Reserved
*
* Permission to use, copy, modify, and distribute this software and
* its documentation for any purpose and without fee is hereby granted,
* provided that the above copyright notice appears in all copies and
* that both the copyright notice and this permission notice appear in
* supporting documentation.
*
* APPLE COMPUTER DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE
* INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE.
*
* IN NO EVENT SHALL APPLE COMPUTER BE LIABLE FOR ANY SPECIAL, INDIRECT, OR
* CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN ACTION OF CONTRACT,
* NEGLIGENCE, OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
* WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/


// for printf() & sprintf()
#include <stdio.h>
// for malloc() & free()
#include <stdlib.h>
#include "DoSCSICommand.h"
#include "SCSI_media.h"
#include "util.h"


/*
* Defines
*/
#define DriverRefNumToSCSI(x)  ((int16_t) (~(x) - 32))


/*
* Types
*/
typedef struct SCSI_media *SCSI_MEDIA;

struct SCSI_media {
   struct media    m;
   long            bus;
   long            id;
};

struct bus_entry {
   long    bus;
   long    sort_value;
   long    max_id;
   long    master_id;
};

struct SCSI_manager {
   long        exists;
   long        kind;
   long        bus_count;
   struct bus_entry *bus_list;
};

typedef struct SCSI_media_iterator *SCSI_MEDIA_ITERATOR;

struct SCSI_media_iterator {
   struct media_iterator   m;
   long                    bus_index;
   long                    bus;
   long                    id;
};

struct linux_order_cache {
   struct cache_item *first;
   struct cache_item *last;
   long next_disk;
   long next_cdrom;
   long loaded;
};

struct cache_item {
   struct cache_item *next;
   long bus;
   long id;
   long value;
   long is_cdrom;
   long unsure;
};


/*
* Global Constants
*/
enum {
   kNoDevice = 0x00FF
};

enum {
   kRequiredSCSIinquiryLength = 36
};


/*
* Global Variables
*/
static long scsi_inited = 0;
static struct SCSI_manager scsi_mgr;
static struct linux_order_cache linux_order;


/*
* Forward declarations
*/
int AsyncSCSIPresent(void);
void scsi_init(void);
SCSI_MEDIA new_scsi_media(void);
long read_scsi_media(MEDIA m, long long offset, uint32_t count, void *address);
long write_scsi_media(MEDIA m, long long offset, uint32_t count, void *address);
long close_scsi_media(MEDIA m);
long os_reload_scsi_media(MEDIA m);
long compute_id(long bus, long device);
int SCSI_ReadBlock(UInt32 id, UInt32 bus, UInt32 block_size, UInt32 block, UInt8 *address);
int SCSI_WriteBlock(UInt32 id, UInt32 bus, UInt32 block_size, UInt32 block, UInt8 *address);
int DoTestUnitReady(UInt8 targetID, int bus);
int DoReadCapacity(UInt32 id, UInt32 bus, UInt32 *blockCount, UInt32 *blockSize);
SCSI_MEDIA_ITERATOR new_scsi_iterator(void);
void reset_scsi_iterator(MEDIA_ITERATOR m);
char *step_scsi_iterator(MEDIA_ITERATOR m);
void delete_scsi_iterator(MEDIA_ITERATOR m);
void fill_bus_entry(struct bus_entry *entry, long bus);
/*long get_bus_sort_value(long bus);*/
int bus_entry_compare(const void* a, const void* b);
int DoInquiry(UInt32 id, UInt32 bus, UInt32 *devType);
void probe_all(void);
void probe_scsi_device(long bus, long id, int unsure);
long lookup_scsi_device(long bus, long id, int *is_cdrom, int *unsure);
long lookup_scsi_index(long index, int is_cdrom, long *bus, long *id);
void add_to_cache(long bus, long id, int is_cdrom, int unsure);
void init_linux_cache(void);
void clear_linux_cache(void);
void mark_linux_cache_loaded(void);
int linux_cache_loaded(void);


/*
* Routines
*/
int
AsyncSCSIPresent(void)
{
   return (TrapAvailable(_SCSIAtomic));
}


void
scsi_init(void)
{
   int i;
   int old_scsi;

   if (scsi_inited != 0) {
       return;
   }
   scsi_inited = 1;

   scsi_mgr.exists = 1;
   scsi_mgr.kind = allocate_media_kind();

   if (AsyncSCSIPresent()) {
       AllocatePB();

       scsi_mgr.bus_count = gSCSIHiBusID + 1;
       old_scsi = 0;
   } else {
       scsi_mgr.bus_count = 1;
       old_scsi = 1;
   }

   scsi_mgr.bus_list = (struct bus_entry *)
           calloc(scsi_mgr.bus_count, sizeof(struct bus_entry));

   if (scsi_mgr.bus_list == 0) {
       scsi_mgr.bus_count = 0;
   } else {
       for (i = 0; i < scsi_mgr.bus_count; i++) {
           if (old_scsi) {
               scsi_mgr.bus_list[i].bus = 0xFF;
           } else {
               scsi_mgr.bus_list[i].bus = i;
           }
           fill_bus_entry(&scsi_mgr.bus_list[i], i);
       }
       qsort((void *)scsi_mgr.bus_list,    /* address of array */
               scsi_mgr.bus_count,         /* number of elements */
               sizeof(struct bus_entry),   /* size of element */
               bus_entry_compare);         /* element comparison routine */
   }

   init_linux_cache();
}

void
fill_bus_entry(struct bus_entry *entry, long bus)
{
   OSErr           status;
   SCSIBusInquiryPB    pb;
   long len;
   long result;
   long x, y;

   if (!AsyncSCSIPresent()) {
       entry->sort_value = 0;
       entry->max_id = 7;
       entry->master_id = 7;
       return;
   }
   len = sizeof(SCSIBusInquiryPB);
   clear_memory((Ptr) &pb, len);
   pb.scsiPBLength = len;
   pb.scsiFunctionCode = SCSIBusInquiry;
   pb.scsiDevice.bus = bus;
   status = SCSIAction((SCSI_PB *) &pb);
   if (status != noErr) {
       result = 6;
   } else {
       switch (pb.scsiHBAslotType) {
       case scsiMotherboardBus:    x = 0; break;
       case scsiPDSBus:            x = 1; break;
       case scsiNuBus:             x = 2; break;
       case scsiPCIBus:            x = 3; break;
       case scsiFireWireBridgeBus: x = 4; break;
       case scsiPCMCIABus:         x = 5; break;
       default:                    x = 7 + pb.scsiHBAslotType; break;
       };

       switch (pb.scsiFeatureFlags & scsiBusInternalExternalMask) {
       case scsiBusInternal:                   y = 0; break;
       case scsiBusInternalExternal:           y = 1; break;
       case scsiBusExternal:                   y = 2; break;
       default:
       case scsiBusInternalExternalUnknown:    y = 3; break;
       };
       result = x * 4 + y;
   }
   entry->sort_value = result;
   entry->max_id = pb.scsiMaxLUN;
   entry->master_id = pb.scsiInitiatorID;
}


int
bus_entry_compare(const void* a, const void* b)
{
   long result;

   const struct bus_entry *x = (const struct bus_entry *) a;
   const struct bus_entry *y = (const struct bus_entry *) b;

   result = x->sort_value - y->sort_value;
   if (result == 0) {
       result = x->bus - y->bus;
   }
   return result;
}


SCSI_MEDIA
new_scsi_media(void)
{
   return (SCSI_MEDIA) new_media(sizeof(struct SCSI_media));
}


MEDIA
open_old_scsi_as_media(long device)
{
   return open_scsi_as_media(kOriginalSCSIBusAdaptor, device);
}


MEDIA
open_scsi_as_media(long bus, long device)
{
   SCSI_MEDIA  a;
   UInt32 blockCount;
   UInt32 blockSize;

   if (scsi_inited == 0) {
       scsi_init();
   }

   if (scsi_mgr.exists == 0) {
       return 0;
   }

   a = 0;
   if (DoTestUnitReady(device, bus) > 0) {
       if (DoReadCapacity(device, bus, &blockCount, &blockSize) != 0) {
           a = new_scsi_media();
           if (a != 0) {
               a->m.kind = scsi_mgr.kind;
               a->m.grain = blockSize;
               a->m.size_in_bytes = ((long long)blockCount) * blockSize;
               a->m.do_read = read_scsi_media;
               a->m.do_write = write_scsi_media;
               a->m.do_close = close_scsi_media;
               a->m.do_os_reload = os_reload_scsi_media;
               a->bus = bus;
               a->id = device;
           }
       }
   }
   return (MEDIA) a;
}


long
read_scsi_media(MEDIA m, long long offset, uint32_t count, void *address)
{
   SCSI_MEDIA a;
   long rtn_value;
   long block;
   long block_count;
   long block_size;
   uint8_t *buffer;
   int i;

   block = (long) offset;
//printf("scsi %d count %d\n", block, count);
   a = (SCSI_MEDIA) m;
   rtn_value = 0;
   if (a == 0) {
       /* no media */
   } else if (a->m.kind != scsi_mgr.kind) {
       /* wrong kind - XXX need to error here - this is an internal problem */
   } else if (count <= 0 || count % a->m.grain != 0) {
       /* can't handle size */
   } else if (offset < 0 || offset % a->m.grain != 0) {
       /* can't handle offset */
   } else if (offset + count > a->m.size_in_bytes) {
       /* check for offset (and offset+count) too large */
   } else {
       /* XXX do a read on the physical device */
       block_size = a->m.grain;
       block = offset / block_size;
       block_count = count / block_size;
       buffer = address;
       rtn_value = 1;
       for (i = 0; i < block_count; i++) {
           if (SCSI_ReadBlock(a->id, a->bus, block_size, block, buffer) == 0) {
               rtn_value = 0;
               break;
           }
           buffer += block_size;
           block += 1;
       }
   }
   return rtn_value;
}


long
write_scsi_media(MEDIA m, long long offset, uint32_t count, void *address)
{
   SCSI_MEDIA a;
   long rtn_value;
   long block;
   long block_count;
   long block_size;
   uint8_t *buffer;
   int i;

   a = (SCSI_MEDIA) m;
   rtn_value = 0;
   if (a == 0) {
       /* no media */
   } else if (a->m.kind != scsi_mgr.kind) {
       /* XXX need to error here - this is an internal problem */
   } else if (count <= 0 || count % a->m.grain != 0) {
       /* can't handle size */
   } else if (offset < 0 || offset % a->m.grain != 0) {
       /* can't handle offset */
   } else if (offset + count > a->m.size_in_bytes) {
       /* check for offset (and offset+count) too large */
   } else {
       /* XXX do a write on the physical device */
       block_size = a->m.grain;
       block = offset / block_size;
       block_count = count / block_size;
       buffer = address;
       rtn_value = 1;
       for (i = 0; i < block_count; i++) {
           if (SCSI_WriteBlock(a->id, a->bus, block_size, block, buffer) == 0) {
               rtn_value = 0;
               break;
           }
           buffer += block_size;
           block += 1;
       }
   }
   return rtn_value;
}


long
close_scsi_media(MEDIA m)
{
   SCSI_MEDIA a;

   a = (SCSI_MEDIA) m;
   if (a == 0) {
       return 0;
   } else if (a->m.kind != scsi_mgr.kind) {
       /* XXX need to error here - this is an internal problem */
       return 0;
   }
   /* XXX nothing to do - I think? */
   return 1;
}


long
os_reload_scsi_media(MEDIA m)
{
   printf("Reboot your system so the partition table will be reread.\n");
   return 1;
}


#pragma mark -


int
DoTestUnitReady(UInt8 targetID, int bus)
{
   OSErr                   status;
   Str255                  errorText;
   char*       msg;
   static const SCSI_6_Byte_Command gTestUnitReadyCommand = {
       kScsiCmdTestUnitReady, 0, 0, 0, 0, 0
   };
   SCSI_Sense_Data         senseData;
   DeviceIdent scsiDevice;
   int rtn_value;

   scsiDevice.diReserved = 0;
   scsiDevice.bus = bus;
   scsiDevice.targetID = targetID;
   scsiDevice.LUN = 0;

   status = DoSCSICommand(
               scsiDevice,
               "\pTest Unit Ready",
               (SCSI_CommandPtr) &gTestUnitReadyCommand,
               NULL,
               0,
               scsiDirectionNone,
               NULL,
               &senseData,
               errorText
               );
   if (status == scsiNonZeroStatus) {
       rtn_value = -1;
   } else if (status != noErr) {
       rtn_value = 0;
   } else {
       rtn_value = 1;
   }
   return rtn_value;
}


int
SCSI_ReadBlock(UInt32 id, UInt32 bus, UInt32 block_size, UInt32 block, UInt8 *address)
{
   OSErr                   status;
   Str255                  errorText;
   char*       msg;
   static SCSI_10_Byte_Command gReadCommand = {
       kScsiCmdRead10, 0, 0, 0, 0, 0, 0, 0, 0, 0
   };
   SCSI_Sense_Data         senseData;
   DeviceIdent scsiDevice;
   int rtn_value;
   long count;

//printf("scsi read %d:%d block %d size %d\n", bus, id, block, block_size);
   scsiDevice.diReserved = 0;
   scsiDevice.bus = bus;
   scsiDevice.targetID = id;
   scsiDevice.LUN = 0;

   gReadCommand.lbn4 = (block >> 24) & 0xFF;
   gReadCommand.lbn3 = (block >> 16) & 0xFF;
   gReadCommand.lbn2 = (block >> 8) & 0xFF;
   gReadCommand.lbn1 = block & 0xFF;

   count = 1;
   gReadCommand.len2 = (count >> 8) & 0xFF;
   gReadCommand.len1 = count & 0xFF;

   status = DoSCSICommand(
               scsiDevice,
               "\pRead",
               (SCSI_CommandPtr) &gReadCommand,
               (Ptr) address,
               count * block_size,
               scsiDirectionIn,
               NULL,
               &senseData,
               errorText
       );
   if (status == noErr) {
       rtn_value = 1;
   } else {
       rtn_value = 0;
   }
   return rtn_value;
}


int
SCSI_WriteBlock(UInt32 id, UInt32 bus, UInt32 block_size, UInt32 block, UInt8 *address)
{
   OSErr                   status;
   Str255                  errorText;
   char*       msg;
   static SCSI_10_Byte_Command gWriteCommand = {
       kScsiCmdWrite10, 0, 0, 0, 0, 0, 0, 0, 0, 0
   };
   SCSI_Sense_Data         senseData;
   DeviceIdent scsiDevice;
   int rtn_value;
   long count;

   scsiDevice.diReserved = 0;
   scsiDevice.bus = bus;
   scsiDevice.targetID = id;
   scsiDevice.LUN = 0;

   gWriteCommand.lbn4 = (block >> 24) & 0xFF;
   gWriteCommand.lbn3 = (block >> 16) & 0xFF;
   gWriteCommand.lbn2 = (block >> 8) & 0xFF;
   gWriteCommand.lbn1 = block & 0xFF;

   count = 1;
   gWriteCommand.len2 = (count >> 8) & 0xFF;
   gWriteCommand.len1 = count & 0xFF;

   status = DoSCSICommand(
               scsiDevice,
               "\pWrite",
               (SCSI_CommandPtr) &gWriteCommand,
               (Ptr) address,
               count * block_size,
               scsiDirectionOut,
               NULL,
               &senseData,
               errorText
       );
   if (status == noErr) {
       rtn_value = 1;
   } else {
       rtn_value = 0;
   }
   return rtn_value;
}


int
DoReadCapacity(UInt32 id, UInt32 bus, UInt32 *blockCount, UInt32 *blockSize)
{
   OSErr       status;
   Str255      errorText;
   static const SCSI_10_Byte_Command gCapacityCommand = {
       kScsiCmdReadCapacity, 0, 0, 0, 0, 0, 0, 0, 0, 0
   };
   SCSI_Sense_Data senseData;
   DeviceIdent     scsiDevice;
   SCSI_Capacity_Data  capacityData;
   UInt32      temp;
   int rtn_value;

   scsiDevice.diReserved = 0;
   scsiDevice.bus = bus;
   scsiDevice.targetID = id;
   scsiDevice.LUN = 0;

   CLEAR(capacityData);

   status = DoSCSICommand(
               scsiDevice,
               "\pRead Capacity",
               (SCSI_CommandPtr) &gCapacityCommand,
               (Ptr) &capacityData,
               sizeof (SCSI_Capacity_Data),
               scsiDirectionIn,
               NULL,
               &senseData,
               errorText
               );

   if (status == noErr) {
       temp = capacityData.lbn4;
       temp = (temp << 8) | capacityData.lbn3;
       temp = (temp << 8) | capacityData.lbn2;
       temp = (temp << 8) | capacityData.lbn1;
       *blockCount = temp;

       temp = capacityData.len4;
       temp = (temp << 8) | capacityData.len3;
       temp = (temp << 8) | capacityData.len2;
       temp = (temp << 8) | capacityData.len1;
       *blockSize = temp;

       rtn_value = 1;
   } else {
       rtn_value = 0;
   }
   return rtn_value;
}


int
DoInquiry(UInt32 id, UInt32 bus, UInt32 *devType)
{
   OSErr       status;
   Str255      errorText;
   static const SCSI_6_Byte_Command gInquiryCommand = {
       kScsiCmdInquiry, 0, 0, 0, kRequiredSCSIinquiryLength, 0
   };
   SCSI_Sense_Data senseData;
   DeviceIdent     scsiDevice;
   SCSI_Inquiry_Data  inquiryData;
   UInt32      temp;
   int rtn_value;

   scsiDevice.diReserved = 0;
   scsiDevice.bus = bus;
   scsiDevice.targetID = id;
   scsiDevice.LUN = 0;

   CLEAR(inquiryData);

   status = DoSCSICommand(
               scsiDevice,
               "\pInquiry",
               (SCSI_CommandPtr) &gInquiryCommand,
               (Ptr) &inquiryData,
               kRequiredSCSIinquiryLength,
               scsiDirectionIn,
               NULL,
               &senseData,
               errorText
               );

   if (status == noErr) {
       *devType = inquiryData.devType & kScsiDevTypeMask;
       rtn_value = 1;
   } else {
       rtn_value = 0;
   }
   return rtn_value;
}


MEDIA
SCSI_FindDevice(long dRefNum)
{
   SCSIDriverPB            pb;
   OSErr                   status;
   short                   targetID;

   status = nsvErr;
   if (AsyncSCSIPresent()) {
       clear_memory((Ptr) &pb, sizeof pb);

       pb.scsiPBLength = sizeof (SCSIDriverPB);
       pb.scsiCompletion = NULL;
       pb.scsiFlags = 0;
       pb.scsiFunctionCode = SCSILookupRefNumXref;
       pb.scsiDevice.bus = kNoDevice;  /* was *((long *) &pb.scsiDevice) = 0xFFFFFFFFL; */

       do {
           status = SCSIAction((SCSI_PB *) &pb);

           if (status != noErr) {
               break;
           } else if (pb.scsiDriver == dRefNum
                   && pb.scsiDevice.bus != kNoDevice) {
               return open_scsi_as_media(pb.scsiDevice.bus, pb.scsiDevice.targetID);

           } else {
               pb.scsiDevice = pb.scsiNextDevice;
           }
       }
       while (pb.scsiDevice.bus != kNoDevice);
   }
   if (status == nsvErr) {
       /*
        * The asynchronous SCSI Manager is missing or the
        * driver didn't register with the SCSI Manager.*/
       targetID = DriverRefNumToSCSI(dRefNum);
       if (targetID >= 0 && targetID <= 6) {
           return open_old_scsi_as_media(targetID);
       }
   }
    return 0;
}


#pragma mark -


SCSI_MEDIA_ITERATOR
new_scsi_iterator(void)
{
   return (SCSI_MEDIA_ITERATOR) new_media_iterator(sizeof(struct SCSI_media_iterator));
}


MEDIA_ITERATOR
create_scsi_iterator(void)
{
   SCSI_MEDIA_ITERATOR a;

   if (scsi_inited == 0) {
       scsi_init();
   }

   if (scsi_mgr.exists == 0) {
       return 0;
   }

   a = new_scsi_iterator();
   if (a != 0) {
       a->m.kind = scsi_mgr.kind;
       a->m.state = kInit;
       a->m.do_reset = reset_scsi_iterator;
       a->m.do_step = step_scsi_iterator;
       a->m.do_delete = delete_scsi_iterator;
       a->bus_index = 0;
       a->bus = 0;
       a->id = 0;
   }

   return (MEDIA_ITERATOR) a;
}


void
reset_scsi_iterator(MEDIA_ITERATOR m)
{
   SCSI_MEDIA_ITERATOR a;

   a = (SCSI_MEDIA_ITERATOR) m;
   if (a == 0) {
       /* no media */
   } else if (a->m.kind != scsi_mgr.kind) {
       /* wrong kind - XXX need to error here - this is an internal problem */
   } else if (a->m.state != kInit) {
       a->m.state = kReset;
   }
}


char *
step_scsi_iterator(MEDIA_ITERATOR m)
{
   SCSI_MEDIA_ITERATOR a;
   char *result;

   a = (SCSI_MEDIA_ITERATOR) m;
   if (a == 0) {
       /* no media */
   } else if (a->m.kind != scsi_mgr.kind) {
       /* wrong kind - XXX need to error here - this is an internal problem */
   } else {
       switch (a->m.state) {
       case kInit:
           /* find # of buses - done in AllocatePB() out of scsi_init() */
           a->m.state = kReset;
           /* fall through to reset */
       case kReset:
           a->bus_index = 0 /* first bus id */;
           a->bus = scsi_mgr.bus_list[a->bus_index].bus;
           a->id = 0 /* first device id */;
           a->m.state = kIterating;
           clear_linux_cache();
           /* fall through to iterate */
       case kIterating:
           while (1) {
               if (a->bus_index >= scsi_mgr.bus_count /* max bus id */) {
                   break;
               }
               if (a->id == scsi_mgr.bus_list[a->bus_index].master_id) {
                   /* next id */
                   a->id += 1;
               }
               if (a->id > scsi_mgr.bus_list[a->bus_index].max_id) {
                   a->bus_index += 1;
                   a->bus = scsi_mgr.bus_list[a->bus_index].bus;
                   a->id = 0 /* first device id */;
                   continue;   /* try again */
               }
               /* generate result */
               result = (char *) malloc(20);
               if (result != NULL) {
                   if (a->bus == 0xFF) {
                       snprintf(result, 20, "/dev/scsi%c", '0'+a->id);
                       probe_scsi_device(a->bus, a->id, 1);
                   } else {
                       // insure bus number in range
                       if (a->bus > 9) {
                           free(result);
                           result = NULL;
                           break;
                       }
                       snprintf(result, 20, "/dev/scsi%c.%c",
                           '0'+a->bus, '0'+a->id);
                       /* only probe out of iterate; so always added in order. */
                       probe_scsi_device(a->bus, a->id, 0);
                   }
               }

               a->id += 1; /* next id */
               return result;
           }
           a->m.state = kEnd;
           /* fall through to end */
       case kEnd:
           mark_linux_cache_loaded();
       default:
           break;
       }
   }
   return 0 /* no entry */;
}


void
delete_scsi_iterator(MEDIA_ITERATOR m)
{
   return;
}


#pragma mark -


MEDIA
open_linux_scsi_as_media(long index, int is_cdrom)
{
   MEDIA m;
   long bus;
   long id;

   if (lookup_scsi_index(index, is_cdrom, &bus, &id) > 0) {
       m = open_scsi_as_media(bus, id);
   } else {
       m = 0;
   }

   return m;
}


char *
linux_old_scsi_name(long id)
{
   linux_scsi_name(kOriginalSCSIBusAdaptor, id);
}


char *
linux_scsi_name(long bus, long id)
{
   char *result = 0;
   long value;
   int is_cdrom;
   int unsure;
   char *suffix;

   /* name is sda, sdb, sdc, ...
    * in order by buses and ids, but only count responding devices ...
    */
   if ((value = lookup_scsi_device(bus, id, &is_cdrom, &unsure)) >= 0) {
       result = (char *) malloc(20);
       if (result != NULL) {
           if (unsure) {
               suffix = " ?";
           } else {
               suffix = "";
           }
           if (is_cdrom) {
               if (value > 9) {
                   // too many CD's, give up
                   free(result); result = NULL;
               } else {
                   snprintf(result, 20, "/dev/scd%c%s", '0' + value, suffix);
               }
           } else {
               if (value < 26) {
                   snprintf(result, 20, "/dev/sd%c%s", 'a' + value, suffix);
               } else {
                   snprintf(result, 20, "/dev/sd%c%c%s",
                           'a' + value / 26, 'a' + value % 26, suffix);
               }
           }
       }
   }
   return result;
}


void
probe_all(void)
{
   MEDIA_ITERATOR iter;
   char *name;

   iter = create_scsi_iterator();
   if (iter == 0) {
       return;
   }

   printf("finding devices ");
   fflush(stdout);
   while ((name = step_media_iterator(iter)) != 0) {
       /* step does the probe for us */
       printf(".");
       fflush(stdout);
       free(name);
   }
   delete_media_iterator(iter);
   printf("\n");
   fflush(stdout);
}


void
probe_scsi_device(long bus, long id, int unsure)
{
   UInt32 devType;

   if (DoInquiry(id, bus, &devType)) {
       if (devType == kScsiDevTypeDirect
               || devType == kScsiDevTypeOptical) {
           add_to_cache(bus, id, 0, unsure);
       } else if (devType == kScsiDevTypeCDROM
               || devType == kScsiDevTypeWorm) {
           add_to_cache(bus, id, 1, unsure);
       }
   }
}


long
lookup_scsi_device(long bus, long id, int *is_cdrom, int *unsure)
{
   /* walk down list looking for bus and id ?
    *
    * only probe out of iterate (so always add in order)
    * reset list if we reset the iterate
    */
   struct cache_item *item;
   struct cache_item *next;
   long result = -1;
   int count = 0;

   if (scsi_inited == 0) {
       scsi_init();
   }

   while (1) {
       count++;
       for (item = linux_order.first; item != NULL; item = item->next) {
           if (item->bus == bus && item->id == id) {
               result = item->value;
               *is_cdrom = item->is_cdrom;
               *unsure = item->unsure;
               break;
           }
       }
       if (count < 2 && result < 0) {
           probe_all();
       } else {
           break;
       }
   };

   return result;
}


/*
* This has the same structure as lookup_scsi_device() except we are
* matching on the value & type rather than the bus & id.
*/
long
lookup_scsi_index(long index, int is_cdrom, long *bus, long *id)
{
   struct cache_item *item;
   struct cache_item *next;
   long result = 0;
   int count = 0;

   if (scsi_inited == 0) {
       scsi_init();
   }

   while (1) {
       count++;
       for (item = linux_order.first; item != NULL; item = item->next) {
           if (item->value == index && item->is_cdrom == is_cdrom
                   && item->unsure == 0) {
               result = 1;
               *bus = item->bus;
               *id = item->id;
               break;
           }
       }
       if (count < 2 && result == 0 && !linux_cache_loaded()) {
           probe_all();
       } else {
           break;
       }
   };

   return result;
}


void
add_to_cache(long bus, long id, int is_cdrom, int unsure)
{
   struct cache_item *item;

   item = malloc(sizeof(struct cache_item));
   if (item == NULL) {
       return;
   } else {
       item->bus = bus;
       item->id = id;
       item->is_cdrom = is_cdrom;
       item->unsure = unsure;
       if (is_cdrom) {
           item->value = linux_order.next_cdrom;
           linux_order.next_cdrom++;
       } else {
           item->value = linux_order.next_disk;
           linux_order.next_disk++;
       }
       item->next = 0;
   }
   if (linux_order.first == NULL) {
       linux_order.first = item;
       linux_order.last = item;
   } else {
       linux_order.last->next = item;
       linux_order.last = item;
   }
}


void
init_linux_cache(void)
{
   linux_order.first = NULL;
   clear_linux_cache();
}


void
clear_linux_cache(void)
{
   struct cache_item *item;
   struct cache_item *next;

   for (item = linux_order.first; item != NULL; item = next) {
       next = item->next;
       free(item);
   }
   /* back to starting value */
   linux_order.first = NULL;
   linux_order.last = NULL;
   linux_order.next_disk = 0;
   linux_order.next_cdrom = 0;
   linux_order.loaded = 0;
}


void
mark_linux_cache_loaded(void)
{
   linux_order.loaded = 1;
}


int
linux_cache_loaded(void)
{
   return linux_order.loaded;
}