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