Time to commit some Work-In-Progress stuff before my diff gets too large..
authorColin Finck <colin@reactos.org>
Fri, 22 May 2015 15:29:07 +0000 (15:29 +0000)
committerColin Finck <colin@reactos.org>
Fri, 22 May 2015 15:29:07 +0000 (15:29 +0000)
[LOCALSPL]
- Begin work on the Local Spooler. Return a structure with function pointers in InitializePrintProvidor.
- Design and document internal structures for managing LocalSpl Handles, Printer Handles, Printers, Print Jobs and Print Processors.
  Manage Printers and Print Processors in Generic Tables, with one Job Queue per Printer managed as a Doubly Linked List.
- Implement LocalOpenPrinter, LocalEnumPrintProcessorDatatypes, LocalEnumPrintProcessors, LocalGetPrintProcessorDirectory, with focus on catching all corner cases.
  Currently working on LocalStartDocPrinter.
- Build upon the documentation at http://www.undocprint.org/formats/winspool/shd to read and write .SHD files.

[WINPRINT]
Begin work on the Standard Print Processor. Implement EnumPrintProcessorDatatypesW.

[WINSPOOL_APITEST]
Add an API Test for winspool.drv, currently testing some corner cases of ClosePrinter, EnumPrintProcessorDatatypesW, GetPrintProcessorDirectoryW, OpenPrinterW, StartDocPrinterW.
TODO: Find a way to actually test the localspl.dll functions instead of only winspool.drv. This DLL doesn't like to be tested standalone under Windows, e.g. without being used through spoolsv/spoolss.

[SPOOLSS]
Implement InitializeRouter by calling the InitializePrintProvidor function of localspl there.
This function should later also initialize further Print Providers.

[SPOOLSV]
Call InitializeRouter when starting up the service.

[WINSPOOL]
Add dummy functions for EnumPrintProcessorDatatypesA/EnumPrintProcessorDatatypesW.

[All modules]
Fix printf format specifiers for errors (%lu) and statuses (%ld).

svn path=/branches/colins-printing-for-freedom/; revision=67847

27 files changed:
reactos/win32ss/printing/base/spoolss/context.c
reactos/win32ss/printing/base/spoolss/main.c
reactos/win32ss/printing/base/spoolss/precomp.h
reactos/win32ss/printing/base/spoolsv/init.c
reactos/win32ss/printing/base/spoolsv/main.c
reactos/win32ss/printing/base/spoolsv/precomp.h
reactos/win32ss/printing/base/spoolsv/rpcserver.c
reactos/win32ss/printing/base/winspool/main.c
reactos/win32ss/printing/base/winspool/winspool.spec
reactos/win32ss/printing/processors/winprint/CMakeLists.txt
reactos/win32ss/printing/processors/winprint/main.c
reactos/win32ss/printing/processors/winprint/winprint.spec
reactos/win32ss/printing/providers/localspl/CMakeLists.txt
reactos/win32ss/printing/providers/localspl/jobs.c [new file with mode: 0644]
reactos/win32ss/printing/providers/localspl/main.c
reactos/win32ss/printing/providers/localspl/precomp.h
reactos/win32ss/printing/providers/localspl/printers.c [new file with mode: 0644]
reactos/win32ss/printing/providers/localspl/printprocessors.c [new file with mode: 0644]
reactos/win32ss/printing/providers/localspl/tools.c [new file with mode: 0644]
rostests/apitests/CMakeLists.txt
rostests/apitests/winspool/CMakeLists.txt [new file with mode: 0644]
rostests/apitests/winspool/ClosePrinter.c [new file with mode: 0644]
rostests/apitests/winspool/EnumPrintProcessorDatatypes.c [new file with mode: 0644]
rostests/apitests/winspool/GetPrintProcessorDirectory.c [new file with mode: 0644]
rostests/apitests/winspool/OpenPrinter.c [new file with mode: 0644]
rostests/apitests/winspool/StartDocPrinter.c [new file with mode: 0644]
rostests/apitests/winspool/testlist.c [new file with mode: 0644]

index 5fce2bc..ca39f5a 100644 (file)
@@ -21,7 +21,7 @@ ImpersonatePrinterClient(HANDLE hToken)
 
     if (!SetThreadToken(NULL, hToken))
     {
 
     if (!SetThreadToken(NULL, hToken))
     {
-        ERR("SetThreadToken failed with error %u!\n", GetLastError());
+        ERR("SetThreadToken failed with error %lu!\n", GetLastError());
         CloseHandle(hToken);
         return FALSE;
     }
         CloseHandle(hToken);
         return FALSE;
     }
@@ -46,14 +46,14 @@ RevertToPrinterSelf()
     // Retrieve our current impersonation token
     if (!OpenThreadToken(GetCurrentThread(), TOKEN_IMPERSONATE, TRUE, &hToken))
     {
     // Retrieve our current impersonation token
     if (!OpenThreadToken(GetCurrentThread(), TOKEN_IMPERSONATE, TRUE, &hToken))
     {
-        ERR("OpenThreadToken failed with error %u!\n", GetLastError());
+        ERR("OpenThreadToken failed with error %lu!\n", GetLastError());
         return NULL;
     }
 
     // Tell the thread to stop impersonating
     if (!SetThreadToken(NULL, NULL))
     {
         return NULL;
     }
 
     // Tell the thread to stop impersonating
     if (!SetThreadToken(NULL, NULL))
     {
-        ERR("SetThreadToken failed with error %u!\n", GetLastError());
+        ERR("SetThreadToken failed with error %lu!\n", GetLastError());
         return NULL;
     }
 
         return NULL;
     }
 
index b37bd30..39a4585 100644 (file)
@@ -7,6 +7,9 @@
 
 #include "precomp.h"
 
 
 #include "precomp.h"
 
+PRINTPROVIDOR LocalSplFuncs;
+
+
 BOOL WINAPI
 ClosePrinter(HANDLE hPrinter)
 {
 BOOL WINAPI
 ClosePrinter(HANDLE hPrinter)
 {
@@ -52,7 +55,32 @@ GetPrintProcessorDirectoryW(LPWSTR pName, LPWSTR pEnvironment, DWORD Level, LPBY
 BOOL WINAPI
 InitializeRouter(HANDLE SpoolerStatusHandle)
 {
 BOOL WINAPI
 InitializeRouter(HANDLE SpoolerStatusHandle)
 {
-    return FALSE;
+    HINSTANCE hinstLocalSpl;
+    PInitializePrintProvidor pfnInitializePrintProvidor;
+
+    // Only initialize localspl.dll for now.
+    // This function should later look for all available print providers in the registry and initialize all of them.
+    hinstLocalSpl = LoadLibraryW(L"localspl");
+    if (!hinstLocalSpl)
+    {
+        ERR("LoadLibraryW for localspl failed with error %lu!\n", GetLastError());
+        return FALSE;
+    }
+
+    pfnInitializePrintProvidor = (PInitializePrintProvidor)GetProcAddress(hinstLocalSpl, "InitializePrintProvidor");
+    if (!pfnInitializePrintProvidor)
+    {
+        ERR("GetProcAddress failed with error %lu!\n", GetLastError());
+        return FALSE;
+    }
+
+    if (!pfnInitializePrintProvidor(&LocalSplFuncs, sizeof(PRINTPROVIDOR), NULL))
+    {
+        ERR("InitializePrintProvidor failed for localspl with error %lu!\n", GetLastError());
+        return FALSE;
+    }
+
+    return TRUE;
 }
 
 BOOL WINAPI
 }
 
 BOOL WINAPI
index fa44233..25f43d7 100644 (file)
 #include <windef.h>
 #include <winbase.h>
 #include <wingdi.h>
 #include <windef.h>
 #include <winbase.h>
 #include <wingdi.h>
+#include <winreg.h>
 #include <winspool.h>
 #include <winspool.h>
+#include <winsplp.h>
 
 #include <wine/debug.h>
 WINE_DEFAULT_DEBUG_CHANNEL(spoolss);
 
 
 #include <wine/debug.h>
 WINE_DEFAULT_DEBUG_CHANNEL(spoolss);
 
+// Function pointer to InitializePrintProvidor of a provider DLL
+typedef BOOL (WINAPI *PInitializePrintProvidor)(LPPRINTPROVIDOR, DWORD, LPWSTR);
+
 #endif
 #endif
index 699505f..2cbb6f6 100644 (file)
 DWORD
 _RpcSpoolerInit()
 {
 DWORD
 _RpcSpoolerInit()
 {
-    DWORD ErrorCode;
+    DWORD dwErrorCode;
 
     // Call SpoolerInit in the security context of the client.
     // This delay-loads spoolss.dll in the user context and all further calls to functions in spoolss.dll will be done in the user context as well.
 
     // Call SpoolerInit in the security context of the client.
     // This delay-loads spoolss.dll in the user context and all further calls to functions in spoolss.dll will be done in the user context as well.
-    ErrorCode = RpcImpersonateClient(NULL);
-    if (ErrorCode != ERROR_SUCCESS)
+    dwErrorCode = RpcImpersonateClient(NULL);
+    if (dwErrorCode != ERROR_SUCCESS)
     {
     {
-        ERR("RpcImpersonateClient failed with status %u!\n", ErrorCode);
-        return ErrorCode;
+        ERR("RpcImpersonateClient failed with error %lu!\n", dwErrorCode);
+        return dwErrorCode;
     }
 
     }
 
-    ErrorCode = SpoolerInit();
-    if (ErrorCode != ERROR_SUCCESS)
+    dwErrorCode = SpoolerInit();
+    if (dwErrorCode != ERROR_SUCCESS)
     {
     {
-        ERR("SpoolerInit failed with status %u!\n", ErrorCode);
+        ERR("SpoolerInit failed with error %lu!\n", dwErrorCode);
         RpcRevertToSelf();
         RpcRevertToSelf();
-        return ErrorCode;
+        return dwErrorCode;
     }
 
     return RpcRevertToSelf();
     }
 
     return RpcRevertToSelf();
index 1b00de0..f00abaa 100644 (file)
@@ -60,18 +60,26 @@ _ServiceMain(DWORD dwArgc, LPWSTR* lpszArgv)
 
     // Create a thread for serving RPC requests
     hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)RpcThreadProc, NULL, 0, NULL);
 
     // Create a thread for serving RPC requests
     hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)RpcThreadProc, NULL, 0, NULL);
-
-    if (hThread)
+    if (!hThread)
     {
     {
-        // We don't need the thread handle. Keeping it open blocks the thread from terminating.
-        CloseHandle(hThread);
-        _UpdateServiceStatus(SERVICE_RUNNING, 0);
+        ERR("CreateThread failed with error %u!\n", GetLastError());
+        _UpdateServiceStatus(SERVICE_STOPPED, 0);
+        return;
     }
     }
-    else
+
+    // We don't need the thread handle. Keeping it open blocks the thread from terminating.
+    CloseHandle(hThread);
+
+    // Initialize the routing layer in spoolss.dll
+    if (!InitializeRouter(hServiceStatus))
     {
     {
-        ERR("CreateThread failed with error %u!\n", GetLastError());
+        ERR("InitializeRouter failed with error %lu!\n", GetLastError());
         _UpdateServiceStatus(SERVICE_STOPPED, 0);
         _UpdateServiceStatus(SERVICE_STOPPED, 0);
+        return;
     }
     }
+
+    // We're alive!
+    _UpdateServiceStatus(SERVICE_RUNNING, 0);
 }
 
 int
 }
 
 int
index a15b0d9..f14c333 100644 (file)
 
 #define WIN32_NO_STATUS
 #include <windef.h>
 
 #define WIN32_NO_STATUS
 #include <windef.h>
+#include <winbase.h>
+#include <winreg.h>
 #include <winsvc.h>
 #include <winsvc.h>
+#include <wingdi.h>
+#include <winspool.h>
+#include <winsplp.h>
 #include <winspool_s.h>
 
 #include <wine/debug.h>
 WINE_DEFAULT_DEBUG_CHANNEL(spoolsv);
 
 // rpcserver.c
 #include <winspool_s.h>
 
 #include <wine/debug.h>
 WINE_DEFAULT_DEBUG_CHANNEL(spoolsv);
 
 // rpcserver.c
-extern DWORD WINAPI RpcThreadProc(LPVOID lpParameter);
+DWORD WINAPI RpcThreadProc(LPVOID lpParameter);
 
 // Undocumented spoolss
 
 // Undocumented spoolss
+BOOL WINAPI InitializeRouter(HANDLE SpoolerStatusHandle);
 DWORD WINAPI SpoolerInit();
 
 #endif
 DWORD WINAPI SpoolerInit();
 
 #endif
index 6e57d7f..869353d 100644 (file)
@@ -15,21 +15,21 @@ RpcThreadProc(LPVOID lpParameter)
     Status = RpcServerUseProtseqEpW(L"ncacn_np", 20, L"\\pipe\\spoolss", NULL);
     if (Status != RPC_S_OK)
     {
     Status = RpcServerUseProtseqEpW(L"ncacn_np", 20, L"\\pipe\\spoolss", NULL);
     if (Status != RPC_S_OK)
     {
-        ERR("RpcServerUseProtseqEpW failed with status %u!\n", Status);
+        ERR("RpcServerUseProtseqEpW failed with status %ld!\n", Status);
         return 0;
     }
 
     Status = RpcServerRegisterIf(winspool_v1_0_s_ifspec, NULL, NULL);
     if (Status != RPC_S_OK)
     {
         return 0;
     }
 
     Status = RpcServerRegisterIf(winspool_v1_0_s_ifspec, NULL, NULL);
     if (Status != RPC_S_OK)
     {
-        ERR("RpcServerRegisterIf failed with status %u!\n", Status);
+        ERR("RpcServerRegisterIf failed with status %ld!\n", Status);
         return 0;
     }
 
     Status = RpcServerListen(1, RPC_C_LISTEN_MAX_CALLS_DEFAULT, 0);
     if (Status != RPC_S_OK)
     {
         return 0;
     }
 
     Status = RpcServerListen(1, RPC_C_LISTEN_MAX_CALLS_DEFAULT, 0);
     if (Status != RPC_S_OK)
     {
-        ERR("RpcServerListen() failed with status %u!\n", Status);
+        ERR("RpcServerListen() failed with status %ld!\n", Status);
     }
 
     return 0;
     }
 
     return 0;
index 5e798c3..ffe2d3a 100644 (file)
@@ -18,7 +18,7 @@ WINSPOOL_HANDLE_bind(WINSPOOL_HANDLE wszName)
     Status = RpcStringBindingComposeW(NULL, L"ncacn_np", wszName, L"\\pipe\\spoolss", NULL, &wszStringBinding);
     if (Status != RPC_S_OK)
     {
     Status = RpcStringBindingComposeW(NULL, L"ncacn_np", wszName, L"\\pipe\\spoolss", NULL, &wszStringBinding);
     if (Status != RPC_S_OK)
     {
-        ERR("RpcStringBindingComposeW failed with status %u!\n", Status);
+        ERR("RpcStringBindingComposeW failed with status %ld!\n", Status);
         return NULL;
     }
 
         return NULL;
     }
 
@@ -26,7 +26,7 @@ WINSPOOL_HANDLE_bind(WINSPOOL_HANDLE wszName)
     Status = RpcBindingFromStringBindingW(wszStringBinding, &hBinding);
     if (Status != RPC_S_OK)
     {
     Status = RpcBindingFromStringBindingW(wszStringBinding, &hBinding);
     if (Status != RPC_S_OK)
     {
-        ERR("RpcBindingFromStringBindingW failed with status %u!\n", Status);
+        ERR("RpcBindingFromStringBindingW failed with status %ld!\n", Status);
         return NULL;
     }
 
         return NULL;
     }
 
@@ -34,7 +34,7 @@ WINSPOOL_HANDLE_bind(WINSPOOL_HANDLE wszName)
     Status = RpcStringFreeW(&wszStringBinding);
     if (Status != RPC_S_OK)
     {
     Status = RpcStringFreeW(&wszStringBinding);
     if (Status != RPC_S_OK)
     {
-        ERR("RpcStringFreeW failed with status %u!\n", Status);
+        ERR("RpcStringFreeW failed with status %ld!\n", Status);
         return NULL;
     }
 
         return NULL;
     }
 
