[NTOS:PS]
authorThomas Faber <thomas.faber@reactos.org>
Sat, 5 Sep 2015 11:49:54 +0000 (11:49 +0000)
committerThomas Faber <thomas.faber@reactos.org>
Sat, 5 Sep 2015 11:49:54 +0000 (11:49 +0000)
- Implement NtApphelpCacheControl. Patch by Mark Jansen
CORE-9914 #resolve

svn path=/trunk/; revision=69022

reactos/boot/bootdata/hivesys.inf
reactos/ntoskrnl/include/internal/ps.h
reactos/ntoskrnl/io/iomgr/iomgr.c
reactos/ntoskrnl/ntos.cmake
reactos/ntoskrnl/po/poshtdwn.c
reactos/ntoskrnl/ps/apphelp.c [new file with mode: 0644]
reactos/ntoskrnl/ps/psmgr.c
rostests/apitests/ntdll/CMakeLists.txt
rostests/apitests/ntdll/NtApphelpCacheControl.c [new file with mode: 0644]
rostests/apitests/ntdll/testlist.c

index af1ad5a..191955c 100644 (file)
@@ -1280,6 +1280,9 @@ HKLM,"SYSTEM\CurrentControlSet\Control\Session Manager","GlobalFlag", 0x00010003
 HKLM,"SYSTEM\CurrentControlSet\Control\Session Manager","ObjectDirectories",0x00010000, \
  "\Windows", \
  "\RPC Control"
+# This is an empty app compat cache
+HKLM,"SYSTEM\CurrentControlSet\Control\Session Manager\AppCompatCache","AppCompatCache", 0x00000001, \
+  fe,0f,dc,ba,00,00,00,00
 
 ; DOS devices
 HKLM,"SYSTEM\CurrentControlSet\Control\Session Manager\DOS Devices","AUX",0x00000000,"\DosDevices\COM1"
index efe81be..86df783 100644 (file)
@@ -412,6 +412,18 @@ BOOLEAN
 NTAPI
 PspIsProcessExiting(IN PEPROCESS Process);
 
+//
+// Apphelp functions
+//
+NTSTATUS
+NTAPI
+INIT_FUNCTION
+ApphelpCacheInitialize(VOID);
+
+VOID
+NTAPI
+ApphelpCacheShutdown(VOID);
+
 //
 // Global data inside the Process Manager
 //
index aa01098..5332c57 100644 (file)
@@ -531,6 +531,9 @@ IoInitSystem(IN PLOADER_PARAMETER_BLOCK LoaderBlock)
     /* Initialize PnP manager */
     IopInitializePlugPlayServices();
 
+    /* Initialize SHIM engine */
+    ApphelpCacheInitialize();
+
     /* Initialize WMI */
     WmiInitialize();
 
