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