@@ -49,7 +49,7 @@ WINSPOOL_HANDLE_unbind(WINSPOOL_HANDLE wszName, handle_t hBinding)
     Status = RpcBindingFree(&hBinding);
     if (Status != RPC_S_OK)
     {
     Status = RpcBindingFree(&hBinding);
     if (Status != RPC_S_OK)
     {
-        ERR("RpcBindingFree failed with status %u!\n", Status);
+        ERR("RpcBindingFree failed with status %ld!\n", Status);
     }
 }
 
     }
 }
 
@@ -119,6 +119,18 @@ EnumPrintersW(DWORD Flags, LPWSTR Name, DWORD Level, LPBYTE pPrinterEnum, DWORD
     return FALSE;
 }
 
     return FALSE;
 }
 
+BOOL WINAPI
+EnumPrintProcessorDatatypesA(LPSTR pName, LPSTR pPrintProcessorName, DWORD Level, LPBYTE pDatatypes, DWORD cbBuf, LPDWORD pcbNeeded, LPDWORD pcReturned)
+{
+    return FALSE;
+}
+
+BOOL WINAPI
+EnumPrintProcessorDatatypesW(LPWSTR pName, LPWSTR pPrintProcessorName, DWORD Level, LPBYTE pDatatypes, DWORD cbBuf, LPDWORD pcbNeeded, LPDWORD pcReturned)
+{
+    return FALSE;
+}
+
 BOOL WINAPI
 GetDefaultPrinterA(LPSTR pszBuffer, LPDWORD pcchBuffer)
 {
 BOOL WINAPI
 GetDefaultPrinterA(LPSTR pszBuffer, LPDWORD pcchBuffer)
 {
@@ -178,7 +190,7 @@ OpenPrinterA(LPSTR pPrinterName, LPHANDLE phPrinter, LPPRINTER_DEFAULTSA pDefaul
         pwszPrinterName = HeapAlloc(GetProcessHeap(), 0, StringLength * sizeof(WCHAR));
         if (!pwszPrinterName)
         {
         pwszPrinterName = HeapAlloc(GetProcessHeap(), 0, StringLength * sizeof(WCHAR));
         if (!pwszPrinterName)
         {
-            ERR("HeapAlloc failed for pwszPrinterName with last error %u!\n", GetLastError());
+            ERR("HeapAlloc failed for pwszPrinterName with last error %lu!\n", GetLastError());
             goto Cleanup;
         }
 
             goto Cleanup;
         }
 
@@ -197,7 +209,7 @@ OpenPrinterA(LPSTR pPrinterName, LPHANDLE phPrinter, LPPRINTER_DEFAULTSA pDefaul
             pwszDatatype = HeapAlloc(GetProcessHeap(), 0, StringLength * sizeof(WCHAR));
             if (!pwszDatatype)
             {
             pwszDatatype = HeapAlloc(GetProcessHeap(), 0, StringLength * sizeof(WCHAR));
             if (!pwszDatatype)
             {
-                ERR("HeapAlloc failed for pwszDatatype with last error %u!\n", GetLastError());
+                ERR("HeapAlloc failed for pwszDatatype with last error %lu!\n", GetLastError());
                 goto Cleanup;
             }
 
                 goto Cleanup;
             }
 
@@ -250,14 +262,14 @@ OpenPrinterW(LPWSTR pPrinterName, LPHANDLE phPrinter, LPPRINTER_DEFAULTSW pDefau
         ErrorCode = _RpcOpenPrinter(pPrinterName, phPrinter, pDatatype, pDevModeContainer, AccessRequired);
         if (ErrorCode)
         {
         ErrorCode = _RpcOpenPrinter(pPrinterName, phPrinter, pDatatype, pDevModeContainer, AccessRequired);
         if (ErrorCode)
         {
-            ERR("_RpcOpenPrinter failed with error %u!\n", ErrorCode);
+            ERR("_RpcOpenPrinter failed with error %lu!\n", ErrorCode);
         }
 
         ReturnValue = (ErrorCode == ERROR_SUCCESS);
     }
     RpcExcept(EXCEPTION_EXECUTE_HANDLER)
     {
         }
 
         ReturnValue = (ErrorCode == ERROR_SUCCESS);
     }
     RpcExcept(EXCEPTION_EXECUTE_HANDLER)
     {
-        ERR("_RpcOpenPrinter failed with exception code %u!\n", RpcExceptionCode());
+        ERR("_RpcOpenPrinter failed with exception code %lu!\n", RpcExceptionCode());
     }
     RpcEndExcept;
 
     }
     RpcEndExcept;
 
@@ -276,14 +288,14 @@ SpoolerInit()
         ErrorCode = _RpcSpoolerInit();
         if (ErrorCode)
         {
         ErrorCode = _RpcSpoolerInit();
         if (ErrorCode)
         {
-            ERR("_RpcSpoolerInit failed with error %u!\n", ErrorCode);
+            ERR("_RpcSpoolerInit failed with error %lu!\n", ErrorCode);
         }
 
         ReturnValue = (ErrorCode == ERROR_SUCCESS);
     }
     RpcExcept(EXCEPTION_EXECUTE_HANDLER)
     {
         }
 
         ReturnValue = (ErrorCode == ERROR_SUCCESS);
     }
     RpcExcept(EXCEPTION_EXECUTE_HANDLER)
     {
-        ERR("_RpcSpoolerInit failed with exception code %u!\n", RpcExceptionCode());
+        ERR("_RpcSpoolerInit failed with exception code %lu!\n", RpcExceptionCode());
     }
     RpcEndExcept;
 
     }
     RpcEndExcept;
 
index 665d785..8b48c11 100644 (file)
@@ -91,8 +91,8 @@
 @ stub EnumPrinterKeyW
 @ stdcall EnumPrintersA(long ptr long ptr long ptr ptr)
 @ stdcall EnumPrintersW(long ptr long ptr long ptr ptr)
 @ stub EnumPrinterKeyW
 @ stdcall EnumPrintersA(long ptr long ptr long ptr ptr)
 @ stdcall EnumPrintersW(long ptr long ptr long ptr ptr)
-@ stub EnumPrintProcessorDatatypesA
-@ stub EnumPrintProcessorDatatypesW
+@ stdcall EnumPrintProcessorDatatypesA(ptr ptr long ptr long ptr ptr)
+@ stdcall EnumPrintProcessorDatatypesW(ptr ptr long ptr long ptr ptr)
 @ stub EnumPrintProcessorsA
 @ stub EnumPrintProcessorsW
 @ stub EXTDEVICEMODE
 @ stub EnumPrintProcessorsA
 @ stub EnumPrintProcessorsW
 @ stub EXTDEVICEMODE
index 0f120e2..1f422ec 100644 (file)
@@ -1,5 +1,5 @@
 
 
-spec2def(winprint winprint.spec ADD_IMPORTLIB)
+spec2def(winprint.dll winprint.spec ADD_IMPORTLIB)
 
 list(APPEND SOURCE
     main.c
 
 list(APPEND SOURCE
     main.c
index df999e2..8c279a8 100644 (file)
@@ -7,3 +7,97 @@
 
 #include "precomp.h"
 
 
 #include "precomp.h"
 
+PCWSTR pwszDatatypes[] = {
+    L"RAW",
+    0
+};
+
+/**
+ * @name EnumPrintProcessorDatatypesW
+ *
+ * Obtains an array of all datatypes supported by this Print Processor.
+ *
+ * @param pName
+ * Server Name. Ignored here, because every caller of EnumPrintProcessorDatatypesW is interested in this Print Processor's information.
+ *
+ * @param pPrintProcessorName
+ * Print Processor Name. Ignored here, because every caller of EnumPrintProcessorDatatypesW is interested in this Print Processor's information.
+ *
+ * @param Level
+ * The level of the structure supplied through pDatatypes. This must be 1.
+ *
+ * @param pDatatypes
+ * Pointer to the buffer that receives an array of DATATYPES_INFO_1W structures.
+ * Can be NULL if you just want to know the required size of the buffer.
+ *
+ * @param cbBuf
+ * Size of the buffer you supplied for pDatatypes, in bytes.
+ *
+ * @param pcbNeeded
+ * Pointer to a variable that receives the required size of the buffer for pDatatypes, in bytes.
+ * This parameter mustn't be NULL!
+ *
+ * @param pcReturned
+ * Pointer to a variable that receives the number of elements of the DATATYPES_INFO_1W array.
+ * This parameter mustn't be NULL!
+ *
+ * @return
+ * TRUE if we successfully copied the array into pDatatypes, FALSE otherwise.
+ * A more specific error code can be obtained through GetLastError.
+ */
+BOOL WINAPI
+EnumPrintProcessorDatatypesW(LPWSTR pName, LPWSTR pPrintProcessorName, DWORD Level, LPBYTE pDatatypes, DWORD cbBuf, LPDWORD pcbNeeded, LPDWORD pcReturned)
+{
+    DATATYPES_INFO_1W DatatypesInfo1;
+    DWORD cbDatatype;
+    PBYTE pCurrentOutputDatatype;
+    PBYTE pCurrentOutputDatatypeInfo;
+    PCWSTR* pCurrentDatatype;
+
+    // Sanity checks
+    if (Level != 1 || !pcbNeeded || !pcReturned)
+    {
+        SetLastError(ERROR_INVALID_PARAMETER);
+        return FALSE;
+    }
+
+    // Count the required buffer size and the number of datatypes.
+    *pcbNeeded = 0;
+    *pcReturned = 0;
+
+    for (pCurrentDatatype = pwszDatatypes; *pCurrentDatatype; pCurrentDatatype++)
+    {
+        cbDatatype = (wcslen(*pCurrentDatatype) + 1) * sizeof(WCHAR);
+        *pcbNeeded += sizeof(DATATYPES_INFO_1W) + cbDatatype;
+        *pcReturned++;
+    }
+
+    // Check if the supplied buffer is large enough.
+    if (cbBuf < *pcbNeeded)
+    {
+        SetLastError(ERROR_INSUFFICIENT_BUFFER);
+        return FALSE;
+    }
+
+    // Put the datatype strings right after the last DATATYPES_INFO_1W structure.
+    pCurrentOutputDatatypeInfo = pDatatypes;
+    pCurrentOutputDatatype = pDatatypes + *pcReturned * sizeof(DATATYPES_INFO_1W);
+
+    // Copy over all datatypes.
+    for (pCurrentDatatype = pwszDatatypes; *pCurrentDatatype; pCurrentDatatype++)
+    {
+        // Copy the datatype string into the output buffer.
+        cbDatatype = (wcslen(*pCurrentDatatype) + 1) * sizeof(WCHAR);
+        CopyMemory(pCurrentOutputDatatype, *pCurrentDatatype, cbDatatype);
+
+        // Fill and copy the DATATYPES_INFO_1W structure belonging to this datatype.
+        DatatypesInfo1.pName = (PWSTR)pCurrentOutputDatatype;
+        CopyMemory(pCurrentOutputDatatypeInfo, &DatatypesInfo1, sizeof(DATATYPES_INFO_1W));
+        
+        // Advance to the next DATATYPES_INFO_1W location and string location in the output buffer.
+        pCurrentOutputDatatype += cbDatatype;
+        pCurrentOutputDatatypeInfo += sizeof(DATATYPES_INFO_1W);
+    }
+
+    return TRUE;
+}
index 745b1b1..947cf94 100644 (file)
@@ -1,6 +1,6 @@
 @ stub ClosePrintProcessor
 @ stub ControlPrintProcessor
 @ stub ClosePrintProcessor
 @ stub ControlPrintProcessor
-@ stub EnumPrintProcessorDatatypesW
+@ stdcall EnumPrintProcessorDatatypesW(ptr ptr long ptr long ptr ptr)
 @ stub GetPrintProcessorCapabilities
 @ stub OpenPrintProcessor
 @ stub PrintDocumentOnPrintProcessor
 @ stub GetPrintProcessorCapabilities
 @ stub OpenPrintProcessor
 @ stub PrintDocumentOnPrintProcessor
index 28ad707..1280f6d 100644 (file)
@@ -1,9 +1,13 @@
 
 
-spec2def(localspl localspl.spec ADD_IMPORTLIB)
+spec2def(localspl.dll localspl.spec ADD_IMPORTLIB)
 
 list(APPEND SOURCE
 
 list(APPEND SOURCE
+    jobs.c
     main.c
     main.c
-    precomp.h)
+    precomp.h
+    printers.c
+    printprocessors.c
+    tools.c)
 
 add_library(localspl SHARED
     ${SOURCE}
 
 add_library(localspl SHARED
     ${SOURCE}
@@ -13,6 +17,6 @@ add_library(localspl SHARED
 
 set_module_type(localspl win32dll UNICODE)
 target_link_libraries(localspl wine)
 
 set_module_type(localspl win32dll UNICODE)
 target_link_libraries(localspl wine)
-add_importlibs(localspl kernel32 msvcrt ntdll)
+add_importlibs(localspl advapi32 msvcrt kernel32 ntdll)
 add_pch(localspl precomp.h SOURCE)
 add_cd_file(TARGET localspl DESTINATION reactos/system32 FOR all)
 add_pch(localspl precomp.h SOURCE)
 add_cd_file(TARGET localspl DESTINATION reactos/system32 FOR all)
diff --git a/reactos/win32ss/printing/providers/localspl/jobs.c b/reactos/win32ss/printing/providers/localspl/jobs.c
new file mode 100644 (file)
index 0000000..7e36f83
--- /dev/null
@@ -0,0 +1,232 @@
+/*
+ * PROJECT:     ReactOS Local Spooler
+ * LICENSE:     GNU LGPL v2.1 or any later version as published by the Free Software Foundation
+ * PURPOSE:     Functions for managing print jobs
+ * COPYRIGHT:   Copyright 2015 Colin Finck <colin@reactos.org>
+ */
+
+#include "precomp.h"
+
+LIST_ENTRY LocalJobQueue;
+
+void
+InitializeJobQueue()
+{
+    const WCHAR wszPath[] = L"\\PRINTERS\\?????.SHD";
+    const DWORD cchPath = sizeof(wszPath) / sizeof(WCHAR) - 1;
+    const DWORD cchFolders = sizeof("\\PRINTERS\\") - 1;
+    const DWORD cchPattern = sizeof("?????") - 1;
+
+    DWORD dwJobID;
+    HANDLE hFind;
+    PLOCAL_JOB pJob;
+    PWSTR p;
+    WCHAR wszFullPath[MAX_PATH];
+    WIN32_FIND_DATAW FindData;
+
+    // Construct the full path search pattern.
+    CopyMemory(wszFullPath, wszSpoolDirectory, cchSpoolDirectory * sizeof(WCHAR));
+    CopyMemory(&wszFullPath[cchSpoolDirectory], wszPath, (cchPath + 1) * sizeof(WCHAR));
+
+    // Use the search pattern to look for unfinished jobs serialized in shadow files (.SHD)
+    hFind = FindFirstFileW(wszFullPath, &FindData);
+    if (hFind == INVALID_HANDLE_VALUE)
+    {
+        // No unfinished jobs found.
+        return;
+    }
+
+    do
+    {
+        // Skip possible subdirectories.
+        if (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+            continue;
+
+        // Extract the Job ID and verify the file name format at the same time.
+        dwJobID = wcstoul(FindData.cFileName, &p, 10);
+        if (!IS_VALID_JOB_ID(dwJobID))
+            continue;
+
+        if (wcsicmp(p, L".SHD") != 0)
+            continue;
+
+        // This shadow file has a valid name. Construct the full path and try to load it.
+        CopyMemory(&wszFullPath[cchSpoolDirectory + cchFolders], FindData.cFileName, cchPattern);
+        pJob = ReadJobShadowFile(wszFullPath);
+        if (!pJob)
+            continue;
+
+        // Add it to the job queue of the respective printer.
+        InsertTailList(&pJob->Printer->JobQueue, &pJob->Entry);
+    }
+    while (FindNextFileW(hFind, &FindData));
+
+    FindClose(hFind);
+}
+
+PLOCAL_JOB
+ReadJobShadowFile(PCWSTR pwszFilePath)
+{
+    DWORD cbFileSize;
+    DWORD cbRead;
+    HANDLE hFile = INVALID_HANDLE_VALUE;
+    PLOCAL_JOB pJob;
+    PLOCAL_JOB pReturnValue = NULL;
+    PLOCAL_PRINTER pPrinter;
+    PSHD_HEADER pShadowFile = NULL;
+    PWSTR pwszPrinterName;
+
+    // Try to open the file.
+    hFile = CreateFileW(pwszFilePath, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
+    if (hFile == INVALID_HANDLE_VALUE)
+    {
+        ERR("CreateFileW failed with error %lu!\n", GetLastError());
+        goto Cleanup;
+    }
+
+    // Get its file size (small enough for a single DWORD) and allocate memory for all of it.
+    cbFileSize = GetFileSize(hFile, NULL);
+    pShadowFile = HeapAlloc(hProcessHeap, 0, cbFileSize);
+    if (!pShadowFile)
+    {
+        ERR("HeapAlloc failed with error %lu!\n", GetLastError());
+        goto Cleanup;
+    }
+
+    // Read the entire file.
+    if (!ReadFile(hFile, pShadowFile, cbFileSize, &cbRead, NULL))
+    {
+        ERR("ReadFile failed with error %lu!\n", GetLastError());
+        goto Cleanup;
+    }
+
+    // Check signature and header size.
+    if (pShadowFile->dwSignature != SHD_WIN2003_SIGNATURE || pShadowFile->cbHeader != sizeof(SHD_HEADER))
+    {
+        ERR("Signature or Header Size mismatch!\n");
+        goto Cleanup;
+    }
+
+    // Retrieve the associated printer from the table.
+    pwszPrinterName = (PWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offPrinterName);
+    pPrinter = RtlLookupElementGenericTable(&PrinterTable, pwszPrinterName);
+    if (!pPrinter)
+    {
+        ERR("This shadow file references a non-existing printer!\n");
+        goto Cleanup;
+    }
+
+    // Create a new job structure and copy over the relevant fields.
+    pJob = HeapAlloc(hProcessHeap, 0, sizeof(LOCAL_JOB));
+    if (!pJob)
+    {
+        ERR("HeapAlloc failed with error %lu!\n", GetLastError());
+        goto Cleanup;
+    }
+
+    pJob->dwJobID = pShadowFile->dwJobID;
+    pJob->Printer = pPrinter;
+    pJob->pwszDatatype = DuplicateStringW((PCWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offDatatype));
+    pJob->pwszDocumentName = DuplicateStringW((PCWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offDocumentName));
+    pJob->pwszOutputFile = NULL;
+    CopyMemory(&pJob->DevMode, (PDEVMODEW)((ULONG_PTR)pShadowFile + pShadowFile->offDevMode), sizeof(DEVMODEW));
+
+    pReturnValue = pJob;
+
+Cleanup:
+    if (pShadowFile)
+        HeapFree(hProcessHeap, 0, pShadowFile);
+
+    if (hFile != INVALID_HANDLE_VALUE)
+        CloseHandle(hFile);
+
+    return pReturnValue;
+}
+
+BOOL
+WriteJobShadowFile(PCWSTR pwszFilePath, const PLOCAL_JOB pJob)
+{
+    BOOL bReturnValue = FALSE;
+    DWORD cbDatatype;
+    DWORD cbDocumentName;
+    DWORD cbFileSize;
+    DWORD cbPrinterName;
+    DWORD cbWritten;
+    DWORD dwCurrentOffset;
+    HANDLE hFile = INVALID_HANDLE_VALUE;
+    PSHD_HEADER pShadowFile = NULL;
+
+    // Try to open the file.
+    hFile = CreateFileW(pwszFilePath, GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, 0, NULL);
+    if (hFile == INVALID_HANDLE_VALUE)
+    {
+        ERR("CreateFileW failed with error %lu!\n", GetLastError());
+        goto Cleanup;
+    }
+
+    // Compute the total size of the shadow file.
+    cbDatatype = (wcslen(pJob->pwszDatatype) + 1) * sizeof(WCHAR);
+    cbDocumentName = (wcslen(pJob->pwszDocumentName) + 1) * sizeof(WCHAR);
+    cbPrinterName = (wcslen(pJob->Printer->pwszPrinterName) + 1) * sizeof(WCHAR);
+    cbFileSize = sizeof(SHD_HEADER) + cbDatatype + cbDocumentName + cbPrinterName;
+
+    // Allocate memory for it.
+    pShadowFile = HeapAlloc(hProcessHeap, HEAP_ZERO_MEMORY, cbFileSize);
+    if (!pShadowFile)
+    {
+        ERR("HeapAlloc failed with error %lu!\n", GetLastError());
+        goto Cleanup;
+    }
+
+    // Fill out the shadow file header information.
+    pShadowFile->dwSignature = SHD_WIN2003_SIGNATURE;
+    pShadowFile->cbHeader = sizeof(SHD_HEADER);
+    pShadowFile->dwJobID = pJob->dwJobID;
+
+    // Add the extra values that are stored as offsets in the shadow file.
+    // The first value begins right after the shadow file header.
+    dwCurrentOffset = sizeof(SHD_HEADER);
+
+    CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pwszDatatype, cbDatatype);
+    pShadowFile->offDatatype = dwCurrentOffset;
+    dwCurrentOffset += cbDatatype;
+
+    CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pwszDocumentName, cbDocumentName);
+    pShadowFile->offDocumentName = dwCurrentOffset;
+    dwCurrentOffset += cbDocumentName;
+
+    CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->Printer->pwszPrinterName, cbPrinterName);
+    pShadowFile->offPrinterName = dwCurrentOffset;
+    dwCurrentOffset += cbPrinterName;
+
+    // Write the file.
+    if (!WriteFile(hFile, pShadowFile, cbFileSize, &cbWritten, NULL))
+    {
+        ERR("WriteFile failed with error %lu!\n", GetLastError());
+        goto Cleanup;
+    }
+
+    bReturnValue = TRUE;
+
+Cleanup:
+    if (pShadowFile)
+        HeapFree(hProcessHeap, 0, pShadowFile);
+
+    if (hFile != INVALID_HANDLE_VALUE)
+        CloseHandle(hFile);
+
+    return bReturnValue;
+}
+
+BOOL
+FreeJob(PLOCAL_JOB pJob)
+{
+    ////////// TODO /////////
+    /// Add some checks
+    HeapFree(hProcessHeap, 0, pJob->pwszDatatype);
+    HeapFree(hProcessHeap, 0, pJob->pwszDocumentName);
+    HeapFree(hProcessHeap, 0, pJob->pwszOutputFile);
+    HeapFree(hProcessHeap, 0, pJob);
+
+    return TRUE;
+}
index 374c21a..a0bd923 100644 (file)
@@ -7,8 +7,153 @@
 
 #include "precomp.h"
 
 
 #include "precomp.h"
 
+// Global Variables
+HANDLE hProcessHeap;
+WCHAR wszSpoolDirectory[MAX_PATH];
+DWORD cchSpoolDirectory;
+
+// Constants
+const WCHAR wszCurrentEnvironment[] =
+#if defined(_X86_)
+    L"Windows NT x86";
+#elif defined(_AMD64_)
+    L"Windows x64";
+#elif defined(_ARM_)
+    L"Windows ARM";
+#else
+    #error Unsupported architecture
+#endif
+
+static const PRINTPROVIDOR PrintProviderFunctions = {
+    LocalOpenPrinter,                           // fpOpenPrinter
+    NULL,                                       // fpSetJob
+    NULL,                                       // fpGetJob
+    NULL,                                       // fpEnumJobs
+    NULL,                                       // fpAddPrinter
+    NULL,                                       // fpDeletePrinter
+    NULL,                                       // fpSetPrinter
+    NULL,                                       // fpGetPrinter
+    LocalEnumPrinters,                          // fpEnumPrinters
+    NULL,                                       // fpAddPrinterDriver
+    NULL,                                       // fpEnumPrinterDrivers
+    NULL,                                       // fpGetPrinterDriver
+    NULL,                                       // fpGetPrinterDriverDirectory
+    NULL,                                       // fpDeletePrinterDriver
+    NULL,                                       // fpAddPrintProcessor
+    LocalEnumPrintProcessors,                   // fpEnumPrintProcessors
+    LocalGetPrintProcessorDirectory,            // fpGetPrintProcessorDirectory
+    NULL,                                       // fpDeletePrintProcessor
+    LocalEnumPrintProcessorDatatypes,           // fpEnumPrintProcessorDatatypes
+    LocalStartDocPrinter,                       // fpStartDocPrinter
+    LocalStartPagePrinter,                      // fpStartPagePrinter
+    LocalWritePrinter,                          // fpWritePrinter
+    LocalEndPagePrinter,                        // fpEndPagePrinter
+    NULL,                                       // fpAbortPrinter
+    NULL,                                       // fpReadPrinter
+    LocalEndDocPrinter,                         // fpEndDocPrinter
+    NULL,                                       // fpAddJob
+    NULL,                                       // fpScheduleJob
+    NULL,                                       // fpGetPrinterData
+    NULL,                                       // fpSetPrinterData
+    NULL,                                       // fpWaitForPrinterChange
+    LocalClosePrinter,                          // fpClosePrinter
+    NULL,                                       // fpAddForm
+    NULL,                                       // fpDeleteForm
+    NULL,                                       // fpGetForm
+    NULL,                                       // fpSetForm
+    NULL,                                       // fpEnumForms
+    NULL,                                       // fpEnumMonitors
+    NULL,                                       // fpEnumPorts
+    NULL,                                       // fpAddPort
+    NULL,                                       // fpConfigurePort
+    NULL,                                       // fpDeletePort
+    NULL,                                       // fpCreatePrinterIC
+    NULL,                                       // fpPlayGdiScriptOnPrinterIC
+    NULL,                                       // fpDeletePrinterIC
+    NULL,                                       // fpAddPrinterConnection
+    NULL,                                       // fpDeletePrinterConnection
+    NULL,                                       // fpPrinterMessageBox
+    NULL,                                       // fpAddMonitor
+    NULL,                                       // fpDeleteMonitor
+    NULL,                                       // fpResetPrinter
+    NULL,                                       // fpGetPrinterDriverEx
+    NULL,                                       // fpFindFirstPrinterChangeNotification
+    NULL,                                       // fpFindClosePrinterChangeNotification
+    NULL,                                       // fpAddPortEx
+    NULL,                                       // fpShutDown
+    NULL,                                       // fpRefreshPrinterChangeNotification
+    NULL,                                       // fpOpenPrinterEx
+    NULL,                                       // fpAddPrinterEx
+    NULL,                                       // fpSetPort
+    NULL,                                       // fpEnumPrinterData
+    NULL,                                       // fpDeletePrinterData
+    NULL,                                       // fpClusterSplOpen
+    NULL,                                       // fpClusterSplClose
+    NULL,                                       // fpClusterSplIsAlive
+    NULL,                                       // fpSetPrinterDataEx
+    NULL,                                       // fpGetPrinterDataEx
+    NULL,                                       // fpEnumPrinterDataEx
+    NULL,                                       // fpEnumPrinterKey
+    NULL,                                       // fpDeletePrinterDataEx
+    NULL,                                       // fpDeletePrinterKey
+    NULL,                                       // fpSeekPrinter
+    NULL,                                       // fpDeletePrinterDriverEx
+    NULL,                                       // fpAddPerMachineConnection
+    NULL,                                       // fpDeletePerMachineConnection
+    NULL,                                       // fpEnumPerMachineConnections
+    NULL,                                       // fpXcvData
+    NULL,                                       // fpAddPrinterDriverEx
+    NULL,                                       // fpSplReadPrinter
+    NULL,                                       // fpDriverUnloadComplete
+    NULL,                                       // fpGetSpoolFileInfo
+    NULL,                                       // fpCommitSpoolData
+    NULL,                                       // fpCloseSpoolFileHandle
+    NULL,                                       // fpFlushPrinter
+    NULL,                                       // fpSendRecvBidiData
+    NULL,                                       // fpAddDriverCatalog
+};
+
+static void
+_GetSpoolDirectory()
+{
+    const WCHAR wszSpoolPath[] = L"\\spool";
+    const DWORD cchSpoolPath = sizeof(wszSpoolPath) / sizeof(WCHAR) - 1;
+
+    // Get the system directory and append the "spool" subdirectory.
+    // Forget about length checks here. If this doesn't fit into MAX_PATH, our OS has more serious problems...
+    cchSpoolDirectory = GetSystemDirectoryW(wszSpoolDirectory, MAX_PATH);
+    CopyMemory(&wszSpoolDirectory[cchSpoolDirectory], wszSpoolPath, (cchSpoolPath + 1) * sizeof(WCHAR));
+    cchSpoolDirectory += cchSpoolPath;
+}
+
+BOOL WINAPI
+DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
+{
+    switch (fdwReason)
+    {
+        case DLL_PROCESS_ATTACH:
+            DisableThreadLibraryCalls(hinstDLL);
+            hProcessHeap = GetProcessHeap();
+            _GetSpoolDirectory();
+            InitializePrintProcessorTable();
+            InitializePrinterTable();
+            break;
+    }
+
+    return TRUE;
+}
+
 BOOL WINAPI
 InitializePrintProvidor(LPPRINTPROVIDOR pPrintProvidor, DWORD cbPrintProvidor, LPWSTR pFullRegistryPath)
 {
 BOOL WINAPI
 InitializePrintProvidor(LPPRINTPROVIDOR pPrintProvidor, DWORD cbPrintProvidor, LPWSTR pFullRegistryPath)
 {
-    return FALSE;
+    DWORD cbCopy;
+
+    if (cbPrintProvidor < sizeof(PRINTPROVIDOR))
+        cbCopy = cbPrintProvidor;
+    else
+        cbCopy = sizeof(PRINTPROVIDOR);
+
+    CopyMemory(pPrintProvidor, &PrintProviderFunctions, cbCopy);
+
+    return TRUE;
 }
 }
index ab00cc3..6af7e4e 100644 (file)
 #define _PRECOMP_H
 
 #define WIN32_NO_STATUS
 #define _PRECOMP_H
 
 #define WIN32_NO_STATUS
+#include <limits.h>
+#include <wchar.h>
+
 #include <windef.h>
 #include <winbase.h>
 #include <wingdi.h>
 #include <winreg.h>
 #include <winspool.h>
 #include <winsplp.h>
 #include <windef.h>
 #include <winbase.h>
 #include <wingdi.h>
 #include <winreg.h>
 #include <winspool.h>
 #include <winsplp.h>
+#include <ndk/rtlfuncs.h>
 
 #include <wine/debug.h>
 WINE_DEFAULT_DEBUG_CHANNEL(localspl);
 
 
 #include <wine/debug.h>
 WINE_DEFAULT_DEBUG_CHANNEL(localspl);
 
+// Macros
+#define IS_VALID_JOB_ID(ID)     (ID >= 1 && ID <= 99999)
+
+// Constants
+#define MAX_PRINTER_NAME        220
+#define SHD_WIN2003_SIGNATURE   0x4968
+
+// Function pointers
+typedef BOOL (WINAPI *PClosePrintProcessor)(HANDLE);
+typedef BOOL (WINAPI *PControlPrintProcessor)(HANDLE, DWORD);
+typedef BOOL (WINAPI *PEnumPrintProcessorDatatypesW)(LPWSTR, LPWSTR, DWORD, LPBYTE, DWORD, LPDWORD, LPDWORD);
+typedef DWORD (WINAPI *PGetPrintProcessorCapabilities)(LPWSTR, DWORD, LPBYTE, DWORD, LPDWORD);
+typedef HANDLE (WINAPI *POpenPrintProcessor)(LPWSTR, PPRINTPROCESSOROPENDATA);
+typedef BOOL (WINAPI *PPrintDocumentOnPrintProcessor)(HANDLE, LPWSTR);
+
+// Structures
+/**
+* Describes a Print Processor.
+*/
+typedef struct _LOCAL_PRINT_PROCESSOR
+{
+    PWSTR pwszName;
+    RTL_GENERIC_TABLE DatatypeTable;
+    PClosePrintProcessor pfnClosePrintProcessor;
+    PControlPrintProcessor pfnControlPrintProcessor;
+    PEnumPrintProcessorDatatypesW pfnEnumPrintProcessorDatatypesW;
+    PGetPrintProcessorCapabilities pfnGetPrintProcessorCapabilities;
+    POpenPrintProcessor pfnOpenPrintProcessor;
+    PPrintDocumentOnPrintProcessor pfnPrintDocumentOnPrintProcessor;
+}
+LOCAL_PRINT_PROCESSOR, *PLOCAL_PRINT_PROCESSOR;
+
+/**
+ * Describes a printer and manages its print jobs.
+ * Created once for every printer at startup.
+ */
+typedef struct _LOCAL_PRINTER
+{
+    PWSTR pwszPrinterName;
+    PWSTR pwszDefaultDatatype;
+    DEVMODEW DefaultDevMode;
+    PLOCAL_PRINT_PROCESSOR pPrintProcessor;
+    LIST_ENTRY JobQueue;
+}
+LOCAL_PRINTER, *PLOCAL_PRINTER;
+
+/**
+ * Describes an entire print job associated to a specific printer through the Printer member.
+ * Created with every valid call to LocalStartDocPrinter.
+ */
+typedef struct _LOCAL_JOB
+{
+    LIST_ENTRY Entry;
+    PLOCAL_PRINTER Printer;
+    DWORD dwJobID;
+    PWSTR pwszDocumentName;
+    PWSTR pwszDatatype;
+    PWSTR pwszOutputFile;
+    DEVMODEW DevMode;
+}
+LOCAL_JOB, *PLOCAL_JOB;
+
+/**
+ * Describes a template for new print jobs for a specific printer.
+ * Created with every valid call to LocalOpenPrinter.
+ *
+ * This is needed, because you can supply defaults in a LocalOpenPrinter call, which affect all subsequent print jobs
+ * started with the same handle and a call to LocalStartDocPrinter.
+ */
+typedef struct _LOCAL_PRINTER_HANDLE
+{
+    PLOCAL_PRINTER Printer;
+    PLOCAL_JOB StartedJob;
+    PWSTR pwszDatatype;
+    DEVMODEW DevMode;
+}
+LOCAL_PRINTER_HANDLE, *PLOCAL_PRINTER_HANDLE;
+
+/**
+ * Describes a handle returned by LocalOpenPrinter.
+ * Suitable for all things that can be opened through LocalOpenPrinter.
+ */
+typedef struct _LOCAL_HANDLE
+{
+    enum { Printer, Monitor, Port } HandleType;
+    PVOID SpecificHandle;
+}
+LOCAL_HANDLE, *PLOCAL_HANDLE;
+
+/**
+ * Describes the header of a print job serialized into a shadow file (.SHD)
+ * Documented in http://www.undocprint.org/formats/winspool/shd
+ * Compatible with Windows Server 2003
+ */
+typedef struct _SHD_HEADER
+{
+    DWORD dwSignature;
+    DWORD cbHeader;
+    WORD wStatus;
+    WORD wUnknown1;
+    DWORD dwJobID;
+    DWORD dwPriority;
+    DWORD offUserName;
+    DWORD offNotifyName;
+    DWORD offDocumentName;
+    DWORD offPort;
+    DWORD offPrinterName;
+    DWORD offDriverName;
+    DWORD offDevMode;
+    DWORD offPrintProcessor;
+    DWORD offDatatype;
+    DWORD dwUnknown2;
+    SYSTEMTIME stSubmitTime;
+    DWORD dwStartTime;
+    DWORD dwUntilTime;
+    DWORD dwUnknown6;
+    DWORD dwPageCount;
+    DWORD cbSecurityDescriptor;
+    DWORD offSecurityDescriptor;
+    DWORD dwUnknown3;
+    DWORD dwUnknown4;
+    DWORD dwUnknown5;
+    DWORD offComputerName;
+    DWORD dwSPLSize;
+}
+SHD_HEADER, *PSHD_HEADER;
+
+
+// jobs.c
+PLOCAL_JOB ReadJobShadowFile(PCWSTR pwszFilePath);
+BOOL WriteJobShadowFile(PCWSTR pwszFilePath, const PLOCAL_JOB pJob);
+
+// main.c
+extern const WCHAR wszCurrentEnvironment[];
+extern HANDLE hProcessHeap;
+extern WCHAR wszSpoolDirectory[MAX_PATH];
+extern DWORD cchSpoolDirectory;
+
+// printers.c
+extern RTL_GENERIC_TABLE PrinterTable;
+void InitializePrinterTable();
+BOOL WINAPI LocalEnumPrinters(DWORD Flags, LPWSTR Name, DWORD Level, LPBYTE pPrinterEnum, DWORD cbBuf, LPDWORD pcbNeeded, LPDWORD pcReturned);
+BOOL WINAPI LocalOpenPrinter(PWSTR lpPrinterName, HANDLE* phPrinter, PPRINTER_DEFAULTSW pDefault);
+DWORD WINAPI LocalStartDocPrinter(HANDLE hPrinter, DWORD Level, LPBYTE pDocInfo);
+BOOL WINAPI LocalStartPagePrinter(HANDLE hPrinter);
+BOOL WINAPI LocalWritePrinter(HANDLE hPrinter, LPVOID pBuf, DWORD cbBuf, LPDWORD pcWritten);
+BOOL WINAPI LocalEndPagePrinter(HANDLE hPrinter);
+BOOL WINAPI LocalEndDocPrinter(HANDLE hPrinter);
+BOOL WINAPI LocalClosePrinter(HANDLE hPrinter);
+
+// printprocessors.c
+extern RTL_GENERIC_TABLE PrintProcessorTable;
+void InitializePrintProcessorTable();
+BOOL WINAPI LocalEnumPrintProcessorDatatypes(LPWSTR pName, LPWSTR pPrintProcessorName, DWORD Level, LPBYTE pDatatypes, DWORD cbBuf, LPDWORD pcbNeeded, LPDWORD pcReturned);
+BOOL WINAPI LocalEnumPrintProcessors(LPWSTR pName, LPWSTR pEnvironment, DWORD Level, LPBYTE pPrintProcessorInfo, DWORD cbBuf, LPDWORD pcbNeeded, LPDWORD pcReturned);
+BOOL WINAPI LocalGetPrintProcessorDirectory(LPWSTR pName, LPWSTR pEnvironment, DWORD Level, LPBYTE pPrintProcessorInfo, DWORD cbBuf, LPDWORD pcbNeeded);
+
+// tools.c
+PWSTR AllocAndRegQueryWSZ(HKEY hKey, PCWSTR pwszValueName);
+PWSTR DuplicateStringW(PCWSTR pwszInput);
+PVOID NTAPI GenericTableAllocateRoutine(PRTL_GENERIC_TABLE Table, CLONG ByteSize);
+VOID NTAPI GenericTableFreeRoutine(PRTL_GENERIC_TABLE Table, PVOID Buffer);
+
 #endif
 #endif
diff --git a/reactos/win32ss/printing/providers/localspl/printers.c b/reactos/win32ss/printing/providers/localspl/printers.c
new file mode 100644 (file)
index 0000000..9149f52
--- /dev/null
@@ -0,0 +1,590 @@
+/*
+ * PROJECT:     ReactOS Local Spooler
+ * LICENSE:     GNU LGPL v2.1 or any later version as published by the Free Software Foundation
+ * PURPOSE:     Functions related to Printers and printing
+ * COPYRIGHT:   Copyright 2015 Colin Finck <colin@reactos.org>
+ */
+
+#include "precomp.h"
+
+// Global Variables
+RTL_GENERIC_TABLE PrinterTable;
+
+
+/**
+ * @name _PrinterTableCompareRoutine
+ *
+ * RTL_GENERIC_COMPARE_ROUTINE for the Printer Table.
+ * Does a case-insensitive comparison, because e.g. LocalOpenPrinter doesn't match the case when looking for Printers.
+ */
+static RTL_GENERIC_COMPARE_RESULTS NTAPI
+_PrinterTableCompareRoutine(PRTL_GENERIC_TABLE Table, PVOID FirstStruct, PVOID SecondStruct)
+{
+    PLOCAL_PRINTER A = (PLOCAL_PRINTER)FirstStruct;
+    PLOCAL_PRINTER B = (PLOCAL_PRINTER)SecondStruct;
+
+    int iResult = wcsicmp(A->pwszPrinterName, B->pwszPrinterName);
+
+    if (iResult < 0)
+        return GenericLessThan;
+    else if (iResult > 0)
+        return GenericGreaterThan;
+    else
+        return GenericEqual;
+}
+
+/**
+ * @name InitializePrinterTable
+ *
+ * Initializes a RTL_GENERIC_TABLE of locally available Printers.
+ * The table is searchable by name and returns information about the printers, including their job queues.
+ * During this process, the job queues are also initialized.
+ */
+void
+InitializePrinterTable()
+{
+    const WCHAR wszPrintersKey[] = L"SYSTEM\\CurrentControlSet\\Control\\Print\\Printers";
+
+    DWORD cbDevMode;
+    DWORD cchPrinterName;
+    DWORD dwSubKeys;
+    DWORD i;
+    HKEY hKey = NULL;
+    HKEY hSubKey = NULL;
+    LONG lStatus;
+    PLOCAL_PRINT_PROCESSOR pPrintProcessor;
+    PLOCAL_PRINTER pPrinter = NULL;
+    PWSTR pwszPrintProcessor = NULL;
+    WCHAR wszPrinterName[MAX_PRINTER_NAME + 1];
+
+    // Initialize an empty table for our printers.
+    // We will search it by printer name.
+    RtlInitializeGenericTable(&PrinterTable, _PrinterTableCompareRoutine, GenericTableAllocateRoutine, GenericTableFreeRoutine, NULL);
+
+    // Open our printers registry key. Each subkey is a local printer there.
+    lStatus = RegOpenKeyExW(HKEY_LOCAL_MACHINE, wszPrintersKey, 0, KEY_READ, &hKey);
+    if (lStatus != ERROR_SUCCESS)
+    {
+        ERR("RegOpenKeyExW failed with status %ld!\n", lStatus);
+        goto Cleanup;
+    }
+
+    // Get the number of subkeys.
+    lStatus = RegQueryInfoKeyW(hKey, NULL, NULL, NULL, &dwSubKeys, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
+    if (lStatus != ERROR_SUCCESS)
+    {
+        ERR("RegQueryInfoKeyW failed with status %ld!\n", lStatus);
+        goto Cleanup;
+    }
+
+    // Loop through all available local printers.
+    for (i = 0; i < dwSubKeys; i++)
+    {
+        // Cleanup tasks from the previous run
+        if (hSubKey)
+        {
+            RegCloseKey(hSubKey);
+            hSubKey = NULL;
+        }
+
+        if (pPrinter)
+        {
+            if (pPrinter->pwszDefaultDatatype)
+                HeapFree(hProcessHeap, 0, pPrinter->pwszDefaultDatatype);
+
+            HeapFree(hProcessHeap, 0, pPrinter);
+            pPrinter = NULL;
+        }
+
+        if (pwszPrintProcessor)
+        {
+            HeapFree(hProcessHeap, 0, pwszPrintProcessor);
+            pwszPrintProcessor = NULL;
+        }
+
+        // Get the name of this printer.
+        cchPrinterName = sizeof(wszPrinterName) / sizeof(WCHAR);
+        lStatus = RegEnumKeyExW(hKey, i, wszPrinterName, &cchPrinterName, NULL, NULL, NULL, NULL);
+        if (lStatus == ERROR_MORE_DATA)
+        {
+            // This printer name exceeds the maximum length and is invalid.
+            continue;
+        }
+        else if (lStatus != ERROR_SUCCESS)
+        {
+            ERR("RegEnumKeyExW failed for iteration %lu with status %ld!\n", i, lStatus);
+            continue;
+        }
+
+        // Open this Printer's registry key.
+        lStatus = RegOpenKeyExW(hKey, wszPrinterName, 0, KEY_READ, &hSubKey);
+        if (lStatus != ERROR_SUCCESS)
+        {
+            ERR("RegOpenKeyExW failed for Printer \"%S\" with status %ld!\n", wszPrinterName, lStatus);
+            continue;
+        }
+
+        // Get the Print Processor.
+        pwszPrintProcessor = AllocAndRegQueryWSZ(hSubKey, L"Print Processor");
+        if (!pwszPrintProcessor)
+            continue;
+
+        // Try to find it in the Print Processor Table.
+        pPrintProcessor = RtlLookupElementGenericTable(&PrintProcessorTable, pwszPrintProcessor);
+        if (!pPrintProcessor)
+        {
+            ERR("Invalid Print Processor \"%S\" for Printer \"%S\"!\n", pwszPrintProcessor, wszPrinterName);
+            continue;
+        }
+
+        // Create a new LOCAL_PRINTER structure for it.
+        pPrinter = HeapAlloc(hProcessHeap, 0, sizeof(LOCAL_PRINTER));
+        if (!pPrinter)
+        {
+            ERR("HeapAlloc failed with error %lu!\n", GetLastError());
+            goto Cleanup;
+        }
+
+        pPrinter->pwszPrinterName = DuplicateStringW(wszPrinterName);
+        pPrinter->pPrintProcessor = pPrintProcessor;
+        InitializeListHead(&pPrinter->JobQueue);
+
+        // Get the default datatype.
+        pPrinter->pwszDefaultDatatype = AllocAndRegQueryWSZ(hSubKey, L"Datatype");
+        if (!pPrinter->pwszDefaultDatatype)
+            continue;
+
+        // Verify that it's valid.
+        if (!RtlLookupElementGenericTable(&pPrintProcessor->DatatypeTable, pPrinter->pwszDefaultDatatype))
+        {
+            ERR("Invalid default datatype \"%S\" for Printer \"%S\"!\n", pPrinter->pwszDefaultDatatype, wszPrinterName);
+            continue;
+        }
+
+        // Get the default DevMode.
+        cbDevMode = sizeof(DEVMODEW);
+        lStatus = RegQueryValueExW(hSubKey, L"Default DevMode", NULL, NULL, (PBYTE)&pPrinter->DefaultDevMode, &cbDevMode);
+        if (lStatus != ERROR_SUCCESS || cbDevMode != sizeof(DEVMODEW))
+        {
+            ERR("Couldn't query DevMode for Printer \"%S\", status is %ld, cbDevMode is %lu!\n", wszPrinterName, lStatus, cbDevMode);
+            continue;
+        }
+
+        // Add this printer to the printer table.
+        if (!RtlInsertElementGenericTable(&PrinterTable, pPrinter, sizeof(LOCAL_PRINTER), NULL))
+        {
+            ERR("RtlInsertElementGenericTable failed with error %lu!\n", GetLastError());
+            goto Cleanup;
+        }
+
+        // Don't let the cleanup routines free this.
+        pPrinter = NULL;
+    }
+
+Cleanup:
+    if (pwszPrintProcessor)
+        HeapFree(hProcessHeap, 0, pwszPrintProcessor);
+
+    if (pPrinter)
+    {
+        if (pPrinter->pwszDefaultDatatype)
+            HeapFree(hProcessHeap, 0, pPrinter->pwszDefaultDatatype);
+
+        HeapFree(hProcessHeap, 0, pPrinter);
+    }
+
+    if (hSubKey)
+        RegCloseKey(hSubKey);
+
+    if (hKey)
+        RegCloseKey(hKey);
+}
+
+
+BOOL WINAPI
+LocalEnumPrinters(DWORD Flags, LPWSTR Name, DWORD Level, LPBYTE pPrinterEnum, DWORD cbBuf, LPDWORD pcbNeeded, LPDWORD pcReturned)
+{
+    ///////////// TODO /////////////////////
+    return FALSE;
+}
+
+BOOL WINAPI
+LocalOpenPrinter(PWSTR lpPrinterName, HANDLE* phPrinter, PPRINTER_DEFAULTSW pDefault)
+{
+    BOOL bReturnValue = ROUTER_UNKNOWN;
+    DWORD cchComputerName;
+    DWORD cchPrinterName;
+    DWORD dwJobID;
+    PWSTR p = lpPrinterName;
+    PWSTR pwszPrinterName = NULL;
+    PLOCAL_JOB pJob;
+    PLOCAL_HANDLE pHandle;
+    PLOCAL_PRINTER pPrinter;
+    PLOCAL_PRINTER_HANDLE pPrinterHandle = NULL;
+    PLIST_ENTRY pEntry;
+    WCHAR wszComputerName[MAX_COMPUTERNAME_LENGTH + 1];
+
+    // Sanity checks
+    if (!lpPrinterName || !phPrinter)
+    {
+        SetLastError(ERROR_INVALID_PARAMETER);
+        goto Cleanup;
+    }
+
+    // Does lpPrinterName begin with two backslashes to indicate a server name?
+    if (lpPrinterName[0] == L'\\' && lpPrinterName[1] == L'\\')
+    {
+        // Skip these two backslashes.
+        lpPrinterName += 2;
+
+        // Look for the closing backslash.
+        p = wcschr(lpPrinterName, L'\\');
+        if (!p)
+        {
+            // We didn't get a proper server name.
+            SetLastError(ERROR_INVALID_PRINTER_NAME);
+            goto Cleanup;
+        }
+
+        // Null-terminate the string here to enable comparison.
+        *p = 0;
+
+        // Get the local computer name for comparison.
+        cchComputerName = sizeof(wszComputerName) / sizeof(WCHAR);
+        if (!GetComputerNameW(wszComputerName, &cchComputerName))
+        {
+            ERR("GetComputerNameW failed with error %lu!\n", GetLastError());
+            goto Cleanup;
+        }
+
+        // Now compare this with the local computer name and reject if it doesn't match, because this print provider only supports local printers.
+        if (wcsicmp(lpPrinterName, wszComputerName) != 0)
+        {
+            SetLastError(ERROR_INVALID_PRINTER_NAME);
+            goto Cleanup;
+        }
+
+        // We have checked the server name and don't need it anymore.
+        lpPrinterName = p + 1;
+    }
+
+    // Look for a comma. If it exists, it indicates the end of the printer name.
+    p = wcschr(lpPrinterName, L',');
+    if (p)
+        cchPrinterName = p - lpPrinterName;
+    else
+        cchPrinterName = wcslen(lpPrinterName);
+
+    // No printer name and no comma? This is invalid!
+    if (!cchPrinterName && !p)
+    {
+        SetLastError(ERROR_INVALID_PRINTER_NAME);
+        goto Cleanup;
+    }
+
+    // Do we have a printer name?
+    if (cchPrinterName)
+    {
+        // Yes, extract it.
+        pwszPrinterName = HeapAlloc(hProcessHeap, 0, (cchPrinterName + 1) * sizeof(WCHAR));
+        CopyMemory(pwszPrinterName, lpPrinterName, cchPrinterName * sizeof(WCHAR));
+        pwszPrinterName[cchPrinterName] = 0;
+
+        // Retrieve the associated printer from the table.
+        pPrinter = RtlLookupElementGenericTable(&PrinterTable, pwszPrinterName);
+        if (!pPrinter)
+        {
+            // The printer does not exist.
+            SetLastError(ERROR_INVALID_PRINTER_NAME);
+            goto Cleanup;
+        }
+
+        // Create a new printer handle.
+        pPrinterHandle = HeapAlloc(hProcessHeap, HEAP_ZERO_MEMORY, sizeof(LOCAL_PRINTER_HANDLE));
+        pPrinterHandle->Printer = pPrinter;
+
+        // Check if a datatype was given.
+        if (pDefault && pDefault->pDatatype)
+        {
+            // Use the datatype if it's valid.
+            if (!RtlLookupElementGenericTable(&pPrinter->pPrintProcessor->DatatypeTable, pDefault->pDatatype))
+            {
+                SetLastError(ERROR_INVALID_DATATYPE);
+                goto Cleanup;
+            }
+
+            pPrinterHandle->pwszDatatype = DuplicateStringW(pDefault->pDatatype);
+        }
+        else
+        {
+            // Use the default datatype.
+            pPrinterHandle->pwszDatatype = DuplicateStringW(pPrinter->pwszDefaultDatatype);
+        }
+
+        // Check if a DevMode was given, otherwise use the default.
+        if (pDefault && pDefault->pDevMode)
+            CopyMemory(&pPrinterHandle->DevMode, pDefault->pDevMode, sizeof(DEVMODEW));
+        else
+            CopyMemory(&pPrinterHandle->DevMode, &pPrinter->DefaultDevMode, sizeof(DEVMODEW));
+
+        // Did we have a comma? Then the user may want a handle to an existing job instead of creating a new job.
+        if (p)
+        {
+            ++p;
+            
+            // Skip whitespace.
+            do
+            {
+                ++p;
+            }
+            while (*p == ' ');
+
+            // The "Job " string has to follow now.
+            if (wcscmp(p, L"Job ") != 0)
+            {
+                SetLastError(ERROR_INVALID_PRINTER_NAME);
+                goto Cleanup;
+            }
+
+            // Skip the "Job " string. 
+            p += sizeof("Job ") - 1;
+
+            // Skip even more whitespace.
+            while (*p == ' ')
+                ++p;
+
+            // Finally extract the desired Job ID.
+            dwJobID = wcstoul(p, NULL, 10);
+            if (!IS_VALID_JOB_ID(dwJobID))
+            {
+                // The user supplied an invalid Job ID.
+                SetLastError(ERROR_INVALID_PRINTER_NAME);
+                goto Cleanup;
+            }
+
+            // Look for this job in the job queue of the printer.
+            pEntry = pPrinter->JobQueue.Flink;
+
+            for (;;)
+            {
+                if (pEntry == &pPrinter->JobQueue)
+                {
+                    // We have reached the end of the list without finding the desired Job ID.
+                    SetLastError(ERROR_INVALID_PRINTER_NAME);
+                    goto Cleanup;
+                }
+
+                // Get our job structure.
+                pJob = CONTAINING_RECORD(pEntry, LOCAL_JOB, Entry);
+
+                if (pJob->dwJobID == dwJobID)
+                {
+                    // We have found the desired job. Give the caller a printer handle referencing it.
+                    pPrinterHandle->StartedJob = pJob;
+                    break;
+                }
+
+                pEntry = pEntry->Flink;
+            }
+        }
+
+        // Create a new handle that references a printer.
+        pHandle = HeapAlloc(hProcessHeap, 0, sizeof(LOCAL_HANDLE));
+        pHandle->HandleType = Printer;
+        pHandle->SpecificHandle = pPrinterHandle;
+    }
+    else
+    {
+        // No printer name, but we have a comma!
+        // This may be a request to a XcvMonitor or XcvPort handle.
+        ++p;
+
+        // Skip whitespace.
+        do
+        {
+            ++p;
+        }
+        while (*p == ' ');
+
+        // Check if this is a request to a XcvMonitor.
+        if (wcscmp(p, L"XcvMonitor ") == 0)
+        {
+            // Skip the "XcvMonitor " string. 
+            p += sizeof("XcvMonitor ") - 1;
+
+            ///////////// TODO /////////////////////
+            pHandle = HeapAlloc(hProcessHeap, 0, sizeof(LOCAL_HANDLE));
+            pHandle->HandleType = Monitor;
+            //pHandle->SpecificHandle = pMonitorHandle;
+        }
+        else if (wcscmp(p, L"XcvPort ") == 0)
+        {
+            // Skip the "XcvPort " string. 
+            p += sizeof("XcvPort ") - 1;
+
+            //////////// TODO //////////////////////
+            pHandle = HeapAlloc(hProcessHeap, 0, sizeof(LOCAL_HANDLE));
+            pHandle->HandleType = Port;
+            //pHandle->SpecificHandle = pPortHandle;
+        }
+        else
+        {
+            SetLastError(ERROR_INVALID_PRINTER_NAME);
+            goto Cleanup;
+        }
+    }
+
+    *phPrinter = (HANDLE)pHandle;
+    bReturnValue = ROUTER_SUCCESS;
+
+    // Don't let the cleanup routines free this.
+    pPrinterHandle = NULL;
+    pwszPrinterName = NULL;
+
+Cleanup:
+    if (pPrinterHandle)
+    {
+        if (pPrinterHandle->pwszDatatype)
+            HeapFree(hProcessHeap, 0, pPrinterHandle->pwszDatatype);
+
+        HeapFree(hProcessHeap, 0, pPrinterHandle);
+    }
+
+    if (pwszPrinterName)
+        HeapFree(hProcessHeap, 0, pwszPrinterName);
+
+    return bReturnValue;
+}
+
+DWORD WINAPI
+LocalStartDocPrinter(HANDLE hPrinter, DWORD Level, LPBYTE pDocInfo)
+{
+    DWORD dwReturnValue = 0;
+    PDOC_INFO_1W pDocumentInfo1;
+    PLOCAL_HANDLE pHandle;
+    PLOCAL_PRINTER_HANDLE pPrinterHandle;
+    PLOCAL_JOB pJob;
+
+    // Sanity checks
+    if (!pDocInfo)
+    {
+        SetLastError(ERROR_INVALID_PARAMETER);
+        return 0;
+    }
+
+    if (!hPrinter)
+    {
+        SetLastError(ERROR_INVALID_HANDLE);
+        return 0;
+    }
+
+    // Check if this is a printer handle.
+    pHandle = (PLOCAL_HANDLE)hPrinter;
+    if (pHandle->HandleType != Printer)
+    {
+        SetLastError(ERROR_INVALID_HANDLE);
+        return 0;
+    }
+
+    pPrinterHandle = (PLOCAL_PRINTER_HANDLE)pHandle->SpecificHandle;
+
+    // Check if this is the right document information level.
+    if (Level != 1)
+    {
+        SetLastError(ERROR_INVALID_LEVEL);
+        return 0;
+    }
+
+    pDocumentInfo1 = (PDOC_INFO_1W)pDocInfo;
+
+    // Create a new job.
+    pJob = HeapAlloc(hProcessHeap, HEAP_ZERO_MEMORY, sizeof(LOCAL_JOB));
+    pJob->Printer = pPrinterHandle->Printer;
+
+    // Check if a datatype was given.
+    if (pDocumentInfo1->pDatatype)
+    {
+        // Use the datatype if it's valid.
+        if (!RtlLookupElementGenericTable(&pJob->Printer->pPrintProcessor->DatatypeTable, pDocumentInfo1->pDatatype))
+        {
+            SetLastError(ERROR_INVALID_DATATYPE);
+            goto Cleanup;
+        }
+
+        pJob->pwszDatatype = DuplicateStringW(pDocumentInfo1->pDatatype);
+    }
+    else
+    {
+        // Use the printer handle datatype.
+        pJob->pwszDatatype = DuplicateStringW(pPrinterHandle->pwszDatatype);
+    }
+
+    // Copy over printer defaults.
+    CopyMemory(&pJob->DevMode, &pPrinterHandle->DevMode, sizeof(DEVMODEW));
+
+    // Copy over supplied information.
+    if (pDocumentInfo1->pDocName)
+        pJob->pwszDocumentName = DuplicateStringW(pDocumentInfo1->pDocName);
+
+    if (pDocumentInfo1->pOutputFile)
+        pJob->pwszOutputFile = DuplicateStringW(pDocumentInfo1->pOutputFile);
+
+    // Enqueue the job.
+    ///////////// TODO /////////////////////
+
+Cleanup:
+    if (pJob)
+        HeapFree(hProcessHeap, 0, pJob);
+
+    return dwReturnValue;
+}
+
+BOOL WINAPI
+LocalStartPagePrinter(HANDLE hPrinter)
+{
+    ///////////// TODO /////////////////////
+    return FALSE;
+}
+
+BOOL WINAPI
+LocalWritePrinter(HANDLE hPrinter, LPVOID pBuf, DWORD cbBuf, LPDWORD pcWritten)
+{
+    ///////////// TODO /////////////////////
+    return FALSE;
+}
+
+BOOL WINAPI
+LocalEndPagePrinter(HANDLE hPrinter)
+{
+    ///////////// TODO /////////////////////
+    return FALSE;
+}
+
+BOOL WINAPI
+LocalEndDocPrinter(HANDLE hPrinter)
+{
+    ///////////// TODO /////////////////////
+    return FALSE;
+}
+
+BOOL WINAPI
+LocalClosePrinter(HANDLE hPrinter)
+{
+    PLOCAL_HANDLE pHandle;
+
+    if (!hPrinter)
+    {
+        SetLastError(ERROR_INVALID_HANDLE);
+        return FALSE;
+    }
+
+    pHandle = (PLOCAL_HANDLE)hPrinter;
+
+    ///////////// TODO /////////////////////
+    /// Check the handle type, do thoroughful checks on all data fields and clean them.
+    ////////////////////////////////////////
+
+    HeapFree(hProcessHeap, 0, pHandle);
+
+    return TRUE;
+}
diff --git a/reactos/win32ss/printing/providers/localspl/printprocessors.c b/reactos/win32ss/printing/providers/localspl/printprocessors.c
new file mode 100644 (file)
index 0000000..ea29a00
--- /dev/null
@@ -0,0 +1,705 @@
+/*
+ * PROJECT:     ReactOS Local Spooler
+ * LICENSE:     GNU LGPL v2.1 or any later version as published by the Free Software Foundation
+ * PURPOSE:     Functions related to Print Processors
+ * COPYRIGHT:   Copyright 2015 Colin Finck <colin@reactos.org>
+ */
+
+#include "precomp.h"
+
+
+// Global Variables
+RTL_GENERIC_TABLE PrintProcessorTable;
+
+/**
+ * @name _OpenEnvironment
+ *
+ * Checks a supplied pEnvironment variable for validity and opens its registry key.
+ *
+ * @param pEnvironment
+ * The pEnvironment variable to check. Can be NULL to use the current environment.
+ *
+ * @param hKey
+ * On success, this variable will contain a HKEY to the opened registry key of the environment.
+ * You can use it for further tasks and have to close it with RegCloseKey.
+ *
+ * @return
+ * TRUE if the environment is valid and a registry key was opened, FALSE otherwise.
+ * In case of failure, Last Error will be set to ERROR_INVALID_ENVIRONMENT.
+ */
+static BOOL
+_OpenEnvironment(PCWSTR pEnvironment, PHKEY hKey)
+{
+    const WCHAR wszEnvironmentsKey[] = L"SYSTEM\\CurrentControlSet\\Control\\Print\\Environments\\";
+    const DWORD cchEnvironmentsKey = sizeof(wszEnvironmentsKey) / sizeof(WCHAR) - 1;
+
+    BOOL bReturnValue = FALSE;
+    DWORD cchEnvironment;
+    LONG lStatus;
+    PWSTR pwszEnvironmentKey = NULL;
+
+    // Use the current environment if none was supplied.
+    if (!pEnvironment)
+        pEnvironment = wszCurrentEnvironment;
+
+    // Construct the registry key of the demanded environment.
+    cchEnvironment = wcslen(pEnvironment);
+    pwszEnvironmentKey = HeapAlloc(hProcessHeap, 0, (cchEnvironmentsKey + cchEnvironment + 1) * sizeof(WCHAR));
+    if (!pwszEnvironmentKey)
+    {
+        ERR("HeapAlloc failed with error %lu!\n", GetLastError());
+        goto Cleanup;
+    }
+
+    CopyMemory(pwszEnvironmentKey, wszEnvironmentsKey, cchEnvironmentsKey * sizeof(WCHAR));
+    CopyMemory(&pwszEnvironmentKey[cchEnvironmentsKey], pEnvironment, (cchEnvironment + 1) * sizeof(WCHAR));
+
+    // Open the registry key.
+    lStatus = RegOpenKeyExW(HKEY_LOCAL_MACHINE, pwszEnvironmentKey, 0, KEY_READ, hKey);
+    if (lStatus == ERROR_FILE_NOT_FOUND)
+    {
+        SetLastError(ERROR_INVALID_ENVIRONMENT);
+        goto Cleanup;
+    }
+    else if (lStatus != ERROR_SUCCESS)
+    {
+        ERR("RegOpenKeyExW failed with status %ld!\n", lStatus);
+        goto Cleanup;
+    }
+
+    bReturnValue = TRUE;
+
+Cleanup:
+    if (pwszEnvironmentKey)
+        HeapFree(hProcessHeap, 0, pwszEnvironmentKey);
+
+    return bReturnValue;
+}
+
+/**
+ * @name _PrinterTableCompareRoutine
+ *
+ * RTL_GENERIC_COMPARE_ROUTINE for the Print Processor Table.
+ * Does a case-insensitive comparison, because e.g. LocalEnumPrintProcessorDatatypes doesn't match the case when looking for Print Processors.
+ */
+static RTL_GENERIC_COMPARE_RESULTS NTAPI
+_PrintProcessorTableCompareRoutine(PRTL_GENERIC_TABLE Table, PVOID FirstStruct, PVOID SecondStruct)
+{
+    PLOCAL_PRINT_PROCESSOR A = (PLOCAL_PRINT_PROCESSOR)FirstStruct;
+    PLOCAL_PRINT_PROCESSOR B = (PLOCAL_PRINT_PROCESSOR)SecondStruct;
+
+    int iResult = wcsicmp(A->pwszName, B->pwszName);
+
+    if (iResult < 0)
+        return GenericLessThan;
+    else if (iResult > 0)
+        return GenericGreaterThan;
+    else
+        return GenericEqual;
+}
+
+/**
+ * @name _DatatypeTableCompareRoutine
+ *
+ * RTL_GENERIC_COMPARE_ROUTINE for the Datatype Table.
+ * Does a case-insensitive comparison, because e.g. LocalOpenPrinter doesn't match the case when looking for Datatypes.
+ */
+static RTL_GENERIC_COMPARE_RESULTS NTAPI
+_DatatypeTableCompareRoutine(PRTL_GENERIC_TABLE Table, PVOID FirstStruct, PVOID SecondStruct)
+{
+    PWSTR A = (PWSTR)FirstStruct;
+    PWSTR B = (PWSTR)SecondStruct;
+
+    int iResult = wcsicmp(A, B);
+
+    if (iResult < 0)
+        return GenericLessThan;
+    else if (iResult > 0)
+        return GenericGreaterThan;
+    else
+        return GenericEqual;
+}
+
+/**
+ * @name InitializePrintProcessorTable
+ *
+ * Initializes a RTL_GENERIC_TABLE of locally available Print Processors.
+ * The table is searchable by name and returns pointers to the functions of the loaded Print Processor DLL.
+ */
+void
+InitializePrintProcessorTable()
+{
+    DWORD cbDatatypes;
+    DWORD cbFileName;
+    DWORD cchPrintProcessorPath;
+    DWORD cchMaxSubKey;
+    DWORD cchPrintProcessorName;
+    DWORD dwDatatypes;
+    DWORD dwSubKeys;
+    DWORD i;
+    DWORD j;
+    HINSTANCE hinstPrintProcessor;
+    HKEY hKey = NULL;
+    HKEY hSubKey = NULL;
+    HKEY hSubSubKey = NULL;
+    LONG lStatus;
+    PDATATYPES_INFO_1W pDatatypesInfo1 = NULL;
+    PLOCAL_PRINT_PROCESSOR pPrintProcessor = NULL;
+    PWSTR pwszDatatype = NULL;
+    PWSTR pwszPrintProcessorName = NULL;
+    WCHAR wszFileName[MAX_PATH];
+    WCHAR wszPrintProcessorPath[MAX_PATH];
+
+    // Initialize an empty table for our Print Processors.
+    // We will search it by Print Processor name.
+    RtlInitializeGenericTable(&PrintProcessorTable, _PrintProcessorTableCompareRoutine, GenericTableAllocateRoutine, GenericTableFreeRoutine, NULL);
+    
+    // Prepare the path to the Print Processor directory.
+    if (!LocalGetPrintProcessorDirectory(NULL, NULL, 1, (PBYTE)wszPrintProcessorPath, sizeof(wszPrintProcessorPath), &cchPrintProcessorPath))
+        goto Cleanup;
+
+    cchPrintProcessorPath /= sizeof(WCHAR);
+    wszPrintProcessorPath[cchPrintProcessorPath++] = L'\\';
+
+    // Open the environment registry key.
+    if (!_OpenEnvironment(NULL, &hKey))
+        goto Cleanup;
+
+    // Open the "Print Processors" subkey.
+    lStatus = RegOpenKeyExW(hKey, L"Print Processors", 0, KEY_READ, &hSubKey);
+    if (lStatus != ERROR_SUCCESS)
+    {
+        ERR("RegOpenKeyExW failed with status %ld!\n", lStatus);
+        goto Cleanup;
+    }
+
+    // Get the number of Print Processors and maximum sub key length.
+    lStatus = RegQueryInfoKeyW(hSubKey, NULL, NULL, NULL, &dwSubKeys, &cchMaxSubKey, NULL, NULL, NULL, NULL, NULL, NULL);
+    if (lStatus != ERROR_SUCCESS)
+    {
+        ERR("RegQueryInfoKeyW failed with status %ld!\n", lStatus);
+        goto Cleanup;
+    }
+
+    // Allocate a temporary buffer for the Print Processor names.
+    pwszPrintProcessorName = HeapAlloc(hProcessHeap, 0, (cchMaxSubKey + 1) * sizeof(WCHAR));
+    if (!pwszPrintProcessorName)
+    {
+        ERR("HeapAlloc failed with error %lu!\n", GetLastError());
+        goto Cleanup;
+    }
+
+    // Loop through all available local Print Processors.
+    for (i = 0; i < dwSubKeys; i++)
+    {
+        // Cleanup tasks from the previous run
+        if (hSubSubKey)
+        {
+            RegCloseKey(hSubSubKey);
+            hSubSubKey = NULL;
+        }
+
+        if (pPrintProcessor)
+        {
+            if (pPrintProcessor->pwszName)
+                HeapFree(hProcessHeap, 0, pPrintProcessor->pwszName);
+
+            HeapFree(hProcessHeap, 0, pPrintProcessor);
+            pPrintProcessor = NULL;
+        }
+
+        if (pDatatypesInfo1)
+        {
+            HeapFree(hProcessHeap, 0, pDatatypesInfo1);
+            pDatatypesInfo1 = NULL;
+        }
+
+        // Get the name of this Print Processor.
+        cchPrintProcessorName = cchMaxSubKey;
+        lStatus = RegEnumKeyExW(hSubKey, i, pwszPrintProcessorName, &cchPrintProcessorName, NULL, NULL, NULL, NULL);
+        if (lStatus != ERROR_SUCCESS)
+        {
+            ERR("RegEnumKeyExW failed with status %ld!\n", lStatus);
+            continue;
+        }
+
+        // Open this Print Processor's registry key.
+        lStatus = RegOpenKeyExW(hSubKey, pwszPrintProcessorName, 0, KEY_READ, &hSubSubKey);
+        if (lStatus != ERROR_SUCCESS)
+        {
+            ERR("RegOpenKeyExW failed for Print Processor \"%S\" with status %ld!\n", pwszPrintProcessorName, lStatus);
+            continue;
+        }
+
+        // Get the file name of the Print Processor.
+        cbFileName = sizeof(wszFileName);
+        lStatus = RegQueryValueExW(hSubSubKey, L"Driver", NULL, NULL, (PBYTE)wszFileName, &cbFileName);
+        if (lStatus != ERROR_SUCCESS)
+        {
+            ERR("RegQueryValueExW failed for Print Processor \"%S\" with status %ld!\n", pwszPrintProcessorName, lStatus);
+            continue;
+        }
+
+        // Verify that our buffer is large enough.
+        if (cchPrintProcessorPath + cbFileName / sizeof(WCHAR) > MAX_PATH)
+        {
+            ERR("Print Processor directory \"%S\" for Print Processor \"%S\" is too long!\n", wszFileName, pwszPrintProcessorName);
+            continue;
+        }
+
+        // Construct the full path to the Print Processor.
+        CopyMemory(&wszPrintProcessorPath[cchPrintProcessorPath], wszFileName, cbFileName);
+
+        // Try to load it.
+        hinstPrintProcessor = LoadLibraryW(wszPrintProcessorPath);
+        if (lStatus != ERROR_SUCCESS)
+        {
+            ERR("LoadLibraryW failed for \"%S\" with error %lu!\n", wszPrintProcessorPath, GetLastError());
+            continue;
+        }
+
+        // Create a new LOCAL_PRINT_PROCESSOR structure for it.
+        pPrintProcessor = HeapAlloc(hProcessHeap, 0, sizeof(LOCAL_PRINT_PROCESSOR));
+        pPrintProcessor->pwszName = DuplicateStringW(pwszPrintProcessorName);
+
+        // Get and verify all its function pointers.
+        pPrintProcessor->pfnClosePrintProcessor = (PClosePrintProcessor)GetProcAddress(hinstPrintProcessor, "ClosePrintProcessor");
+        if (!pPrintProcessor->pfnClosePrintProcessor)
+        {
+            ERR("Print Processor \"%S\" exports no ClosePrintProcessor!\n", wszPrintProcessorPath);
+            continue;
+        }
+
+        pPrintProcessor->pfnControlPrintProcessor = (PControlPrintProcessor)GetProcAddress(hinstPrintProcessor, "ControlPrintProcessor");
+        if (!pPrintProcessor->pfnControlPrintProcessor)
+        {
+            ERR("Print Processor \"%S\" exports no ControlPrintProcessor!\n", wszPrintProcessorPath);
+            continue;
+        }
+
+        pPrintProcessor->pfnEnumPrintProcessorDatatypesW = (PEnumPrintProcessorDatatypesW)GetProcAddress(hinstPrintProcessor, "EnumPrintProcessorDatatypesW");
+        if (!pPrintProcessor->pfnEnumPrintProcessorDatatypesW)
+        {
+            ERR("Print Processor \"%S\" exports no EnumPrintProcessorDatatypesW!\n", wszPrintProcessorPath);
+            continue;
+        }
+
+        pPrintProcessor->pfnGetPrintProcessorCapabilities = (PGetPrintProcessorCapabilities)GetProcAddress(hinstPrintProcessor, "GetPrintProcessorCapabilities");
+        if (!pPrintProcessor->pfnGetPrintProcessorCapabilities)
+        {
+            ERR("Print Processor \"%S\" exports no GetPrintProcessorCapabilities!\n", wszPrintProcessorPath);
+            continue;
+        }
+
+        pPrintProcessor->pfnOpenPrintProcessor = (POpenPrintProcessor)GetProcAddress(hinstPrintProcessor, "OpenPrintProcessor");
+        if (!pPrintProcessor->pfnOpenPrintProcessor)
+        {
+            ERR("Print Processor \"%S\" exports no OpenPrintProcessor!\n", wszPrintProcessorPath);
+            continue;
+        }
+
+        pPrintProcessor->pfnPrintDocumentOnPrintProcessor = (PPrintDocumentOnPrintProcessor)GetProcAddress(hinstPrintProcessor, "PrintDocumentOnPrintProcessor");
+        if (!pPrintProcessor->pfnPrintDocumentOnPrintProcessor)
+        {
+            ERR("Print Processor \"%S\" exports no PrintDocumentOnPrintProcessor!\n", wszPrintProcessorPath);
+            continue;
+        }
+
+        // Get all supported datatypes.
+        pPrintProcessor->pfnEnumPrintProcessorDatatypesW(NULL, NULL, 1, NULL, 0, &cbDatatypes, &dwDatatypes);
+        pDatatypesInfo1 = HeapAlloc(hProcessHeap, 0, cbDatatypes);
+        if (!pDatatypesInfo1)
+        {
+            ERR("HeapAlloc failed with error %lu!\n", GetLastError());
+            goto Cleanup;
+        }
+
+        if (!pPrintProcessor->pfnEnumPrintProcessorDatatypesW(NULL, NULL, 1, (PBYTE)pDatatypesInfo1, cbDatatypes, &cbDatatypes, &dwDatatypes))
+        {
+            ERR("EnumPrintProcessorDatatypesW failed for Print Processor \"%S\" with error %lu!\n", wszPrintProcessorPath, GetLastError());
+            continue;
+        }
+
+        // Add the supported datatypes to the datatype table.
+        RtlInitializeGenericTable(&pPrintProcessor->DatatypeTable, _DatatypeTableCompareRoutine, GenericTableAllocateRoutine, GenericTableFreeRoutine, NULL);
+
+        for (j = 0; j < dwDatatypes; j++)
+        {
+            pwszDatatype = DuplicateStringW(pDatatypesInfo1->pName);
+
+            if (!RtlInsertElementGenericTable(&pPrintProcessor->DatatypeTable, pDatatypesInfo1->pName, sizeof(PWSTR), NULL))
+            {
+                ERR("RtlInsertElementGenericTable failed for iteration %lu with error %lu!\n", j, GetLastError());
+                goto Cleanup;
+            }
+
+            ++pDatatypesInfo1;
+        }
+
+        // Add the Print Processor to the table.
+        if (!RtlInsertElementGenericTable(&PrintProcessorTable, pPrintProcessor, sizeof(LOCAL_PRINT_PROCESSOR), NULL))
+        {
+            ERR("RtlInsertElementGenericTable failed for iteration %lu with error %lu!\n", i, GetLastError());
+            goto Cleanup;
+        }
+
+        // Don't let the cleanup routines free this.
+        pwszDatatype = NULL;
+        pPrintProcessor = NULL;
+    }
+
+Cleanup:
+    if (pwszDatatype)
+        HeapFree(hProcessHeap, 0, pwszDatatype);
+
+    if (pDatatypesInfo1)
+        HeapFree(hProcessHeap, 0, pDatatypesInfo1);
+
+    if (pPrintProcessor)
+    {
+        if (pPrintProcessor->pwszName)
+            HeapFree(hProcessHeap, 0, pPrintProcessor->pwszName);
+
+        HeapFree(hProcessHeap, 0, pPrintProcessor);
+    }
+
+    if (pwszPrintProcessorName)
+        HeapFree(hProcessHeap, 0, pwszPrintProcessorName);
+
+    if (hSubSubKey)
+        RegCloseKey(hSubSubKey);
+
+    if (hSubKey)
+        RegCloseKey(hSubKey);
+
+    if (hKey)
+        RegCloseKey(hKey);
+}
+
+/**
+ * @name LocalEnumPrintProcessorDatatypes
+ *
+ * Obtains an array of all datatypes supported by a particular Print Processor.
+ * Print Provider function for EnumPrintProcessorDatatypesA/EnumPrintProcessorDatatypesW.
+ *
+ * @param pName
+ * Server Name. Ignored here, because every caller of LocalEnumPrintProcessorDatatypes is interested in the local directory.
+ *
+ * @param pPrintProcessorName
+ * The (case-insensitive) name of the Print Processor to query.
+ *
+ * @param Level
+ * The level of the structure supplied through pDatatypes. This must be 1.
+ *
+ * @param pDatatypes
+ * Pointer to the buffer that receives an array of DATATYPES_INFO_1W structures.
+ * Can be NULL if you just want to know the required size of the buffer.
+ *
+ * @param cbBuf
+ * Size of the buffer you supplied for pDatatypes, in bytes.
+ *
+ * @param pcbNeeded
+ * Pointer to a variable that receives the required size of the buffer for pDatatypes, in bytes.
+ * This parameter mustn't be NULL!
+ *
+ * @param pcReturned
+ * Pointer to a variable that receives the number of elements of the DATATYPES_INFO_1W array.
+ * This parameter mustn't be NULL!
+ *
+ * @return
+ * TRUE if we successfully copied the array into pDatatypes, FALSE otherwise.
+ * A more specific error code can be obtained through GetLastError.
+ */
+BOOL WINAPI
+LocalEnumPrintProcessorDatatypes(LPWSTR pName, LPWSTR pPrintProcessorName, DWORD Level, LPBYTE pDatatypes, DWORD cbBuf, LPDWORD pcbNeeded, LPDWORD pcReturned)
+{
+    PLOCAL_PRINT_PROCESSOR pPrintProcessor;
+
+    // Sanity checks
+    if (Level != 1)
+    {
+        SetLastError(ERROR_INVALID_LEVEL);
+        return FALSE;
+    }
+
+    // Try to find the Print Processor.
+    pPrintProcessor = RtlLookupElementGenericTable(&PrintProcessorTable, pPrintProcessorName);
+    if (!pPrintProcessor)
+    {
+        SetLastError(ERROR_UNKNOWN_PRINTPROCESSOR);
+        return FALSE;
+    }
+
+    // Call its EnumPrintProcessorDatatypesW function.
+    return pPrintProcessor->pfnEnumPrintProcessorDatatypesW(pName, pPrintProcessorName, Level, pDatatypes, cbBuf, pcbNeeded, pcReturned);
+}
+
+/**
+ * @name LocalEnumPrintProcessors
+ *
+ * Obtains an array of all available Print Processors on this computer.
+ * Print Provider function for EnumPrintProcessorsA/EnumPrintProcessorsW.
+ *
+ * @param pName
+ * Server Name. Ignored here, because every caller of LocalEnumPrintProcessors is interested in the local directory.
+ *
+ * @param pEnvironment
+ * One of the predefined operating system and architecture "environment" strings (like "Windows NT x86").
+ * Alternatively, NULL to output the Print Processor directory of the current environment.
+ *
+ * @param Level
+ * The level of the structure supplied through pPrintProcessorInfo. This must be 1.
+ *
+ * @param pPrintProcessorInfo
+ * Pointer to the buffer that receives an array of PRINTPROCESSOR_INFO_1W structures.
+ * Can be NULL if you just want to know the required size of the buffer.
+ *
+ * @param cbBuf
+ * Size of the buffer you supplied for pPrintProcessorInfo, in bytes.
+ *
+ * @param pcbNeeded
+ * Pointer to a variable that receives the required size of the buffer for pPrintProcessorInfo, in bytes.
+ * This parameter mustn't be NULL!
+ *
+ * @param pcReturned
+ * Pointer to a variable that receives the number of elements of the PRINTPROCESSOR_INFO_1W array.
+ * This parameter mustn't be NULL!
+ *
+ * @return
+ * TRUE if we successfully copied the array into pPrintProcessorInfo, FALSE otherwise.
+ * A more specific error code can be obtained through GetLastError.
+ */
+BOOL WINAPI
+LocalEnumPrintProcessors(LPWSTR pName, LPWSTR pEnvironment, DWORD Level, LPBYTE pPrintProcessorInfo, DWORD cbBuf, LPDWORD pcbNeeded, LPDWORD pcReturned)
+{
+    BOOL bReturnValue = FALSE;
+    DWORD cchMaxSubKey;
+    DWORD cchPrintProcessor;
+    DWORD i;
+    HKEY hKey = NULL;
+    HKEY hSubKey = NULL;
+    LONG lStatus;
+    PBYTE pCurrentOutputPrintProcessor;
+    PBYTE pCurrentOutputPrintProcessorInfo;
+    PRINTPROCESSOR_INFO_1W PrintProcessorInfo1;
+    PWSTR pwszEnvironmentKey = NULL;
+    PWSTR pwszTemp = NULL;
+
+    // Sanity checks
+    if (Level != 1)
+    {
+        SetLastError(ERROR_INVALID_LEVEL);
+        goto Cleanup;
+    }
+
+    if (!pcbNeeded || !pcReturned)
+    {
+        // This error must be caught by RPC and returned as RPC_X_NULL_REF_POINTER.
+        ERR("pcbNeeded or pcReturned is NULL!\n");
+        goto Cleanup;
+    }
+
+    // Verify pEnvironment and open its registry key.
+    if (!_OpenEnvironment(pEnvironment, &hKey))
+        goto Cleanup;
+
+    // Open the "Print Processors" subkey.
+    lStatus = RegOpenKeyExW(hKey, L"Print Processors", 0, KEY_READ, &hSubKey);
+    if (lStatus != ERROR_SUCCESS)
+    {
+        ERR("RegOpenKeyExW failed with status %ld!\n", lStatus);
+        goto Cleanup;
+    }
+
+    // Get the number of Print Processors and maximum sub key length.
+    lStatus = RegQueryInfoKeyW(hSubKey, NULL, NULL, NULL, pcReturned, &cchMaxSubKey, NULL, NULL, NULL, NULL, NULL, NULL);
+    if (lStatus != ERROR_SUCCESS)
+    {
+        ERR("RegQueryInfoKeyW failed with status %ld!\n", lStatus);
+        goto Cleanup;
+    }
+
+    // Allocate a temporary buffer to let RegEnumKeyExW succeed.
+    pwszTemp = HeapAlloc(hProcessHeap, 0, (cchMaxSubKey + 1) * sizeof(WCHAR));
+    if (!pwszTemp)
+    {
+        ERR("HeapAlloc failed with error %lu!\n", GetLastError());
+        goto Cleanup;
+    }
+
+    // Determine the required size of the output buffer.
+    *pcbNeeded = 0;
+
+    for (i = 0; i < *pcReturned; i++)
+    {
+        // RegEnumKeyExW sucks! Unlike similar API functions, it only returns the actual numbers of characters copied when you supply a buffer large enough.
+        // So use pwszTemp with its size cchMaxSubKey for this.
+        cchPrintProcessor = cchMaxSubKey;
+        lStatus = RegEnumKeyExW(hSubKey, i, pwszTemp, &cchPrintProcessor, NULL, NULL, NULL, NULL);
+        if (lStatus != ERROR_SUCCESS)
+        {
+            ERR("RegEnumKeyExW failed with status %ld!\n", lStatus);
+            goto Cleanup;
+        }
+
+        *pcbNeeded += sizeof(PRINTPROCESSOR_INFO_1W) + (cchPrintProcessor + 1) * sizeof(WCHAR);
+    }
+
+    // Check if the supplied buffer is large enough.
+    if (cbBuf < *pcbNeeded)
+    {
+        SetLastError(ERROR_INSUFFICIENT_BUFFER);
+        goto Cleanup;
+    }
+
+    // Put the Print Processor strings right after the last PRINTPROCESSOR_INFO_1W structure.
+    pCurrentOutputPrintProcessorInfo = pPrintProcessorInfo;
+    pCurrentOutputPrintProcessor = pPrintProcessorInfo + *pcReturned * sizeof(PRINTPROCESSOR_INFO_1W);
+
+    // Copy over all Print Processors.
+    for (i = 0; i < *pcReturned; i++)
+    {
+        // This isn't really correct, but doesn't cause any harm, because we've extensively checked the size of the supplied buffer above.
+        cchPrintProcessor = cchMaxSubKey;
+
+        // Copy the Print Processor name.
+        lStatus = RegEnumKeyExW(hSubKey, i, (PWSTR)pCurrentOutputPrintProcessor, &cchPrintProcessor, NULL, NULL, NULL, NULL);
+        if (lStatus != ERROR_SUCCESS)
+        {
+            ERR("RegEnumKeyExW failed with status %ld!\n", lStatus);
+            goto Cleanup;
+        }
+
+        // Fill and copy the PRINTPROCESSOR_INFO_1W structure belonging to this Print Processor.
+        PrintProcessorInfo1.pName = (PWSTR)pCurrentOutputPrintProcessor;
+        CopyMemory(pCurrentOutputPrintProcessorInfo, &PrintProcessorInfo1, sizeof(PRINTPROCESSOR_INFO_1W));
+
+        // Advance to the next PRINTPROCESSOR_INFO_1W location and string location in the output buffer.
+        pCurrentOutputPrintProcessor += (cchPrintProcessor + 1) * sizeof(WCHAR);
+        pCurrentOutputPrintProcessorInfo += sizeof(PRINTPROCESSOR_INFO_1W);
+    }
+
+    // We've finished successfully!
+    SetLastError(ERROR_SUCCESS);
+    bReturnValue = TRUE;
+
+Cleanup:
+    if (pwszTemp)
+        HeapFree(hProcessHeap, 0, pwszTemp);
+
+    if (pwszEnvironmentKey)
+        HeapFree(hProcessHeap, 0, pwszEnvironmentKey);
+
+    if (hSubKey)
+        RegCloseKey(hSubKey);
+
+    if (hKey)
+        RegCloseKey(hKey);
+
+    return bReturnValue;
+}
+
+/**
+ * @name LocalGetPrintProcessorDirectory
+ *
+ * Obtains the path to the local Print Processor directory.
+ * Print Provider function for GetPrintProcessorDirectoryA/GetPrintProcessorDirectoryW.
+ *
+ * @param pName
+ * Server Name. Ignored here, because every caller of LocalGetPrintProcessorDirectory is interested in the local directory.
+ *
+ * @param pEnvironment
+ * One of the predefined operating system and architecture "environment" strings (like "Windows NT x86").
+ * Alternatively, NULL to output the Print Processor directory of the current environment.
+ *
+ * @param Level
+ * The level of the (non-existing) structure supplied through pPrintProcessorInfo. This must be 1.
+ *
+ * @param pPrintProcessorInfo
+ * Pointer to the buffer that receives the full path to the Print Processor directory.
+ * Can be NULL if you just want to know the required size of the buffer.
+ *
+ * @param cbBuf
+ * Size of the buffer you supplied for pPrintProcessorInfo, in bytes.
+ *
+ * @param pcbNeeded
+ * Pointer to a variable that receives the required size of the buffer for pPrintProcessorInfo, in bytes.
+ * This parameter mustn't be NULL!
+ *
+ * @return
+ * TRUE if we successfully copied the directory into pPrintProcessorInfo, FALSE otherwise.
+ * A more specific error code can be obtained through GetLastError.
+ */
+BOOL WINAPI
+LocalGetPrintProcessorDirectory(LPWSTR pName, LPWSTR pEnvironment, DWORD Level, LPBYTE pPrintProcessorInfo, DWORD cbBuf, LPDWORD pcbNeeded)
+{
+    const WCHAR wszPath[] = L"\\PRTPROCS\\";
+    const DWORD cchPath = sizeof(wszPath) / sizeof(WCHAR) - 1;
+
+    BOOL bReturnValue = FALSE;
+    DWORD cbDataWritten;
+    HKEY hKey = NULL;
+    LONG lStatus;
+    PWSTR pwszEnvironmentKey = NULL;
+
+    // Sanity checks
+    if (Level != 1)
+    {
+        SetLastError(ERROR_INVALID_LEVEL);
+        goto Cleanup;
+    }
+
+    if (!pcbNeeded)
+    {
+        // This error must be caught by RPC and returned as RPC_X_NULL_REF_POINTER.
+        ERR("pcbNeeded is NULL!\n");
+        goto Cleanup;
+    }
+
+    // Verify pEnvironment and open its registry key.
+    if (!_OpenEnvironment(pEnvironment, &hKey))
+        goto Cleanup;
+
+    // Determine the size of the required buffer.
+    lStatus = RegQueryValueExW(hKey, L"Directory", NULL, NULL, NULL, pcbNeeded);
+    if (lStatus != ERROR_SUCCESS)
+    {
+        ERR("RegQueryValueExW failed with status %ld!\n", lStatus);
+        goto Cleanup;
+    }
+
+    *pcbNeeded += cchSpoolDirectory;
+    *pcbNeeded += cchPath;
+
+    // Is the supplied buffer large enough?
+    if (cbBuf < *pcbNeeded)
+    {
+        SetLastError(ERROR_INSUFFICIENT_BUFFER);
+        goto Cleanup;
+    }
+
+    // Copy the path to the "prtprocs" directory into pPrintProcessorInfo
+    CopyMemory(pPrintProcessorInfo, wszSpoolDirectory, cchSpoolDirectory * sizeof(WCHAR));
+    CopyMemory(&pPrintProcessorInfo[cchSpoolDirectory], wszPath, cchPath * sizeof(WCHAR));
+
+    // Get the directory name from the registry.
+    lStatus = RegQueryValueExW(hKey, L"Directory", NULL, NULL, &pPrintProcessorInfo[cchSpoolDirectory + cchPath], &cbDataWritten);
+    if (lStatus != ERROR_SUCCESS)
+    {
+        ERR("RegQueryValueExW failed with status %ld!\n", lStatus);
+        goto Cleanup;
+    }
+
+    // We've finished successfully!
+    SetLastError(ERROR_SUCCESS);
+    bReturnValue = TRUE;
+
+Cleanup:
+    if (pwszEnvironmentKey)
+        HeapFree(hProcessHeap, 0, pwszEnvironmentKey);
+
+    if (hKey)
+        RegCloseKey(hKey);
+
+    return bReturnValue;
+}
diff --git a/reactos/win32ss/printing/providers/localspl/tools.c b/reactos/win32ss/printing/providers/localspl/tools.c
new file mode 100644 (file)
index 0000000..fa0b733
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+ * PROJECT:     ReactOS Local Spooler
+ * LICENSE:     GNU LGPL v2.1 or any later version as published by the Free Software Foundation
+ * PURPOSE:     Various tools
+ * COPYRIGHT:   Copyright 2015 Colin Finck <colin@reactos.org>
+ */
+
+#include "precomp.h"
+
+/**
+ * @name AllocAndRegQueryWSZ
+ *
+ * Queries a REG_SZ value in the registry, allocates memory for it and returns a buffer containing the value.
+ * You have to free this buffer using HeapFree.
+ *
+ * @param hKey
+ * HKEY variable of the key opened with RegOpenKeyExW.
+ *
+ * @param pwszValueName
+ * Name of the REG_SZ value to query.
+ *
+ * @return
+ * Pointer to the buffer containing the value or NULL in case of failure.
+ */
+PWSTR
+AllocAndRegQueryWSZ(HKEY hKey, PCWSTR pwszValueName)
+{
+    DWORD cbNeeded;
+    LONG lStatus;
+    PWSTR pwszValue;
+
+    // Determine the size of the required buffer.
+    lStatus = RegQueryValueExW(hKey, pwszValueName, NULL, NULL, NULL, &cbNeeded);
+    if (lStatus != ERROR_SUCCESS)
+    {
+        ERR("RegQueryValueExW failed with status %ld!\n", lStatus);
+        return NULL;
+    }
+
+    // Allocate it.
+    pwszValue = HeapAlloc(hProcessHeap, 0, cbNeeded);
+    if (!pwszValue)
+    {
+        ERR("HeapAlloc failed with error %lu!\n", GetLastError());
+        return NULL;
+    }
+
+    // Now get the actual value.
+    lStatus = RegQueryValueExW(hKey, pwszValueName, NULL, NULL, (PBYTE)pwszValue, &cbNeeded);
+    if (lStatus != ERROR_SUCCESS)
+    {
+        ERR("RegQueryValueExW failed with status %ld!\n", lStatus);
+        HeapFree(hProcessHeap, 0, pwszValue);
+        return NULL;
+    }
+
+    return pwszValue;
+}
+
+/**
+ * @name DuplicateStringW
+ *
+ * Allocates memory for a Unicode string and copies the input string into it.
+ * Equivalent of wcsdup, but the returned buffer must be freed with HeapFree instead of free.
+ *
+ * @param pwszInput
+ * The input string to copy
+ *
+ * @return
+ * Pointer to the copied string or NULL if no memory could be allocated.
+ */
+PWSTR
+DuplicateStringW(PCWSTR pwszInput)
+{
+    DWORD cchInput;
+    PWSTR pwszOutput;
+
+    cchInput = wcslen(pwszInput);
+
+    pwszOutput = HeapAlloc(hProcessHeap, 0, (cchInput + 1) * sizeof(WCHAR));
+    if (!pwszOutput)
+    {
+        ERR("HeapAlloc failed with error %lu!\n", GetLastError());
+        return NULL;
+    }
+
+    CopyMemory(pwszOutput, pwszInput, (cchInput + 1) * sizeof(WCHAR));
+
+    return pwszOutput;
+}
+
+/**
+ * @name GenericTableAllocateRoutine
+ *
+ * RTL_GENERIC_ALLOCATE_ROUTINE for all our RTL_GENERIC_TABLEs, using HeapAlloc.
+ */
+PVOID NTAPI
+GenericTableAllocateRoutine(PRTL_GENERIC_TABLE Table, CLONG ByteSize)
+{
+    return HeapAlloc(hProcessHeap, 0, ByteSize);
+}
+
+/**
+ * @name GenericTableFreeRoutine
+ *
+ * RTL_GENERIC_FREE_ROUTINE for all our RTL_GENERIC_TABLEs, using HeapFree.
+ */
+VOID NTAPI
+GenericTableFreeRoutine(PRTL_GENERIC_TABLE Table, PVOID Buffer)
+{
+    HeapFree(hProcessHeap, 0, Buffer);
+}
index 5bd8117..792a98d 100644 (file)
@@ -24,4 +24,5 @@ if(NOT ARCH STREQUAL "amd64")
 endif()
 add_subdirectory(winhttp)
 add_subdirectory(wininet)
 endif()
 add_subdirectory(winhttp)
 add_subdirectory(wininet)
+add_subdirectory(winspool)
 add_subdirectory(ws2_32)
 add_subdirectory(ws2_32)
diff --git a/rostests/apitests/winspool/CMakeLists.txt b/rostests/apitests/winspool/CMakeLists.txt
new file mode 100644 (file)
index 0000000..6e4484d
--- /dev/null
@@ -0,0 +1,14 @@
+
+list(APPEND SOURCE
+    ClosePrinter.c
+    EnumPrintProcessorDatatypes.c
+    GetPrintProcessorDirectory.c
+    OpenPrinter.c
+    StartDocPrinter.c
+    testlist.c)
+
+add_executable(winspool_apitest ${SOURCE})
+target_link_libraries(winspool_apitest wine ${PSEH_LIB})
+set_module_type(winspool_apitest win32cui)
+add_importlibs(winspool_apitest winspool msvcrt kernel32 ntdll)
+add_cd_file(TARGET winspool_apitest DESTINATION reactos/bin FOR all)
diff --git a/rostests/apitests/winspool/ClosePrinter.c b/rostests/apitests/winspool/ClosePrinter.c
new file mode 100644 (file)
index 0000000..86865b4
--- /dev/null
@@ -0,0 +1,21 @@
+/*
+ * PROJECT:     ReactOS Print Spooler DLL API Tests
+ * LICENSE:     GNU GPLv2 or any later version as published by the Free Software Foundation
+ * PURPOSE:     Tests for ClosePrinter
+ * COPYRIGHT:   Copyright 2015 Colin Finck <colin@reactos.org>
+ */
+
+#include <apitest.h>
+
+#define WIN32_NO_STATUS
+#include <windef.h>
+#include <winbase.h>
+#include <wingdi.h>
+#include <winspool.h>
+
+START_TEST(ClosePrinter)
+{
+    SetLastError(0xDEADBEEF);
+    ok(!ClosePrinter(NULL), "ClosePrinter returns TRUE!\n");
+    ok(GetLastError() == ERROR_INVALID_HANDLE, "ClosePrinter returns error %lu!\n", GetLastError());
+}
diff --git a/rostests/apitests/winspool/EnumPrintProcessorDatatypes.c b/rostests/apitests/winspool/EnumPrintProcessorDatatypes.c
new file mode 100644 (file)
index 0000000..29c7977
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * PROJECT:     ReactOS Print Spooler DLL API Tests
+ * LICENSE:     GNU GPLv2 or any later version as published by the Free Software Foundation
+ * PURPOSE:     Tests for EnumPrintProcessorDatatypesA/EnumPrintProcessorDatatypesW
+ * COPYRIGHT:   Copyright 2015 Colin Finck <colin@reactos.org>
+ */
+
+#include <apitest.h>
+
+#define WIN32_NO_STATUS
+#include <windef.h>
+#include <winbase.h>
+#include <wingdi.h>
+#include <winspool.h>
+
+START_TEST(EnumPrintProcessorDatatypes)
+{
+    DWORD cbNeeded;
+    DWORD dwReturned;
+    PDATATYPES_INFO_1W pDatatypesInfo1;
+
+    // Try with an invalid level, this needs to be caught first.
+    SetLastError(0xDEADBEEF);
+    ok(!EnumPrintProcessorDatatypesW(NULL, NULL, 0, NULL, 0, NULL, NULL), "EnumPrintProcessorDatatypesW returns TRUE!\n");
+    ok(GetLastError() == ERROR_INVALID_LEVEL, "EnumPrintProcessorDatatypesW returns error %lu!\n", GetLastError());
+
+    // Now try with valid level, but no pcbNeeded and no pcReturned.
+    SetLastError(0xDEADBEEF);
+    ok(!EnumPrintProcessorDatatypesW(NULL, NULL, 1, NULL, 0, NULL, NULL), "EnumPrintProcessorDatatypesW returns TRUE!\n");
+    ok(GetLastError() == RPC_X_NULL_REF_POINTER, "EnumPrintProcessorDatatypesW returns error %lu!\n", GetLastError());
+
+    // Now try with pcbNeeded and pcReturned, but give no Print Processor.
+    SetLastError(0xDEADBEEF);
+    ok(!EnumPrintProcessorDatatypesW(NULL, NULL, 1, NULL, 0, &cbNeeded, &dwReturned), "EnumPrintProcessorDatatypesW returns TRUE!\n");
+    ok(GetLastError() == ERROR_UNKNOWN_PRINTPROCESSOR, "EnumPrintProcessorDatatypesW returns error %lu!\n", GetLastError());
+
+    // Same error has to occur when looking for an invalid Print Processor.
+    SetLastError(0xDEADBEEF);
+    ok(!EnumPrintProcessorDatatypesW(NULL, L"invalid", 1, NULL, 0, &cbNeeded, &dwReturned), "EnumPrintProcessorDatatypesW returns TRUE!\n");
+    ok(GetLastError() == ERROR_UNKNOWN_PRINTPROCESSOR, "EnumPrintProcessorDatatypesW returns error %lu!\n", GetLastError());
+
+    // Now get the required buffer size by supplying all information. This needs to fail with ERROR_INSUFFICIENT_BUFFER.
+    // This also verifies that the function is really doing a case-insensitive lookup.
+    SetLastError(0xDEADBEEF);
+    ok(!EnumPrintProcessorDatatypesW(NULL, L"wInPrInT", 1, NULL, 0, &cbNeeded, &dwReturned), "EnumPrintProcessorDatatypesW returns TRUE!\n");
+    ok(GetLastError() == ERROR_INSUFFICIENT_BUFFER, "EnumPrintProcessorDatatypesW returns error %lu!\n", GetLastError());
+    ok(cbNeeded > 0, "cbNeeded is 0!\n");
+    ok(dwReturned > 0, "dwReturned is 0!\n");
+
+    // Now provide the demanded size, but no buffer.
+    SetLastError(0xDEADBEEF);
+    ok(!EnumPrintProcessorDatatypesW(NULL, L"wInPrInT", 1, NULL, cbNeeded, &cbNeeded, &dwReturned), "EnumPrintProcessorDatatypesW returns TRUE!\n");
+    ok(GetLastError() == ERROR_INVALID_USER_BUFFER, "EnumPrintProcessorDatatypesW returns error %lu!\n", GetLastError());
+
+    // This also has to fail the same way when no Print Processor was given at all.
+    SetLastError(0xDEADBEEF);
+    ok(!EnumPrintProcessorDatatypesW(NULL, NULL, 1, NULL, cbNeeded, &cbNeeded, &dwReturned), "EnumPrintProcessorDatatypesW returns TRUE!\n");
+    ok(GetLastError() == ERROR_INVALID_USER_BUFFER, "EnumPrintProcessorDatatypesW returns error %lu!\n", GetLastError());
+
+    // Same error has to occur with a valid Print Processor, but a size too small.
+    SetLastError(0xDEADBEEF);
+    ok(!EnumPrintProcessorDatatypesW(NULL, L"wInPrInT", 1, NULL, cbNeeded, &cbNeeded, &dwReturned), "EnumPrintProcessorDatatypesW returns TRUE!\n");
+    ok(GetLastError() == ERROR_INVALID_USER_BUFFER, "EnumPrintProcessorDatatypesW returns error %lu!\n", GetLastError());
+
+    // Finally use the function as intended and aim for success!
+    pDatatypesInfo1 = HeapAlloc(GetProcessHeap(), 0, cbNeeded);
+    SetLastError(0xDEADBEEF);
+    ok(EnumPrintProcessorDatatypesW(NULL, L"wInPrInT", 1, (PBYTE)pDatatypesInfo1, cbNeeded, &cbNeeded, &dwReturned), "EnumPrintProcessorDatatypesW returns FALSE!\n");
+    ok(GetLastError() == ERROR_SUCCESS, "EnumPrintProcessorDatatypesW returns error %lu!\n", GetLastError());
+    HeapFree(GetProcessHeap(), 0, pDatatypesInfo1);
+}
diff --git a/rostests/apitests/winspool/GetPrintProcessorDirectory.c b/rostests/apitests/winspool/GetPrintProcessorDirectory.c
new file mode 100644 (file)
index 0000000..bece06e
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * PROJECT:     ReactOS Print Spooler DLL API Tests
+ * LICENSE:     GNU GPLv2 or any later version as published by the Free Software Foundation
+ * PURPOSE:     Tests for GetPrintProcessorDirectoryA/GetPrintProcessorDirectoryW
+ * COPYRIGHT:   Copyright 2015 Colin Finck <colin@reactos.org>
+ */
+
+#include <apitest.h>
+
+#define WIN32_NO_STATUS
+#include <windef.h>
+#include <winbase.h>
+#include <wingdi.h>
+#include <winspool.h>
+
+START_TEST(GetPrintProcessorDirectory)
+{
+    DWORD cbNeeded;
+    PWSTR pwszBuffer;
+
+    // Try with an invalid level, this needs to be caught first.
+    SetLastError(0xDEADBEEF);
+    ok(!GetPrintProcessorDirectoryW(NULL, NULL, 0, NULL, 0, NULL), "GetPrintProcessorDirectoryW returns TRUE!\n");
+    ok(GetLastError() == ERROR_INVALID_LEVEL, "GetPrintProcessorDirectoryW returns error %lu!\n", GetLastError());
+
+    // Now try with valid level, but no pcbNeeded.
+    SetLastError(0xDEADBEEF);
+    ok(!GetPrintProcessorDirectoryW(NULL, NULL, 1, NULL, 0, NULL), "GetPrintProcessorDirectoryW returns TRUE!\n");
+    ok(GetLastError() == RPC_X_NULL_REF_POINTER, "GetPrintProcessorDirectoryW returns error %lu!\n", GetLastError());
+
+    // Now get the required buffer size by supplying pcbNeeded. This needs to fail with ERROR_INSUFFICIENT_BUFFER.
+    SetLastError(0xDEADBEEF);
+    ok(!GetPrintProcessorDirectoryW(NULL, NULL, 1, NULL, 0, &cbNeeded), "GetPrintProcessorDirectoryW returns TRUE!\n");
+    ok(GetLastError() == ERROR_INSUFFICIENT_BUFFER, "GetPrintProcessorDirectoryW returns error %lu!\n", GetLastError());
+    ok(cbNeeded > 0, "cbNeeded is 0!\n");
+
+    // Now provide the demanded size, but no buffer.
+    SetLastError(0xDEADBEEF);
+    ok(!GetPrintProcessorDirectoryW(NULL, NULL, 1, NULL, cbNeeded, &cbNeeded), "GetPrintProcessorDirectoryW returns TRUE!\n");
+    ok(GetLastError() == ERROR_INVALID_USER_BUFFER, "GetPrintProcessorDirectoryW returns error %lu!\n", GetLastError());
+
+    // Same error has to occur with a size too small.
+    SetLastError(0xDEADBEEF);
+    ok(!GetPrintProcessorDirectoryW(NULL, NULL, 1, NULL, 1, &cbNeeded), "GetPrintProcessorDirectoryW returns TRUE!\n");
+    ok(GetLastError() == ERROR_INVALID_USER_BUFFER, "GetPrintProcessorDirectoryW returns error %lu!\n", GetLastError());
+
+    // Try with an invalid environment as well.
+    SetLastError(0xDEADBEEF);
+    ok(!GetPrintProcessorDirectoryW(NULL, L"invalid", 1, NULL, 0, &cbNeeded), "GetPrintProcessorDirectoryW returns TRUE!\n");
+    ok(GetLastError() == ERROR_INVALID_ENVIRONMENT, "GetPrintProcessorDirectoryW returns error %lu!\n", GetLastError());
+
+    // Finally use the function as intended and aim for success!
+    pwszBuffer = HeapAlloc(GetProcessHeap(), 0, cbNeeded);
+    SetLastError(0xDEADBEEF);
+    ok(GetPrintProcessorDirectoryW(NULL, NULL, 1, (PBYTE)pwszBuffer, cbNeeded, &cbNeeded), "GetPrintProcessorDirectoryW returns FALSE!\n");
+    ok(GetLastError() == ERROR_SUCCESS, "GetPrintProcessorDirectoryW returns error %lu!\n", GetLastError());
+    HeapFree(GetProcessHeap(), 0, pwszBuffer);
+}
diff --git a/rostests/apitests/winspool/OpenPrinter.c b/rostests/apitests/winspool/OpenPrinter.c
new file mode 100644 (file)
index 0000000..7049edf
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * PROJECT:     ReactOS Print Spooler DLL API Tests
+ * LICENSE:     GNU GPLv2 or any later version as published by the Free Software Foundation
+ * PURPOSE:     Tests for OpenPrinterA/OpenPrinterW
+ * COPYRIGHT:   Copyright 2015 Colin Finck <colin@reactos.org>
+ */
+
+#include <apitest.h>
+
+#define WIN32_NO_STATUS
+#include <windef.h>
+#include <winbase.h>
+#include <wingdi.h>
+#include <winspool.h>
+
+START_TEST(OpenPrinter)
+{
+    HANDLE hPrinter;
+
+    SetLastError(0xDEADBEEF);
+    ok(!OpenPrinterW(NULL, NULL, NULL), "OpenPrinterW returns TRUE!\n");
+    ok(GetLastError() == ERROR_INVALID_PARAMETER, "OpenPrinterW returns error %lu!\n", GetLastError());
+
+    SetLastError(0xDEADBEEF);
+    ok(!OpenPrinterW(NULL, &hPrinter, NULL), "OpenPrinterW returns TRUE!\n");
+    ok(GetLastError() == ERROR_INVALID_PARAMETER, "OpenPrinterW returns error %lu!\n", GetLastError());
+}
diff --git a/rostests/apitests/winspool/StartDocPrinter.c b/rostests/apitests/winspool/StartDocPrinter.c
new file mode 100644 (file)
index 0000000..24af7dd
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * PROJECT:     ReactOS Print Spooler DLL API Tests
+ * LICENSE:     GNU GPLv2 or any later version as published by the Free Software Foundation
+ * PURPOSE:     Tests for StartDocPrinterA/StartDocPrinterW
+ * COPYRIGHT:   Copyright 2015 Colin Finck <colin@reactos.org>
+ */
+
+#include <apitest.h>
+
+#define WIN32_NO_STATUS
+#include <windef.h>
+#include <winbase.h>
+#include <wingdi.h>
+#include <winspool.h>
+
+START_TEST(StartDocPrinter)
+{
+    DWORD dwResult;
+    DOCINFOW docInfo = { 0 };
+
+    SetLastError(0xDEADBEEF);
+    dwResult = StartDocPrinterW(NULL, 0, NULL);
+    ok(dwResult == 0, "StartDocPrinterW returns %lu!\n", dwResult);
+    ok(GetLastError() == ERROR_INVALID_PARAMETER, "StartDocPrinter returns error %lu!\n", GetLastError());
+
+    SetLastError(0xDEADBEEF);
+    dwResult = StartDocPrinterW(NULL, 1, NULL);
+    ok(dwResult == 0, "StartDocPrinterW returns %lu!\n", dwResult);
+    ok(GetLastError() == ERROR_INVALID_PARAMETER, "StartDocPrinter returns error %lu!\n", GetLastError());
+
+    SetLastError(0xDEADBEEF);
+    dwResult = StartDocPrinterW(NULL, 0, (LPBYTE)&docInfo);
+    ok(dwResult == 0, "StartDocPrinterW returns %lu!\n", dwResult);
+    ok(GetLastError() == ERROR_INVALID_HANDLE, "StartDocPrinter returns error %lu!\n", GetLastError());
+
+    SetLastError(0xDEADBEEF);
+    dwResult = StartDocPrinterW(NULL, 1, (LPBYTE)&docInfo);
+    ok(dwResult == 0, "StartDocPrinterW returns %lu!\n", dwResult);
+    ok(GetLastError() == ERROR_INVALID_HANDLE, "StartDocPrinter returns error %lu!\n", GetLastError());
+
+    /// ERROR_INVALID_LEVEL with correct handle but invalid Level
+}
diff --git a/rostests/apitests/winspool/testlist.c b/rostests/apitests/winspool/testlist.c
new file mode 100644 (file)
index 0000000..1193c3a
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * PROJECT:     ReactOS Print Spooler DLL API Tests
+ * LICENSE:     GNU GPLv2 or any later version as published by the Free Software Foundation
+ * PURPOSE:     Test list
+ * COPYRIGHT:   Copyright 2015 Colin Finck <colin@reactos.org>
+ */
+
+#define __ROS_LONG64__
+
+#define STANDALONE
+#include <apitest.h>
+
+extern void func_ClosePrinter(void);
+extern void func_EnumPrintProcessorDatatypes(void);
+extern void func_GetPrintProcessorDirectory(void);
+extern void func_OpenPrinter(void);
+extern void func_StartDocPrinter(void);
+
+const struct test winetest_testlist[] =
+{
+    { "ClosePrinter", func_ClosePrinter },
+    { "EnumPrintProcessorDatatypes", func_EnumPrintProcessorDatatypes },
+    { "GetPrintProcessorDirectory", func_GetPrintProcessorDirectory },
+    { "OpenPrinter", func_OpenPrinter },
+    { "StartDocPrinter", func_StartDocPrinter },
+
+    { 0, 0 }
+};