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