[LOCALSPL]
[reactos.git] / reactos / win32ss / printing / providers / localspl / jobs.c
index 7fa5753..b080bf9 100644 (file)
@@ -102,7 +102,7 @@ GetNextJobID(PDWORD dwJobID)
     return TRUE;
 }
 
     return TRUE;
 }
 
-void
+BOOL
 InitializeGlobalJobList()
 {
     const WCHAR wszPath[] = L"\\PRINTERS\\?????.SHD";
 InitializeGlobalJobList()
 {
     const WCHAR wszPath[] = L"\\PRINTERS\\?????.SHD";
@@ -110,6 +110,7 @@ InitializeGlobalJobList()
     const DWORD cchFolders = sizeof("\\PRINTERS\\") - 1;
     const DWORD cchPattern = sizeof("?????") - 1;
 
     const DWORD cchFolders = sizeof("\\PRINTERS\\") - 1;
     const DWORD cchPattern = sizeof("?????") - 1;
 
+    DWORD dwErrorCode;
     DWORD dwJobID;
     HANDLE hFind;
     PLOCAL_JOB pJob = NULL;
     DWORD dwJobID;
     HANDLE hFind;
     PLOCAL_JOB pJob = NULL;
@@ -133,6 +134,7 @@ InitializeGlobalJobList()
     if (hFind == INVALID_HANDLE_VALUE)
     {
         // No unfinished jobs found.
     if (hFind == INVALID_HANDLE_VALUE)
     {
         // No unfinished jobs found.
+        dwErrorCode = ERROR_SUCCESS;
         goto Cleanup;
     }
 
         goto Cleanup;
     }
 
@@ -159,23 +161,30 @@ InitializeGlobalJobList()
         // Add it to the Global Job List.
         if (!InsertElementSkiplist(&GlobalJobList, pJob))
         {
         // Add it to the Global Job List.
         if (!InsertElementSkiplist(&GlobalJobList, pJob))
         {
+            dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
             ERR("InsertElementSkiplist failed for job %lu for the GlobalJobList!\n", pJob->dwJobID);
             goto Cleanup;
         }
 
         // Add it to the Printer's Job List.
             ERR("InsertElementSkiplist failed for job %lu for the GlobalJobList!\n", pJob->dwJobID);
             goto Cleanup;
         }
 
         // Add it to the Printer's Job List.
-        if (!InsertElementSkiplist(&pJob->Printer->JobList, pJob))
+        if (!InsertElementSkiplist(&pJob->pPrinter->JobList, pJob))
         {
         {
+            dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
             ERR("InsertElementSkiplist failed for job %lu for the Printer's Job List!\n", pJob->dwJobID);
             goto Cleanup;
         }
     }
     while (FindNextFileW(hFind, &FindData));
 
             ERR("InsertElementSkiplist failed for job %lu for the Printer's Job List!\n", pJob->dwJobID);
             goto Cleanup;
         }
     }
     while (FindNextFileW(hFind, &FindData));
 
+    dwErrorCode = ERROR_SUCCESS;
+
 Cleanup:
     // Outside the loop
     if (hFind)
         FindClose(hFind);
 Cleanup:
     // Outside the loop
     if (hFind)
         FindClose(hFind);
+
+    SetLastError(dwErrorCode);
+    return (dwErrorCode == ERROR_SUCCESS);
 }
 
 void
 }
 
 void
