/* Installable drivers for windows.  Less common code.
 */
#include "user.h"

/*--------------------------------------------------------------------------*\
**
**  NewSignalProc() -
**
\*--------------------------------------------------------------------------*/
#define SG_EXIT     0x0020
#define SG_LOAD_DLL 0x0040
#define SG_EXIT_DLL 0x0080
#define SG_GP_FAULT 0x0666

BOOL
CALLBACK NewSignalProc(
    HTASK hTask,
    WORD message,
    WPARAM wParam,
    LPARAM lParam
    )
{
    BOOL fRet;

    // Notify installable drivers this app is going away.
    if ( message == SG_EXIT || message == SG_GP_FAULT ) {
        InternalBroadcastDriverMessage( NULL, DRV_EXITAPPLICATION,
                                       (message == SG_GP_FAULT
                                                    ? DRVEA_ABNORMALEXIT
                                                    : DRVEA_NORMALEXIT),
                                       0L, IBDM_FIRSTINSTANCEONLY );
    }

    //
    // Pass notification on to WOW32 (which passes on to USER32)
    //

    fRet = SignalProc( hTask, message, wParam, lParam );

    //
    // After letting WOW32 and User32 cleanup, destroy the shadow
    // message queue created by InitApp.
    //

    if ( message == SG_EXIT || message == SG_GP_FAULT ) {
        DeleteQueue();
    }

    return fRet;
}

HINSTANCE LoadAliasedLibrary(LPCSTR szLibFileName,
			     LPCSTR szSection,
			     LPCSTR szIniFile,
			     LPSTR  lpstrTail,
			     WORD   cbTail)
{
  char          sz[128];
  LPSTR         pch;
  OFSTRUCT      os;
  HFILE         fd;
  HINSTANCE     h;
  WORD          errMode;

  if (!szLibFileName || !*szLibFileName)
      return((HINSTANCE)2); /* File not found */

  /* read the filename and additional info. into sz
   */
  GetPrivateProfileString(szSection,          // ini section
			  szLibFileName,      // key name
			  szLibFileName,      // default if no match
			  sz,                 // return buffer
			  sizeof(sz),         // return buffer size
			  szIniFile);         // ini. file

  sz[sizeof(sz)-1] = 0;

  /* strip off the additional info. Remember, DS!=SS so we need to get a lpstr
   * to our stack allocated sz.
   */
  pch = (LPSTR)sz;
  while (*pch)
    {
      if (*pch == ' ')
	{
	  *pch++ = '\0';
	  break;
	}
      pch++;
    }

  // pch pts to ch after first space or null ch

  fd = OpenFile(sz, &os, OF_EXIST|OF_SHARE_DENY_NONE);
  if (fd < 0)
      return((HINSTANCE)2);

  /* copy additional info. to lpstrTail
   */

  if (lpstrTail && cbTail)
    {
      while (cbTail-- && (*lpstrTail++ = *pch++))
	  ;
      *(lpstrTail-1) = 0;
    }

  errMode = SetErrorMode(0x8001);

  h = LoadLibrary(sz);

  SetErrorMode(errMode);

  return (h);
}



int GetDrvrUsage(HMODULE hModule)
/* effects: Runs through the driver list and figures out how many instances of
 * this driver module handle we have.  We use this instead of GetModuleUsage
 * so that we can have drivers loaded as normal DLLs and as installable
 * drivers.
 */
{
  LPDRIVERTABLE lpdt;
  int           index;
  int           count;

  if (!hInstalledDriverList || !cInstalledDrivers || !hModule)
      return(0);

  count = 0;

  lpdt = (LPDRIVERTABLE)MAKELP(hInstalledDriverList,0);

  for (index=0;index<cInstalledDrivers;index++)
    {
       if (lpdt->hModule==hModule)
           count++;

       lpdt++;
    }


  return(count);
}


BOOL PASCAL CheckValidDriverProc(LPDRIVERTABLE lpdt, HDRVR hdrv)
/* effects: Some vendors shipped multimedia style installable drivers with
 * bogus entry procs. This test checks for these bogus drivers and refuses to
 * install them.
 */
{
  WORD  currentSP;
  WORD  saveSP;

  _asm mov saveSP, sp
  (void)(lpdt->lpDriverEntryPoint)(0, hdrv, 0, 0L, 0L);
  _asm mov currentSP, sp
  _asm mov sp, saveSP

  if (saveSP != currentSP)
      DebugErr(DBF_ERROR, "Invalid driver entry proc address");

  return (saveSP == currentSP);
}

