
/**
 * @file: keyedevent.c
 *
 * Tento soubor implementuje nekolik Windows API-like funkci, jejichz cilem je 
 * zesnadnit praci s udalosti na klic (Keyed Event), nedokumentovanym synchronizacnim
 * primitivem dostupnym od Windows XP a vylepsenym ve Windows Vista.
 *
 * Udalost na klic podporuje dve operace:
 *   WAIT(k) - uspi vlakno, dokud se nekdo jiny nerozhodne provest operaci probuzeni
 *     s klicem K.
 *   RELEASE(K) - probudi vlakno uspane pomoci operace WAIT na klici K. Pokud takove
 *     vlakno neexistuje, probouzeci vlakno usne, dokud se takove vlakno neobjevi - tzn.
 *     dokud nebude existovat vlakno, ktere muze byt probuzeno.
 *
 *   Jedna se tedy mimo jine o reseni problemu randez-vous pro dve entity.
 *   Klic K je ukazatel a jeho hodnota je jedinecna v ramci procesu, ne celeho systemu.
 ¨*  Klic musi byt sude cislo.
 */

#include <stdio.h>
#include <windows.h>
#include <winternl.h>
#include "keyedevent.h"




// Definice konstant NTSTATUS, ktere jsou potrebne, ale nejsou obsazene ve standardnich
// hlavickovych souborech pro uzivatelsky rezim
#define STATUS_SUCCESS         0x00000000L
#define STATUS_UNSUCCESSFUL    0xC0000001L
#define STATUS_ABANDONED       0x00000080L


// Rutiny pro praci s udalostmi na klic jsou hledany dynamicky, je tedy nutne
// zadefinovat jejich prototypy. Rutina RtlInitUnicodeString take neni importovana
// prostrednictvim standardnich hlavickovych souboru.
typedef NTSTATUS (NTAPI *NTCREATEKEYEDEVENT)(PHANDLE KeytedEventHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, ULONG Flags);
typedef NTSTATUS (NTAPI *NTOPENKEYEDEVENT)(PHANDLE KeyedEventHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes);
typedef NTSTATUS (NTAPI *NTRELEASEKEYEDEVENT)(HANDLE KeyedEventHandle, PVOID KeyValue, BOOLEAN Alertable, PLARGE_INTEGER Timeout);
typedef NTSTATUS (NTAPI *NTWAITFORKEYEDEVENT)(HANDLE KeyedEventHandle, PVOID KeyValue, BOOLEAN Alertable, PLARGE_INTEGER Timeout);
typedef ULONG (WINAPI *RTLNTSTATUSTODOSERROR)(NTSTATUS Status);

typedef VOID (NTAPI *RTLINITUNICODESTRING)(PUNICODE_STRING Dest, PCWSTR WideString);


// Promenne pro ulozeni adres jednotlivych dynamicky hledanych rutin.
static NTCREATEKEYEDEVENT _NtCreateKeyedEvent = NULL;
static NTOPENKEYEDEVENT _NtOpenKeyedEvent = NULL;
static NTRELEASEKEYEDEVENT _NtReleaseKeyedEvent = NULL;
static NTWAITFORKEYEDEVENT _NtWaitForKeyedEvent = NULL;

static RTLINITUNICODESTRING _RtlInitUnicodeString = NULL;
static RTLNTSTATUSTODOSERROR _RtlNtStatusToDosError = NULL;


/** Vytvori objekt udalosti na klic.
 *
 * Pokud volajici specifikuje jmeno, rutina pred nej vlozi prefix \BaseNamedObjects\.
 * Pokud jiz objekt daneho jmena existuje, chovani rutiny je shodne s OpenKeyedEvent.
 *
 * @param DesiredAccess Tento parametr specifikuje, jakymi pristupovymi pravy ma handle
 * k nove vytvorenemu objektu disponovat. Moznosti jsou nasledujici:
 *   @value KEYEDEVENT_WAIT Dovoluje provadet operaci WAIT(K) (cekani).
 *   @value KEYEDEVENT_WAKE Dovoluje provadet operaci RELEASE(K) (probouzeni).
 *   @value KEYEDEVENT_ALL_ACCESS Uplny pristup
 * @param Name Udava jmeno nove vytvoreneho objektu. Rutina k nemu automaticky prida
 * prefix \BaseNamedObjects\. Pokud ma tento parametr hodnotu NULL, rutina se pokusi
 * vytvorit bezejmennou udalost na klic.
 * 
 * @return Vraci handle nove vytvoreneho objektu udalosti na klic. V pripade neuspechu
 * vraci hodnotu NULL.
 */
