
/** @file driver.c
 *
 * Hlavni zdrojovy soubor ovladace.
 *
 * Obsahuje funkce a procedury, ktere vyzaduje specifikace architektury
 * ovladacu jadra. Obstarava inicializaci behem nacitani do jadra, uklid
 * pri odstranovani a komunikaci s aplikacemi.
 */

#include <ntddk.h>
#include "ioctls.h"
#include "lists.h"
#include "events.h"
#include "notify_events.h"
#include "notify.h"
#include "driver.h"


VOID DriverUnload(PDRIVER_OBJECT DriverObject);

/** Obsluha volani funkci Windows API CreateFile a Closehandle na nase
 *  komunikacni zarizeni. 
 *
 * Instruujeme system, ze obsluha pozadavku je
 * ukoncena a vratime uspech (STATUS_SUCCESS).
 *
 * @param DeviceObject Struktura DEVICE_OBJECT, ktera reprezentuje nase 
 *   zarizeni.
 * @param Irp Pozadavek na nase zarizeni (vygenerovany volanim CreateFile,
 *   nebo CloseHandle).
 */
NTSTATUS DriverCreateClose(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
  NTSTATUS status = STATUS_UNSUCCESSFUL;
  KdPrint(("driver.c: DriverCreateClose(DeviceObject=0x%p; Irp=0x%p)\n", DeviceObject, Irp));

  // Upozornime system, aby povazoval pozadavek (IRP) za obslouzeny a vratime uspech
  IoCompleteRequest(Irp, IO_NO_INCREMENT);
  status = STATUS_SUCCESS;

  KdPrint(("driver.c: DriverCreateClose(-):0x%x\n", status));
  return status;
}


/** Obsluha volani funkce Windows API DeviceIoControlFile na nase komunikacni
 *  zarizeni.
 *
 * Tato rutina se vyvola, kdykoliv nejaka aplikace zavola funkci DeviceIoControl
 * a posle tak pozadavek nasemu zarizeni. DeviceIoControl je cesta, kterou muze
 * s nasim ovladacem komunikovat libovolna aplikace.
 *
 * Ovladac podporuje pouze jeden druh pozadavku - pozadavek na vraceni zaznamenane
 * udalosti ze zivota procesu, vlaken ci modulu (knihoven DLL a ovladacu jadra).
 * 
 * Pokud je seznam prazdny, rutina vrati chybu STATUS_NO_MORE_ENTRIES. Pokud
 * seznam udalosti neni prazdny a zadajici aplikace poskytuje dostatecne velky
 * buffer pro zapsani udalosti, prvni udalost se odebere ze seznamu a zkopiruje
 * do vystupniho bufferu. Je-li velikost bufferu nedostatecna, rutina vrati chybu
 * STATUS_BUFFER_TOO_SMALL.
 *
 * @param DeviceObject Struktura DEVICE_OBJECT reprezentujici nase zarizeni.
 * @param Pozadavek od volajici aplikace (vygenerovany volanim DeviceIoControl).
 */