@@ -196,36 +205,54 @@ LocalAddJob(HANDLE hPrinter, DWORD Level, LPBYTE pData, DWORD cbBuf, LPDWORD pcb
     const DWORD cchSpl = _countof("?????.SPL") - 1;
 
     ADDJOB_INFO_1W AddJobInfo1;
     const DWORD cchSpl = _countof("?????.SPL") - 1;
 
     ADDJOB_INFO_1W AddJobInfo1;
-    BOOL bReturnValue = FALSE;
     DWORD cchMachineName;
     DWORD cchUserName;
     DWORD cchMachineName;
     DWORD cchUserName;
+    DWORD dwErrorCode;
     PBYTE p;
     PLOCAL_HANDLE pHandle;
     PLOCAL_JOB pJob;
     PLOCAL_PRINTER_HANDLE pPrinterHandle;
     RPC_BINDING_HANDLE hServerBinding = NULL;
     PBYTE p;
     PLOCAL_HANDLE pHandle;
     PLOCAL_JOB pJob;
     PLOCAL_PRINTER_HANDLE pPrinterHandle;
     RPC_BINDING_HANDLE hServerBinding = NULL;
-    RPC_STATUS Status;
     RPC_WSTR pwszBinding = NULL;
     RPC_WSTR pwszMachineName = NULL;
 
     // Check if this is a printer handle.
     pHandle = (PLOCAL_HANDLE)hPrinter;
     RPC_WSTR pwszBinding = NULL;
     RPC_WSTR pwszMachineName = NULL;
 
     // Check if this is a printer handle.
     pHandle = (PLOCAL_HANDLE)hPrinter;
-    if (pHandle->HandleType != Printer)
+    if (pHandle->HandleType != HandleType_Printer)
+    {
+        dwErrorCode = ERROR_INVALID_HANDLE;
         goto Cleanup;
         goto Cleanup;
+    }
 
 
-    pPrinterHandle = (PLOCAL_PRINTER_HANDLE)pHandle->SpecificHandle;
+    pPrinterHandle = (PLOCAL_PRINTER_HANDLE)pHandle->pSpecificHandle;
+
+    // This handle must not have started a job yet!
+    if (pPrinterHandle->pStartedJob)
+    {
+        dwErrorCode = ERROR_INVALID_HANDLE;
+        goto Cleanup;
+    }
 
     // Check if this is the right structure level.
     if (Level != 1)
 
     // Check if this is the right structure level.
     if (Level != 1)
+    {
+        dwErrorCode = ERROR_INVALID_LEVEL;
         goto Cleanup;
         goto Cleanup;
+    }
 
 
-    // FIXME: This needs to fail if the printer is set to do direct printing.
+    // Check if the printer is set to do direct printing.
+    // The Job List isn't used in this case.
+    if (pPrinterHandle->pPrinter->dwAttributes & PRINTER_ATTRIBUTE_DIRECT)
+    {
+        dwErrorCode = ERROR_INVALID_ACCESS;
+        goto Cleanup;
+    }
 
     // Check if the supplied buffer is large enough.
     *pcbNeeded = sizeof(ADDJOB_INFO_1W) + (cchSpoolDirectory + cchPrintersPath + cchSpl + 1) * sizeof(WCHAR);
     if (cbBuf < *pcbNeeded)
     {
 
     // Check if the supplied buffer is large enough.
     *pcbNeeded = sizeof(ADDJOB_INFO_1W) + (cchSpoolDirectory + cchPrintersPath + cchSpl + 1) * sizeof(WCHAR);
     if (cbBuf < *pcbNeeded)
     {
-        SetLastError(ERROR_INSUFFICIENT_BUFFER);
+        dwErrorCode = ERROR_INSUFFICIENT_BUFFER;
         goto Cleanup;
     }
 
         goto Cleanup;
     }
 
@@ -233,20 +260,26 @@ LocalAddJob(HANDLE hPrinter, DWORD Level, LPBYTE pData, DWORD cbBuf, LPDWORD pcb
     pJob = DllAllocSplMem(sizeof(LOCAL_JOB));
     if (!pJob)
     {
     pJob = DllAllocSplMem(sizeof(LOCAL_JOB));
     if (!pJob)
     {
-        ERR("DllAllocSplMem failed with error %lu!\n", GetLastError());
+        dwErrorCode = GetLastError();
+        ERR("DllAllocSplMem failed with error %lu!\n", dwErrorCode);
         goto Cleanup;
     }
 
     // Reserve an ID for this job.
     if (!GetNextJobID(&pJob->dwJobID))
         goto Cleanup;
     }
 
     // Reserve an ID for this job.
     if (!GetNextJobID(&pJob->dwJobID))
+    {
+        dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
         goto Cleanup;
         goto Cleanup;
+    }
 
     // Copy over defaults to the LOCAL_JOB structure.
 
     // Copy over defaults to the LOCAL_JOB structure.
-    pJob->Printer = pPrinterHandle->Printer;
+    pJob->pPrinter = pPrinterHandle->pPrinter;
+    pJob->pPrintProcessor = pPrinterHandle->pPrinter->pPrintProcessor;
     pJob->dwPriority = DEF_PRIORITY;
     pJob->dwPriority = DEF_PRIORITY;
+    pJob->dwStatus = JOB_STATUS_SPOOLING;
     pJob->pwszDatatype = AllocSplStr(pPrinterHandle->pwszDatatype);
     pJob->pwszDocumentName = AllocSplStr(wszDefaultDocumentName);
     pJob->pwszDatatype = AllocSplStr(pPrinterHandle->pwszDatatype);
     pJob->pwszDocumentName = AllocSplStr(wszDefaultDocumentName);
-    CopyMemory(&pJob->DevMode, &pPrinterHandle->DevMode, sizeof(DEVMODEW));
+    pJob->pDevMode = DuplicateDevMode(pPrinterHandle->pDevMode);
     GetSystemTime(&pJob->stSubmitted);
 
     // Get the user name for the Job.
     GetSystemTime(&pJob->stSubmitted);
 
     // Get the user name for the Job.
@@ -254,7 +287,8 @@ LocalAddJob(HANDLE hPrinter, DWORD Level, LPBYTE pData, DWORD cbBuf, LPDWORD pcb
     pJob->pwszUserName = DllAllocSplMem(cchUserName * sizeof(WCHAR));
     if (!GetUserNameW(pJob->pwszUserName, &cchUserName))
     {
     pJob->pwszUserName = DllAllocSplMem(cchUserName * sizeof(WCHAR));
     if (!GetUserNameW(pJob->pwszUserName, &cchUserName))
     {
-        ERR("GetUserNameW failed with error %lu!\n", GetLastError());
+        dwErrorCode = GetLastError();
+        ERR("GetUserNameW failed with error %lu!\n", dwErrorCode);
         goto Cleanup;
     }
 
         goto Cleanup;
     }
 
@@ -262,24 +296,24 @@ LocalAddJob(HANDLE hPrinter, DWORD Level, LPBYTE pData, DWORD cbBuf, LPDWORD pcb
     pJob->pwszNotifyName = AllocSplStr(pJob->pwszUserName);
 
     // Get the name of the machine that submitted the Job over RPC.
     pJob->pwszNotifyName = AllocSplStr(pJob->pwszUserName);
 
     // Get the name of the machine that submitted the Job over RPC.
-    Status = RpcBindingServerFromClient(NULL, &hServerBinding);
-    if (Status != RPC_S_OK)
+    dwErrorCode = RpcBindingServerFromClient(NULL, &hServerBinding);
+    if (dwErrorCode != RPC_S_OK)
     {
     {
-        ERR("RpcBindingServerFromClient failed with status %lu!\n", Status);
+        ERR("RpcBindingServerFromClient failed with status %lu!\n", dwErrorCode);
         goto Cleanup;
     }
 
         goto Cleanup;
     }
 
-    Status = RpcBindingToStringBindingW(hServerBinding, &pwszBinding);
-    if (Status != RPC_S_OK)
+    dwErrorCode = RpcBindingToStringBindingW(hServerBinding, &pwszBinding);
+    if (dwErrorCode != RPC_S_OK)
     {
     {
-        ERR("RpcBindingToStringBindingW failed with status %lu!\n", Status);
+        ERR("RpcBindingToStringBindingW failed with status %lu!\n", dwErrorCode);
         goto Cleanup;
     }
 
         goto Cleanup;
     }
 
-    Status = RpcStringBindingParseW(pwszBinding, NULL, NULL, &pwszMachineName, NULL, NULL);
-    if (Status != RPC_S_OK)
+    dwErrorCode = RpcStringBindingParseW(pwszBinding, NULL, NULL, &pwszMachineName, NULL, NULL);
+    if (dwErrorCode != RPC_S_OK)
     {
     {
-        ERR("RpcStringBindingParseW failed with status %lu!\n", Status);
+        ERR("RpcStringBindingParseW failed with status %lu!\n", dwErrorCode);
         goto Cleanup;
     }
 
         goto Cleanup;
     }
 
@@ -291,14 +325,16 @@ LocalAddJob(HANDLE hPrinter, DWORD Level, LPBYTE pData, DWORD cbBuf, LPDWORD pcb
     // Add the job to the Global Job List.
     if (!InsertElementSkiplist(&GlobalJobList, pJob))
     {
     // Add the job to the Global Job List.
     if (!InsertElementSkiplist(&GlobalJobList, pJob))
     {
+        dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
         ERR("InsertElementSkiplist failed for job %lu for the GlobalJobList!\n", pJob->dwJobID);
         goto Cleanup;
     }
 
     // Add the job at the end of the Printer's Job List.
     // As all new jobs are created with default priority, we can be sure that it would always be inserted at the end.
         ERR("InsertElementSkiplist failed for job %lu for the GlobalJobList!\n", pJob->dwJobID);
         goto Cleanup;
     }
 
     // Add the job at the end of the Printer's Job List.
     // As all new jobs are created with default priority, we can be sure that it would always be inserted at the end.
-    if (!InsertTailElementSkiplist(&pJob->Printer->JobList, pJob))
+    if (!InsertTailElementSkiplist(&pJob->pPrinter->JobList, pJob))
     {
     {
+        dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
         ERR("InsertTailElementSkiplist failed for job %lu for the Printer's Job List!\n", pJob->dwJobID);
         goto Cleanup;
     }
         ERR("InsertTailElementSkiplist failed for job %lu for the Printer's Job List!\n", pJob->dwJobID);
         goto Cleanup;
     }
@@ -315,7 +351,7 @@ LocalAddJob(HANDLE hPrinter, DWORD Level, LPBYTE pData, DWORD cbBuf, LPDWORD pcb
     p += cchPrintersPath;
     swprintf((PWSTR)p, L"%05lu.SPL", pJob->dwJobID);
 
     p += cchPrintersPath;
     swprintf((PWSTR)p, L"%05lu.SPL", pJob->dwJobID);
 
-    bReturnValue = TRUE;
+    dwErrorCode = ERROR_SUCCESS;
 
 Cleanup:
     if (pwszMachineName)
 
 Cleanup:
     if (pwszMachineName)
@@ -327,127 +363,165 @@ Cleanup:
     if (hServerBinding)
         RpcBindingFree(&hServerBinding);
 
     if (hServerBinding)
         RpcBindingFree(&hServerBinding);
 
-    return bReturnValue;
+    SetLastError(dwErrorCode);
+    return (dwErrorCode == ERROR_SUCCESS);
 }
 
 
 }
 
 
-static BOOL
-_LocalGetJobLevel1(PLOCAL_PRINTER_HANDLE pPrinterHandle, PLOCAL_JOB pJob, PBYTE pOutput, DWORD cbBuf, PDWORD pcbNeeded)
+static DWORD
+_LocalGetJobLevel1(PLOCAL_PRINTER_HANDLE pPrinterHandle, PLOCAL_JOB pJob, PBYTE* ppStart, PBYTE* ppEnd, DWORD cbBuf, PDWORD pcbNeeded)
 {
     DWORD cbDatatype = (wcslen(pJob->pwszDatatype) + 1) * sizeof(WCHAR);
     DWORD cbDocumentName = (wcslen(pJob->pwszDocumentName) + 1) * sizeof(WCHAR);
     DWORD cbMachineName = (wcslen(pJob->pwszMachineName) + 1) * sizeof(WCHAR);
 {
     DWORD cbDatatype = (wcslen(pJob->pwszDatatype) + 1) * sizeof(WCHAR);
     DWORD cbDocumentName = (wcslen(pJob->pwszDocumentName) + 1) * sizeof(WCHAR);
     DWORD cbMachineName = (wcslen(pJob->pwszMachineName) + 1) * sizeof(WCHAR);
-    DWORD cbPrinterName = (wcslen(pJob->Printer->pwszPrinterName) + 1) * sizeof(WCHAR);
+    DWORD cbPrinterName = (wcslen(pJob->pPrinter->pwszPrinterName) + 1) * sizeof(WCHAR);
+    DWORD cbStatus = 0;
     DWORD cbUserName = (wcslen(pJob->pwszUserName) + 1) * sizeof(WCHAR);
     DWORD cbUserName = (wcslen(pJob->pwszUserName) + 1) * sizeof(WCHAR);
+    DWORD dwErrorCode;
     JOB_INFO_1W JobInfo1 = { 0 };
     JOB_INFO_1W JobInfo1 = { 0 };
-    PBYTE pString;
+
+    // A Status Message is optional.
+    if (pJob->pwszStatus)
+        cbStatus = (wcslen(pJob->pwszStatus) + 1) * sizeof(WCHAR);
 
     // Check if the supplied buffer is large enough.
 
     // Check if the supplied buffer is large enough.
-    *pcbNeeded = sizeof(JOB_INFO_1W) + cbDatatype + cbDocumentName + cbMachineName + cbPrinterName + cbUserName;
+    *pcbNeeded += sizeof(JOB_INFO_1W) + cbDatatype + cbDocumentName + cbMachineName + cbPrinterName + cbStatus + cbUserName;
     if (cbBuf < *pcbNeeded)
     {
     if (cbBuf < *pcbNeeded)
     {
-        SetLastError(ERROR_INSUFFICIENT_BUFFER);
-        return FALSE;
+        dwErrorCode = ERROR_INSUFFICIENT_BUFFER;
+        goto Cleanup;
     }
 
     }
 
-    // Put the strings right after the JOB_INFO_1W structure.
-    pString = pOutput + sizeof(JOB_INFO_1W);
+    // Put the strings at the end of the buffer.
+    *ppEnd -= cbDatatype;
+    JobInfo1.pDatatype = (PWSTR)*ppEnd;
+    CopyMemory(*ppEnd, pJob->pwszDatatype, cbDatatype);
 
 
-    JobInfo1.pDatatype = (PWSTR)pString;
-    CopyMemory(pString, pJob->pwszDatatype, cbDatatype);
-    pString += cbDatatype;
+    *ppEnd -= cbDocumentName;
+    JobInfo1.pDocument = (PWSTR)*ppEnd;
+    CopyMemory(*ppEnd, pJob->pwszDocumentName, cbDocumentName);
 
 
-    JobInfo1.pDocument = (PWSTR)pString;
-    CopyMemory(pString, pJob->pwszDocumentName, cbDocumentName);
-    pString += cbDocumentName;
+    *ppEnd -= cbMachineName;
+    JobInfo1.pMachineName = (PWSTR)*ppEnd;
+    CopyMemory(*ppEnd, pJob->pwszMachineName, cbMachineName);
 
 
-    JobInfo1.pMachineName = (PWSTR)pString;
-    CopyMemory(pString, pJob->pwszMachineName, cbMachineName);
-    pString += cbMachineName;
+    *ppEnd -= cbPrinterName;
+    JobInfo1.pPrinterName = (PWSTR)*ppEnd;
+    CopyMemory(*ppEnd, pJob->pPrinter->pwszPrinterName, cbPrinterName);
 
 
-    JobInfo1.pPrinterName = (PWSTR)pString;
-    CopyMemory(pString, pJob->Printer->pwszPrinterName, cbPrinterName);
-    pString += cbPrinterName;
+    if (cbStatus)
+    {
+        *ppEnd -= cbStatus;
+        JobInfo1.pStatus = (PWSTR)*ppEnd;
+        CopyMemory(*ppEnd, pJob->pwszStatus, cbStatus);
+    }
 
 
-    JobInfo1.pUserName = (PWSTR)pString;
-    CopyMemory(pString, pJob->pwszUserName, cbUserName);
-    pString += cbUserName;
+    *ppEnd -= cbUserName;
+    JobInfo1.pUserName = (PWSTR)*ppEnd;
+    CopyMemory(*ppEnd, pJob->pwszUserName, cbUserName);
 
 
-    // Fill the structure and copy it as well.
+    // Fill the rest of the structure.
     JobInfo1.JobId = pJob->dwJobID;
     JobInfo1.Priority = pJob->dwPriority;
     JobInfo1.Status = pJob->dwStatus;
     JobInfo1.TotalPages = pJob->dwTotalPages;
     CopyMemory(&JobInfo1.Submitted, &pJob->stSubmitted, sizeof(SYSTEMTIME));
     JobInfo1.JobId = pJob->dwJobID;
     JobInfo1.Priority = pJob->dwPriority;
     JobInfo1.Status = pJob->dwStatus;
     JobInfo1.TotalPages = pJob->dwTotalPages;
     CopyMemory(&JobInfo1.Submitted, &pJob->stSubmitted, sizeof(SYSTEMTIME));
-    CopyMemory(pOutput, &JobInfo1, sizeof(JOB_INFO_1W));
 
 
-    return TRUE;
+    // Finally copy the structure to the output pointer.
+    CopyMemory(*ppStart, &JobInfo1, sizeof(JOB_INFO_1W));
+    *ppStart += sizeof(JOB_INFO_1W);
+    dwErrorCode = ERROR_SUCCESS;
+
+Cleanup:
+    return dwErrorCode;
 }
 
 }
 
-static BOOL
-_LocalGetJobLevel2(PLOCAL_PRINTER_HANDLE pPrinterHandle, PLOCAL_JOB pJob, PBYTE pOutput, DWORD cbBuf, PDWORD pcbNeeded)
+static DWORD
+_LocalGetJobLevel2(PLOCAL_PRINTER_HANDLE pPrinterHandle, PLOCAL_JOB pJob, PBYTE* ppStart, PBYTE* ppEnd, DWORD cbBuf, PDWORD pcbNeeded)
 {
     DWORD cbDatatype = (wcslen(pJob->pwszDatatype) + 1) * sizeof(WCHAR);
 {
     DWORD cbDatatype = (wcslen(pJob->pwszDatatype) + 1) * sizeof(WCHAR);
+    DWORD cbDevMode = pJob->pDevMode->dmSize + pJob->pDevMode->dmDriverExtra;
     DWORD cbDocumentName = (wcslen(pJob->pwszDocumentName) + 1) * sizeof(WCHAR);
     DWORD cbDocumentName = (wcslen(pJob->pwszDocumentName) + 1) * sizeof(WCHAR);
-    DWORD cbDriverName = (wcslen(pJob->Printer->pwszPrinterDriver) + 1) * sizeof(WCHAR);
+    DWORD cbDriverName = (wcslen(pJob->pPrinter->pwszPrinterDriver) + 1) * sizeof(WCHAR);
     DWORD cbMachineName = (wcslen(pJob->pwszMachineName) + 1) * sizeof(WCHAR);
     DWORD cbNotifyName = (wcslen(pJob->pwszNotifyName) + 1) * sizeof(WCHAR);
     DWORD cbMachineName = (wcslen(pJob->pwszMachineName) + 1) * sizeof(WCHAR);
     DWORD cbNotifyName = (wcslen(pJob->pwszNotifyName) + 1) * sizeof(WCHAR);
-    DWORD cbPrinterName = (wcslen(pJob->Printer->pwszPrinterName) + 1) * sizeof(WCHAR);
-    DWORD cbPrintProcessor = (wcslen(pJob->Printer->pPrintProcessor->pwszName) + 1) * sizeof(WCHAR);
+    DWORD cbPrinterName = (wcslen(pJob->pPrinter->pwszPrinterName) + 1) * sizeof(WCHAR);
+    DWORD cbPrintProcessor = (wcslen(pJob->pPrintProcessor->pwszName) + 1) * sizeof(WCHAR);
+    DWORD cbPrintProcessorParameters = 0;
+    DWORD cbStatus = 0;
     DWORD cbUserName = (wcslen(pJob->pwszUserName) + 1) * sizeof(WCHAR);
     DWORD cbUserName = (wcslen(pJob->pwszUserName) + 1) * sizeof(WCHAR);
+    DWORD dwErrorCode;
     FILETIME ftNow;
     FILETIME ftSubmitted;
     JOB_INFO_2W JobInfo2 = { 0 };
     FILETIME ftNow;
     FILETIME ftSubmitted;
     JOB_INFO_2W JobInfo2 = { 0 };
-    PBYTE pString;
     ULARGE_INTEGER uliNow;
     ULARGE_INTEGER uliSubmitted;
 
     ULARGE_INTEGER uliNow;
     ULARGE_INTEGER uliSubmitted;
 
+    // Print Processor Parameters and Status Message are optional.
+    if (pJob->pwszPrintProcessorParameters)
+        cbPrintProcessorParameters = (wcslen(pJob->pwszPrintProcessorParameters) + 1) * sizeof(WCHAR);
+
+    if (pJob->pwszStatus)
+        cbStatus = (wcslen(pJob->pwszStatus) + 1) * sizeof(WCHAR);
+
     // Check if the supplied buffer is large enough.
     // Check if the supplied buffer is large enough.
-    *pcbNeeded = sizeof(JOB_INFO_2W) + cbDatatype + sizeof(DEVMODEW) + cbDocumentName + cbDriverName + cbMachineName + cbNotifyName + cbPrinterName + cbPrintProcessor + cbUserName;
+    *pcbNeeded += sizeof(JOB_INFO_2W) + cbDatatype + cbDevMode + cbDocumentName + cbDriverName + cbMachineName + cbNotifyName + cbPrinterName + cbPrintProcessor + cbPrintProcessorParameters + cbStatus + cbUserName;
     if (cbBuf < *pcbNeeded)
     {
     if (cbBuf < *pcbNeeded)
     {
-        SetLastError(ERROR_INSUFFICIENT_BUFFER);
-        return FALSE;
+        dwErrorCode = ERROR_INSUFFICIENT_BUFFER;
+        goto Cleanup;
     }
 
     }
 
-    // Put the strings right after the JOB_INFO_2W structure.
-    pString = pOutput + sizeof(JOB_INFO_2W);
+    // Put the strings at the end of the buffer.
+    *ppEnd -= cbDatatype;
+    JobInfo2.pDatatype = (PWSTR)*ppEnd;
+    CopyMemory(*ppEnd, pJob->pwszDatatype, cbDatatype);
 
 
-    JobInfo2.pDatatype = (PWSTR)pString;
-    CopyMemory(pString, pJob->pwszDatatype, cbDatatype);
-    pString += cbDatatype;
+    *ppEnd -= cbDevMode;
+    JobInfo2.pDevMode = (PDEVMODEW)*ppEnd;
+    CopyMemory(*ppEnd, pJob->pDevMode, cbDevMode);
 
 
-    JobInfo2.pDevMode = (PDEVMODEW)pString;
-    CopyMemory(pString, &pJob->DevMode, sizeof(DEVMODEW));
-    pString += sizeof(DEVMODEW);
+    *ppEnd -= cbDocumentName;
+    JobInfo2.pDocument = (PWSTR)*ppEnd;
+    CopyMemory(*ppEnd, pJob->pwszDocumentName, cbDocumentName);
 
 
-    JobInfo2.pDocument = (PWSTR)pString;
-    CopyMemory(pString, pJob->pwszDocumentName, cbDocumentName);
-    pString += cbDocumentName;
+    *ppEnd -= cbDriverName;
+    JobInfo2.pDriverName = (PWSTR)*ppEnd;
+    CopyMemory(*ppEnd, pJob->pPrinter->pwszPrinterDriver, cbDriverName);
 
 
-    JobInfo2.pDriverName = (PWSTR)pString;
-    CopyMemory(pString, pJob->Printer->pwszPrinterDriver, cbDriverName);
-    pString += cbDriverName;
+    *ppEnd -= cbMachineName;
+    JobInfo2.pMachineName = (PWSTR)*ppEnd;
+    CopyMemory(*ppEnd, pJob->pwszMachineName, cbMachineName);
 
 
-    JobInfo2.pMachineName = (PWSTR)pString;
-    CopyMemory(pString, pJob->pwszMachineName, cbMachineName);
-    pString += cbMachineName;
+    *ppEnd -= cbNotifyName;
+    JobInfo2.pNotifyName = (PWSTR)*ppEnd;
+    CopyMemory(*ppEnd, pJob->pwszNotifyName, cbNotifyName);
 
 
-    JobInfo2.pNotifyName = (PWSTR)pString;
-    CopyMemory(pString, pJob->pwszNotifyName, cbNotifyName);
-    pString += cbNotifyName;
+    *ppEnd -= cbPrinterName;
+    JobInfo2.pPrinterName = (PWSTR)*ppEnd;
+    CopyMemory(*ppEnd, pJob->pPrinter->pwszPrinterName, cbPrinterName);
 
 
-    JobInfo2.pPrinterName = (PWSTR)pString;
-    CopyMemory(pString, pJob->Printer->pwszPrinterName, cbPrinterName);
-    pString += cbPrinterName;
+    *ppEnd -= cbPrintProcessor;
+    JobInfo2.pPrintProcessor = (PWSTR)*ppEnd;
+    CopyMemory(*ppEnd, pJob->pPrintProcessor->pwszName, cbPrintProcessor);
 
 
-    JobInfo2.pPrintProcessor = (PWSTR)pString;
-    CopyMemory(pString, pJob->Printer->pPrintProcessor->pwszName, cbPrintProcessor);
-    pString += cbPrintProcessor;
+    if (cbPrintProcessorParameters)
+    {
+        *ppEnd -= cbPrintProcessorParameters;
+        JobInfo2.pParameters = (PWSTR)*ppEnd;
+        CopyMemory(*ppEnd, pJob->pwszPrintProcessorParameters, cbPrintProcessorParameters);
+    }
+
+    if (cbStatus)
+    {
+        *ppEnd -= cbStatus;
+        JobInfo2.pStatus = (PWSTR)*ppEnd;
+        CopyMemory(*ppEnd, pJob->pwszStatus, cbStatus);
+    }
 
 
-    JobInfo2.pUserName = (PWSTR)pString;
-    CopyMemory(pString, pJob->pwszUserName, cbUserName);
-    pString += cbUserName;
+    *ppEnd -= cbUserName;
+    JobInfo2.pUserName = (PWSTR)*ppEnd;
+    CopyMemory(*ppEnd, pJob->pwszUserName, cbUserName);
 
     // Time in JOB_INFO_2W is the number of milliseconds elapsed since the job was submitted. Calculate this time.
     if (!SystemTimeToFileTime(&pJob->stSubmitted, &ftSubmitted))
 
     // Time in JOB_INFO_2W is the number of milliseconds elapsed since the job was submitted. Calculate this time.
     if (!SystemTimeToFileTime(&pJob->stSubmitted, &ftSubmitted))
@@ -465,10 +539,11 @@ _LocalGetJobLevel2(PLOCAL_PRINTER_HANDLE pPrinterHandle, PLOCAL_JOB pJob, PBYTE
 
     // Position in JOB_INFO_2W is the 1-based index of the job in the processing queue.
     // Retrieve this through the element index of the job in the Printer's Job List.
 
     // Position in JOB_INFO_2W is the 1-based index of the job in the processing queue.
     // Retrieve this through the element index of the job in the Printer's Job List.
-    if (!LookupElementSkiplist(&pJob->Printer->JobList, pJob, &JobInfo2.Position))
+    if (!LookupElementSkiplist(&pJob->pPrinter->JobList, pJob, &JobInfo2.Position))
     {
     {
+        dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
         ERR("pJob could not be located in the Printer's Job List!\n");
         ERR("pJob could not be located in the Printer's Job List!\n");
-        return FALSE;
+        goto Cleanup;
     }
 
     // Make the index 1-based.
     }
 
     // Make the index 1-based.
@@ -485,39 +560,503 @@ _LocalGetJobLevel2(PLOCAL_PRINTER_HANDLE pPrinterHandle, PLOCAL_JOB pJob, PBYTE
     CopyMemory(&JobInfo2.Submitted, &pJob->stSubmitted, sizeof(SYSTEMTIME));
 
     // Finally copy the structure to the output pointer.
     CopyMemory(&JobInfo2.Submitted, &pJob->stSubmitted, sizeof(SYSTEMTIME));
 
     // Finally copy the structure to the output pointer.
-    CopyMemory(pOutput, &JobInfo2, sizeof(JOB_INFO_2W));
-    return TRUE;
+    CopyMemory(*ppStart, &JobInfo2, sizeof(JOB_INFO_2W));
+    *ppStart += sizeof(JOB_INFO_2W);
+    dwErrorCode = ERROR_SUCCESS;
+
+Cleanup:
+    return dwErrorCode;
 }
 
 BOOL WINAPI
 }
 
 BOOL WINAPI
-LocalGetJob(HANDLE hPrinter, DWORD JobId, DWORD Level, LPBYTE pOutput, DWORD cbBuf, LPDWORD pcbNeeded)
+LocalGetJob(HANDLE hPrinter, DWORD JobId, DWORD Level, PBYTE pStart, DWORD cbBuf, LPDWORD pcbNeeded)
 {
 {
+    DWORD dwErrorCode;
+    PBYTE pEnd = &pStart[cbBuf];
     PLOCAL_HANDLE pHandle;
     PLOCAL_JOB pJob;
     PLOCAL_PRINTER_HANDLE pPrinterHandle;
 
     // Check if this is a printer handle.
     pHandle = (PLOCAL_HANDLE)hPrinter;
     PLOCAL_HANDLE pHandle;
     PLOCAL_JOB pJob;
     PLOCAL_PRINTER_HANDLE pPrinterHandle;
 
     // Check if this is a printer handle.
     pHandle = (PLOCAL_HANDLE)hPrinter;
-    if (pHandle->HandleType != Printer)
-        return FALSE;
+    if (pHandle->HandleType != HandleType_Printer)
+    {
+        dwErrorCode = ERROR_INVALID_HANDLE;
+        goto Cleanup;
+    }
 
 
-    pPrinterHandle = (PLOCAL_PRINTER_HANDLE)pHandle->SpecificHandle;
+    pPrinterHandle = (PLOCAL_PRINTER_HANDLE)pHandle->pSpecificHandle;
 
     // Get the desired job.
     pJob = LookupElementSkiplist(&GlobalJobList, &JobId, NULL);
 
     // Get the desired job.
     pJob = LookupElementSkiplist(&GlobalJobList, &JobId, NULL);
-    if (!pJob || pJob->Printer != pPrinterHandle->Printer)
+    if (!pJob || pJob->pPrinter != pPrinterHandle->pPrinter)
     {
     {
-        SetLastError(ERROR_INVALID_PARAMETER);
-        return FALSE;
+        dwErrorCode = ERROR_INVALID_PARAMETER;
+        goto Cleanup;
     }
 
     }
 
+    // Begin counting.
+    *pcbNeeded = 0;
+
     // The function behaves differently for each level.
     if (Level == 1)
     // The function behaves differently for each level.
     if (Level == 1)
-        return _LocalGetJobLevel1(pPrinterHandle, pJob, pOutput, cbBuf, pcbNeeded);
+        dwErrorCode = _LocalGetJobLevel1(pPrinterHandle, pJob, &pStart, &pEnd, cbBuf, pcbNeeded);
     else if (Level == 2)
     else if (Level == 2)
-        return _LocalGetJobLevel2(pPrinterHandle, pJob, pOutput, cbBuf, pcbNeeded);
+        dwErrorCode = _LocalGetJobLevel2(pPrinterHandle, pJob, &pStart, &pEnd, cbBuf, pcbNeeded);
+    else
+        dwErrorCode = ERROR_INVALID_LEVEL;
+
+Cleanup:
+    SetLastError(dwErrorCode);
+    return (dwErrorCode == ERROR_SUCCESS);
+}
+
+static DWORD
+_LocalSetJobLevel1(PLOCAL_PRINTER_HANDLE pPrinterHandle, PLOCAL_JOB pJob, PJOB_INFO_1W pJobInfo)
+{
+    DWORD dwErrorCode;
+
+    // First check the validity of the input before changing anything.
+    if (!FindDatatype(pJob->pPrintProcessor, pJobInfo->pDatatype))
+    {
+        dwErrorCode = ERROR_INVALID_DATATYPE;
+        goto Cleanup;
+    }
+
+    // Check if the datatype has changed.
+    if (wcscmp(pJob->pwszDatatype, pJobInfo->pDatatype) != 0)
+    {
+        // Use the new value.
+        if (!ReallocSplStr(&pJob->pwszDatatype, pJobInfo->pDatatype))
+        {
+            dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
+            ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
+            goto Cleanup;
+        }
+    }
+
+    // Check if the document name has changed.
+    if (wcscmp(pJob->pwszDocumentName, pJobInfo->pDocument) != 0)
+    {
+        // Use the new value.
+        if (!ReallocSplStr(&pJob->pwszDocumentName, pJobInfo->pDocument))
+        {
+            dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
+            ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
+            goto Cleanup;
+        }
+    }
+
+    // Check if the status message has changed.
+    if ((!pJob->pwszStatus && pJobInfo->pStatus) || wcscmp(pJob->pwszStatus, pJobInfo->pStatus) != 0)
+    {
+        // Use the new value.
+        if (!ReallocSplStr(&pJob->pwszStatus, pJobInfo->pStatus))
+        {
+            dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
+            ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
+            goto Cleanup;
+        }
+    }
+
+    // Check if the user name has changed.
+    if (wcscmp(pJob->pwszUserName, pJobInfo->pUserName) != 0)
+    {
+        // The new user name doesn't need to exist, so no additional verification is required.
+
+        // Use the new value.
+        if (!ReallocSplStr(&pJob->pwszUserName, pJobInfo->pUserName))
+        {
+            dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
+            ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
+            goto Cleanup;
+        }
+    }
+
+    // Check if the priority has changed.
+    if (pJob->dwPriority != pJobInfo->Priority && IS_VALID_PRIORITY(pJobInfo->Priority))
+    {
+        // Set the new priority.
+        pJob->dwPriority = pJobInfo->Priority;
+
+        // Remove and reinsert the job in the Printer's Job List.
+        // The Compare function will be used to find the right position now considering the new priority.
+        DeleteElementSkiplist(&pJob->pPrinter->JobList, pJob);
+        InsertElementSkiplist(&pJob->pPrinter->JobList, pJob);
+    }
+
+    // Check if the status flags have changed.
+    if (pJob->dwStatus != pJobInfo->Status)
+    {
+        // Only add status flags that make sense.
+        if (pJobInfo->Status & JOB_STATUS_PAUSED)
+            pJob->dwStatus |= JOB_STATUS_PAUSED;
+
+        if (pJobInfo->Status & JOB_STATUS_ERROR)
+            pJob->dwStatus |= JOB_STATUS_ERROR;
+
+        if (pJobInfo->Status & JOB_STATUS_OFFLINE)
+            pJob->dwStatus |= JOB_STATUS_OFFLINE;
+
+        if (pJobInfo->Status & JOB_STATUS_PAPEROUT)
+            pJob->dwStatus |= JOB_STATUS_PAPEROUT;
+    }
+
+    dwErrorCode = ERROR_SUCCESS;
+
+Cleanup:
+    return dwErrorCode;
+}
+
+static DWORD
+_LocalSetJobLevel2(PLOCAL_PRINTER_HANDLE pPrinterHandle, PLOCAL_JOB pJob, PJOB_INFO_2W pJobInfo)
+{
+    DWORD dwErrorCode;
+    PLOCAL_PRINT_PROCESSOR pPrintProcessor;
+
+    // First check the validity of the input before changing anything.
+    pPrintProcessor = FindPrintProcessor(pJobInfo->pPrintProcessor);
+    if (!pPrintProcessor)
+    {
+        dwErrorCode = ERROR_UNKNOWN_PRINTPROCESSOR;
+        goto Cleanup;
+    }
+
+    if (!FindDatatype(pPrintProcessor, pJobInfo->pDatatype))
+    {
+        dwErrorCode = ERROR_INVALID_DATATYPE;
+        goto Cleanup;
+    }
+
+    // Check if the datatype has changed.
+    if (wcscmp(pJob->pwszDatatype, pJobInfo->pDatatype) != 0)
+    {
+        // Use the new value.
+        if (!ReallocSplStr(&pJob->pwszDatatype, pJobInfo->pDatatype))
+        {
+            dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
+            ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
+            goto Cleanup;
+        }
+    }
+
+    // Check if the document name has changed.
+    if (wcscmp(pJob->pwszDocumentName, pJobInfo->pDocument) != 0)
+    {
+        // Use the new value.
+        if (!ReallocSplStr(&pJob->pwszDocumentName, pJobInfo->pDocument))
+        {
+            dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
+            ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
+            goto Cleanup;
+        }
+    }
+
+    // Check if the notify name has changed.
+    if (wcscmp(pJob->pwszNotifyName, pJobInfo->pNotifyName) != 0)
+    {
+        // The new notify name doesn't need to exist, so no additional verification is required.
+
+        // Use the new value.
+        if (!ReallocSplStr(&pJob->pwszNotifyName, pJobInfo->pNotifyName))
+        {
+            dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
+            ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
+            goto Cleanup;
+        }
+    }
+
+    // Check if the (optional) Print Processor Parameters have changed.
+    if ((!pJob->pwszPrintProcessorParameters && pJobInfo->pParameters) || wcscmp(pJob->pwszPrintProcessorParameters, pJobInfo->pParameters) != 0)
+    {
+        // Use the new value.
+        if (!ReallocSplStr(&pJob->pwszPrintProcessorParameters, pJobInfo->pParameters))
+        {
+            dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
+            ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
+            goto Cleanup;
+        }
+    }
+
+    // Check if the (optional) Status Message has changed.
+    if ((!pJob->pwszStatus && pJobInfo->pStatus) || wcscmp(pJob->pwszStatus, pJobInfo->pStatus) != 0)
+    {
+        // Use the new value.
+        if (!ReallocSplStr(&pJob->pwszStatus, pJobInfo->pStatus))
+        {
+            dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
+            ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
+            goto Cleanup;
+        }
+    }
+
+    // Check if the user name has changed.
+    if (wcscmp(pJob->pwszUserName, pJobInfo->pUserName) != 0)
+    {
+        // The new user name doesn't need to exist, so no additional verification is required.
+
+        // Use the new value.
+        if (!ReallocSplStr(&pJob->pwszUserName, pJobInfo->pUserName))
+        {
+            dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
+            ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
+            goto Cleanup;
+        }
+    }
+
+    // Check if the priority has changed.
+    if (pJob->dwPriority != pJobInfo->Priority && IS_VALID_PRIORITY(pJobInfo->Priority))
+    {
+        // Set the new priority.
+        pJob->dwPriority = pJobInfo->Priority;
+
+        // Remove and reinsert the job in the Printer's Job List.
+        // The Compare function will be used to find the right position now considering the new priority.
+        DeleteElementSkiplist(&pJob->pPrinter->JobList, pJob);
+        InsertElementSkiplist(&pJob->pPrinter->JobList, pJob);
+    }
+
+    // Check if the status flags have changed.
+    if (pJob->dwStatus != pJobInfo->Status)
+    {
+        // Only add status flags that make sense.
+        if (pJobInfo->Status & JOB_STATUS_PAUSED)
+            pJob->dwStatus |= JOB_STATUS_PAUSED;
+
+        if (pJobInfo->Status & JOB_STATUS_ERROR)
+            pJob->dwStatus |= JOB_STATUS_ERROR;
+
+        if (pJobInfo->Status & JOB_STATUS_OFFLINE)
+            pJob->dwStatus |= JOB_STATUS_OFFLINE;
+
+        if (pJobInfo->Status & JOB_STATUS_PAPEROUT)
+            pJob->dwStatus |= JOB_STATUS_PAPEROUT;
+    }
+
+    dwErrorCode = ERROR_SUCCESS;
+
+Cleanup:
+    return dwErrorCode;
+}
+
+BOOL WINAPI
+LocalSetJob(HANDLE hPrinter, DWORD JobId, DWORD Level, PBYTE pJobInfo, DWORD Command)
+{
+    const WCHAR wszFolder[] = L"\\PRINTERS\\";
+    const DWORD cchFolder = _countof(wszFolder) - 1;
+
+    DWORD dwErrorCode;
+    PLOCAL_HANDLE pHandle;
+    PLOCAL_JOB pJob;
+    PLOCAL_PRINTER_HANDLE pPrinterHandle;
+    WCHAR wszFullPath[MAX_PATH];
+
+    // Check if this is a printer handle.
+    pHandle = (PLOCAL_HANDLE)hPrinter;
+    if (pHandle->HandleType != HandleType_Printer)
+    {
+        dwErrorCode = ERROR_INVALID_HANDLE;
+        goto Cleanup;
+    }
+
+    pPrinterHandle = (PLOCAL_PRINTER_HANDLE)pHandle->pSpecificHandle;
+
+    // Get the desired job.
+    pJob = LookupElementSkiplist(&GlobalJobList, &JobId, NULL);
+    if (!pJob || pJob->pPrinter != pPrinterHandle->pPrinter)
+    {
+        dwErrorCode = ERROR_INVALID_PARAMETER;
+        goto Cleanup;
+    }
+
+    // Setting job information is handled differently for each level.
+    if (Level)
+    {
+        if (Level == 1)
+            dwErrorCode = _LocalSetJobLevel1(pPrinterHandle, pJob, (PJOB_INFO_1W)pJobInfo);
+        else if (Level == 2)
+            dwErrorCode = _LocalSetJobLevel2(pPrinterHandle, pJob, (PJOB_INFO_2W)pJobInfo);
+        else
+            dwErrorCode = ERROR_INVALID_LEVEL;
+    }
+
+    if (dwErrorCode != ERROR_SUCCESS)
+        goto Cleanup;
+
+    // Construct the full path to the shadow file.
+    CopyMemory(wszFullPath, wszSpoolDirectory, cchSpoolDirectory * sizeof(WCHAR));
+    CopyMemory(&wszFullPath[cchSpoolDirectory], wszFolder, cchFolder * sizeof(WCHAR));
+    swprintf(&wszFullPath[cchSpoolDirectory + cchFolder], L"%05lu.SHD", JobId);
+
+    // Write the job data into the shadow file.
+    WriteJobShadowFile(wszFullPath, pJob);
+
+    // Perform an additional command if desired.
+    if (Command)
+    {
+        // TODO
+    }
+
+    dwErrorCode = ERROR_SUCCESS;
+
+Cleanup:
+    SetLastError(dwErrorCode);
+    return (dwErrorCode == ERROR_SUCCESS);
+}
+
+BOOL WINAPI
+LocalEnumJobs(HANDLE hPrinter, DWORD FirstJob, DWORD NoJobs, DWORD Level, PBYTE pStart, DWORD cbBuf, LPDWORD pcbNeeded, LPDWORD pcReturned)
+{
+    DWORD dwErrorCode;
+    DWORD i;
+    PBYTE pEnd = &pStart[cbBuf];
+    PLOCAL_HANDLE pHandle;
+    PLOCAL_JOB pJob;
+    PSKIPLIST_NODE pFirstJobNode;
+    PSKIPLIST_NODE pNode;
+    PLOCAL_PRINTER_HANDLE pPrinterHandle;
+
+    // Check if this is a printer handle.
+    pHandle = (PLOCAL_HANDLE)hPrinter;
+    if (pHandle->HandleType != HandleType_Printer)
+    {
+        dwErrorCode = ERROR_INVALID_HANDLE;
+        goto Cleanup;
+    }
+
+    pPrinterHandle = (PLOCAL_PRINTER_HANDLE)pHandle->pSpecificHandle;
+
+    // Check the level.
+    if (Level > 2)
+    {
+        dwErrorCode = ERROR_INVALID_LEVEL;
+        goto Cleanup;
+    }
+
+    // Begin counting.
+    *pcbNeeded = 0;
+    *pcReturned = 0;
+
+    // Lookup the node of the first job requested by the caller in the Printer's Job List.
+    pFirstJobNode = LookupNodeByIndexSkiplist(&pPrinterHandle->pPrinter->JobList, FirstJob);
+
+    // Count the required buffer size and the number of jobs.
+    i = 0;
+    pNode = pFirstJobNode;
+
+    while (i < NoJobs && pNode)
+    {
+        pJob = (PLOCAL_JOB)pNode->Element;
+
+        // The function behaves differently for each level.
+        if (Level == 1)
+            _LocalGetJobLevel1(pPrinterHandle, pJob, NULL, NULL, 0, pcbNeeded);
+        else if (Level == 2)
+            _LocalGetJobLevel2(pPrinterHandle, pJob, NULL, NULL, 0, pcbNeeded);
+
+        // We stop either when there are no more jobs in the list or when the caller didn't request more, whatever comes first.
+        i++;
+        pNode = pNode->Next[0];
+    }
+
+    // Check if the supplied buffer is large enough.
+    if (cbBuf < *pcbNeeded)
+    {
+        dwErrorCode = ERROR_INSUFFICIENT_BUFFER;
+        goto Cleanup;
+    }
 
 
-    return FALSE;
+    // Begin counting again and also empty the given buffer.
+    *pcbNeeded = 0;
+    ZeroMemory(pStart, cbBuf);
+
+    // Now call the same functions again to copy the actual data for each job into the buffer.
+    i = 0;
+    pNode = pFirstJobNode;
+
+    while (i < NoJobs && pNode)
+    {
+        pJob = (PLOCAL_JOB)pNode->Element;
+
+        // The function behaves differently for each level.
+        if (Level == 1)
+            dwErrorCode = _LocalGetJobLevel1(pPrinterHandle, pJob, &pStart, &pEnd, cbBuf, pcbNeeded);
+        else if (Level == 2)
+            dwErrorCode = _LocalGetJobLevel2(pPrinterHandle, pJob, &pStart, &pEnd, cbBuf, pcbNeeded);
+
+        if (dwErrorCode != ERROR_SUCCESS)
+            goto Cleanup;
+
+        // We stop either when there are no more jobs in the list or when the caller didn't request more, whatever comes first.
+        i++;
+        pNode = pNode->Next[0];
+    }
+
+    *pcReturned = i;
+    dwErrorCode = ERROR_SUCCESS;
+
+Cleanup:
+    SetLastError(dwErrorCode);
+    return (dwErrorCode == ERROR_SUCCESS);
+}
+
+BOOL WINAPI
+LocalScheduleJob(HANDLE hPrinter, DWORD dwJobID)
+{
+    const WCHAR wszFolder[] = L"\\PRINTERS\\";
+    const DWORD cchFolder = _countof(wszFolder) - 1;
+
+    DWORD dwAttributes;
+    DWORD dwErrorCode;
+    PLOCAL_HANDLE pHandle;
+    PLOCAL_JOB pJob;
+    PLOCAL_PRINTER_HANDLE pPrinterHandle;
+    WCHAR wszFullPath[MAX_PATH];
+
+    // Check if this is a printer handle.
+    pHandle = (PLOCAL_HANDLE)hPrinter;
+    if (pHandle->HandleType != HandleType_Printer)
+    {
+        dwErrorCode = ERROR_INVALID_HANDLE;
+        goto Cleanup;
+    }
+
+    pPrinterHandle = (PLOCAL_PRINTER_HANDLE)pHandle->pSpecificHandle;
+
+    // Check if the Job ID is valid.
+    pJob = LookupElementSkiplist(&GlobalJobList, &dwJobID, NULL);
+    if (!pJob || pJob->pPrinter != pPrinterHandle->pPrinter)
+    {
+        dwErrorCode = ERROR_INVALID_PARAMETER;
+        goto Cleanup;
+    }
+
+    // Construct the full path to the spool file.
+    CopyMemory(wszFullPath, wszSpoolDirectory, cchSpoolDirectory * sizeof(WCHAR));
+    CopyMemory(&wszFullPath[cchSpoolDirectory], wszFolder, cchFolder * sizeof(WCHAR));
+    swprintf(&wszFullPath[cchSpoolDirectory + cchFolder], L"%05lu.SPL", dwJobID);
+
+    // Check if it exists.
+    dwAttributes = GetFileAttributesW(wszFullPath);
+    if (dwAttributes == INVALID_FILE_ATTRIBUTES || dwAttributes & FILE_ATTRIBUTE_DIRECTORY)
+    {
+        dwErrorCode = ERROR_SPOOL_FILE_NOT_FOUND;
+        goto Cleanup;
+    }
+
+    // Switch from spooling to printing.
+    pJob->dwStatus &= ~JOB_STATUS_SPOOLING;
+    pJob->dwStatus |= JOB_STATUS_PRINTING;
+
+    // Write the job data into the shadow file.
+    wcscpy(wcsrchr(wszFullPath, L'.'), L".SHD");
+    WriteJobShadowFile(wszFullPath, pJob);
+
+    dwErrorCode = ERROR_SUCCESS;
+
+Cleanup:
+    SetLastError(dwErrorCode);
+    return (dwErrorCode == ERROR_SUCCESS);
 }
 
 PLOCAL_JOB
 }
 
 PLOCAL_JOB
@@ -529,11 +1068,13 @@ ReadJobShadowFile(PCWSTR pwszFilePath)
     PLOCAL_JOB pJob;
     PLOCAL_JOB pReturnValue = NULL;
     PLOCAL_PRINTER pPrinter;
     PLOCAL_JOB pJob;
     PLOCAL_JOB pReturnValue = NULL;
     PLOCAL_PRINTER pPrinter;
+    PLOCAL_PRINT_PROCESSOR pPrintProcessor;
     PSHD_HEADER pShadowFile = NULL;
     PWSTR pwszPrinterName;
     PSHD_HEADER pShadowFile = NULL;
     PWSTR pwszPrinterName;
+    PWSTR pwszPrintProcessor;
 
     // Try to open the file.
 
     // Try to open the file.
-    hFile = CreateFileW(pwszFilePath, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
+    hFile = CreateFileW(pwszFilePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
     if (hFile == INVALID_HANDLE_VALUE)
     {
         ERR("CreateFileW failed with error %lu for file \"%S\"!\n", GetLastError(), pwszFilePath);
     if (hFile == INVALID_HANDLE_VALUE)
     {
         ERR("CreateFileW failed with error %lu for file \"%S\"!\n", GetLastError(), pwszFilePath);
@@ -545,7 +1086,7 @@ ReadJobShadowFile(PCWSTR pwszFilePath)
     pShadowFile = DllAllocSplMem(cbFileSize);
     if (!pShadowFile)
     {
     pShadowFile = DllAllocSplMem(cbFileSize);
     if (!pShadowFile)
     {
-        ERR("DllAllocSplMem failed with error %lufor file \"%S\"!\n", GetLastError(), pwszFilePath);
+        ERR("DllAllocSplMem failed with error %lu for file \"%S\"!\n", GetLastError(), pwszFilePath);
         goto Cleanup;
     }
 
         goto Cleanup;
     }
 
@@ -568,7 +1109,16 @@ ReadJobShadowFile(PCWSTR pwszFilePath)
     pPrinter = LookupElementSkiplist(&PrinterList, &pwszPrinterName, NULL);
     if (!pPrinter)
     {
     pPrinter = LookupElementSkiplist(&PrinterList, &pwszPrinterName, NULL);
     if (!pPrinter)
     {
-        ERR("Shadow file \"%S\" references a non-existing printer!\n", pwszFilePath);
+        ERR("Shadow file \"%S\" references a non-existing printer \"%S\"!\n", pwszFilePath, pwszPrinterName);
+        goto Cleanup;
+    }
+
+    // Retrieve the associated Print Processor from the list.
+    pwszPrintProcessor = (PWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offPrintProcessor);
+    pPrintProcessor = FindPrintProcessor(pwszPrintProcessor);
+    if (!pPrintProcessor)
+    {
+        ERR("Shadow file \"%S\" references a non-existing Print Processor \"%S\"!\n", pwszFilePath, pwszPrintProcessor);
         goto Cleanup;
     }
 
         goto Cleanup;
     }
 
@@ -581,14 +1131,23 @@ ReadJobShadowFile(PCWSTR pwszFilePath)
     }
 
     pJob->dwJobID = pShadowFile->dwJobID;
     }
 
     pJob->dwJobID = pShadowFile->dwJobID;
-    pJob->dwTotalPages = pShadowFile->dwTotalPages;
     pJob->dwPriority = pShadowFile->dwPriority;
     pJob->dwPriority = pShadowFile->dwPriority;
-    pJob->Printer = pPrinter;
+    pJob->dwStartTime = pShadowFile->dwStartTime;
+    pJob->dwTotalPages = pShadowFile->dwTotalPages;
+    pJob->dwUntilTime = pShadowFile->dwUntilTime;    
+    pJob->pPrinter = pPrinter;
+    pJob->pPrintProcessor = pPrintProcessor;
+    pJob->pDevMode = DuplicateDevMode((PDEVMODEW)((ULONG_PTR)pShadowFile + pShadowFile->offDevMode));
     pJob->pwszDatatype = AllocSplStr((PCWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offDatatype));
     pJob->pwszDocumentName = AllocSplStr((PCWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offDocumentName));
     pJob->pwszDatatype = AllocSplStr((PCWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offDatatype));
     pJob->pwszDocumentName = AllocSplStr((PCWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offDocumentName));
-    pJob->pwszOutputFile = NULL;
+    pJob->pwszMachineName = AllocSplStr((PCWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offMachineName));
+    pJob->pwszNotifyName = AllocSplStr((PCWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offNotifyName));
+
+    if (pShadowFile->offPrintProcessorParameters)
+        pJob->pwszPrintProcessorParameters = AllocSplStr((PCWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offPrintProcessorParameters));
+
+    pJob->pwszUserName = AllocSplStr((PCWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offUserName));
     CopyMemory(&pJob->stSubmitted, &pShadowFile->stSubmitted, sizeof(SYSTEMTIME));
     CopyMemory(&pJob->stSubmitted, &pShadowFile->stSubmitted, sizeof(SYSTEMTIME));
-    CopyMemory(&pJob->DevMode, (PDEVMODEW)((ULONG_PTR)pShadowFile + pShadowFile->offDevMode), sizeof(DEVMODEW));
 
     pReturnValue = pJob;
 
 
     pReturnValue = pJob;
 
@@ -603,31 +1162,50 @@ Cleanup:
 }
 
 BOOL
 }
 
 BOOL
-WriteJobShadowFile(PCWSTR pwszFilePath, const PLOCAL_JOB pJob)
+WriteJobShadowFile(PWSTR pwszFilePath, const PLOCAL_JOB pJob)
 {
     BOOL bReturnValue = FALSE;
     DWORD cbDatatype;
 {
     BOOL bReturnValue = FALSE;
     DWORD cbDatatype;
+    DWORD cbDevMode;
     DWORD cbDocumentName;
     DWORD cbFileSize;
     DWORD cbDocumentName;
     DWORD cbFileSize;
+    DWORD cbMachineName;
+    DWORD cbNotifyName;
+    DWORD cbPrinterDriver;
     DWORD cbPrinterName;
     DWORD cbPrinterName;
+    DWORD cbPrintProcessor;
+    DWORD cbPrintProcessorParameters = 0;
+    DWORD cbUserName;
     DWORD cbWritten;
     DWORD dwCurrentOffset;
     DWORD cbWritten;
     DWORD dwCurrentOffset;
-    HANDLE hFile = INVALID_HANDLE_VALUE;
+    HANDLE hSHDFile = INVALID_HANDLE_VALUE;
+    HANDLE hSPLFile = INVALID_HANDLE_VALUE;
     PSHD_HEADER pShadowFile = NULL;
 
     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)
+    // Try to open the SHD file.
+    hSHDFile = CreateFileW(pwszFilePath, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, 0, NULL);
+    if (hSHDFile == INVALID_HANDLE_VALUE)
     {
         ERR("CreateFileW failed with error %lu for file \"%S\"!\n", GetLastError(), pwszFilePath);
         goto Cleanup;
     }
 
     // Compute the total size of the shadow file.
     {
         ERR("CreateFileW failed with error %lu for file \"%S\"!\n", GetLastError(), pwszFilePath);
         goto Cleanup;
     }
 
     // Compute the total size of the shadow file.
-    cbPrinterName = (wcslen(pJob->Printer->pwszPrinterName) + 1) * sizeof(WCHAR);
     cbDatatype = (wcslen(pJob->pwszDatatype) + 1) * sizeof(WCHAR);
     cbDatatype = (wcslen(pJob->pwszDatatype) + 1) * sizeof(WCHAR);
+    cbDevMode = pJob->pDevMode->dmSize + pJob->pDevMode->dmDriverExtra;
     cbDocumentName = (wcslen(pJob->pwszDocumentName) + 1) * sizeof(WCHAR);
     cbDocumentName = (wcslen(pJob->pwszDocumentName) + 1) * sizeof(WCHAR);
-    cbFileSize = sizeof(SHD_HEADER) + cbPrinterName + cbDatatype + cbDocumentName + sizeof(DEVMODEW);
+    cbMachineName = (wcslen(pJob->pwszMachineName) + 1) * sizeof(WCHAR);
+    cbNotifyName = (wcslen(pJob->pwszNotifyName) + 1) * sizeof(WCHAR);
+    cbPrinterDriver = (wcslen(pJob->pPrinter->pwszPrinterDriver) + 1) * sizeof(WCHAR);
+    cbPrinterName = (wcslen(pJob->pPrinter->pwszPrinterName) + 1) * sizeof(WCHAR);
+    cbPrintProcessor = (wcslen(pJob->pPrintProcessor->pwszName) + 1) * sizeof(WCHAR);
+    cbUserName = (wcslen(pJob->pwszUserName) + 1) * sizeof(WCHAR);
+
+    // Print Processor Parameters are optional.
+    if (pJob->pwszPrintProcessorParameters)
+        cbPrintProcessorParameters = (wcslen(pJob->pwszPrintProcessorParameters) + 1) * sizeof(WCHAR);
+
+    cbFileSize = sizeof(SHD_HEADER) + cbDatatype + cbDocumentName + cbDevMode + cbMachineName + cbNotifyName + cbPrinterDriver + cbPrinterName + cbPrintProcessor + cbPrintProcessorParameters + cbUserName;
 
     // Allocate memory for it.
     pShadowFile = DllAllocSplMem(cbFileSize);
 
     // Allocate memory for it.
     pShadowFile = DllAllocSplMem(cbFileSize);
@@ -643,18 +1221,22 @@ WriteJobShadowFile(PCWSTR pwszFilePath, const PLOCAL_JOB pJob)
 
     // Copy the values.
     pShadowFile->dwJobID = pJob->dwJobID;
 
     // Copy the values.
     pShadowFile->dwJobID = pJob->dwJobID;
-    pShadowFile->dwTotalPages = pJob->dwTotalPages;
     pShadowFile->dwPriority = pJob->dwPriority;
     pShadowFile->dwPriority = pJob->dwPriority;
+    pShadowFile->dwStartTime = pJob->dwStartTime;
+    pShadowFile->dwTotalPages = pJob->dwTotalPages;
+    pShadowFile->dwUntilTime = pJob->dwUntilTime;
     CopyMemory(&pShadowFile->stSubmitted, &pJob->stSubmitted, sizeof(SYSTEMTIME));
 
     CopyMemory(&pShadowFile->stSubmitted, &pJob->stSubmitted, sizeof(SYSTEMTIME));
 
+    // Determine the file size of the .SPL file
+    wcscpy(wcsrchr(pwszFilePath, L'.'), L".SPL");
+    hSPLFile = CreateFileW(pwszFilePath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
+    if (hSPLFile != INVALID_HANDLE_VALUE)
+        pShadowFile->dwSPLSize = GetFileSize(hSPLFile, NULL);
+
     // 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);
 
     // 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->Printer->pwszPrinterName, cbPrinterName);
-    pShadowFile->offPrinterName = dwCurrentOffset;
-    dwCurrentOffset += cbPrinterName;
-
     CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pwszDatatype, cbDatatype);
     pShadowFile->offDatatype = dwCurrentOffset;
     dwCurrentOffset += cbDatatype;
     CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pwszDatatype, cbDatatype);
     pShadowFile->offDatatype = dwCurrentOffset;
     dwCurrentOffset += cbDatatype;
@@ -663,12 +1245,44 @@ WriteJobShadowFile(PCWSTR pwszFilePath, const PLOCAL_JOB pJob)
     pShadowFile->offDocumentName = dwCurrentOffset;
     dwCurrentOffset += cbDocumentName;
 
     pShadowFile->offDocumentName = dwCurrentOffset;
     dwCurrentOffset += cbDocumentName;
 
-    CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, &pJob->DevMode, sizeof(DEVMODEW));
+    CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pDevMode, cbDevMode);
     pShadowFile->offDevMode = dwCurrentOffset;
     pShadowFile->offDevMode = dwCurrentOffset;
-    dwCurrentOffset += sizeof(DEVMODEW);
+    dwCurrentOffset += cbDevMode;
+
+    // offDriverName is only written, but automatically determined through offPrinterName when reading.
+    CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pPrinter->pwszPrinterDriver, cbPrinterDriver);
+    pShadowFile->offDriverName = dwCurrentOffset;
+    dwCurrentOffset += cbPrinterDriver;
+
+    CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pwszMachineName, cbMachineName);
+    pShadowFile->offMachineName = dwCurrentOffset;
+    dwCurrentOffset += cbMachineName;
+
+    CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pwszNotifyName, cbNotifyName);
+    pShadowFile->offNotifyName = dwCurrentOffset;
+    dwCurrentOffset += cbNotifyName;
+
+    CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pPrinter->pwszPrinterName, cbPrinterName);
+    pShadowFile->offPrinterName = dwCurrentOffset;
+    dwCurrentOffset += cbPrinterName;
+
+    CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pPrintProcessor->pwszName, cbPrintProcessor);
+    pShadowFile->offPrintProcessor = dwCurrentOffset;
+    dwCurrentOffset += cbPrintProcessor;
+
+    if (cbPrintProcessorParameters)
+    {
+        CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pwszPrintProcessorParameters, cbPrintProcessorParameters);
+        pShadowFile->offPrintProcessorParameters = dwCurrentOffset;
+        dwCurrentOffset += cbPrintProcessorParameters;
+    }
+
+    CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pwszUserName, cbUserName);
+    pShadowFile->offUserName = dwCurrentOffset;
+    dwCurrentOffset += cbUserName;
 
     // Write the file.
 
     // Write the file.
-    if (!WriteFile(hFile, pShadowFile, cbFileSize, &cbWritten, NULL))
+    if (!WriteFile(hSHDFile, pShadowFile, cbFileSize, &cbWritten, NULL))
     {
         ERR("WriteFile failed with error %lu for file \"%S\"!\n", GetLastError(), pwszFilePath);
         goto Cleanup;
     {
         ERR("WriteFile failed with error %lu for file \"%S\"!\n", GetLastError(), pwszFilePath);
         goto Cleanup;
@@ -680,8 +1294,11 @@ Cleanup:
     if (pShadowFile)
         DllFreeSplMem(pShadowFile);
 
     if (pShadowFile)
         DllFreeSplMem(pShadowFile);
 
-    if (hFile != INVALID_HANDLE_VALUE)
-        CloseHandle(hFile);
+    if (hSHDFile != INVALID_HANDLE_VALUE)
+        CloseHandle(hSHDFile);
+
+    if (hSPLFile != INVALID_HANDLE_VALUE)
+        CloseHandle(hSPLFile);
 
     return bReturnValue;
 }
 
     return bReturnValue;
 }