index f51673c..10880e2 100644 (file)
@@ -248,6 +248,7 @@ list(APPEND SOURCE
     ${REACTOS_SOURCE_DIR}/ntoskrnl/ps/job.c
     ${REACTOS_SOURCE_DIR}/ntoskrnl/ps/kill.c
     ${REACTOS_SOURCE_DIR}/ntoskrnl/ps/process.c
+    ${REACTOS_SOURCE_DIR}/ntoskrnl/ps/apphelp.c
     ${REACTOS_SOURCE_DIR}/ntoskrnl/ps/psmgr.c
     ${REACTOS_SOURCE_DIR}/ntoskrnl/ps/psnotify.c
     ${REACTOS_SOURCE_DIR}/ntoskrnl/ps/query.c
index 8b3981c..3ebf899 100644 (file)
@@ -267,6 +267,9 @@ PopGracefulShutdown(IN PVOID Context)
     DPRINT("HAL shutting down\n");
     HalEndOfBoot();
 
+    /* Shut down the Shim cache if enabled */
+    ApphelpCacheShutdown();
+
     /* In this step, the I/O manager does first-chance shutdown notification */
     DPRINT("I/O manager shutting down in phase 0\n");
     IoShutdownSystem(0);
diff --git a/reactos/ntoskrnl/ps/apphelp.c b/reactos/ntoskrnl/ps/apphelp.c
new file mode 100644 (file)
index 0000000..d6c24dc
--- /dev/null
@@ -0,0 +1,739 @@
+/*
+ * PROJECT:         ReactOS Kernel
+ * LICENSE:         BSD - See COPYING.ARM in the top level directory
+ * FILE:            ntoskrnl/ps/apphelp.c
+ * PURPOSE:         SHIM engine caching.
+ *                  This caching speeds up checks for the apphelp compatibility layer.
+ * PROGRAMMERS:     Mark Jansen
+ */
+
+/*
+Useful references:
+https://github.com/mandiant/ShimCacheParser/blob/master/ShimCacheParser.py
+http://technet.microsoft.com/en-us/library/dd837644(v=ws.10).aspx
+http://msdn.microsoft.com/en-us/library/bb432182(v=vs.85).aspx
+http://www.alex-ionescu.com/?p=43
+http://recxltd.blogspot.nl/2012/04/windows-appcompat-research-notes-part-1.html
+http://journeyintoir.blogspot.ch/2013/12/revealing-recentfilecachebcf-file.html
+https://dl.mandiant.com/EE/library/Whitepaper_ShimCacheParser.pdf
+*/
+
+/* INCLUDES ******************************************************************/
+
+#include <ntoskrnl.h>
+#define NDEBUG
+#include <debug.h>
+
+/* GLOBALS *******************************************************************/
+
+static BOOLEAN ApphelpCacheEnabled = FALSE;
+static ERESOURCE ApphelpCacheLock;
+static RTL_AVL_TABLE ApphelpShimCache;
+static LIST_ENTRY ApphelpShimCacheAge;
+
+extern ULONG InitSafeBootMode;
+
+static UNICODE_STRING AppCompatCacheKey = RTL_CONSTANT_STRING(L"\\Registry\\MACHINE\\System\\CurrentControlSet\\Control\\Session Manager\\AppCompatCache");
+static OBJECT_ATTRIBUTES AppCompatKeyAttributes = RTL_CONSTANT_OBJECT_ATTRIBUTES(&AppCompatCacheKey, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE);
+static UNICODE_STRING AppCompatCacheValue = RTL_CONSTANT_STRING(L"AppCompatCache");
+
+#define EMPTY_SHIM_ENTRY    { { 0 }, { { 0 } }, 0 }
+#define MAX_SHIM_ENTRIES    0x200
+#define TAG_SHIM            'MIHS'
+
+#ifndef INVALID_HANDLE_VALUE
+#define INVALID_HANDLE_VALUE (HANDLE)(-1)
+#endif
+
+#include <pshpack1.h>
+
+typedef struct SHIM_PERSISTENT_CACHE_HEADER_52
+{
+    ULONG Magic;
+    ULONG NumEntries;
+} SHIM_PERSISTENT_CACHE_HEADER_52, *PSHIM_PERSISTENT_CACHE_HEADER_52;
+
+/* The data that is present in the registry (Win2k3 version) */
+typedef struct SHIM_PERSISTENT_CACHE_ENTRY_52
+{
+    UNICODE_STRING ImageName;
+    LARGE_INTEGER DateTime;
+    LARGE_INTEGER FileSize;
+} SHIM_PERSISTENT_CACHE_ENTRY_52, *PSHIM_PERSISTENT_CACHE_ENTRY_52;
+
+#include <poppack.h>
+
+#define CACHE_MAGIC_NT_52 0xbadc0ffe
+#define CACHE_HEADER_SIZE_NT_52 0x8
+#define NT52_PERSISTENT_ENTRY_SIZE32 0x18
+#define NT52_PERSISTENT_ENTRY_SIZE64 0x20
+
+//#define CACHE_MAGIC_NT_61 0xbadc0fee
+//#define CACHE_HEADER_SIZE_NT_61 0x80
+//#define NT61_PERSISTENT_ENTRY_SIZE32 0x20
+//#define NT61_PERSISTENT_ENTRY_SIZE64 0x30
+
+#define SHIM_CACHE_MAGIC                    CACHE_MAGIC_NT_52
+#define SHIM_CACHE_HEADER_SIZE              CACHE_HEADER_SIZE_NT_52
+#ifdef _WIN64
+#define SHIM_PERSISTENT_CACHE_ENTRY_SIZE    NT52_PERSISTENT_ENTRY_SIZE64
+#else
+#define SHIM_PERSISTENT_CACHE_ENTRY_SIZE    NT52_PERSISTENT_ENTRY_SIZE32
+#endif
+#define SHIM_PERSISTENT_CACHE_HEADER        SHIM_PERSISTENT_CACHE_HEADER_52
+#define PSHIM_PERSISTENT_CACHE_HEADER      PSHIM_PERSISTENT_CACHE_HEADER_52
+#define SHIM_PERSISTENT_CACHE_ENTRY         SHIM_PERSISTENT_CACHE_ENTRY_52
+#define PSHIM_PERSISTENT_CACHE_ENTRY       PSHIM_PERSISTENT_CACHE_ENTRY_52
+
+C_ASSERT(sizeof(SHIM_PERSISTENT_CACHE_ENTRY) == SHIM_PERSISTENT_CACHE_ENTRY_SIZE);
+C_ASSERT(sizeof(SHIM_PERSISTENT_CACHE_HEADER) == SHIM_CACHE_HEADER_SIZE);
+
+/* The struct we keep in memory */
+typedef struct SHIM_CACHE_ENTRY
+{
+    LIST_ENTRY List;
+    SHIM_PERSISTENT_CACHE_ENTRY Persistent;
+    ULONG CompatFlags;
+} SHIM_CACHE_ENTRY, *PSHIM_CACHE_ENTRY;
+
+/* PRIVATE FUNCTIONS *********************************************************/
+
+PVOID
+ApphelpAlloc(
+    _In_ ULONG ByteSize)
+{
+    return ExAllocatePoolWithTag(PagedPool, ByteSize, TAG_SHIM);
+}
+
+VOID
+ApphelpFree(
+    _In_ PVOID Data)
+{
+    ExFreePoolWithTag(Data, TAG_SHIM);
+}
+
+VOID
+ApphelpCacheAcquireLock(VOID)
+{
+    KeEnterCriticalRegion();
+    ExAcquireResourceExclusiveLite(&ApphelpCacheLock, TRUE);
+}
+
+BOOLEAN
+ApphelpCacheTryAcquireLock(VOID)
+{
+    KeEnterCriticalRegion();
+    if (!ExTryToAcquireResourceExclusiveLite(&ApphelpCacheLock))
+    {
+        KeLeaveCriticalRegion();
+        return FALSE;
+    }
+    return TRUE;
+}
+
+VOID
+ApphelpCacheReleaseLock(VOID)
+{
+    ExReleaseResourceLite(&ApphelpCacheLock);
+    KeLeaveCriticalRegion();
+}
+
+VOID
+ApphelpDuplicateUnicodeString(
+    _Out_ PUNICODE_STRING Destination,
+    _In_ PCUNICODE_STRING Source)
+{
+    Destination->Length = Source->Length;
+    if (Destination->Length)
+    {
+        Destination->MaximumLength = Destination->Length + sizeof(WCHAR);
+        Destination->Buffer = ApphelpAlloc(Destination->MaximumLength);
+        RtlCopyMemory(Destination->Buffer, Source->Buffer, Destination->Length);
+        Destination->Buffer[Destination->Length / sizeof(WCHAR)] = UNICODE_NULL;
+    }
+    else
+    {
+        Destination->MaximumLength = 0;
+        Destination->Buffer = NULL;
+    }
+}
+
+VOID
+ApphelpFreeUnicodeString(
+    _Inout_ PUNICODE_STRING String)
+{
+    if (String->Buffer)
+    {
+        ApphelpFree(String->Buffer);
+    }
+    String->Length = 0;
+    String->MaximumLength = 0;
+    String->Buffer = NULL;
+}
+
+/* Query file info from a handle, storing it in Entry */
+NTSTATUS
+ApphelpCacheQueryInfo(
+    _In_ HANDLE ImageHandle,
+    _Out_ PSHIM_CACHE_ENTRY Entry)
+{
+    IO_STATUS_BLOCK IoStatusBlock;
+    FILE_BASIC_INFORMATION FileBasic;
+    FILE_STANDARD_INFORMATION FileStandard;
+    NTSTATUS Status;
+
+    Status = ZwQueryInformationFile(ImageHandle, &IoStatusBlock,
+        &FileBasic, sizeof(FileBasic), FileBasicInformation);
+    if (NT_SUCCESS(Status))
+    {
+        Status = ZwQueryInformationFile(ImageHandle, &IoStatusBlock,
+            &FileStandard, sizeof(FileStandard), FileStandardInformation);
+        if (NT_SUCCESS(Status))
+        {
+            Entry->Persistent.DateTime = FileBasic.LastWriteTime;
+            Entry->Persistent.FileSize = FileStandard.EndOfFile;
+        }
+    }
+    return Status;
+}
+
+RTL_GENERIC_COMPARE_RESULTS
+NTAPI
+ApphelpShimCacheCompareRoutine(
+    _In_ PRTL_AVL_TABLE Table,
+    _In_ PVOID FirstStruct,
+    _In_ PVOID SecondStruct)
+{
+    LONG lResult = RtlCompareUnicodeString(
+        &((PSHIM_CACHE_ENTRY)FirstStruct)->Persistent.ImageName,
+        &((PSHIM_CACHE_ENTRY)SecondStruct)->Persistent.ImageName, TRUE);
+
+    if (lResult < 0)
+        return GenericLessThan;
+    else if (lResult == 0)
+        return GenericEqual;
+    return GenericGreaterThan;
+}
+
+PVOID
+NTAPI
+ApphelpShimCacheAllocateRoutine(
+    _In_ PRTL_AVL_TABLE Table,
+    _In_ CLONG ByteSize)
+{
+    return ApphelpAlloc(ByteSize);
+}
+
+VOID
+NTAPI
+ApphelpShimCacheFreeRoutine(
+    _In_ PRTL_AVL_TABLE Table,
+    _In_ PVOID Buffer)
+{
+    ApphelpFree(Buffer);
+}
+
+NTSTATUS
+ApphelpCacheParse(
+    _In_reads_(DataLength) PUCHAR Data,
+    _In_ ULONG DataLength)
+{
+    PSHIM_PERSISTENT_CACHE_HEADER Header = (PSHIM_PERSISTENT_CACHE_HEADER)Data;
+
+    if (DataLength < CACHE_HEADER_SIZE_NT_52)
+    {
+        DPRINT1("SHIMS: ApphelpCacheParse not enough data for a minimal header (0x%x)\n", DataLength);
+        return STATUS_INVALID_PARAMETER;
+    }
+    if (Header->Magic == SHIM_CACHE_MAGIC)
+    {
+        ULONG Cur;
+        ULONG NumEntries = Header->NumEntries;
+        DPRINT1("SHIMS: ApphelpCacheParse walking %d entries\n", NumEntries);
+        for (Cur = 0; Cur < NumEntries; ++Cur)
+        {
+            UNICODE_STRING String;
+            SHIM_CACHE_ENTRY Entry = EMPTY_SHIM_ENTRY;
+            PSHIM_CACHE_ENTRY Result;
+            PSHIM_PERSISTENT_CACHE_ENTRY pPersistent =
+                (PSHIM_PERSISTENT_CACHE_ENTRY)(Data + SHIM_CACHE_HEADER_SIZE +
+                                                (Cur * SHIM_PERSISTENT_CACHE_ENTRY_SIZE));
+            /* The entry in the Persitent storage is not really a UNICODE_STRING,
+                so we have to convert the offset into a real pointer before using it. */
+            String.Length = pPersistent->ImageName.Length;
+            String.MaximumLength = pPersistent->ImageName.MaximumLength;
+            String.Buffer = (PWCHAR)((ULONG_PTR)pPersistent->ImageName.Buffer + Data);
+
+            /* Now we copy all data to a local buffer, that can be safely duplicated by RtlInsert */
+            Entry.Persistent = *pPersistent;
+            ApphelpDuplicateUnicodeString(&Entry.Persistent.ImageName, &String);
+            Result = RtlInsertElementGenericTableAvl(&ApphelpShimCache, &Entry, sizeof(Entry), NULL);
+            if (!Result)
+            {
+                DPRINT1("SHIMS: ApphelpCacheParse insert failed\n");
+                ApphelpFreeUnicodeString(&Entry.Persistent.ImageName);
+                return STATUS_INVALID_PARAMETER;
+            }
+            InsertTailList(&ApphelpShimCacheAge, &Result->List);
+        }
+        return STATUS_SUCCESS;
+    }
+    DPRINT1("SHIMS: ApphelpCacheParse found invalid magic (0x%x)\n", Header->Magic);
+    return STATUS_INVALID_PARAMETER;
+}
+
+BOOLEAN
+ApphelpCacheRead(VOID)
+{
+    HANDLE KeyHandle;
+    NTSTATUS Status;
+    KEY_VALUE_PARTIAL_INFORMATION KeyValueObject;
+    PKEY_VALUE_PARTIAL_INFORMATION KeyValueInformation = &KeyValueObject;
+    ULONG KeyInfoSize, ResultSize;
+
+    Status = ZwOpenKey(&KeyHandle, KEY_QUERY_VALUE, &AppCompatKeyAttributes);
+
+    if (!NT_SUCCESS(Status))
+    {
+        DPRINT1("SHIMS: ApphelpCacheRead could not even open Session Manager\\AppCompatCache (0x%x)\n", Status);
+        return FALSE;
+    }
+
+    Status = ZwQueryValueKey(KeyHandle, &AppCompatCacheValue,
+            KeyValuePartialInformation, KeyValueInformation,
+            sizeof(KeyValueObject), &ResultSize);
+
+    if (Status == STATUS_BUFFER_OVERFLOW)
+    {
+        KeyInfoSize = sizeof(KEY_VALUE_PARTIAL_INFORMATION) + KeyValueInformation->DataLength;
+        KeyValueInformation = ApphelpAlloc(KeyInfoSize);
+        if (KeyValueInformation != NULL)
+        {
+            Status = ZwQueryValueKey(KeyHandle, &AppCompatCacheValue,
+                        KeyValuePartialInformation, KeyValueInformation,
+                        KeyInfoSize, &ResultSize);
+        }
+    }
+
+    if (NT_SUCCESS(Status) && KeyValueInformation->Type == REG_BINARY)
+    {
+        Status = ApphelpCacheParse(KeyValueInformation->Data,
+                                    KeyValueInformation->DataLength);
+    }
+    else
+    {
+        DPRINT1("SHIMS: ApphelpCacheRead not loaded from registry (0x%x)\n", Status);
+    }
+
+    if (KeyValueInformation != &KeyValueObject && KeyValueInformation != NULL)
+        ApphelpFree(KeyValueInformation);
+
+    ZwClose(KeyHandle);
+    return NT_SUCCESS(Status);
+}
+
+BOOLEAN
+ApphelpCacheWrite(VOID)
+{
+    ULONG Length = SHIM_CACHE_HEADER_SIZE;
+    ULONG NumEntries = 0;
+    PLIST_ENTRY ListEntry;
+    PUCHAR Buffer, BufferNamePos;
+    PSHIM_PERSISTENT_CACHE_HEADER Header;
+    PSHIM_PERSISTENT_CACHE_ENTRY WriteEntry;
+    HANDLE KeyHandle;
+    NTSTATUS Status;
+
+    /* First we have to calculate the required size. */
+    ApphelpCacheAcquireLock();
+    ListEntry = ApphelpShimCacheAge.Flink;
+    while (ListEntry != &ApphelpShimCacheAge)
+    {
+        PSHIM_CACHE_ENTRY Entry = CONTAINING_RECORD(ListEntry, SHIM_CACHE_ENTRY, List);
+        Length += SHIM_PERSISTENT_CACHE_ENTRY_SIZE;
+        Length += Entry->Persistent.ImageName.MaximumLength;
+        ++NumEntries;
+        ListEntry = ListEntry->Flink;
+    }
+    DPRINT1("SHIMS: ApphelpCacheWrite, %d Entries, total size: %d\n", NumEntries, Length);
+    Length = ROUND_UP(Length, sizeof(ULONGLONG));
+    DPRINT1("SHIMS: ApphelpCacheWrite, Rounded to: %d\n", Length);
+
+    /* Now we allocate and prepare some helpers */
+    Buffer = ApphelpAlloc(Length);
+    BufferNamePos = Buffer + Length;
+    Header = (PSHIM_PERSISTENT_CACHE_HEADER)Buffer;
+    WriteEntry = (PSHIM_PERSISTENT_CACHE_ENTRY)(Buffer + SHIM_CACHE_HEADER_SIZE);
+
+    Header->Magic = SHIM_CACHE_MAGIC;
+    Header->NumEntries = NumEntries;
+
+    ListEntry = ApphelpShimCacheAge.Flink;
+    while (ListEntry != &ApphelpShimCacheAge)
+    {
+        PSHIM_CACHE_ENTRY Entry = CONTAINING_RECORD(ListEntry, SHIM_CACHE_ENTRY, List);
+        USHORT ImageNameLen = Entry->Persistent.ImageName.MaximumLength;
+        /* Copy the Persistent structure over */
+        *WriteEntry = Entry->Persistent;
+        BufferNamePos -= ImageNameLen;
+        /* Copy the image name over */
+        RtlCopyMemory(BufferNamePos, Entry->Persistent.ImageName.Buffer, ImageNameLen);
+        /* Fix the Persistent structure, so that Buffer is once again an offset */
+        WriteEntry->ImageName.Buffer = (PWCH)(BufferNamePos - Buffer);
+
+        ++WriteEntry;
+        ListEntry = ListEntry->Flink;
+    }
+    ApphelpCacheReleaseLock();
+
+    Status = ZwOpenKey(&KeyHandle, KEY_SET_VALUE, &AppCompatKeyAttributes);
+    if (NT_SUCCESS(Status))
+    {
+        Status = ZwSetValueKey(KeyHandle, &AppCompatCacheValue, 0, REG_BINARY, Buffer, Length);
+        ZwClose(KeyHandle);
+    }
+    else
+    {
+        DPRINT1("SHIMS: ApphelpCacheWrite could not even open Session Manager\\AppCompatCache (0x%x)\n", Status);
+    }
+
+    ApphelpFree(Buffer);
+    return NT_SUCCESS(Status);
+}
+
+
+NTSTATUS
+NTAPI
+INIT_FUNCTION
+ApphelpCacheInitialize(VOID)
+{
+    DPRINT1("SHIMS: ApphelpCacheInitialize\n");
+    /* If we are booting in safemode we do not want to use the apphelp cache */
+    if (InitSafeBootMode)
+    {
+        DPRINT1("SHIMS: Safe mode detected, disabling cache.\n");
+        ApphelpCacheEnabled = FALSE;
+    }
+    else
+    {
+        ExInitializeResourceLite(&ApphelpCacheLock);
+        RtlInitializeGenericTableAvl(&ApphelpShimCache,
+            ApphelpShimCacheCompareRoutine,
+            ApphelpShimCacheAllocateRoutine,
+            ApphelpShimCacheFreeRoutine,
+            NULL);
+        InitializeListHead(&ApphelpShimCacheAge);
+        ApphelpCacheEnabled = ApphelpCacheRead();
+    }
+    DPRINT1("SHIMS: ApphelpCacheInitialize: %d\n", ApphelpCacheEnabled);
+    return STATUS_SUCCESS;
+}
+
+VOID
+NTAPI
+ApphelpCacheShutdown(VOID)
+{
+    if (ApphelpCacheEnabled)
+    {
+        ApphelpCacheWrite();
+    }
+}
+
+NTSTATUS
+ApphelpValidateData(
+    _In_opt_ PAPPHELP_CACHE_SERVICE_LOOKUP ServiceData,
+    _Out_ PUNICODE_STRING ImageName,
+    _Out_ PHANDLE ImageHandle)
+{
+    NTSTATUS Status = STATUS_INVALID_PARAMETER;
+
+    if (ServiceData)
+    {
+        UNICODE_STRING LocalImageName;
+        _SEH2_TRY
+        {
+            ProbeForRead(ServiceData, sizeof(APPHELP_CACHE_SERVICE_LOOKUP), sizeof(ULONG));
+            LocalImageName = ServiceData->ImageName;
+            *ImageHandle = ServiceData->ImageHandle;
+            if (LocalImageName.Length && LocalImageName.Buffer)
+            {
+                ProbeForRead(LocalImageName.Buffer, LocalImageName.Length * sizeof(WCHAR), 1);
+                ApphelpDuplicateUnicodeString(ImageName, &LocalImageName);
+                Status = STATUS_SUCCESS;
+            }
+        }
+        _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
+        {
+            _SEH2_YIELD(return _SEH2_GetExceptionCode());
+        }
+        _SEH2_END;
+    }
+    if (!NT_SUCCESS(Status))
+    {
+        DPRINT1("SHIMS: ApphelpValidateData: invalid data passed\n");
+    }
+    return Status;
+}
+
+NTSTATUS
+ApphelpCacheRemoveEntryNolock(
+    _In_ PSHIM_CACHE_ENTRY Entry)
+{
+    if (Entry)
+    {
+        PWSTR Buffer = Entry->Persistent.ImageName.Buffer;
+        RemoveEntryList(&Entry->List);
+        if (RtlDeleteElementGenericTableAvl(&ApphelpShimCache, Entry))
+            ApphelpFree(Buffer);
+        return STATUS_SUCCESS;
+    }
+    return STATUS_NOT_FOUND;
+}
+
+NTSTATUS
+ApphelpCacheLookupEntry(
+    _In_ PUNICODE_STRING ImageName,
+    _In_ HANDLE ImageHandle)
+{
+    NTSTATUS Status = STATUS_NOT_FOUND;
+
+    if (ApphelpCacheTryAcquireLock())
+    {
+        SHIM_CACHE_ENTRY Lookup = EMPTY_SHIM_ENTRY;
+        PSHIM_CACHE_ENTRY Entry;
+        Lookup.Persistent.ImageName = *ImageName;
+        Entry = RtlLookupElementGenericTableAvl(&ApphelpShimCache, &Lookup);
+        if (Entry)
+        {
+            DPRINT1("SHIMS: ApphelpCacheLookupEntry: found %wZ\n", ImageName);
+            if (ImageHandle == INVALID_HANDLE_VALUE)
+            {
+                DPRINT1("SHIMS: ApphelpCacheLookupEntry: ok\n");
+                /* just return if we know it, do not query file info */
+                Status = STATUS_SUCCESS;
+            }
+            else if (NT_SUCCESS(ApphelpCacheQueryInfo(ImageHandle, &Lookup)) &&
+                Lookup.Persistent.DateTime.QuadPart == Entry->Persistent.DateTime.QuadPart &&
+                Lookup.Persistent.FileSize.QuadPart == Entry->Persistent.FileSize.QuadPart)
+            {
+                DPRINT1("SHIMS: ApphelpCacheLookupEntry: found & validated\n");
+                Status = STATUS_SUCCESS;
+                /* move it to the front to keep it alive */
+                RemoveEntryList(&Entry->List);
+                InsertHeadList(&ApphelpShimCacheAge, &Entry->List);
+            }
+            else
+            {
+                DPRINT1("SHIMS: ApphelpCacheLookupEntry: file info mismatch\n");
+                /* Could not read file info, or it did not match, drop it from the cache */
+                ApphelpCacheRemoveEntryNolock(Entry);
+            }
+        }
+        else
+        {
+            DPRINT1("SHIMS: ApphelpCacheLookupEntry: could not find %wZ\n", ImageName);
+        }
+        ApphelpCacheReleaseLock();
+    }
+    return Status;
+}
+
+NTSTATUS
+ApphelpCacheRemoveEntry(
+    _In_ PUNICODE_STRING ImageName)
+{
+    PSHIM_CACHE_ENTRY Entry;
+    NTSTATUS Status;
+
+    ApphelpCacheAcquireLock();
+    Entry = RtlLookupElementGenericTableAvl(&ApphelpShimCache, ImageName);
+    Status = ApphelpCacheRemoveEntryNolock(Entry);
+    ApphelpCacheReleaseLock();
+    return Status;
+}
+
+/* Validate that we are either called from r0, or from a service-like context */
+NTSTATUS
+ApphelpCacheAccessCheck(VOID)
+{
+    if (ExGetPreviousMode() != KernelMode)
+    {
+        if (!SeSinglePrivilegeCheck(SeTcbPrivilege, UserMode))
+        {
+            DPRINT1("SHIMS: ApphelpCacheAccessCheck failed\n");
+            return STATUS_ACCESS_DENIED;
+        }
+    }
+    return STATUS_SUCCESS;
+}
+
+NTSTATUS
+ApphelpCacheUpdateEntry(
+    _In_ PUNICODE_STRING ImageName,
+    _In_ HANDLE ImageHandle)
+{
+    NTSTATUS Status = STATUS_SUCCESS;
+    SHIM_CACHE_ENTRY Entry = EMPTY_SHIM_ENTRY;
+    PSHIM_CACHE_ENTRY Lookup;
+    PVOID NodeOrParent;
+    TABLE_SEARCH_RESULT SearchResult;
+
+    ApphelpCacheAcquireLock();
+
+    /* If we got a file handle, query it for info */
+    if (ImageHandle != INVALID_HANDLE_VALUE)
+    {
+        Status = ApphelpCacheQueryInfo(ImageHandle, &Entry);
+    }
+
+    if (NT_SUCCESS(Status))
+    {
+        /* Use ImageName for the lookup, don't actually duplicate it */
+        Entry.Persistent.ImageName = *ImageName;
+        Lookup = RtlLookupElementGenericTableFullAvl(&ApphelpShimCache, &Entry,
+                                                    &NodeOrParent, &SearchResult);
+        if (Lookup)
+        {
+            DPRINT1("SHIMS: ApphelpCacheUpdateEntry: Entry already exists, reusing it\n");
+            /* Unlink the found item, so we can put it back at the front,
+                and copy the earlier obtained file info*/
+            RemoveEntryList(&Lookup->List);
+            Lookup->Persistent.DateTime = Entry.Persistent.DateTime;
+            Lookup->Persistent.FileSize = Entry.Persistent.FileSize;
+        }
+        else
+        {
+            DPRINT1("SHIMS: ApphelpCacheUpdateEntry: Inserting new Entry\n");
+            /* Insert a new entry, with its own copy of the ImageName */
+            ApphelpDuplicateUnicodeString(&Entry.Persistent.ImageName, ImageName);
+            Lookup = RtlInsertElementGenericTableFullAvl(&ApphelpShimCache,
+                        &Entry, sizeof(Entry), 0, NodeOrParent, SearchResult);
+            if (!Lookup)
+            {
+                ApphelpFreeUnicodeString(&Entry.Persistent.ImageName);
+                Status = STATUS_NO_MEMORY;
+            }
+        }
+        if (Lookup)
+        {
+            /* Either we re-used an existing item, or we inserted a new one, keep it alive */
+            InsertHeadList(&ApphelpShimCacheAge, &Lookup->List);
+            if (RtlNumberGenericTableElementsAvl(&ApphelpShimCache) > MAX_SHIM_ENTRIES)
+            {
+                PSHIM_CACHE_ENTRY Remove;
+                DPRINT1("SHIMS: ApphelpCacheUpdateEntry: Cache growing too big, dropping oldest item\n");
+                Remove = CONTAINING_RECORD(ApphelpShimCacheAge.Blink, SHIM_CACHE_ENTRY, List);
+                Status = ApphelpCacheRemoveEntryNolock(Remove);
+            }
+        }
+    }
+    ApphelpCacheReleaseLock();
+    return Status;
+}
+
+NTSTATUS
+ApphelpCacheFlush(VOID)
+{
+    PVOID p;
+
+    DPRINT1("SHIMS: ApphelpCacheFlush\n");
+    ApphelpCacheAcquireLock();
+    while ((p = RtlEnumerateGenericTableAvl(&ApphelpShimCache, TRUE)))
+    {
+        ApphelpCacheRemoveEntryNolock((PSHIM_CACHE_ENTRY)p);
+    }
+    ApphelpCacheReleaseLock();
+    return STATUS_SUCCESS;
+}
+
+NTSTATUS
+ApphelpCacheDump(VOID)
+{
+    PLIST_ENTRY ListEntry;
+
+    DPRINT1("SHIMS: NtApphelpCacheControl( Dumping entries, newset to oldest )\n");
+    ApphelpCacheAcquireLock();
+    ListEntry = ApphelpShimCacheAge.Flink;
+    while (ListEntry != &ApphelpShimCacheAge)
+    {
+        PSHIM_CACHE_ENTRY Entry = CONTAINING_RECORD(ListEntry, SHIM_CACHE_ENTRY, List);
+        DPRINT1("Entry: %S\n", Entry->Persistent.ImageName.Buffer);
+        DPRINT1("DateTime High: 0x%x, Low: 0x%x\n",
+            Entry->Persistent.DateTime.HighPart, Entry->Persistent.DateTime.LowPart);
+        DPRINT1("FileSize High: 0x%x, Low: 0x%x\n",
+            Entry->Persistent.FileSize.HighPart, Entry->Persistent.FileSize.LowPart);
+        DPRINT1("Flags: 0x%x\n", Entry->CompatFlags);
+        ListEntry = ListEntry->Flink;
+    }
+    ApphelpCacheReleaseLock();
+    return STATUS_SUCCESS;
+}
+
+/* PUBLIC FUNCTIONS **********************************************************/
+
+NTSTATUS
+NTAPI
+NtApphelpCacheControl(
+    _In_ APPHELPCACHESERVICECLASS Service,
+    _In_opt_ PAPPHELP_CACHE_SERVICE_LOOKUP ServiceData)
+{
+    NTSTATUS Status = STATUS_INVALID_PARAMETER;
+    UNICODE_STRING ImageName = { 0 };
+    HANDLE Handle = INVALID_HANDLE_VALUE;
+
+    if (!ApphelpCacheEnabled)
+    {
+        DPRINT1("NtApphelpCacheControl: ApphelpCacheEnabled == 0\n");
+        return Status;
+    }
+    switch (Service)
+    {
+        case ApphelpCacheServiceLookup:
+            DPRINT1("SHIMS: NtApphelpCacheControl( ApphelpCacheServiceLookup )\n");
+            Status = ApphelpValidateData(ServiceData, &ImageName, &Handle);
+            if (NT_SUCCESS(Status))
+                Status = ApphelpCacheLookupEntry(&ImageName, Handle);
+            break;
+        case ApphelpCacheServiceRemove:
+            DPRINT1("SHIMS: NtApphelpCacheControl( ApphelpCacheServiceRemove )\n");
+            Status = ApphelpValidateData(ServiceData, &ImageName, &Handle);
+            if (NT_SUCCESS(Status))
+                Status = ApphelpCacheRemoveEntry(&ImageName);
+            break;
+        case ApphelpCacheServiceUpdate:
+            DPRINT1("SHIMS: NtApphelpCacheControl( ApphelpCacheServiceUpdate )\n");
+            Status = ApphelpCacheAccessCheck();
+            if (NT_SUCCESS(Status))
+            {
+                Status = ApphelpValidateData(ServiceData, &ImageName, &Handle);
+                if (NT_SUCCESS(Status))
+                    Status = ApphelpCacheUpdateEntry(&ImageName, Handle);
+            }
+            break;
+        case ApphelpCacheServiceFlush:
+            Status = ApphelpCacheFlush();
+            break;
+        case ApphelpCacheServiceDump:
+            Status = ApphelpCacheDump();
+            break;
+        case ApphelpDBGReadRegistry:
+            DPRINT1("SHIMS: NtApphelpCacheControl( ApphelpDBGReadRegistry ): flushing cache.\n");
+            ApphelpCacheFlush();
+            DPRINT1("SHIMS: NtApphelpCacheControl( ApphelpDBGReadRegistry ): reading cache.\n");
+            Status = ApphelpCacheRead() ? STATUS_SUCCESS : STATUS_NOT_FOUND;
+            break;
+        case ApphelpDBGWriteRegistry:
+            DPRINT1("SHIMS: NtApphelpCacheControl( ApphelpDBGWriteRegistry ): writing cache.\n");
+            Status = ApphelpCacheWrite() ? STATUS_SUCCESS : STATUS_NOT_FOUND;
+            break;
+        default:
+            DPRINT1("SHIMS: NtApphelpCacheControl( Invalid service requested )\n");
+            break;
+    }
+    if (ImageName.Buffer)
+    {
+        ApphelpFreeUnicodeString(&ImageName);
+    }
+    return Status;
+}
+
index 27ddadb..ed01622 100644 (file)
@@ -250,7 +250,7 @@ PspMapSystemDll(IN PEPROCESS Process,
     LARGE_INTEGER Offset = {{0, 0}};
     SIZE_T ViewSize = 0;
     PVOID ImageBase = 0;
-    
+
     /* Map the System DLL */
     Status = MmMapViewOfSection(PspSystemDllSection,
                                 Process,
@@ -267,7 +267,7 @@ PspMapSystemDll(IN PEPROCESS Process,
         /* Normalize status code */
         Status = STATUS_CONFLICTING_ADDRESSES;
     }
-    
+
     /* Write the image base and return status */
     if (DllBase) *DllBase = ImageBase;
     return Status;
@@ -677,13 +677,4 @@ PsGetVersion(OUT PULONG MajorVersion OPTIONAL,
     return (NtBuildNumber >> 28) == 0xC;
 }
 
-NTSTATUS
-NTAPI
-NtApphelpCacheControl(IN APPHELPCACHESERVICECLASS Service,
-                      IN PAPPHELP_CACHE_SERVICE_LOOKUP ServiceData)
-{
-    UNIMPLEMENTED;
-    return STATUS_NOT_IMPLEMENTED;
-}
-
 /* EOF */
index 570faec..3c7fc7d 100644 (file)
@@ -2,6 +2,7 @@
 list(APPEND SOURCE
     LdrEnumResources.c
     NtAllocateVirtualMemory.c
+    NtApphelpCacheControl.c
     NtContinue.c
     NtCreateFile.c
     NtCreateThread.c
diff --git a/rostests/apitests/ntdll/NtApphelpCacheControl.c b/rostests/apitests/ntdll/NtApphelpCacheControl.c
new file mode 100644 (file)
index 0000000..aee3928
--- /dev/null
@@ -0,0 +1,367 @@
+/*
+ * PROJECT:         ReactOS API Tests
+ * LICENSE:         LGPL - See COPYING.LIB in the top level directory
+ * PURPOSE:         Tests for SHIM engine caching.
+ * PROGRAMMER:      Mark Jansen
+ */
+
+#include <apitest.h>
+
+#include <windows.h>
+
+#define WIN32_NO_STATUS
+#include <ntndk.h>
+
+enum ServiceCommands
+{
+    RegisterShimCacheWithHandle = 128,
+    RegisterShimCacheWithoutHandle = 129,
+};
+
+
+NTSTATUS CallCacheControl(UNICODE_STRING* PathName, BOOLEAN WithMapping, APPHELPCACHESERVICECLASS Service)
+{
+    APPHELP_CACHE_SERVICE_LOOKUP CacheEntry = { {0} };
+    NTSTATUS Status;
+    CacheEntry.ImageName = *PathName;
+    if (WithMapping)
+    {
+        OBJECT_ATTRIBUTES LocalObjectAttributes;
+        IO_STATUS_BLOCK IoStatusBlock;
+        InitializeObjectAttributes(&LocalObjectAttributes, PathName,
+            OBJ_CASE_INSENSITIVE, NULL, NULL);
+        Status = NtOpenFile(&CacheEntry.ImageHandle,
+                    SYNCHRONIZE | FILE_READ_ATTRIBUTES | FILE_READ_DATA | FILE_EXECUTE,
+                    &LocalObjectAttributes, &IoStatusBlock,
+                    FILE_SHARE_READ | FILE_SHARE_DELETE,
+                    FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE);
+        ok_ntstatus(Status, STATUS_SUCCESS);
+    }
+    else
+    {
+        CacheEntry.ImageHandle = INVALID_HANDLE_VALUE;
+    }
+    Status = NtApphelpCacheControl(Service, &CacheEntry);
+    if (CacheEntry.ImageHandle != INVALID_HANDLE_VALUE)
+        NtClose(CacheEntry.ImageHandle);
+    return Status;
+}
+
+int InitEnv(UNICODE_STRING* PathName)
+{
+    NTSTATUS Status = CallCacheControl(PathName, FALSE, ApphelpCacheServiceRemove);
+    if (Status == STATUS_INVALID_PARAMETER)
+    {
+        /* Windows Vista+ has a different layout for APPHELP_CACHE_SERVICE_LOOKUP */
+        return 0;
+    }
+    ok(Status == STATUS_SUCCESS || Status == STATUS_NOT_FOUND,
+        "Wrong value for Status, expected: SUCCESS or NOT_FOUND, got: 0x%lx\n",
+        Status);
+    return 1;
+}
+
+void CheckValidation(UNICODE_STRING* PathName)
+{
+    APPHELP_CACHE_SERVICE_LOOKUP CacheEntry = { {0} };
+    NTSTATUS Status;
+
+    /* Validate the handling of a NULL pointer */
+    Status = NtApphelpCacheControl(ApphelpCacheServiceRemove, NULL);
+    ok_ntstatus(Status, STATUS_INVALID_PARAMETER);
+    Status = NtApphelpCacheControl(ApphelpCacheServiceLookup, NULL);
+    ok_ntstatus(Status, STATUS_INVALID_PARAMETER);
+
+    /* Validate the handling of a NULL pointer inside the struct */
+    Status = NtApphelpCacheControl(ApphelpCacheServiceRemove, &CacheEntry);
+    ok_ntstatus(Status, STATUS_INVALID_PARAMETER);
+    Status = NtApphelpCacheControl(ApphelpCacheServiceLookup, &CacheEntry);
+    ok_ntstatus(Status, STATUS_INVALID_PARAMETER);
+
+    /* Just call the dump function */
+    Status = NtApphelpCacheControl(ApphelpCacheServiceDump, NULL);
+    ok_ntstatus(Status, STATUS_SUCCESS);
+
+    /* Validate the handling of an invalid handle inside the struct */
+    CacheEntry.ImageName = *PathName;
+    CacheEntry.ImageHandle = (HANDLE)2;
+    Status = NtApphelpCacheControl(ApphelpCacheServiceLookup, &CacheEntry);
+    ok_ntstatus(Status, STATUS_NOT_FOUND);
+
+    /* Validate the handling of an invalid service number */
+    Status = NtApphelpCacheControl(999, NULL);
+    ok_ntstatus(Status, STATUS_INVALID_PARAMETER);
+    Status = NtApphelpCacheControl(999, &CacheEntry);
+    ok_ntstatus(Status, STATUS_INVALID_PARAMETER);
+}
+
+static BOOLEAN RequestAddition(SC_HANDLE service_handle, BOOLEAN WithMapping)
+{
+    SERVICE_STATUS Status;
+    ControlService(service_handle, WithMapping ? RegisterShimCacheWithHandle :
+                    RegisterShimCacheWithoutHandle, &Status);
+    /* TODO: how to get a return code from the service? */
+    return TRUE;
+}
+
+static void RunApphelpCacheControlTests(SC_HANDLE service_handle)
+{
+    WCHAR szPath[MAX_PATH];
+    UNICODE_STRING ntPath;
+    BOOLEAN Result;
+    NTSTATUS Status;
+    APPHELP_CACHE_SERVICE_LOOKUP CacheEntry;
+
+    GetModuleFileNameW(NULL, szPath, sizeof(szPath) / sizeof(szPath[0]));
+    Result = RtlDosPathNameToNtPathName_U(szPath, &ntPath, NULL, NULL);
+    ok(Result == TRUE, "RtlDosPathNameToNtPathName_U\n");
+    if (!InitEnv(&ntPath))
+    {
+        skip("NtApphelpCacheControl expects a different structure layout\n");
+        return;
+    }
+    /* At this point we have made sure that our binary is not present in the cache,
+        and that the NtApphelpCacheControl function expects the struct layout we use. */
+    CheckValidation(&ntPath);
+
+    /* We expect not to find it */
+    Status = CallCacheControl(&ntPath, TRUE, ApphelpCacheServiceLookup);
+    ok_ntstatus(Status, STATUS_NOT_FOUND);
+    Status = CallCacheControl(&ntPath, FALSE, ApphelpCacheServiceLookup);
+    ok_ntstatus(Status, STATUS_NOT_FOUND);
+
+    /* First we add our process without a file handle (so it will be registered without file info) */
+    RequestAddition(service_handle, FALSE);
+
+    /* now we try to find it without validating file info */
+    Status = CallCacheControl(&ntPath, FALSE, ApphelpCacheServiceLookup);
+    ok_ntstatus(Status, STATUS_SUCCESS);
+    /* when validating file info the cache notices the file is wrong, so it is dropped from the cache */
+    Status = CallCacheControl(&ntPath, TRUE, ApphelpCacheServiceLookup);
+    ok_ntstatus(Status, STATUS_NOT_FOUND);
+    /* making the second check without info also fail. */
+    Status = CallCacheControl(&ntPath, FALSE, ApphelpCacheServiceLookup);
+    ok_ntstatus(Status, STATUS_NOT_FOUND);
+
+
+    /* Now we add the file with file info */
+    RequestAddition(service_handle, TRUE);
+
+    /* so both checks should succeed */
+    Status = CallCacheControl(&ntPath, TRUE, ApphelpCacheServiceLookup);
+    ok_ntstatus(Status, STATUS_SUCCESS);
+    Status = CallCacheControl(&ntPath, FALSE, ApphelpCacheServiceLookup);
+    ok_ntstatus(Status, STATUS_SUCCESS);
+
+    /* We know the file is in the cache now (assuming previous tests succeeded,
+        let's test invalid handle behavior */
+    CacheEntry.ImageName = ntPath;
+    CacheEntry.ImageHandle = 0;
+    Status = NtApphelpCacheControl(ApphelpCacheServiceLookup, &CacheEntry);
+    ok_ntstatus(Status, STATUS_NOT_FOUND);
+
+    /* re-add it for the next test */
+    RequestAddition(service_handle, TRUE);
+    Status = CallCacheControl(&ntPath, TRUE, ApphelpCacheServiceLookup);
+    ok_ntstatus(Status, STATUS_SUCCESS);
+    CacheEntry.ImageHandle = (HANDLE)1;
+    Status = NtApphelpCacheControl(ApphelpCacheServiceLookup, &CacheEntry);
+    ok_ntstatus(Status, STATUS_NOT_FOUND);
+
+    /* and again */
+    RequestAddition(service_handle, TRUE);
+    Status = CallCacheControl(&ntPath, TRUE, ApphelpCacheServiceLookup);
+    ok_ntstatus(Status, STATUS_SUCCESS);
+    CacheEntry.ImageHandle = (HANDLE)0x80000000;
+    Status = NtApphelpCacheControl(ApphelpCacheServiceLookup, &CacheEntry);
+    ok_ntstatus(Status, STATUS_NOT_FOUND);
+
+    RtlFreeHeap(RtlGetProcessHeap(), 0, ntPath.Buffer);
+}
+
+
+/* Most service related code was taken from services_winetest:service and modified for usage here
+    The rest came from MSDN */
+
+static SERVICE_STATUS_HANDLE (WINAPI *pRegisterServiceCtrlHandlerExA)(LPCSTR,LPHANDLER_FUNCTION_EX,LPVOID);
+static char service_name[100] = "apphelp_test_service";
+static HANDLE service_stop_event;
+static SERVICE_STATUS_HANDLE service_status;
+
+static BOOLEAN RegisterInShimCache(BOOLEAN WithMapping)
+{
+    WCHAR szPath[MAX_PATH];
+    UNICODE_STRING ntPath;
+    BOOLEAN Result;
+    NTSTATUS Status;
+    GetModuleFileNameW(NULL, szPath, sizeof(szPath) / sizeof(szPath[0]));
+    Result = RtlDosPathNameToNtPathName_U(szPath, &ntPath, NULL, NULL);
+    if (!Result)
+    {
+        DbgPrint("RegisterInShimCache: RtlDosPathNameToNtPathName_U failed\n");
+        return FALSE;
+    }
+
+    Status = CallCacheControl(&ntPath, WithMapping, ApphelpCacheServiceUpdate);
+    if (!NT_SUCCESS(Status))
+    {
+        DbgPrint("RegisterInShimCache: CallCacheControl failed\n");
+        RtlFreeHeap(RtlGetProcessHeap(), 0, ntPath.Buffer);
+        return FALSE;
+    }
+    RtlFreeHeap(RtlGetProcessHeap(), 0, ntPath.Buffer);
+    return TRUE;
+}
+
+
+static DWORD WINAPI service_handler(DWORD ctrl, DWORD event_type, void *event_data, void *context)
+{
+    SERVICE_STATUS status = {0};
+    status.dwServiceType = SERVICE_WIN32;
+    status.dwControlsAccepted = SERVICE_ACCEPT_STOP;
+
+    switch(ctrl)
+    {
+    case SERVICE_CONTROL_STOP:
+    case SERVICE_CONTROL_SHUTDOWN:
+        status.dwCurrentState = SERVICE_STOP_PENDING;
+        status.dwControlsAccepted = 0;
+        SetServiceStatus(service_status, &status);
+        SetEvent(service_stop_event);
+        return NO_ERROR;
+    case RegisterShimCacheWithHandle:
+        if (!RegisterInShimCache(TRUE))
+        {
+            /* TODO: how should we communicate a failure? */
+        }
+        break;
+    case RegisterShimCacheWithoutHandle:
+        if (!RegisterInShimCache(FALSE))
+        {
+            /* TODO: how should we communicate a failure? */
+        }
+        break;
+    default:
+        DbgPrint("Unhandled: %d\n", ctrl);
+        break;
+    }
+    status.dwCurrentState = SERVICE_RUNNING;
+    SetServiceStatus(service_status, &status);
+    return NO_ERROR;
+}
+
+static void WINAPI service_main(DWORD argc, char **argv)
+{
+    SERVICE_STATUS status = {0};
+    service_status = pRegisterServiceCtrlHandlerExA(service_name, service_handler, NULL);
+    if(!service_status)
+        return;
+
+    status.dwServiceType = SERVICE_WIN32;
+    status.dwCurrentState = SERVICE_RUNNING;
+    status.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
+    SetServiceStatus(service_status, &status);
+
+    WaitForSingleObject(service_stop_event, INFINITE);
+
+    status.dwCurrentState = SERVICE_STOPPED;
+    status.dwControlsAccepted = 0;
+    SetServiceStatus(service_status, &status);
+}
+
+static SC_HANDLE InstallService(SC_HANDLE scm_handle)
+{
+    char service_cmd[MAX_PATH+150], *ptr;
+    SC_HANDLE service;
+
+    ptr = service_cmd + GetModuleFileNameA(NULL, service_cmd, MAX_PATH);
+    strcpy(ptr, " NtApphelpCacheControl service");
+    ptr += strlen(ptr);
+
+    service = CreateServiceA(scm_handle, service_name, service_name, GENERIC_ALL,
+                             SERVICE_WIN32_OWN_PROCESS, SERVICE_DEMAND_START, SERVICE_ERROR_IGNORE,
+                             service_cmd, NULL, NULL, NULL, NULL, NULL);
+    if (!service)
+    {
+        skip("Could not create helper service\n");
+        return NULL;
+    }
+    return service;
+}
+
+static void WaitService(SC_HANDLE service_handle, DWORD Status, SERVICE_STATUS_PROCESS* ssp)
+{
+    DWORD dwBytesNeeded;
+    DWORD dwStartTime = GetTickCount();
+    while (ssp->dwCurrentState != Status)
+    {
+        Sleep(40);
+        if (!QueryServiceStatusEx(service_handle, SC_STATUS_PROCESS_INFO,
+            (LPBYTE)ssp, sizeof(SERVICE_STATUS_PROCESS), &dwBytesNeeded ))
+        {
+            ok(0, "QueryServiceStatusEx failed waiting for %lu\n", Status);
+            break;
+        }
+        if ((GetTickCount() - dwStartTime) > 1000)
+        {
+            ok(0, "Timeout waiting for (%lu) from service, is: %lu.\n",
+                Status, ssp->dwCurrentState);
+            break;
+        }
+    }
+}
+
+static void RunTest()
+{
+    SC_HANDLE scm_handle = OpenSCManagerA(NULL, NULL, SC_MANAGER_ALL_ACCESS);
+    SC_HANDLE service_handle = InstallService(scm_handle);
+    if (service_handle)
+    {
+        SERVICE_STATUS_PROCESS ssp = {0};
+        BOOL res = StartServiceA(service_handle, 0, NULL);
+        if (res)
+        {
+            WaitService(service_handle, SERVICE_RUNNING, &ssp);
+            RunApphelpCacheControlTests(service_handle);
+            ControlService(service_handle, SERVICE_CONTROL_STOP, (LPSERVICE_STATUS)&ssp);
+            WaitService(service_handle, SERVICE_STOPPED, &ssp);
+        }
+        else
+        {
+            skip("Could not start helper service\n");
+        }
+        DeleteService(service_handle);
+    }
+    CloseServiceHandle(scm_handle);
+}
+
+START_TEST(NtApphelpCacheControl)
+{
+    char **argv;
+    int argc;
+
+    pRegisterServiceCtrlHandlerExA = (void*)GetProcAddress(GetModuleHandleA("advapi32.dll"), "RegisterServiceCtrlHandlerExA");
+    if (!pRegisterServiceCtrlHandlerExA)
+    {
+        win_skip("RegisterServiceCtrlHandlerExA not available, skipping tests\n");
+        return;
+    }
+    argc = winetest_get_mainargs(&argv);
+    if(argc < 3)
+    {
+        RunTest();
+    }
+    else
+    {
+        SERVICE_TABLE_ENTRYA servtbl[] = {
+            {service_name, service_main},
+            {NULL, NULL}
+        };
+        service_stop_event = CreateEventA(NULL, TRUE, FALSE, NULL);
+        StartServiceCtrlDispatcherA(servtbl);
+        Sleep(50);
+        CloseHandle(service_stop_event);
+    }
+}
+
+
index daefbaa..db77b2f 100644 (file)
@@ -5,6 +5,7 @@
 
 extern void func_LdrEnumResources(void);
 extern void func_NtAllocateVirtualMemory(void);
+extern void func_NtApphelpCacheControl(void);
 extern void func_NtContinue(void);
 extern void func_NtCreateFile(void);
 extern void func_NtCreateThread(void);
@@ -40,6 +41,7 @@ const struct test winetest_testlist[] =
 {
     { "LdrEnumResources",               func_LdrEnumResources },
     { "NtAllocateVirtualMemory",        func_NtAllocateVirtualMemory },
+    { "NtApphelpCacheControl",          func_NtApphelpCacheControl },
     { "NtContinue",                     func_NtContinue },
     { "NtCreateFile",                   func_NtCreateFile },
     { "NtCreateThread",                 func_NtCreateThread },