NTSTATUS DriverDeviceControl(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
  PVOID OutBuffer = NULL;
  ULONG ControlCode = 0;
  ULONG OutBufferLength = 0;
  PIO_STACK_LOCATION IrpStack = NULL;
  NTSTATUS status = STATUS_UNSUCCESSFUL;
  KdPrint(("driver.c: DriverDeviceControl(DeviceObject=0x%p; Irp=0x%p)\n", DeviceObject, Irp));

  /* Zjistime adresu a velikost vystupniho bufferu a kod zpravy, kterou
     aplikace posila. Velikost vstupniho bufferu nema smysl zjistovat, 
     protoze veskere potrebne informace dostaneme v kodu zpravy.
   */
  IrpStack = IoGetCurrentIrpStackLocation(Irp);
  OutBufferLength = IrpStack->Parameters.DeviceIoControl.OutputBufferLength;
  OutBuffer = Irp->AssociatedIrp.SystemBuffer;
  ControlCode = IrpStack->Parameters.DeviceIoControl.IoControlCode;
  Irp->IoStatus.Information = 0;

  // Zjistime, o ktery prikaz se jedna
  switch (ControlCode) {
    // Poslat aplikaci prvni zpravu ze seznamu udalosti
    case IOCTL_LOGPTM_GET_EVENT: {
      KLOCK_QUEUE_HANDLE QHandle;
      PEVENT e = NULL;

      EventListLock(&QHandle);
      // Ziskame prvni udalost ze seznamu udalosti
      e = EventGetFirst();
      if (e != NULL) {
        // Seznam je neprazdny. Zjistime, zda je vystupni buffer dostatecne velky    
        if (EVENT_DATA_SIZE(e) + sizeof(UM_EVENT) <= OutBufferLength) {
          /*
             Nastavime promenne na adresy prvni udalosti ze seznamu a vystupniho
             bufferu. Bude se snadneji kopirovat.
           */
          PUM_EVENT ue = (PUM_EVENT)OutBuffer;    

          // Vyplnime obecne polozky udalosti a zkopirujeme jeji data
          ue->Type = e->Type;
          ue->Size = EVENT_DATA_SIZE(e) + sizeof(UM_EVENT);
          ue->Time.QuadPart = e->Time.QuadPart;
          RtlCopyMemory(UM_EVENT_TO_DATA(ue), EVENT_TO_DATA(e), EVENT_DATA_SIZE(e));

          // Nastavime pocet bytu, ktere ma spravce vstupne/vystupnich zarizeni
          // zkopirovat do rpivatni casti pametoveho prostoru aplikace.
          Irp->IoStatus.Information = ue->Size;
          status = STATUS_SUCCESS;

          // Uvolnime pamet zabranou zaznamem o udalosti
          EventFree(e);
        } else {
          EventInsertFirst(e);
          status = STATUS_BUFFER_TOO_SMALL;
        }
      } else status = STATUS_NO_MORE_ENTRIES;

      EventListUnlock(&QHandle);
    }
      break;

    // Jine pozadavky ovladac neobsluhuje, nejsou implementovany
    default:
      KdPrint(("driver.c: DriverDeviceControl(): Invalid Device control request\n"));
      status = STATUS_NOT_IMPLEMENTED;
      break;
  }  

  // Sdelime systemu, ze pozadavek aplikace je obslouzen
  Irp->IoStatus.Status = status;
  IoCompleteRequest(Irp, IO_NO_INCREMENT);

  KdPrint(("driver.c: DriverDeviceControl(-):0x%x\n", status));
  return status;
}

NTSTATUS DriverInit(PDRIVER_OBJECT DriverObject)
{
  UNICODE_STRING udevice;
  UNICODE_STRING usymlink;
  NTSTATUS Status = STATUS_UNSUCCESSFUL;
  KdPrint(("driver.c: DriverInit(DriverObject=0x%p)\n", DriverObject));

  // Vytvorime zarizeni, pres ktere s budou s ovladacem komunikovat aplikace
  RtlInitUnicodeString(&udevice, LOGPTM_DEVICE);
  Status = IoCreateDevice(DriverObject, 0, &udevice, FILE_DEVICE_UNKNOWN, 0, FALSE, &DriverObject->DeviceObject);
  if (NT_SUCCESS(Status)) {
    // Komunikacni zarizeni bylo vytvoreno v oblasti, do ktere normalni aplikace
    // nemaji za vyuziti dokumentovanych prostredku pristup. Zarizeni zpristupnime
    // vytvorenim symbolickeho odkazu
    RtlInitUnicodeString(&usymlink, LOGPTM_SYMLINK);
    Status = IoCreateSymbolicLink(&usymlink, &udevice);
    if (NT_SUCCESS(Status)) {
      // Nastavime rutinu, kterou ma system zavolat pri odstranovani ovladace
      // z jadra. Neni-li tato rutina nastavena, ovladac nelze z jadra odstranit
      DriverObject->DriverUnload = DriverUnload;

      // Nastavime funkce, ktere system preda rizeni, kdykoliv nejaka aplikace
      // zavola CreateFile ci CloseHandle na nase zarizeni
      DriverObject->MajorFunction[IRP_MJ_CREATE] = DriverCreateClose;
      DriverObject->MajorFunction[IRP_MJ_CLOSE] = DriverCreateClose;
  
      // Nase odpoved na volani DeviceIoControl
      DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DriverDeviceControl;
    } else IoDeleteDevice(DriverObject->DeviceObject);
  }

  KdPrint(("driver.c: DriverInit(-):0x%x\n", Status));
  return Status;
}