LRESULT FAR InternalLoadDriver(LPCSTR szDriverName,
			       LPCSTR szSectionName,
			       LPCSTR lpstrTail,
                               WORD   cbTail,
                               BOOL   fSendEnable)
{
  int           index;
  int           i;
  LPDRIVERTABLE lpdt;
  LPDRIVERTABLE lpdtBegin;
  LRESULT	result;
  HGLOBAL	h;
  HINSTANCE	hInstance;
  char          szDrivers[20];
  char          szSystemIni[20];

  /* Drivers receive the following messages: if the driver was loaded,
   * DRV_LOAD.  If DRV_LOAD returns non-zero and fSendEnable, DRV_ENABLE.
   */

  if (!hInstalledDriverList)
      h = GlobalAlloc(GHND|GMEM_SHARE, (DWORD)((WORD)sizeof(DRIVERTABLE)));
  else
      /* Alloc space for the next driver we will install. We may not really
       * install the driver in the last slot but rather in an intermediate
       * slot which was freed.
       */
      h = GlobalReAlloc(hInstalledDriverList,
                     (DWORD)((WORD)sizeof(DRIVERTABLE)*(cInstalledDrivers+1)),
                     GHND|GMEM_SHARE);

  if (!h)
      return(0L);

  cInstalledDrivers++;
  hInstalledDriverList = h;

  if (!szSectionName)
      LoadString(hInstanceWin, STR_DRIVERS, szDrivers, sizeof(szDrivers));
  LoadString(hInstanceWin, STR_SYSTEMINI, szSystemIni, sizeof(szSystemIni));

  lpdtBegin = lpdt = (LPDRIVERTABLE)MAKELP(hInstalledDriverList, NULL);

  /* Find an empty driver entry */
  for (i = 0; i < cInstalledDrivers; i++)
    {
      if (lpdt->hModule == NULL)
        {
          index = i;
	  break;
        }

      lpdt++;
    }

  if (index + 1 < cInstalledDrivers)
      /* The driver went into an unused slot in the middle somewhere so
       * decrement cInstalledDrivers count.
       */
      cInstalledDrivers--;

  /* Temporarly use an hModule to 1 to reserve this entry in case the driver
   * loads another driver in its LibMain.
   */
  lpdt->hModule = (HMODULE)1;

  hInstance = LoadAliasedLibrary(szDriverName,
                         szSectionName ? szSectionName : szDrivers,
                         szSystemIni,
                         lpstrTail,
                         cbTail);
  if (hInstance < HINSTANCE_ERROR)
    {
      lpdt->hModule = NULL;

      /* Load failed with an error. Return error code in highword.
       */
      return(MAKELRESULT(0, hInstance));
    }

  (FARPROC)lpdt->lpDriverEntryPoint = GetProcAddress(hInstance, "DriverProc");

  if (!lpdt->lpDriverEntryPoint)
    {
      FreeLibrary(hInstance);
      lpdt->hModule = 0;
      result = 0L;
      goto LoadCleanUp;
    }

  lpdt->hModule = hInstance;

  /* Save either the alias or filename of this driver. (depends on what the
   * app passed to us to load it)
   */
  lstrcpy(lpdt->szAliasName, szDriverName);

  if (GetDrvrUsage(hInstance) == 1)
    {
      /* If this is the first instance, send the drv_load message. Don't use
       * SendDriverMessage because we haven't initialized the linked list yet
       */
      if (!CheckValidDriverProc(lpdt, (HDRVR)(index+1)) ||
          !(lpdt->lpDriverEntryPoint)(lpdt->dwDriverIdentifier,
				      (HDRVR)(index+1),
                                      DRV_LOAD,
                                      0L, 0L))
        {
          /* Driver failed load call.
	   */
          lpdt->lpDriverEntryPoint = NULL;
          lpdt->hModule = NULL;
	  FreeLibrary(hInstance);
          result = 0L;
          goto LoadCleanUp;
        }

      lpdt->fFirstEntry = 1;
    }

  /* Put driver in the load order linked list
   */
  if (idFirstDriver == -1)
    {
      /* Initialize everything when first driver is loaded.
       */
      idFirstDriver      = index;
      idLastDriver       = index;
      lpdt->idNextDriver = -1;
      lpdt->idPrevDriver = -1;
    }
  else
    {
      /* Insert this driver at the end of the load chain.
       */
      lpdtBegin[idLastDriver].idNextDriver = index;
      lpdt->idPrevDriver = idLastDriver;
      lpdt->idNextDriver = -1;
      idLastDriver = index;
    }

  if (fSendEnable && lpdt->fFirstEntry)
      SendDriverMessage((HDRVR)(index+1), DRV_ENABLE, 0L, 0L);

  result = MAKELRESULT(index+1, hInstance);

LoadCleanUp:
  return(result);
}



