1 /*
2  * PROJECT:     ReactOS Local Spooler
3  * LICENSE:     GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4  * PURPOSE:     Functions for managing print jobs
5  * COPYRIGHT:   Copyright 2015-2017 Colin Finck (colin@reactos.org)
6  */
7 
8 #include "precomp.h"
9 
10 // Global Variables
11 SKIPLIST GlobalJobList;
12 
13 // Local Variables
14 static DWORD _dwLastJobID;
15 
16 // Local Constants
17 static DWORD dwJobInfo1Offsets[] = {
18     FIELD_OFFSET(JOB_INFO_1W, pPrinterName),
19     FIELD_OFFSET(JOB_INFO_1W, pMachineName),
20     FIELD_OFFSET(JOB_INFO_1W, pUserName),
21     FIELD_OFFSET(JOB_INFO_1W, pDocument),
22     FIELD_OFFSET(JOB_INFO_1W, pDatatype),
23     FIELD_OFFSET(JOB_INFO_1W, pStatus),
24     MAXDWORD
25 };
26 
27 static DWORD dwJobInfo2Offsets[] = {
28     FIELD_OFFSET(JOB_INFO_2W, pPrinterName),
29     FIELD_OFFSET(JOB_INFO_2W, pMachineName),
30     FIELD_OFFSET(JOB_INFO_2W, pUserName),
31     FIELD_OFFSET(JOB_INFO_2W, pDocument),
32     FIELD_OFFSET(JOB_INFO_2W, pNotifyName),
33     FIELD_OFFSET(JOB_INFO_2W, pDatatype),
34     FIELD_OFFSET(JOB_INFO_2W, pPrintProcessor),
35     FIELD_OFFSET(JOB_INFO_2W, pParameters),
36     FIELD_OFFSET(JOB_INFO_2W, pDriverName),
37     FIELD_OFFSET(JOB_INFO_2W, pStatus),
38     MAXDWORD
39 };
40 
41 
42 /**
43  * @name _EqualStrings
44  *
45  * Returns whether two strings are equal.
46  * Unlike wcscmp, this function also works with NULL strings.
47  *
48  * @param pwszA
49  * First string to compare.
50  *
51  * @param pwszB
52  * Second string to compare.
53  *
54  * @return
55  * TRUE if the strings are equal, FALSE if they differ.
56  */
57 static __inline BOOL
58 _EqualStrings(PCWSTR pwszA, PCWSTR pwszB)
59 {
60     if (!pwszA && !pwszB)
61         return TRUE;
62 
63     if (pwszA && !pwszB)
64         return FALSE;
65 
66     if (!pwszA && pwszB)
67         return FALSE;
68 
69     return (wcscmp(pwszA, pwszB) == 0);
70 }
71 
72 static BOOL
73 _GetNextJobID(PDWORD dwJobID)
74 {
75     ++_dwLastJobID;
76 
77     while (LookupElementSkiplist(&GlobalJobList, &_dwLastJobID, NULL))
78     {
79         // This ID is already taken. Try the next one.
80         ++_dwLastJobID;
81     }
82 
83     if (!IS_VALID_JOB_ID(_dwLastJobID))
84     {
85         ERR("Job ID %lu isn't valid!\n", _dwLastJobID);
86         return FALSE;
87     }
88 
89     *dwJobID = _dwLastJobID;
90     return TRUE;
91 }
92 
93 /**
94  * @name _GlobalJobListCompareRoutine
95  *
96  * SKIPLIST_COMPARE_ROUTINE for the Global Job List.
97  * We need the Global Job List to check whether a Job ID is already in use. Consequently, this list is sorted by ID.
98  */
99 static int WINAPI
100 _GlobalJobListCompareRoutine(PVOID FirstStruct, PVOID SecondStruct)
101 {
102     PLOCAL_JOB A = (PLOCAL_JOB)FirstStruct;
103     PLOCAL_JOB B = (PLOCAL_JOB)SecondStruct;
104 
105     return A->dwJobID - B->dwJobID;
106 }
107 
108 /**
109  * @name _PrinterJobListCompareRoutine
110  *
111  * SKIPLIST_COMPARE_ROUTINE for the each Printer's Job List.
112  * Jobs in this list are sorted in the desired order of processing.
113  */
114 static int WINAPI
115 _PrinterJobListCompareRoutine(PVOID FirstStruct, PVOID SecondStruct)
116 {
117     PLOCAL_JOB A = (PLOCAL_JOB)FirstStruct;
118     PLOCAL_JOB B = (PLOCAL_JOB)SecondStruct;
119     int iComparison;
120     FILETIME ftSubmittedA;
121     FILETIME ftSubmittedB;
122     ULARGE_INTEGER uliSubmittedA;
123     ULARGE_INTEGER uliSubmittedB;
124     ULONGLONG ullResult;
125 
126     // First compare the priorities to determine the order.
127     // The job with a higher priority shall come first.
128     iComparison = A->dwPriority - B->dwPriority;
129     if (iComparison != 0)
130         return iComparison;
131 
132     // Both have the same priority, so go by creation time.
133     // Comparison is done using the MSDN-recommended way for comparing SYSTEMTIMEs.
134     if (!SystemTimeToFileTime(&A->stSubmitted, &ftSubmittedA))
135     {
136         ERR("SystemTimeToFileTime failed for A with error %lu!\n", GetLastError());
137         return 0;
138     }
139 
140     if (!SystemTimeToFileTime(&B->stSubmitted, &ftSubmittedB))
141     {
142         ERR("SystemTimeToFileTime failed for B with error %lu!\n", GetLastError());
143         return 0;
144     }
145 
146     uliSubmittedA.LowPart = ftSubmittedA.dwLowDateTime;
147     uliSubmittedA.HighPart = ftSubmittedA.dwHighDateTime;
148     uliSubmittedB.LowPart = ftSubmittedB.dwLowDateTime;
149     uliSubmittedB.HighPart = ftSubmittedB.dwHighDateTime;
150     ullResult = uliSubmittedA.QuadPart - uliSubmittedB.QuadPart;
151 
152     if (ullResult < 0)
153         return -1;
154     else if (ullResult > 0)
155         return 1;
156 
157     return 0;
158 }
159 
160 DWORD
161 GetJobFilePath(PCWSTR pwszExtension, DWORD dwJobID, PWSTR pwszOutput)
162 {
163     TRACE("GetJobFilePath(%S, %lu, %p)\n", pwszExtension, dwJobID, pwszOutput);
164 
165     if (pwszOutput)
166     {
167         CopyMemory(pwszOutput, wszJobDirectory, cchJobDirectory * sizeof(WCHAR));
168         swprintf(&pwszOutput[cchJobDirectory], L"\\%05lu.%s", dwJobID, pwszExtension);
169     }
170 
171     // pwszExtension may be L"SPL" or L"SHD", same length for both!
172     return (cchJobDirectory + sizeof("\\?????.SPL")) * sizeof(WCHAR);
173 }
174 
175 BOOL
176 InitializeGlobalJobList(void)
177 {
178     const WCHAR wszPath[] = L"\\?????.SHD";
179     const DWORD cchPath = _countof(wszPath) - 1;
180 
181     DWORD dwErrorCode;
182     DWORD dwJobID;
183     HANDLE hFind;
184     PLOCAL_JOB pJob = NULL;
185     PWSTR p;
186     WCHAR wszFullPath[MAX_PATH];
187     WIN32_FIND_DATAW FindData;
188 
189     TRACE("InitializeGlobalJobList()\n");
190 
191     // This one is incremented in _GetNextJobID.
192     _dwLastJobID = 0;
193 
194     // Initialize an empty list for all jobs of all local printers.
195     // We will search it by Job ID (supply a pointer to a DWORD in LookupElementSkiplist).
196     InitializeSkiplist(&GlobalJobList, DllAllocSplMem, _GlobalJobListCompareRoutine, (PSKIPLIST_FREE_ROUTINE)DllFreeSplMem);
197 
198     // Construct the full path search pattern.
199     CopyMemory(wszFullPath, wszJobDirectory, cchJobDirectory * sizeof(WCHAR));
200     CopyMemory(&wszFullPath[cchJobDirectory], wszPath, (cchPath + 1) * sizeof(WCHAR));
201 
202     // Use the search pattern to look for unfinished jobs serialized in shadow files (.SHD)
203     hFind = FindFirstFileW(wszFullPath, &FindData);
204     if (hFind == INVALID_HANDLE_VALUE)
205     {
206         // No unfinished jobs found.
207         dwErrorCode = ERROR_SUCCESS;
208         goto Cleanup;
209     }
210 
211     do
212     {
213         // Skip possible subdirectories.
214         if (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
215             continue;
216 
217         // Extract the Job ID and verify the file name format at the same time.
218         // This includes all valid names (like "00005.SHD") and excludes invalid ones (like "10ABC.SHD").
219         dwJobID = wcstoul(FindData.cFileName, &p, 10);
220         if (!IS_VALID_JOB_ID(dwJobID))
221             continue;
222 
223         if (wcsicmp(p, L".SHD") != 0)
224             continue;
225 
226         // This shadow file has a valid name. Construct the full path and try to load it.
227         GetJobFilePath(L"SHD", dwJobID, wszFullPath);
228         pJob = ReadJobShadowFile(wszFullPath);
229         if (!pJob)
230             continue;
231 
232         // Add it to the Global Job List.
233         if (!InsertElementSkiplist(&GlobalJobList, pJob))
234         {
235             dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
236             ERR("InsertElementSkiplist failed for job %lu for the GlobalJobList!\n", pJob->dwJobID);
237             goto Cleanup;
238         }
239 
240         // Add it to the Printer's Job List.
241         if (!InsertElementSkiplist(&pJob->pPrinter->JobList, pJob))
242         {
243             dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
244             ERR("InsertElementSkiplist failed for job %lu for the Printer's Job List!\n", pJob->dwJobID);
245             goto Cleanup;
246         }
247     }
248     while (FindNextFileW(hFind, &FindData));
249 
250     dwErrorCode = ERROR_SUCCESS;
251 
252 Cleanup:
253     // Outside the loop
254     if (hFind)
255         FindClose(hFind);
256 
257     SetLastError(dwErrorCode);
258     return (dwErrorCode == ERROR_SUCCESS);
259 }
260 
261 void
262 InitializePrinterJobList(PLOCAL_PRINTER pPrinter)
263 {
264     TRACE("InitializePrinterJobList(%p)\n", pPrinter);
265 
266     // Initialize an empty list for this printer's jobs.
267     // This one is only for sorting the jobs. If you need to lookup a job, search the GlobalJobList by Job ID.
268     InitializeSkiplist(&pPrinter->JobList, DllAllocSplMem, _PrinterJobListCompareRoutine, (PSKIPLIST_FREE_ROUTINE)DllFreeSplMem);
269 }
270 
271 DWORD WINAPI
272 CreateJob(PLOCAL_PRINTER_HANDLE pPrinterHandle)
273 {
274     const WCHAR wszDoubleBackslash[] = L"\\";
275     const DWORD cchDoubleBackslash = _countof(wszDoubleBackslash) - 1;
276 
277     DWORD cchMachineName;
278     DWORD cchUserName;
279     DWORD dwErrorCode;
280     PLOCAL_JOB pJob;
281     RPC_BINDING_HANDLE hServerBinding = NULL;
282     RPC_WSTR pwszBinding = NULL;
283     RPC_WSTR pwszMachineName = NULL;
284 
285     TRACE("CreateJob(%p)\n", pPrinterHandle);
286 
287     // Create a new job.
288     pJob = DllAllocSplMem(sizeof(LOCAL_JOB));
289     if (!pJob)
290     {
291         dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
292         ERR("DllAllocSplMem failed!\n");
293         goto Cleanup;
294     }
295 
296     // Reserve an ID for this job.
297     if (!_GetNextJobID(&pJob->dwJobID))
298     {
299         dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
300         goto Cleanup;
301     }
302 
303     // Copy over defaults to the LOCAL_JOB structure.
304     pJob->pPrinter = pPrinterHandle->pPrinter;
305     pJob->pPrintProcessor = pPrinterHandle->pPrinter->pPrintProcessor;
306     pJob->dwPriority = DEF_PRIORITY;
307     pJob->dwStatus = JOB_STATUS_SPOOLING;
308     pJob->pwszDatatype = AllocSplStr(pPrinterHandle->pwszDatatype);
309     pJob->pwszDocumentName = AllocSplStr(wszDefaultDocumentName);
310     pJob->pDevMode = DuplicateDevMode(pPrinterHandle->pDevMode);
311     GetSystemTime(&pJob->stSubmitted);
312 
313     // Get the user name for the Job.
314     cchUserName = UNLEN + 1;
315     pJob->pwszUserName = DllAllocSplMem(cchUserName * sizeof(WCHAR));
316     if (!GetUserNameW(pJob->pwszUserName, &cchUserName))
317     {
318         dwErrorCode = GetLastError();
319         ERR("GetUserNameW failed with error %lu!\n", dwErrorCode);
320         goto Cleanup;
321     }
322 
323     // FIXME: For now, pwszNotifyName equals pwszUserName.
324     pJob->pwszNotifyName = AllocSplStr(pJob->pwszUserName);
325 
326     // Get the name of the machine that submitted the Job over RPC.
327     dwErrorCode = RpcBindingServerFromClient(NULL, &hServerBinding);
328     if (dwErrorCode != RPC_S_OK)
329     {
330         ERR("RpcBindingServerFromClient failed with status %lu!\n", dwErrorCode);
331         goto Cleanup;
332     }
333 
334     dwErrorCode = RpcBindingToStringBindingW(hServerBinding, &pwszBinding);
335     if (dwErrorCode != RPC_S_OK)
336     {
337         ERR("RpcBindingToStringBindingW failed with status %lu!\n", dwErrorCode);
338         goto Cleanup;
339     }
340 
341     dwErrorCode = RpcStringBindingParseW(pwszBinding, NULL, NULL, &pwszMachineName, NULL, NULL);
342     if (dwErrorCode != RPC_S_OK)
343     {
344         ERR("RpcStringBindingParseW failed with status %lu!\n", dwErrorCode);
345         goto Cleanup;
346     }
347 
348     cchMachineName = wcslen(pwszMachineName);
349     pJob->pwszMachineName = DllAllocSplMem((cchMachineName + cchDoubleBackslash + 1) * sizeof(WCHAR));
350     CopyMemory(pJob->pwszMachineName, wszDoubleBackslash, cchDoubleBackslash * sizeof(WCHAR));
351     CopyMemory(&pJob->pwszMachineName[cchDoubleBackslash], pwszMachineName, (cchMachineName + 1) * sizeof(WCHAR));
352 
353     // Add the job to the Global Job List.
354     if (!InsertElementSkiplist(&GlobalJobList, pJob))
355     {
356         dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
357         ERR("InsertElementSkiplist failed for job %lu for the GlobalJobList!\n", pJob->dwJobID);
358         goto Cleanup;
359     }
360 
361     // Add the job at the end of the Printer's Job List.
362     // As all new jobs are created with default priority, we can be sure that it would always be inserted at the end.
363     if (!InsertTailElementSkiplist(&pJob->pPrinter->JobList, pJob))
364     {
365         dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
366         ERR("InsertTailElementSkiplist failed for job %lu for the Printer's Job List!\n", pJob->dwJobID);
367         goto Cleanup;
368     }
369 
370     // We were successful!
371     pPrinterHandle->bStartedDoc = TRUE;
372     pPrinterHandle->pJob = pJob;
373     dwErrorCode = ERROR_SUCCESS;
374 
375     // Don't let the cleanup routine free this.
376     pJob = NULL;
377 
378 Cleanup:
379     if (pJob)
380         DllFreeSplMem(pJob);
381 
382     if (pwszMachineName)
383         RpcStringFreeW(&pwszMachineName);
384 
385     if (pwszBinding)
386         RpcStringFreeW(&pwszBinding);
387 
388     if (hServerBinding)
389         RpcBindingFree(&hServerBinding);
390 
391     return dwErrorCode;
392 }
393 
394 BOOL WINAPI
395 LocalAddJob(HANDLE hPrinter, DWORD Level, PBYTE pData, DWORD cbBuf, PDWORD pcbNeeded)
396 {
397     ADDJOB_INFO_1W AddJobInfo1;
398     DWORD dwErrorCode;
399     PLOCAL_HANDLE pHandle = (PLOCAL_HANDLE)hPrinter;
400     PLOCAL_PRINTER_HANDLE pPrinterHandle;
401 
402     TRACE("LocalAddJob(%p, %lu, %p, %lu, %p)\n", hPrinter, Level, pData, cbBuf, pcbNeeded);
403 
404     // Check if this is a printer handle.
405     if (pHandle->HandleType != HandleType_Printer)
406     {
407         dwErrorCode = ERROR_INVALID_HANDLE;
408         goto Cleanup;
409     }
410 
411     pPrinterHandle = (PLOCAL_PRINTER_HANDLE)pHandle->pSpecificHandle;
412 
413     // This handle must not have started a job yet!
414     if (pPrinterHandle->pJob)
415     {
416         dwErrorCode = ERROR_INVALID_HANDLE;
417         goto Cleanup;
418     }
419 
420     // Check if this is the right structure level.
421     if (Level != 1)
422     {
423         dwErrorCode = ERROR_INVALID_LEVEL;
424         goto Cleanup;
425     }
426 
427     // Check if the printer is set to do direct printing.
428     // The Job List isn't used in this case.
429     if (pPrinterHandle->pPrinter->dwAttributes & PRINTER_ATTRIBUTE_DIRECT)
430     {
431         dwErrorCode = ERROR_INVALID_ACCESS;
432         goto Cleanup;
433     }
434 
435     // Check if the supplied buffer is large enough.
436     *pcbNeeded = sizeof(ADDJOB_INFO_1W) + GetJobFilePath(L"SPL", 0, NULL);
437     if (cbBuf < *pcbNeeded)
438     {
439         dwErrorCode = ERROR_INSUFFICIENT_BUFFER;
440         goto Cleanup;
441     }
442 
443     // All requirements are met - create a new job.
444     dwErrorCode = CreateJob(pPrinterHandle);
445     if (dwErrorCode != ERROR_SUCCESS)
446         goto Cleanup;
447 
448     // Mark that this job was started with AddJob (so that it can be scheduled for printing with ScheduleJob).
449     pPrinterHandle->pJob->bAddedJob = TRUE;
450 
451     // Return a proper ADDJOB_INFO_1W structure.
452     AddJobInfo1.JobId = pPrinterHandle->pJob->dwJobID;
453     AddJobInfo1.Path = (PWSTR)(pData + sizeof(ADDJOB_INFO_1W));
454 
455     CopyMemory(pData, &AddJobInfo1, sizeof(ADDJOB_INFO_1W));
456     GetJobFilePath(L"SPL", AddJobInfo1.JobId, AddJobInfo1.Path);
457 
458 Cleanup:
459     SetLastError(dwErrorCode);
460     return (dwErrorCode == ERROR_SUCCESS);
461 }
462 
463 
464 static void
465 _LocalGetJobLevel1(PLOCAL_JOB pJob, PJOB_INFO_1W* ppJobInfo, PBYTE* ppJobInfoEnd, PDWORD pcbNeeded)
466 {
467     DWORD cbDatatype;
468     DWORD cbDocumentName = 0;
469     DWORD cbMachineName;
470     DWORD cbPrinterName;
471     DWORD cbStatus = 0;
472     DWORD cbUserName = 0;
473     PWSTR pwszStrings[6];
474 
475     // Calculate the string lengths.
476     if (!ppJobInfo)
477     {
478         cbDatatype = (wcslen(pJob->pwszDatatype) + 1) * sizeof(WCHAR);
479         cbMachineName = (wcslen(pJob->pwszMachineName) + 1) * sizeof(WCHAR);
480         cbPrinterName = (wcslen(pJob->pPrinter->pwszPrinterName) + 1) * sizeof(WCHAR);
481 
482         // These values are optional.
483         if (pJob->pwszDocumentName)
484             cbDocumentName = (wcslen(pJob->pwszDocumentName) + 1) * sizeof(WCHAR);
485 
486         if (pJob->pwszStatus)
487             cbStatus = (wcslen(pJob->pwszStatus) + 1) * sizeof(WCHAR);
488 
489         if (pJob->pwszUserName)
490             cbUserName = (wcslen(pJob->pwszUserName) + 1) * sizeof(WCHAR);
491 
492         *pcbNeeded += sizeof(JOB_INFO_1W) + cbDatatype + cbDocumentName + cbMachineName + cbPrinterName + cbStatus + cbUserName;
493         return;
494     }
495 
496     // Set the general fields.
497     (*ppJobInfo)->JobId = pJob->dwJobID;
498     (*ppJobInfo)->Status = pJob->dwStatus;
499     (*ppJobInfo)->Priority = pJob->dwPriority;
500     (*ppJobInfo)->TotalPages = pJob->dwTotalPages;
501     (*ppJobInfo)->PagesPrinted = pJob->dwPagesPrinted;
502     CopyMemory(&(*ppJobInfo)->Submitted, &pJob->stSubmitted, sizeof(SYSTEMTIME));
503 
504     // Position in JOB_INFO_1W is the 1-based index of the job in the processing queue.
505     // Retrieve this through the element index of the job in the Printer's Job List.
506     if (!LookupElementSkiplist(&pJob->pPrinter->JobList, pJob, &(*ppJobInfo)->Position))
507     {
508         ERR("pJob could not be located in the Printer's Job List!\n");
509         return;
510     }
511 
512     // Make the index 1-based.
513     ++(*ppJobInfo)->Position;
514 
515     // Set the pPrinterName field.
516     pwszStrings[0] = pJob->pPrinter->pwszPrinterName;
517 
518     // Set the pMachineName field.
519     pwszStrings[1] = pJob->pwszMachineName;
520 
521     // Set the pUserName field.
522     pwszStrings[2] = pJob->pwszUserName;
523 
524     // Set the pDocument field.
525     pwszStrings[3] = pJob->pwszDocumentName;
526 
527     // Set the pDatatype field.
528     pwszStrings[4] = pJob->pwszDatatype;
529 
530     // Set the pStatus field.
531     pwszStrings[5] = pJob->pwszStatus;
532 
533     // Finally copy the structure and advance to the next one in the output buffer.
534     *ppJobInfoEnd = PackStrings(pwszStrings, (PBYTE)(*ppJobInfo), dwJobInfo1Offsets, *ppJobInfoEnd);
535     (*ppJobInfo)++;
536 }
537 
538 static void
539 _LocalGetJobLevel2(PLOCAL_JOB pJob, PJOB_INFO_2W* ppJobInfo, PBYTE* ppJobInfoEnd, PDWORD pcbNeeded)
540 {
541     DWORD cbDatatype;
542     DWORD cbDevMode;
543     DWORD cbDocumentName = 0;
544     DWORD cbDriverName;
545     DWORD cbMachineName;
546     DWORD cbNotifyName = 0;
547     DWORD cbPrinterName;
548     DWORD cbPrintProcessor;
549     DWORD cbPrintProcessorParameters = 0;
550     DWORD cbStatus = 0;
551     DWORD cbUserName = 0;
552     FILETIME ftNow;
553     FILETIME ftSubmitted;
554     PWSTR pwszStrings[10];
555     ULARGE_INTEGER uliNow;
556     ULARGE_INTEGER uliSubmitted;
557 
558     // Calculate the string lengths.
559     cbDevMode = pJob->pDevMode->dmSize + pJob->pDevMode->dmDriverExtra;
560 
561     if (!ppJobInfo)
562     {
563         cbDatatype = (wcslen(pJob->pwszDatatype) + 1) * sizeof(WCHAR);
564         cbDriverName = (wcslen(pJob->pPrinter->pwszPrinterDriver) + 1) * sizeof(WCHAR);
565         cbMachineName = (wcslen(pJob->pwszMachineName) + 1) * sizeof(WCHAR);
566         cbPrinterName = (wcslen(pJob->pPrinter->pwszPrinterName) + 1) * sizeof(WCHAR);
567         cbPrintProcessor = (wcslen(pJob->pPrintProcessor->pwszName) + 1) * sizeof(WCHAR);
568 
569         // These values are optional.
570         if (pJob->pwszDocumentName)
571             cbDocumentName = (wcslen(pJob->pwszDocumentName) + 1) * sizeof(WCHAR);
572 
573         if (pJob->pwszNotifyName)
574             cbNotifyName = (wcslen(pJob->pwszNotifyName) + 1) * sizeof(WCHAR);
575 
576         if (pJob->pwszPrintProcessorParameters)
577             cbPrintProcessorParameters = (wcslen(pJob->pwszPrintProcessorParameters) + 1) * sizeof(WCHAR);
578 
579         if (pJob->pwszStatus)
580             cbStatus = (wcslen(pJob->pwszStatus) + 1) * sizeof(WCHAR);
581 
582         if (pJob->pwszUserName)
583             cbUserName = (wcslen(pJob->pwszUserName) + 1) * sizeof(WCHAR);
584 
585         *pcbNeeded += sizeof(JOB_INFO_2W) + cbDatatype + cbDevMode + cbDocumentName + cbDriverName + cbMachineName + cbNotifyName + cbPrinterName + cbPrintProcessor + cbPrintProcessorParameters + cbStatus + cbUserName;
586         return;
587     }
588 
589     // Set the general fields.
590     (*ppJobInfo)->JobId = pJob->dwJobID;
591     (*ppJobInfo)->Status = pJob->dwStatus;
592     (*ppJobInfo)->Priority = pJob->dwPriority;
593     (*ppJobInfo)->StartTime = pJob->dwStartTime;
594     (*ppJobInfo)->UntilTime = pJob->dwUntilTime;
595     (*ppJobInfo)->TotalPages = pJob->dwTotalPages;
596     (*ppJobInfo)->PagesPrinted = pJob->dwPagesPrinted;
597     CopyMemory(&(*ppJobInfo)->Submitted, &pJob->stSubmitted, sizeof(SYSTEMTIME));
598 
599     // Time in JOB_INFO_2W is the number of milliseconds elapsed since the job was submitted. Calculate this time.
600     if (!SystemTimeToFileTime(&pJob->stSubmitted, &ftSubmitted))
601     {
602         ERR("SystemTimeToFileTime failed with error %lu!\n", GetLastError());
603         return;
604     }
605 
606     GetSystemTimeAsFileTime(&ftNow);
607     uliSubmitted.LowPart = ftSubmitted.dwLowDateTime;
608     uliSubmitted.HighPart = ftSubmitted.dwHighDateTime;
609     uliNow.LowPart = ftNow.dwLowDateTime;
610     uliNow.HighPart = ftNow.dwHighDateTime;
611     (*ppJobInfo)->Time = (DWORD)((uliNow.QuadPart - uliSubmitted.QuadPart) / 10000);
612 
613     // Position in JOB_INFO_2W is the 1-based index of the job in the processing queue.
614     // Retrieve this through the element index of the job in the Printer's Job List.
615     if (!LookupElementSkiplist(&pJob->pPrinter->JobList, pJob, &(*ppJobInfo)->Position))
616     {
617         ERR("pJob could not be located in the Printer's Job List!\n");
618         return;
619     }
620 
621     // Make the index 1-based.
622     ++(*ppJobInfo)->Position;
623 
624     // FIXME!
625     FIXME("Setting pSecurityDescriptor and Size to 0 for now!\n");
626     (*ppJobInfo)->pSecurityDescriptor = NULL;
627     (*ppJobInfo)->Size = 0;
628 
629     // Set the pDevMode field (and copy the DevMode).
630     *ppJobInfoEnd -= cbDevMode;
631     CopyMemory(*ppJobInfoEnd, pJob->pDevMode, cbDevMode);
632     (*ppJobInfo)->pDevMode = (PDEVMODEW)(*ppJobInfoEnd);
633 
634     // Set the pPrinterName field.
635     pwszStrings[0] = pJob->pPrinter->pwszPrinterName;
636 
637     // Set the pMachineName field.
638     pwszStrings[1] = pJob->pwszMachineName;
639 
640     // Set the pUserName field.
641     pwszStrings[2] = pJob->pwszUserName;
642 
643     // Set the pDocument field.
644     pwszStrings[3] = pJob->pwszDocumentName;
645 
646     // Set the pNotifyName field.
647     pwszStrings[4] = pJob->pwszNotifyName;
648 
649     // Set the pDatatype field.
650     pwszStrings[5] = pJob->pwszDatatype;
651 
652     // Set the pPrintProcessor field.
653     pwszStrings[6] = pJob->pPrintProcessor->pwszName;
654 
655     // Set the pParameters field.
656     pwszStrings[7] = pJob->pwszPrintProcessorParameters;
657 
658     // Set the pDriverName field.
659     pwszStrings[8] = pJob->pPrinter->pwszPrinterDriver;
660 
661     // Set the pStatus field.
662     pwszStrings[9] = pJob->pwszStatus;
663 
664     // Finally copy the structure and advance to the next one in the output buffer.
665     *ppJobInfoEnd = PackStrings(pwszStrings, (PBYTE)(*ppJobInfo), dwJobInfo2Offsets, *ppJobInfoEnd);
666     (*ppJobInfo)++;
667 }
668 
669 BOOL WINAPI
670 LocalGetJob(HANDLE hPrinter, DWORD JobId, DWORD Level, PBYTE pStart, DWORD cbBuf, LPDWORD pcbNeeded)
671 {
672     DWORD dwErrorCode;
673     PBYTE pEnd = &pStart[cbBuf];
674     PLOCAL_HANDLE pHandle;
675     PLOCAL_JOB pJob;
676     PLOCAL_PRINTER_HANDLE pPrinterHandle;
677 
678     TRACE("LocalGetJob(%p, %lu, %lu, %p, %lu, %p)\n", hPrinter, JobId, Level, pStart, cbBuf, pcbNeeded);
679 
680     // Check if this is a printer handle.
681     pHandle = (PLOCAL_HANDLE)hPrinter;
682     if (pHandle->HandleType != HandleType_Printer)
683     {
684         dwErrorCode = ERROR_INVALID_HANDLE;
685         goto Cleanup;
686     }
687 
688     pPrinterHandle = (PLOCAL_PRINTER_HANDLE)pHandle->pSpecificHandle;
689 
690     // Get the desired job.
691     pJob = LookupElementSkiplist(&GlobalJobList, &JobId, NULL);
692     if (!pJob || pJob->pPrinter != pPrinterHandle->pPrinter)
693     {
694         dwErrorCode = ERROR_INVALID_PARAMETER;
695         goto Cleanup;
696     }
697 
698     if (Level > 2)
699     {
700         // The caller supplied an invalid level for GetJob.
701         dwErrorCode = ERROR_INVALID_LEVEL;
702         goto Cleanup;
703     }
704 
705     // Count the required buffer size.
706     *pcbNeeded = 0;
707 
708     if (Level == 1)
709         _LocalGetJobLevel1(pJob, NULL, NULL, pcbNeeded);
710     else if (Level == 2)
711         _LocalGetJobLevel2(pJob, NULL, NULL, pcbNeeded);
712 
713     // Check if the supplied buffer is large enough.
714     if (cbBuf < *pcbNeeded)
715     {
716         dwErrorCode = ERROR_INSUFFICIENT_BUFFER;
717         goto Cleanup;
718     }
719 
720     // Copy over the Job information.
721     pEnd = &pStart[*pcbNeeded];
722 
723     if (Level == 1)
724         _LocalGetJobLevel1(pJob, (PJOB_INFO_1W*)&pStart, &pEnd, NULL);
725     else if (Level == 2)
726         _LocalGetJobLevel2(pJob, (PJOB_INFO_2W*)&pStart, &pEnd, NULL);
727 
728     dwErrorCode = ERROR_SUCCESS;
729 
730 Cleanup:
731     SetLastError(dwErrorCode);
732     return (dwErrorCode == ERROR_SUCCESS);
733 }
734 
735 static DWORD
736 _LocalSetJobLevel1(PLOCAL_PRINTER_HANDLE pPrinterHandle, PLOCAL_JOB pJob, PJOB_INFO_1W pJobInfo)
737 {
738     DWORD dwErrorCode;
739 
740     // First check the validity of the input before changing anything.
741     if (!FindDatatype(pJob->pPrintProcessor, pJobInfo->pDatatype))
742     {
743         dwErrorCode = ERROR_INVALID_DATATYPE;
744         goto Cleanup;
745     }
746 
747     // Check if the datatype has changed.
748     if (!_EqualStrings(pJob->pwszDatatype, pJobInfo->pDatatype))
749     {
750         // Use the new value.
751         if (!ReallocSplStr(&pJob->pwszDatatype, pJobInfo->pDatatype))
752         {
753             dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
754             ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
755             goto Cleanup;
756         }
757     }
758 
759     // Check if the document name has changed. An empty string is permitted here!
760     if (!_EqualStrings(pJob->pwszDocumentName, pJobInfo->pDocument))
761     {
762         // Use the new value.
763         if (!ReallocSplStr(&pJob->pwszDocumentName, pJobInfo->pDocument))
764         {
765             dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
766             ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
767             goto Cleanup;
768         }
769     }
770 
771     // Check if the status message has changed. An empty string is permitted here!
772     if (!_EqualStrings(pJob->pwszStatus, pJobInfo->pStatus))
773     {
774         // Use the new value.
775         if (!ReallocSplStr(&pJob->pwszStatus, pJobInfo->pStatus))
776         {
777             dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
778             ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
779             goto Cleanup;
780         }
781     }
782 
783     // Check if the user name has changed. An empty string is permitted here!
784     if (!_EqualStrings(pJob->pwszUserName, pJobInfo->pUserName) != 0)
785     {
786         // The new user name doesn't need to exist, so no additional verification is required.
787 
788         // Use the new value.
789         if (!ReallocSplStr(&pJob->pwszUserName, pJobInfo->pUserName))
790         {
791             dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
792             ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
793             goto Cleanup;
794         }
795     }
796 
797     // Check if the priority has changed.
798     if (pJob->dwPriority != pJobInfo->Priority && IS_VALID_PRIORITY(pJobInfo->Priority))
799     {
800         // Set the new priority.
801         pJob->dwPriority = pJobInfo->Priority;
802 
803         // Remove and reinsert the job in the Printer's Job List.
804         // The Compare function will be used to find the right position now considering the new priority.
805         DeleteElementSkiplist(&pJob->pPrinter->JobList, pJob);
806         InsertElementSkiplist(&pJob->pPrinter->JobList, pJob);
807     }
808 
809     // Check if the status flags have changed.
810     if (pJob->dwStatus != pJobInfo->Status)
811     {
812         // Only add status flags that make sense.
813         if (pJobInfo->Status & JOB_STATUS_PAUSED)
814             pJob->dwStatus |= JOB_STATUS_PAUSED;
815 
816         if (pJobInfo->Status & JOB_STATUS_ERROR)
817             pJob->dwStatus |= JOB_STATUS_ERROR;
818 
819         if (pJobInfo->Status & JOB_STATUS_OFFLINE)
820             pJob->dwStatus |= JOB_STATUS_OFFLINE;
821 
822         if (pJobInfo->Status & JOB_STATUS_PAPEROUT)
823             pJob->dwStatus |= JOB_STATUS_PAPEROUT;
824     }
825 
826     dwErrorCode = ERROR_SUCCESS;
827 
828 Cleanup:
829     return dwErrorCode;
830 }
831 
832 static DWORD
833 _LocalSetJobLevel2(PLOCAL_PRINTER_HANDLE pPrinterHandle, PLOCAL_JOB pJob, PJOB_INFO_2W pJobInfo)
834 {
835     DWORD dwErrorCode;
836     PLOCAL_PRINT_PROCESSOR pPrintProcessor;
837 
838     // First check the validity of the input before changing anything.
839     pPrintProcessor = FindPrintProcessor(pJobInfo->pPrintProcessor);
840     if (!pPrintProcessor)
841     {
842         dwErrorCode = ERROR_UNKNOWN_PRINTPROCESSOR;
843         goto Cleanup;
844     }
845 
846     if (!FindDatatype(pPrintProcessor, pJobInfo->pDatatype))
847     {
848         dwErrorCode = ERROR_INVALID_DATATYPE;
849         goto Cleanup;
850     }
851 
852     // Check if the datatype has changed.
853     if (!_EqualStrings(pJob->pwszDatatype, pJobInfo->pDatatype))
854     {
855         // Use the new value.
856         if (!ReallocSplStr(&pJob->pwszDatatype, pJobInfo->pDatatype))
857         {
858             dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
859             ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
860             goto Cleanup;
861         }
862     }
863 
864     // Check if the document name has changed. An empty string is permitted here!
865     if (!_EqualStrings(pJob->pwszDocumentName, pJobInfo->pDocument))
866     {
867         // Use the new value.
868         if (!ReallocSplStr(&pJob->pwszDocumentName, pJobInfo->pDocument))
869         {
870             dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
871             ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
872             goto Cleanup;
873         }
874     }
875 
876     // Check if the notify name has changed. An empty string is permitted here!
877     if (!_EqualStrings(pJob->pwszNotifyName, pJobInfo->pNotifyName))
878     {
879         // The new notify name doesn't need to exist, so no additional verification is required.
880 
881         // Use the new value.
882         if (!ReallocSplStr(&pJob->pwszNotifyName, pJobInfo->pNotifyName))
883         {
884             dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
885             ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
886             goto Cleanup;
887         }
888     }
889 
890     // Check if the Print Processor Parameters have changed. An empty string is permitted here!
891     if (!_EqualStrings(pJob->pwszPrintProcessorParameters, pJobInfo->pParameters))
892     {
893         // Use the new value.
894         if (!ReallocSplStr(&pJob->pwszPrintProcessorParameters, pJobInfo->pParameters))
895         {
896             dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
897             ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
898             goto Cleanup;
899         }
900     }
901 
902     // Check if the Status Message has changed. An empty string is permitted here!
903     if (!_EqualStrings(pJob->pwszStatus, pJobInfo->pStatus))
904     {
905         // Use the new value.
906         if (!ReallocSplStr(&pJob->pwszStatus, pJobInfo->pStatus))
907         {
908             dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
909             ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
910             goto Cleanup;
911         }
912     }
913 
914     // Check if the user name has changed. An empty string is permitted here!
915     if (!_EqualStrings(pJob->pwszUserName, pJobInfo->pUserName))
916     {
917         // The new user name doesn't need to exist, so no additional verification is required.
918 
919         // Use the new value.
920         if (!ReallocSplStr(&pJob->pwszUserName, pJobInfo->pUserName))
921         {
922             dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
923             ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
924             goto Cleanup;
925         }
926     }
927 
928     // Check if the priority has changed.
929     if (pJob->dwPriority != pJobInfo->Priority && IS_VALID_PRIORITY(pJobInfo->Priority))
930     {
931         // Set the new priority.
932         pJob->dwPriority = pJobInfo->Priority;
933 
934         // Remove and reinsert the job in the Printer's Job List.
935         // The Compare function will be used to find the right position now considering the new priority.
936         DeleteElementSkiplist(&pJob->pPrinter->JobList, pJob);
937         InsertElementSkiplist(&pJob->pPrinter->JobList, pJob);
938     }
939 
940     // Check if the status flags have changed.
941     if (pJob->dwStatus != pJobInfo->Status)
942     {
943         // Only add status flags that make sense.
944         if (pJobInfo->Status & JOB_STATUS_PAUSED)
945             pJob->dwStatus |= JOB_STATUS_PAUSED;
946 
947         if (pJobInfo->Status & JOB_STATUS_ERROR)
948             pJob->dwStatus |= JOB_STATUS_ERROR;
949 
950         if (pJobInfo->Status & JOB_STATUS_OFFLINE)
951             pJob->dwStatus |= JOB_STATUS_OFFLINE;
952 
953         if (pJobInfo->Status & JOB_STATUS_PAPEROUT)
954             pJob->dwStatus |= JOB_STATUS_PAPEROUT;
955     }
956 
957     dwErrorCode = ERROR_SUCCESS;
958 
959 Cleanup:
960     return dwErrorCode;
961 }
962 
963 BOOL WINAPI
964 LocalSetJob(HANDLE hPrinter, DWORD JobId, DWORD Level, PBYTE pJobInfo, DWORD Command)
965 {
966     DWORD dwErrorCode = ERROR_SUCCESS;
967     PLOCAL_HANDLE pHandle;
968     PLOCAL_JOB pJob;
969     PLOCAL_PRINTER_HANDLE pPrinterHandle;
970     WCHAR wszFullPath[MAX_PATH];
971 
972     TRACE("LocalSetJob(%p, %lu, %lu, %p, %lu)\n", hPrinter, JobId, Level, pJobInfo, Command);
973 
974     // Check if this is a printer handle.
975     pHandle = (PLOCAL_HANDLE)hPrinter;
976     if (!pHandle || pHandle->HandleType != HandleType_Printer)
977     {
978         dwErrorCode = ERROR_INVALID_HANDLE;
979         goto Cleanup;
980     }
981 
982     pPrinterHandle = (PLOCAL_PRINTER_HANDLE)pHandle->pSpecificHandle;
983 
984     // Get the desired job.
985     pJob = LookupElementSkiplist(&GlobalJobList, &JobId, NULL);
986     if (!pJob || pJob->pPrinter != pPrinterHandle->pPrinter)
987     {
988         dwErrorCode = ERROR_INVALID_PARAMETER;
989         goto Cleanup;
990     }
991 
992     // Set new job information if a valid level was given.
993     if (Level == 1)
994         dwErrorCode = _LocalSetJobLevel1(pPrinterHandle, pJob, (PJOB_INFO_1W)pJobInfo);
995     else if (Level == 2)
996         dwErrorCode = _LocalSetJobLevel2(pPrinterHandle, pJob, (PJOB_INFO_2W)pJobInfo);
997 
998     if (dwErrorCode != ERROR_SUCCESS)
999         goto Cleanup;
1000 
1001     // If we do spooled printing, the job information is written down into a shadow file.
1002     if (!(pPrinterHandle->pPrinter->dwAttributes & PRINTER_ATTRIBUTE_DIRECT))
1003     {
1004         // Write the job data into the shadow file.
1005         GetJobFilePath(L"SHD", JobId, wszFullPath);
1006         WriteJobShadowFile(wszFullPath, pJob);
1007     }
1008 
1009     // Perform an additional command if desired.
1010     if (Command)
1011     {
1012         if (Command == JOB_CONTROL_SENT_TO_PRINTER)
1013         {
1014             // This indicates the end of the Print Job.
1015 
1016             // Cancel the Job at the Print Processor.
1017             if (pJob->hPrintProcessor)
1018                 pJob->pPrintProcessor->pfnControlPrintProcessor(pJob->hPrintProcessor, JOB_CONTROL_CANCEL);
1019 
1020             FreeJob(pJob);
1021 
1022             // TODO: All open handles associated with the job need to be invalidated.
1023             // This certainly needs handle tracking...
1024         }
1025         else
1026         {
1027             ERR("Unimplemented SetJob Command: %lu!\n", Command);
1028         }
1029     }
1030 
1031 Cleanup:
1032     SetLastError(dwErrorCode);
1033     return (dwErrorCode == ERROR_SUCCESS);
1034 }
1035 
1036 BOOL WINAPI
1037 LocalEnumJobs(HANDLE hPrinter, DWORD FirstJob, DWORD NoJobs, DWORD Level, PBYTE pStart, DWORD cbBuf, LPDWORD pcbNeeded, LPDWORD pcReturned)
1038 {
1039     DWORD dwErrorCode;
1040     DWORD i;
1041     PBYTE pEnd;
1042     PLOCAL_HANDLE pHandle;
1043     PLOCAL_JOB pJob;
1044     PSKIPLIST_NODE pFirstJobNode;
1045     PSKIPLIST_NODE pNode;
1046     PLOCAL_PRINTER_HANDLE pPrinterHandle;
1047 
1048     TRACE("LocalEnumJobs(%p, %lu, %lu, %lu, %p, %lu, %p, %p)\n", hPrinter, FirstJob, NoJobs, Level, pStart, cbBuf, pcbNeeded, pcReturned);
1049 
1050     // Check if this is a printer handle.
1051     pHandle = (PLOCAL_HANDLE)hPrinter;
1052     if (pHandle->HandleType != HandleType_Printer)
1053     {
1054         dwErrorCode = ERROR_INVALID_HANDLE;
1055         goto Cleanup;
1056     }
1057 
1058     pPrinterHandle = (PLOCAL_PRINTER_HANDLE)pHandle->pSpecificHandle;
1059 
1060     // Check the level.
1061     if (Level > 2)
1062     {
1063         dwErrorCode = ERROR_INVALID_LEVEL;
1064         goto Cleanup;
1065     }
1066 
1067     // Begin counting.
1068     *pcbNeeded = 0;
1069     *pcReturned = 0;
1070 
1071     // Lookup the node of the first job requested by the caller in the Printer's Job List.
1072     pFirstJobNode = LookupNodeByIndexSkiplist(&pPrinterHandle->pPrinter->JobList, FirstJob);
1073 
1074     // Count the required buffer size and the number of jobs.
1075     i = 0;
1076     pNode = pFirstJobNode;
1077 
1078     while (i < NoJobs && pNode)
1079     {
1080         pJob = (PLOCAL_JOB)pNode->Element;
1081 
1082         if (Level == 1)
1083             _LocalGetJobLevel1(pJob, NULL, NULL, pcbNeeded);
1084         else if (Level == 2)
1085             _LocalGetJobLevel2(pJob, NULL, NULL, pcbNeeded);
1086 
1087         // We stop either when there are no more jobs in the list or when the caller didn't request more, whatever comes first.
1088         i++;
1089         pNode = pNode->Next[0];
1090     }
1091 
1092     // Check if the supplied buffer is large enough.
1093     if (cbBuf < *pcbNeeded)
1094     {
1095         dwErrorCode = ERROR_INSUFFICIENT_BUFFER;
1096         goto Cleanup;
1097     }
1098 
1099     // Copy over the Job information.
1100     i = 0;
1101     pNode = pFirstJobNode;
1102     pEnd = &pStart[*pcbNeeded];
1103 
1104     while (i < NoJobs && pNode)
1105     {
1106         pJob = (PLOCAL_JOB)pNode->Element;
1107 
1108         if (Level == 1)
1109             _LocalGetJobLevel1(pJob, (PJOB_INFO_1W*)&pStart, &pEnd, NULL);
1110         else if (Level == 2)
1111             _LocalGetJobLevel2(pJob, (PJOB_INFO_2W*)&pStart, &pEnd, NULL);
1112 
1113         // We stop either when there are no more jobs in the list or when the caller didn't request more, whatever comes first.
1114         i++;
1115         pNode = pNode->Next[0];
1116     }
1117 
1118     *pcReturned = i;
1119     dwErrorCode = ERROR_SUCCESS;
1120 
1121 Cleanup:
1122     SetLastError(dwErrorCode);
1123     return (dwErrorCode == ERROR_SUCCESS);
1124 }
1125 
1126 BOOL WINAPI
1127 LocalScheduleJob(HANDLE hPrinter, DWORD dwJobID)
1128 {
1129     DWORD dwAttributes;
1130     DWORD dwErrorCode;
1131     HANDLE hThread;
1132     PLOCAL_JOB pJob;
1133     PLOCAL_HANDLE pHandle = (PLOCAL_HANDLE)hPrinter;
1134     PLOCAL_PRINTER_HANDLE pPrinterHandle;
1135     WCHAR wszFullPath[MAX_PATH];
1136 
1137     TRACE("LocalScheduleJob(%p, %lu)\n", hPrinter, dwJobID);
1138 
1139     // Check if this is a printer handle.
1140     if (pHandle->HandleType != HandleType_Printer)
1141     {
1142         dwErrorCode = ERROR_INVALID_HANDLE;
1143         goto Cleanup;
1144     }
1145 
1146     pPrinterHandle = (PLOCAL_PRINTER_HANDLE)pHandle->pSpecificHandle;
1147 
1148     // Check if the Job ID is valid.
1149     pJob = LookupElementSkiplist(&GlobalJobList, &dwJobID, NULL);
1150     if (!pJob || pJob->pPrinter != pPrinterHandle->pPrinter)
1151     {
1152         dwErrorCode = ERROR_INVALID_PARAMETER;
1153         goto Cleanup;
1154     }
1155 
1156     // Check if this Job was started with AddJob.
1157     if (!pJob->bAddedJob)
1158     {
1159         dwErrorCode = ERROR_SPL_NO_ADDJOB;
1160         goto Cleanup;
1161     }
1162 
1163     // Construct the full path to the spool file.
1164     GetJobFilePath(L"SPL", dwJobID, wszFullPath);
1165 
1166     // Check if it exists.
1167     dwAttributes = GetFileAttributesW(wszFullPath);
1168     if (dwAttributes == INVALID_FILE_ATTRIBUTES || dwAttributes & FILE_ATTRIBUTE_DIRECTORY)
1169     {
1170         dwErrorCode = ERROR_SPOOL_FILE_NOT_FOUND;
1171         goto Cleanup;
1172     }
1173 
1174     // Spooling is finished at this point.
1175     pJob->dwStatus &= ~JOB_STATUS_SPOOLING;
1176 
1177     // Write the job data into the shadow file.
1178     wcscpy(wcsrchr(wszFullPath, L'.'), L".SHD");
1179     WriteJobShadowFile(wszFullPath, pJob);
1180 
1181     // Create the thread for performing the printing process.
1182     hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)PrintingThreadProc, pJob, 0, NULL);
1183     if (!hThread)
1184     {
1185         dwErrorCode = GetLastError();
1186         ERR("CreateThread failed with error %lu!\n", dwErrorCode);
1187         goto Cleanup;
1188     }
1189 
1190     // We don't need the thread handle. Keeping it open blocks the thread from terminating.
1191     CloseHandle(hThread);
1192 
1193     // ScheduleJob has done its job. The rest happens inside the thread.
1194     dwErrorCode = ERROR_SUCCESS;
1195 
1196 Cleanup:
1197     SetLastError(dwErrorCode);
1198     return (dwErrorCode == ERROR_SUCCESS);
1199 }
1200 
1201 PLOCAL_JOB
1202 ReadJobShadowFile(PCWSTR pwszFilePath)
1203 {
1204     DWORD cbFileSize;
1205     DWORD cbRead;
1206     HANDLE hFile = INVALID_HANDLE_VALUE;
1207     PLOCAL_JOB pJob;
1208     PLOCAL_JOB pReturnValue = NULL;
1209     PLOCAL_PRINTER pPrinter;
1210     PLOCAL_PRINT_PROCESSOR pPrintProcessor;
1211     PSHD_HEADER pShadowFile = NULL;
1212     PWSTR pwszPrinterName;
1213     PWSTR pwszPrintProcessor;
1214 
1215     TRACE("ReadJobShadowFile(%S)\n", pwszFilePath);
1216 
1217     // Try to open the file.
1218     hFile = CreateFileW(pwszFilePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
1219     if (hFile == INVALID_HANDLE_VALUE)
1220     {
1221         ERR("CreateFileW failed with error %lu for file \"%S\"!\n", GetLastError(), pwszFilePath);
1222         goto Cleanup;
1223     }
1224 
1225     // Get its file size (small enough for a single DWORD) and allocate memory for all of it.
1226     cbFileSize = GetFileSize(hFile, NULL);
1227     pShadowFile = DllAllocSplMem(cbFileSize);
1228     if (!pShadowFile)
1229     {
1230         ERR("DllAllocSplMem failed for file \"%S\"!\n", pwszFilePath);
1231         goto Cleanup;
1232     }
1233 
1234     // Read the entire file.
1235     if (!ReadFile(hFile, pShadowFile, cbFileSize, &cbRead, NULL))
1236     {
1237         ERR("ReadFile failed with error %lu for file \"%S\"!\n", GetLastError(), pwszFilePath);
1238         goto Cleanup;
1239     }
1240 
1241     // Check signature and header size.
1242     if (pShadowFile->dwSignature != SHD_WIN2003_SIGNATURE || pShadowFile->cbHeader != sizeof(SHD_HEADER))
1243     {
1244         ERR("Signature or Header Size mismatch for file \"%S\"!\n", pwszFilePath);
1245         goto Cleanup;
1246     }
1247 
1248     // Retrieve the associated printer from the list.
1249     pwszPrinterName = (PWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offPrinterName);
1250     pPrinter = LookupElementSkiplist(&PrinterList, &pwszPrinterName, NULL);
1251     if (!pPrinter)
1252     {
1253         ERR("Shadow file \"%S\" references a non-existing printer \"%S\"!\n", pwszFilePath, pwszPrinterName);
1254         goto Cleanup;
1255     }
1256 
1257     // Retrieve the associated Print Processor from the list.
1258     pwszPrintProcessor = (PWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offPrintProcessor);
1259     pPrintProcessor = FindPrintProcessor(pwszPrintProcessor);
1260     if (!pPrintProcessor)
1261     {
1262         ERR("Shadow file \"%S\" references a non-existing Print Processor \"%S\"!\n", pwszFilePath, pwszPrintProcessor);
1263         goto Cleanup;
1264     }
1265 
1266     // Create a new job structure and copy over the relevant fields.
1267     pJob = DllAllocSplMem(sizeof(LOCAL_JOB));
1268     if (!pJob)
1269     {
1270         ERR("DllAllocSplMem failed for file \"%S\"!\n", pwszFilePath);
1271         goto Cleanup;
1272     }
1273 
1274     pJob->dwJobID = pShadowFile->dwJobID;
1275     pJob->dwPriority = pShadowFile->dwPriority;
1276     pJob->dwStartTime = pShadowFile->dwStartTime;
1277     pJob->dwTotalPages = pShadowFile->dwTotalPages;
1278     pJob->dwUntilTime = pShadowFile->dwUntilTime;
1279     pJob->pPrinter = pPrinter;
1280     pJob->pPrintProcessor = pPrintProcessor;
1281     pJob->pDevMode = DuplicateDevMode((PDEVMODEW)((ULONG_PTR)pShadowFile + pShadowFile->offDevMode));
1282     pJob->pwszDatatype = AllocSplStr((PCWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offDatatype));
1283     pJob->pwszMachineName = AllocSplStr((PCWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offMachineName));
1284     CopyMemory(&pJob->stSubmitted, &pShadowFile->stSubmitted, sizeof(SYSTEMTIME));
1285 
1286     // Copy the optional values.
1287     if (pShadowFile->offDocumentName)
1288         pJob->pwszDocumentName = AllocSplStr((PCWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offDocumentName));
1289 
1290     if (pShadowFile->offNotifyName)
1291         pJob->pwszNotifyName = AllocSplStr((PCWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offNotifyName));
1292 
1293     if (pShadowFile->offPrintProcessorParameters)
1294         pJob->pwszPrintProcessorParameters = AllocSplStr((PCWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offPrintProcessorParameters));
1295 
1296     if (pShadowFile->offUserName)
1297         pJob->pwszUserName = AllocSplStr((PCWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offUserName));
1298 
1299     // Jobs read from shadow files were always added using AddJob.
1300     pJob->bAddedJob = TRUE;
1301 
1302     pReturnValue = pJob;
1303 
1304 Cleanup:
1305     if (pShadowFile)
1306         DllFreeSplMem(pShadowFile);
1307 
1308     if (hFile != INVALID_HANDLE_VALUE)
1309         CloseHandle(hFile);
1310 
1311     return pReturnValue;
1312 }
1313 
1314 BOOL
1315 WriteJobShadowFile(PWSTR pwszFilePath, const PLOCAL_JOB pJob)
1316 {
1317     BOOL bReturnValue = FALSE;
1318     DWORD cbDatatype = (wcslen(pJob->pwszDatatype) + 1) * sizeof(WCHAR);
1319     DWORD cbDevMode = pJob->pDevMode->dmSize + pJob->pDevMode->dmDriverExtra;
1320     DWORD cbDocumentName = 0;
1321     DWORD cbFileSize;
1322     DWORD cbMachineName = (wcslen(pJob->pwszMachineName) + 1) * sizeof(WCHAR);
1323     DWORD cbNotifyName = 0;
1324     DWORD cbPrinterDriver = (wcslen(pJob->pPrinter->pwszPrinterDriver) + 1) * sizeof(WCHAR);
1325     DWORD cbPrinterName = (wcslen(pJob->pPrinter->pwszPrinterName) + 1) * sizeof(WCHAR);
1326     DWORD cbPrintProcessor = (wcslen(pJob->pPrintProcessor->pwszName) + 1) * sizeof(WCHAR);
1327     DWORD cbPrintProcessorParameters = 0;
1328     DWORD cbUserName = 0;
1329     DWORD cbWritten;
1330     DWORD dwCurrentOffset;
1331     HANDLE hSHDFile = INVALID_HANDLE_VALUE;
1332     HANDLE hSPLFile = INVALID_HANDLE_VALUE;
1333     PSHD_HEADER pShadowFile = NULL;
1334 
1335     TRACE("WriteJobShadowFile(%S, %p)\n", pwszFilePath, pJob);
1336 
1337     // Try to open the SHD file.
1338     hSHDFile = CreateFileW(pwszFilePath, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, 0, NULL);
1339     if (hSHDFile == INVALID_HANDLE_VALUE)
1340     {
1341         ERR("CreateFileW failed with error %lu for file \"%S\"!\n", GetLastError(), pwszFilePath);
1342         goto Cleanup;
1343     }
1344 
1345     // Calculate the lengths of the optional values and the total size of the shadow file.
1346     if (pJob->pwszDocumentName)
1347         cbDocumentName = (wcslen(pJob->pwszDocumentName) + 1) * sizeof(WCHAR);
1348 
1349     if (pJob->pwszNotifyName)
1350         cbNotifyName = (wcslen(pJob->pwszNotifyName) + 1) * sizeof(WCHAR);
1351 
1352     if (pJob->pwszPrintProcessorParameters)
1353         cbPrintProcessorParameters = (wcslen(pJob->pwszPrintProcessorParameters) + 1) * sizeof(WCHAR);
1354 
1355     if (pJob->pwszUserName)
1356         cbUserName = (wcslen(pJob->pwszUserName) + 1) * sizeof(WCHAR);
1357 
1358     cbFileSize = sizeof(SHD_HEADER) + cbDatatype + cbDocumentName + cbDevMode + cbMachineName + cbNotifyName + cbPrinterDriver + cbPrinterName + cbPrintProcessor + cbPrintProcessorParameters + cbUserName;
1359 
1360     // Allocate memory for it.
1361     pShadowFile = DllAllocSplMem(cbFileSize);
1362     if (!pShadowFile)
1363     {
1364         ERR("DllAllocSplMem failed for file \"%S\"!\n", pwszFilePath);
1365         goto Cleanup;
1366     }
1367 
1368     // Fill out the shadow file header information.
1369     pShadowFile->dwSignature = SHD_WIN2003_SIGNATURE;
1370     pShadowFile->cbHeader = sizeof(SHD_HEADER);
1371 
1372     // Copy the values.
1373     pShadowFile->dwJobID = pJob->dwJobID;
1374     pShadowFile->dwPriority = pJob->dwPriority;
1375     pShadowFile->dwStartTime = pJob->dwStartTime;
1376     pShadowFile->dwTotalPages = pJob->dwTotalPages;
1377     pShadowFile->dwUntilTime = pJob->dwUntilTime;
1378     CopyMemory(&pShadowFile->stSubmitted, &pJob->stSubmitted, sizeof(SYSTEMTIME));
1379 
1380     // Determine the file size of the .SPL file
1381     wcscpy(wcsrchr(pwszFilePath, L'.'), L".SPL");
1382     hSPLFile = CreateFileW(pwszFilePath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
1383     if (hSPLFile != INVALID_HANDLE_VALUE)
1384         pShadowFile->dwSPLSize = GetFileSize(hSPLFile, NULL);
1385 
1386     // Add the extra values that are stored as offsets in the shadow file.
1387     // The first value begins right after the shadow file header.
1388     dwCurrentOffset = sizeof(SHD_HEADER);
1389 
1390     CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pwszDatatype, cbDatatype);
1391     pShadowFile->offDatatype = dwCurrentOffset;
1392     dwCurrentOffset += cbDatatype;
1393 
1394     CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pDevMode, cbDevMode);
1395     pShadowFile->offDevMode = dwCurrentOffset;
1396     dwCurrentOffset += cbDevMode;
1397 
1398     // offDriverName is only written, but automatically determined through offPrinterName when reading.
1399     CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pPrinter->pwszPrinterDriver, cbPrinterDriver);
1400     pShadowFile->offDriverName = dwCurrentOffset;
1401     dwCurrentOffset += cbPrinterDriver;
1402 
1403     CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pwszMachineName, cbMachineName);
1404     pShadowFile->offMachineName = dwCurrentOffset;
1405     dwCurrentOffset += cbMachineName;
1406 
1407     CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pPrinter->pwszPrinterName, cbPrinterName);
1408     pShadowFile->offPrinterName = dwCurrentOffset;
1409     dwCurrentOffset += cbPrinterName;
1410 
1411     CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pPrintProcessor->pwszName, cbPrintProcessor);
1412     pShadowFile->offPrintProcessor = dwCurrentOffset;
1413     dwCurrentOffset += cbPrintProcessor;
1414 
1415     // Copy the optional values.
1416     if (cbDocumentName)
1417     {
1418         CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pwszDocumentName, cbDocumentName);
1419         pShadowFile->offDocumentName = dwCurrentOffset;
1420         dwCurrentOffset += cbDocumentName;
1421     }
1422 
1423     if (cbNotifyName)
1424     {
1425         CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pwszNotifyName, cbNotifyName);
1426         pShadowFile->offNotifyName = dwCurrentOffset;
1427         dwCurrentOffset += cbNotifyName;
1428     }
1429 
1430     if (cbPrintProcessorParameters)
1431     {
1432         CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pwszPrintProcessorParameters, cbPrintProcessorParameters);
1433         pShadowFile->offPrintProcessorParameters = dwCurrentOffset;
1434         dwCurrentOffset += cbPrintProcessorParameters;
1435     }
1436 
1437     if (cbUserName)
1438     {
1439         CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pwszUserName, cbUserName);
1440         pShadowFile->offUserName = dwCurrentOffset;
1441         dwCurrentOffset += cbUserName;
1442     }
1443 
1444     // Write the file.
1445     if (!WriteFile(hSHDFile, pShadowFile, cbFileSize, &cbWritten, NULL))
1446     {
1447         ERR("WriteFile failed with error %lu for file \"%S\"!\n", GetLastError(), pwszFilePath);
1448         goto Cleanup;
1449     }
1450 
1451     bReturnValue = TRUE;
1452 
1453 Cleanup:
1454     if (pShadowFile)
1455         DllFreeSplMem(pShadowFile);
1456 
1457     if (hSHDFile != INVALID_HANDLE_VALUE)
1458         CloseHandle(hSHDFile);
1459 
1460     if (hSPLFile != INVALID_HANDLE_VALUE)
1461         CloseHandle(hSPLFile);
1462 
1463     return bReturnValue;
1464 }
1465 
1466 void
1467 FreeJob(PLOCAL_JOB pJob)
1468 {
1469     PWSTR pwszSHDFile;
1470 
1471     TRACE("FreeJob(%p)\n", pJob);
1472 
1473     // Remove the Job from both Job Lists.
1474     DeleteElementSkiplist(&pJob->pPrinter->JobList, pJob);
1475     DeleteElementSkiplist(&GlobalJobList, pJob);
1476 
1477     // Try to delete the corresponding .SHD file.
1478     pwszSHDFile = DllAllocSplMem(GetJobFilePath(L"SHD", 0, NULL));
1479     if (pwszSHDFile && GetJobFilePath(L"SHD", pJob->dwJobID, pwszSHDFile))
1480         DeleteFileW(pwszSHDFile);
1481 
1482     // Free memory for the mandatory fields.
1483     DllFreeSplMem(pJob->pDevMode);
1484     DllFreeSplStr(pJob->pwszDatatype);
1485     DllFreeSplStr(pJob->pwszDocumentName);
1486     DllFreeSplStr(pJob->pwszMachineName);
1487     DllFreeSplStr(pJob->pwszNotifyName);
1488     DllFreeSplStr(pJob->pwszUserName);
1489 
1490     // Free memory for the optional fields if they are present.
1491     if (pJob->pwszOutputFile)
1492         DllFreeSplStr(pJob->pwszOutputFile);
1493 
1494     if (pJob->pwszPrintProcessorParameters)
1495         DllFreeSplStr(pJob->pwszPrintProcessorParameters);
1496 
1497     if (pJob->pwszStatus)
1498         DllFreeSplStr(pJob->pwszStatus);
1499 
1500     // Finally free the job structure itself.
1501     DllFreeSplMem(pJob);
1502 }
1503