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