WORD FAR InternalFreeDriver(HDRVR hDriver, BOOL fSendDisable)
{
  LPDRIVERTABLE lpdt;
  WORD          w;
  int           id;

  /*  The driver will receive the following message sequence:
   *
   *      if usage count of driver is 1
   *          DRV_DISABLE (normally)
   *          DRV_FREE
   */

  if ((int)hDriver > cInstalledDrivers || !hDriver)
      return(0);

  lpdt = (LPDRIVERTABLE)MAKELP(hInstalledDriverList,0);

  if (!lpdt[(int)hDriver-1].hModule)
      return(0);

  /* If the driver usage count is 1, then send free and disable messages.
   */

  /* Clear dwDriverIdentifier so that the sendmessage for DRV_OPEN and
   * DRV_ENABLE have dwDriverIdentifier = 0 if an entry gets reused and so
   * that the DRV_DISABLE and DRV_FREE messages below also get
   * dwDriverIdentifier = 0.
   */

  lpdt[(int)hDriver-1].dwDriverIdentifier = 0;

  w = GetDrvrUsage(lpdt[(int)hDriver-1].hModule);
  if (w == 1)
    {
      if (fSendDisable)
	  SendDriverMessage(hDriver, DRV_DISABLE, 0L, 0L);
      SendDriverMessage(hDriver, DRV_FREE, 0L, 0L);
    }
  FreeLibrary(lpdt[(int)hDriver-1].hModule);

  // Clear the rest of the table entry

  lpdt[(int)hDriver-1].hModule = 0;	       // this indicates free entry
  lpdt[(int)hDriver-1].fFirstEntry = 0;        // this is also just to be tidy
  lpdt[(int)hDriver-1].lpDriverEntryPoint = 0; // this is also just to be tidy

  /* Fix up the driver load linked list */
  if (idFirstDriver == (int)hDriver-1)
    {
      idFirstDriver = lpdt[(int)hDriver-1].idNextDriver;
      if (idFirstDriver == -1)
        {
          /* No more drivers in the chain */
          idFirstDriver    = -1;
          idLastDriver     = -1;
          cInstalledDrivers= 0;
          goto Done;
        }
      else
        {
          /* Make prev entry of new first driver -1 */
          lpdt[idFirstDriver].idPrevDriver = -1;
        }
    }
  else if (idLastDriver == (int)hDriver-1)
    {
      /* We are freeing the last driver. So find a new last driver. */
      idLastDriver = lpdt[(int)hDriver-1].idPrevDriver;
      lpdt[idLastDriver].idNextDriver = -1;
    }
  else
    {
      /* We are freeing a driver in the middle of the list somewhere. */
      id = lpdt[(int)hDriver-1].idPrevDriver;
      lpdt[id].idNextDriver = lpdt[(int)hDriver-1].idNextDriver;

      id = lpdt[(int)hDriver-1].idNextDriver;
      lpdt[id].idPrevDriver = lpdt[(int)hDriver-1].idPrevDriver;
    }

Done:
  return(w-1);
}