VOID DriverFinit(PDRIVER_OBJECT DriverObject)
{
  UNICODE_STRING usymlink;
  KdPrint(("driver.c: DriverFinit(DriverObject=0x%p)\n", DriverObject));

  // Smazeme symbolicky odkaz, takze dalsi aplikace se nemohou pomoci volani
  // CreateFile pripojit na nase zarizeni.
  RtlInitUnicodeString(&usymlink, LOGPTM_SYMLINK);
  IoDeleteSymbolicLink(&usymlink);

  // Odstranime nase komunikacni zarizeni ze systemu
  IoDeleteDevice(DriverObject->DeviceObject);

  KdPrint(("driver.c: DriverFinit(-)\n"));
  return;
}

/** Rutina se vola tesne pred odstranenim ovladace z jadra systemu.
 *
 * Tato procedure je mistem, kde musime uklidit vse, co ovladac vytvoril.
 * Po skonceni vykonavani DriverUnload bude ovladac okamzite odstranen z
 * pameti.
 *
 * @param Struktura DRIVER_OBJECT reprezentujici nas ovladac.
 */
VOID DriverUnload(IN PDRIVER_OBJECT DriverObject)
{
  KdPrint(("driver.c: DriverUnload(DriverObject=0x%p)\n", DriverObject));

  // Odstranime symbolicky link a objekt zarizeni
  DriverFinit(DriverObject);

  // Odregistrujeme notifikacni rutiny a uvolnime pamet, kterou zabira
  // seznam udalosti
  NotifyFinit();
  EventsFinit();

  KdPrint(("driver.c: DriverUnload(-)\n"));
  return;
}


/** Vstupni rutina ovladace.
 *
 * Tato funkce je jadrem zavolana v posledni fazi procesu nacitani ovladace
 * do pameti. Je to idealni misto pro inicializace vsech prostredku, ktere
 * bude ovladac vyuzivat behem sve aktivity. 
 *
 * Uspesne dokonceni nacteni do jadra zavisi na navratovem hodnote teto rutiny.
 * 
 * @param DriverObject Struktura DRIVER_OBJECT popisujici nas ovladac.
 * @param RegistryPath Plny nazev klice sluzby reprezentujici nas ovladac.
 */
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
  NTSTATUS Status = STATUS_UNSUCCESSFUL;
  KdPrint(("driver.c: DriverEntry(DriverObject=0x%p; RegistryPath=\"%S\")\n", DriverObject, RegistryPath->Buffer));

  // Inicializujeme seznam, do ktereho budeme zaznamenavat prichazeici udalosti
  // o procesech, vlaknech a modulech
  Status = EventsInit();
  if (NT_SUCCESS(Status)) {
    // Zaregistrujeme notifikacni rutiny, ktere system zavola pri vytvareni/ukoncovani
    // procesu a vlakna a behem nacitani noveho modulu do pameti
    Status = NotifyInit();
    if (NT_SUCCESS(Status)) {
      Status = DriverInit(DriverObject);
      if (!NT_SUCCESS(Status)) {
        // Pokud se neco nezdarilo, musime vse uvest do puvodniho stavu
        NotifyFinit();
        EventsFinit();
      }
    } else EventsFinit();
  }

  KdPrint(("driver.c: DriverEntry(-):0x%x\n", Status));
  return Status;
}