HANDLE CreateKeyedEvent(DWORD DesiredAccess, PCWSTR Name)
{
  NTSTATUS Status = STATUS_UNSUCCESSFUL;
  HANDLE Ret = NULL;
  UNICODE_STRING uName;
  WCHAR FullName[300];
  PUNICODE_STRING ObjectName = NULL;
  OBJECT_ATTRIBUTES ObjectAttributes;

  if (Name != NULL) {
    ZeroMemory(&FullName, sizeof(FullName));
    _snwprintf(FullName, 299, L"\\BaseNamedObjects\\%s", Name);
    _RtlInitUnicodeString(&uName, (PCWSTR)&FullName);
    ObjectName = &uName;
  }

  InitializeObjectAttributes(&ObjectAttributes, ObjectName, OBJ_CASE_INSENSITIVE | OBJ_OPENIF, NULL, NULL);
  Status = _NtCreateKeyedEvent(&Ret, DesiredAccess, &ObjectAttributes, 0);
  SetLastError(_RtlNtStatusToDosError(Status));

  return Ret;
}


/** Vytvori handle k existujicimu objektu udalosti na klic.
 *
 * @param DesiredAccess Tento parametr specifikuje, jakymi pristupovymi pravy ma nove
 * handle k objektu disponovat. Moznosti jsou nasledujici:
 *   @value KEYEDEVENT_WAIT Dovoluje provadet operaci WAIT(K) (cekani).
 *   @value KEYEDEVENT_WAKE Dovoluje provadet operaci RELEASE(K) (probouzeni).
 *   @value KEYEDEVENT_ALL_ACCESS Uplny pristup
 * @param Name Udava jmeno ciloveho objektu. Rutina k nemu automaticky prida
 * prefix \BaseNamedObjects\.
 * 
 * @return Vraci handle objektu udalosti na klic. V pripade neuspechu
 * vraci hodnotu NULL.
 */
HANDLE OpenKeyedEvent(DWORD DesiredAccess, PCWSTR Name)
{
  NTSTATUS Status = STATUS_UNSUCCESSFUL;
  HANDLE Ret = NULL;
  UNICODE_STRING uName;
  WCHAR FullName[300];
  OBJECT_ATTRIBUTES ObjectAttributes;

  if (Name != NULL) {
    ZeroMemory(&FullName, sizeof(FullName));
    _snwprintf(FullName, 299, L"\\BaseNamedObjects\\%s", Name);
    _RtlInitUnicodeString(&uName, (PCWSTR)&FullName);
    InitializeObjectAttributes(&ObjectAttributes, &uName, OBJ_CASE_INSENSITIVE | OBJ_OPENIF, NULL, NULL);
    Status = _NtOpenKeyedEvent(&Ret, DesiredAccess, &ObjectAttributes);
    SetLastError(_RtlNtStatusToDosError(Status));
  } else SetLastError(ERROR_INVALID_PARAMETER);

  return Ret;
}


/** Provede na zadane udalosti na klic operaci RELEASE(K).
 * 
 * @param KeyedEvent Handle cilove udalosti
 * @param Key Klic, ktery ma byt pouzit pri operaci RELEASE.
 * @param Alertable Pokud je tento parametr nastaven na TRUE, vlakno i behem cekani na
 * operaci WAIT(K) muze obsluhovat mechanismus asynchronni volani procedur.
 * @param Timeout Pocet 100-nanosekundovych jednotek, po ktery se ma vlakno pokouset
 * provest operaci RELEASE(K). Hodnota NULL ma stejny vyznam jako konstanta INFINITE
 * u rutiny WaitForSingleObject.
 *
 * @return Vraci jednu z nasledujicich hodnot:
 *   @value WAIT_OBJECT_0 Operace RELEASE(K) probehla uspesne a zpusobila probuzeni
 *   jineho vlakna.
 *   @value WAIT_TIMEOUT Casovy interval zadany v parametru Timeout vyprsel, aniz by se
 *   podarilo operace RELEASE(K) provest.
 *   @value WAIT_FAILED Operace RELEASE(K) selhala.
 */
DWORD ReleaseKeyedEvent(HANDLE KeyedEvent, PVOID Key, BOOL Alertable, PLARGE_INTEGER Timeout)
{
  DWORD Ret = 0;
  NTSTATUS Status = STATUS_UNSUCCESSFUL;

  Status = _NtReleaseKeyedEvent(KeyedEvent, Key, Alertable, Timeout);
  switch (Status) {
    case STATUS_SUCCESS: Ret = WAIT_OBJECT_0; break;
    case STATUS_TIMEOUT: Ret = WAIT_TIMEOUT; break;
    default: Ret = WAIT_FAILED; break;
  }

  return Ret;
}