LRESULT InternalOpenDriver(LPCSTR szDriverName,
				   LPCSTR szSectionName,
				   LPARAM lParam2,
                                   BOOL  fSendEnable)
{
  HDRVR 	hDriver;
  LPDRIVERTABLE lpdt;
  LRESULT	result;
  char          sz[128];

  if (hDriver = (HDRVR)LOWORD(InternalLoadDriver(szDriverName, szSectionName,
					  sz, sizeof(sz), fSendEnable)))
    {
      /* Set the driver identifier to the DRV_OPEN call to the driver
       * handle. This will let people build helper functions that the driver
       * can call with a unique identifier if they want to.
       */

      lpdt = (LPDRIVERTABLE)MAKELP(hInstalledDriverList,0);

      lpdt[(int)hDriver-1].dwDriverIdentifier = (DWORD)(WORD)hDriver;

      result = SendDriverMessage(hDriver,
				 DRV_OPEN,
				 (LPARAM)(LPSTR)sz,
				 lParam2);
      if (!result)
	  InternalFreeDriver(hDriver, fSendEnable);
      else
	{
	  lpdt = (LPDRIVERTABLE)MAKELONG(0,hInstalledDriverList);

	  lpdt[(int)hDriver-1].dwDriverIdentifier = (DWORD)result;

	  result = (LRESULT)(DWORD)(WORD)hDriver;
	}
    }
  else
      result = 0L;

  return(result);
}


LRESULT InternalCloseDriver(HDRVR hDriver, LPARAM lParam1, LPARAM lParam2, BOOL fSendDisable)
{
  LPDRIVERTABLE lpdt;
  LRESULT	result;
  int           index;
  BOOL          f;
  HMODULE       hm;

  // check handle in valid range.

  if ((int)hDriver > cInstalledDrivers)
      return(FALSE);

  lpdt = (LPDRIVERTABLE)MAKELP(hInstalledDriverList,0);

  if (!lpdt[(int)hDriver-1].hModule)
      return(FALSE);

  result = SendDriverMessage(hDriver, DRV_CLOSE, lParam1, lParam2);

  if (result)
    {
      // Driver didn't abort close

      f  = lpdt[(int)hDriver-1].fFirstEntry;
      hm = lpdt[(int)hDriver-1].hModule;

      if (InternalFreeDriver(hDriver, fSendDisable) && f)
	{
          lpdt = (LPDRIVERTABLE)MAKELP(hInstalledDriverList,0);

	  /* Only one entry for the driver in the driver list has the first
	   * instance flag set. This is to make it easier to handle system
	   * messages that only need to be sent to a driver once.
	   *
	   * To maintain the flag, we must set the flag in one of the other
	   * entries if we remove the driver entry with the flag set.
	   *
	   * Note that InternalFreeDriver returns the new usage count of
	   * the driver so if it is zero, we know that there are no other
	   * entries for the driver in the list and so we don't have to
	   * do this loop.
	   */

	  for (index=0;index<cInstalledDrivers;index++)
	      if (lpdt[index].hModule == hm && !lpdt[index].fFirstEntry)
		{
		  lpdt[index].fFirstEntry = 1;
		  break;
		}
	}

    }

  return(result);
}


HDRVR API IOpenDriver(LPCSTR szDriverName, LPCSTR szSectionName, LPARAM lParam)
{
  LRESULT result;

  /* The driver receives the following messages when it is opened. If it isn't
   * loaded, the library is loaded and the DRV_LOAD message is sent.  If
   * DRV_LOAD returns nonzero, the DRV_ENABLE message is sent.  Once the
   * driver is loaded or if it was previously loaded, the DRV_OPEN message is
   * sent.
   */
  result = InternalOpenDriver(szDriverName, szSectionName, lParam, TRUE);

  return((HDRVR)LOWORD(result));
}


LRESULT API ICloseDriver(HDRVR hDriver, LPARAM lParam1, LPARAM lParam2)
{
  /*  The driver will receive the following message sequence:
   *
   *      DRV_CLOSE
   *      if DRV_CLOSE returns non-zero
   *          if driver usage count = 1
   *              DRV_DISABLE
   *              DRV_FREE
   */

   return(InternalCloseDriver(hDriver, lParam1, lParam2, TRUE));
}


HINSTANCE API IGetDriverModuleHandle(HDRVR hDriver)
/* effects: Returns the module handle associated with the given driver ID.
 */
{
  LPDRIVERTABLE lpdt;
  HINSTANCE hModule = NULL;

  if (hDriver && ((int)hDriver <= cInstalledDrivers))
    {
      lpdt = (LPDRIVERTABLE)MAKELP(hInstalledDriverList,0);

      return lpdt[(int)hDriver-1].hModule;
    }
  else
    return NULL;
}