/** Provede na zadane udalosti na klic operaci WAIT(K).
 * 
 * @param KeyedEvent Handle cilove udalosti
 * @param Key Klic, ktery ma byt pouzit pri operaci WAIT.
 * @param Alertable Pokud je tento parametr nastaven na TRUE, vlakno i behem cekani na
 * operaci RELEASE(K) muze obsluhovat mechanismus asynchronni volani procedur.
 * @param Timeout Pocet 100-nanosekundovych jednotek, po ktery se ma vlakno pokouset
 * provest operaci WAIT(K). Hodnota NULL ma stejny vyznam jako konstanta INFINITE
 * u rutiny WaitForSingleObject.
 *
 * @return Vraci jednu z nasledujicich hodnot:
 *   @value WAIT_OBJECT_0 Operace WAIT(K) probehla uspesne a zpusobila probuzeni
 *   jineho vlakna.
 *   @value WAIT_TIMEOUT Casovy interval zadany v parametru Timeout vyprsel, aniz by se
 *   podarilo operace WAIT(K) provest.
 *   @value WAIT_FAILED Operace WAIT(K) selhala.
 */
DWORD WaitForKeyedEvent(HANDLE KeyedEvent, PVOID Key, BOOL Alertable, PLARGE_INTEGER Timeout)
{
  DWORD Ret = 0;
  NTSTATUS Status = STATUS_UNSUCCESSFUL;

  Status = _NtWaitForKeyedEvent(KeyedEvent, Key, Alertable, Timeout);
  switch (Status) {
    case STATUS_SUCCESS: Ret = WAIT_OBJECT_0; break;
    case STATUS_TIMEOUT: Ret = WAIT_TIMEOUT; break;
    default: Ret = WAIT_FAILED; break;
  }

  return Ret;
}



/** Inicializuje modul pro snadnejsi praci s udalostmi na klic.
 *
 * Inicializace spociva v tom, ze se rutina pokusi nalezt adresy funkci NtCreateKeyedEvent,
 * NtOpenKeyedEvent, NtReleaseKeyedEvent, NtWaitForKeyedEvent a RtlInitUnicodeString.
 * K tomuto ucelu vyuziva dvojice funkci Windows API GetModuleHandleW a GetProccAddress.
 *
 * @return Funkce vraci TRUE, pokud se adresy vsech potrebnych rutin podari najit. V
 * opacnem pripade vraci FALSE.
 */
BOOL KeyedEventInit(VOID)
{
  BOOL Ret = FALSE;
  HMODULE hNtdll = NULL;

  hNtdll = GetModuleHandleW(L"ntdll.dll");
  Ret = hNtdll != NULL;
  if (Ret) {
    _NtCreateKeyedEvent = (NTCREATEKEYEDEVENT)GetProcAddress(hNtdll, "NtCreateKeyedEvent");
    _NtOpenKeyedEvent = (NTOPENKEYEDEVENT)GetProcAddress(hNtdll, "NtOpenKeyedEvent");
	_NtReleaseKeyedEvent = (NTRELEASEKEYEDEVENT)GetProcAddress(hNtdll, "NtReleaseKeyedEvent");
	_NtWaitForKeyedEvent = (NTWAITFORKEYEDEVENT)GetProcAddress(hNtdll, "NtWaitForKeyedEvent");
	_RtlInitUnicodeString = (RTLINITUNICODESTRING)GetProcAddress(hNtdll, "RtlInitUnicodeString");
    _RtlNtStatusToDosError = (RTLNTSTATUSTODOSERROR)GetProcAddress(hNtdll, "RtlNtStatusToDosError");
    Ret = (_NtCreateKeyedEvent != NULL && _NtOpenKeyedEvent != NULL &&
           _NtReleaseKeyedEvent != NULL && _NtWaitForKeyedEvent != NULL &&
           _RtlInitUnicodeString != NULL && _RtlNtStatusToDosError != NULL);
  } else printf("KeyedEventInit(): Nepodarilo se najit ntdll.dll. Chyba %u\n", GetLastError());

  return Ret;
}


/** Provede uklid po modulu pro praci s udalostmi na klic.
 *
 * Protoze pri inicializaci nedochazi k zmenam v jadre ani v adresovem prostoru procesu,
 * tato rutina nedela nic.
 */
VOID KeyedEventFinit(VOID)
{
  return;
}
