mirror of
https://github.com/reactos/reactos.git
synced 2025-01-03 21:09:19 +00:00
9393fc320e
Excluded: 3rd-party code (incl. wine) and most of the win32ss.
1487 lines
48 KiB
C
1487 lines
48 KiB
C
/*
|
|
* PROJECT: ReactOS Local Spooler
|
|
* LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
|
|
* PURPOSE: Functions for managing print jobs
|
|
* COPYRIGHT: Copyright 2015-2017 Colin Finck (colin@reactos.org)
|
|
*/
|
|
|
|
#include "precomp.h"
|
|
|
|
// Global Variables
|
|
SKIPLIST GlobalJobList;
|
|
|
|
// Local Variables
|
|
static DWORD _dwLastJobID;
|
|
|
|
// Local Constants
|
|
static DWORD dwJobInfo1Offsets[] = {
|
|
FIELD_OFFSET(JOB_INFO_1W, pPrinterName),
|
|
FIELD_OFFSET(JOB_INFO_1W, pMachineName),
|
|
FIELD_OFFSET(JOB_INFO_1W, pUserName),
|
|
FIELD_OFFSET(JOB_INFO_1W, pDocument),
|
|
FIELD_OFFSET(JOB_INFO_1W, pDatatype),
|
|
FIELD_OFFSET(JOB_INFO_1W, pStatus),
|
|
MAXDWORD
|
|
};
|
|
|
|
static DWORD dwJobInfo2Offsets[] = {
|
|
FIELD_OFFSET(JOB_INFO_2W, pPrinterName),
|
|
FIELD_OFFSET(JOB_INFO_2W, pMachineName),
|
|
FIELD_OFFSET(JOB_INFO_2W, pUserName),
|
|
FIELD_OFFSET(JOB_INFO_2W, pDocument),
|
|
FIELD_OFFSET(JOB_INFO_2W, pNotifyName),
|
|
FIELD_OFFSET(JOB_INFO_2W, pDatatype),
|
|
FIELD_OFFSET(JOB_INFO_2W, pPrintProcessor),
|
|
FIELD_OFFSET(JOB_INFO_2W, pParameters),
|
|
FIELD_OFFSET(JOB_INFO_2W, pDriverName),
|
|
FIELD_OFFSET(JOB_INFO_2W, pStatus),
|
|
MAXDWORD
|
|
};
|
|
|
|
|
|
/**
|
|
* @name _EqualStrings
|
|
*
|
|
* Returns whether two strings are equal.
|
|
* Unlike wcscmp, this function also works with NULL strings.
|
|
*
|
|
* @param pwszA
|
|
* First string to compare.
|
|
*
|
|
* @param pwszB
|
|
* Second string to compare.
|
|
*
|
|
* @return
|
|
* TRUE if the strings are equal, FALSE if they differ.
|
|
*/
|
|
static __inline BOOL
|
|
_EqualStrings(PCWSTR pwszA, PCWSTR pwszB)
|
|
{
|
|
if (!pwszA && !pwszB)
|
|
return TRUE;
|
|
|
|
if (pwszA && !pwszB)
|
|
return FALSE;
|
|
|
|
if (!pwszA && pwszB)
|
|
return FALSE;
|
|
|
|
return (wcscmp(pwszA, pwszB) == 0);
|
|
}
|
|
|
|
static BOOL
|
|
_GetNextJobID(PDWORD dwJobID)
|
|
{
|
|
++_dwLastJobID;
|
|
|
|
while (LookupElementSkiplist(&GlobalJobList, &_dwLastJobID, NULL))
|
|
{
|
|
// This ID is already taken. Try the next one.
|
|
++_dwLastJobID;
|
|
}
|
|
|
|
if (!IS_VALID_JOB_ID(_dwLastJobID))
|
|
{
|
|
ERR("Job ID %lu isn't valid!\n", _dwLastJobID);
|
|
return FALSE;
|
|
}
|
|
|
|
*dwJobID = _dwLastJobID;
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* @name _GlobalJobListCompareRoutine
|
|
*
|
|
* SKIPLIST_COMPARE_ROUTINE for the Global Job List.
|
|
* We need the Global Job List to check whether a Job ID is already in use. Consequently, this list is sorted by ID.
|
|
*/
|
|
static int WINAPI
|
|
_GlobalJobListCompareRoutine(PVOID FirstStruct, PVOID SecondStruct)
|
|
{
|
|
PLOCAL_JOB A = (PLOCAL_JOB)FirstStruct;
|
|
PLOCAL_JOB B = (PLOCAL_JOB)SecondStruct;
|
|
|
|
return A->dwJobID - B->dwJobID;
|
|
}
|
|
|
|
/**
|
|
* @name _PrinterJobListCompareRoutine
|
|
*
|
|
* SKIPLIST_COMPARE_ROUTINE for each Printer's Job List.
|
|
* Jobs in this list are sorted in the desired order of processing.
|
|
*/
|
|
static int WINAPI
|
|
_PrinterJobListCompareRoutine(PVOID FirstStruct, PVOID SecondStruct)
|
|
{
|
|
PLOCAL_JOB A = (PLOCAL_JOB)FirstStruct;
|
|
PLOCAL_JOB B = (PLOCAL_JOB)SecondStruct;
|
|
int iComparison;
|
|
FILETIME ftSubmittedA;
|
|
FILETIME ftSubmittedB;
|
|
|
|
// First compare the priorities to determine the order.
|
|
// The job with a higher priority shall come first.
|
|
iComparison = A->dwPriority - B->dwPriority;
|
|
if (iComparison != 0)
|
|
return iComparison;
|
|
|
|
// Both have the same priority, so go by creation time.
|
|
if (!SystemTimeToFileTime(&A->stSubmitted, &ftSubmittedA))
|
|
{
|
|
ERR("SystemTimeToFileTime failed for A with error %lu!\n", GetLastError());
|
|
return 0;
|
|
}
|
|
|
|
if (!SystemTimeToFileTime(&B->stSubmitted, &ftSubmittedB))
|
|
{
|
|
ERR("SystemTimeToFileTime failed for B with error %lu!\n", GetLastError());
|
|
return 0;
|
|
}
|
|
|
|
return CompareFileTime(&ftSubmittedA, &ftSubmittedB);
|
|
}
|
|
|
|
DWORD
|
|
GetJobFilePath(PCWSTR pwszExtension, DWORD dwJobID, PWSTR pwszOutput)
|
|
{
|
|
TRACE("GetJobFilePath(%S, %lu, %p)\n", pwszExtension, dwJobID, pwszOutput);
|
|
|
|
if (pwszOutput)
|
|
{
|
|
CopyMemory(pwszOutput, wszJobDirectory, cchJobDirectory * sizeof(WCHAR));
|
|
swprintf(&pwszOutput[cchJobDirectory], L"\\%05lu.%s", dwJobID, pwszExtension);
|
|
}
|
|
|
|
// pwszExtension may be L"SPL" or L"SHD", same length for both!
|
|
return (cchJobDirectory + sizeof("\\?????.SPL")) * sizeof(WCHAR);
|
|
}
|
|
|
|
BOOL
|
|
InitializeGlobalJobList(void)
|
|
{
|
|
const WCHAR wszPath[] = L"\\?????.SHD";
|
|
const DWORD cchPath = _countof(wszPath) - 1;
|
|
|
|
DWORD dwErrorCode;
|
|
DWORD dwJobID;
|
|
HANDLE hFind;
|
|
PLOCAL_JOB pJob = NULL;
|
|
PWSTR p;
|
|
WCHAR wszFullPath[MAX_PATH];
|
|
WIN32_FIND_DATAW FindData;
|
|
|
|
TRACE("InitializeGlobalJobList()\n");
|
|
|
|
// This one is incremented in _GetNextJobID.
|
|
_dwLastJobID = 0;
|
|
|
|
// Initialize an empty list for all jobs of all local printers.
|
|
// We will search it by Job ID (supply a pointer to a DWORD in LookupElementSkiplist).
|
|
InitializeSkiplist(&GlobalJobList, DllAllocSplMem, _GlobalJobListCompareRoutine, (PSKIPLIST_FREE_ROUTINE)DllFreeSplMem);
|
|
|
|
// Construct the full path search pattern.
|
|
CopyMemory(wszFullPath, wszJobDirectory, cchJobDirectory * sizeof(WCHAR));
|
|
CopyMemory(&wszFullPath[cchJobDirectory], wszPath, (cchPath + 1) * sizeof(WCHAR));
|
|
|
|
// Use the search pattern to look for unfinished jobs serialized in shadow files (.SHD)
|
|
hFind = FindFirstFileW(wszFullPath, &FindData);
|
|
if (hFind == INVALID_HANDLE_VALUE)
|
|
{
|
|
// No unfinished jobs found.
|
|
dwErrorCode = ERROR_SUCCESS;
|
|
goto Cleanup;
|
|
}
|
|
|
|
do
|
|
{
|
|
// Skip possible subdirectories.
|
|
if (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
|
continue;
|
|
|
|
// Extract the Job ID and verify the file name format at the same time.
|
|
// This includes all valid names (like "00005.SHD") and excludes invalid ones (like "10ABC.SHD").
|
|
dwJobID = wcstoul(FindData.cFileName, &p, 10);
|
|
if (!IS_VALID_JOB_ID(dwJobID))
|
|
continue;
|
|
|
|
if (wcsicmp(p, L".SHD") != 0)
|
|
continue;
|
|
|
|
// This shadow file has a valid name. Construct the full path and try to load it.
|
|
GetJobFilePath(L"SHD", dwJobID, wszFullPath);
|
|
pJob = ReadJobShadowFile(wszFullPath);
|
|
if (!pJob)
|
|
continue;
|
|
|
|
// Add it to the Global Job List.
|
|
if (!InsertElementSkiplist(&GlobalJobList, pJob))
|
|
{
|
|
dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
ERR("InsertElementSkiplist failed for job %lu for the GlobalJobList!\n", pJob->dwJobID);
|
|
goto Cleanup;
|
|
}
|
|
|
|
// Add it to the Printer's Job List.
|
|
if (!InsertElementSkiplist(&pJob->pPrinter->JobList, pJob))
|
|
{
|
|
dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
ERR("InsertElementSkiplist failed for job %lu for the Printer's Job List!\n", pJob->dwJobID);
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
while (FindNextFileW(hFind, &FindData));
|
|
|
|
dwErrorCode = ERROR_SUCCESS;
|
|
|
|
Cleanup:
|
|
// Outside the loop
|
|
if (hFind)
|
|
FindClose(hFind);
|
|
|
|
SetLastError(dwErrorCode);
|
|
return (dwErrorCode == ERROR_SUCCESS);
|
|
}
|
|
|
|
void
|
|
InitializePrinterJobList(PLOCAL_PRINTER pPrinter)
|
|
{
|
|
TRACE("InitializePrinterJobList(%p)\n", pPrinter);
|
|
|
|
// Initialize an empty list for this printer's jobs.
|
|
// This one is only for sorting the jobs. If you need to lookup a job, search the GlobalJobList by Job ID.
|
|
InitializeSkiplist(&pPrinter->JobList, DllAllocSplMem, _PrinterJobListCompareRoutine, (PSKIPLIST_FREE_ROUTINE)DllFreeSplMem);
|
|
}
|
|
|
|
DWORD WINAPI
|
|
CreateJob(PLOCAL_PRINTER_HANDLE pPrinterHandle)
|
|
{
|
|
const WCHAR wszDoubleBackslash[] = L"\\";
|
|
const DWORD cchDoubleBackslash = _countof(wszDoubleBackslash) - 1;
|
|
|
|
DWORD cchMachineName;
|
|
DWORD cchUserName;
|
|
DWORD dwErrorCode;
|
|
PLOCAL_JOB pJob;
|
|
RPC_BINDING_HANDLE hServerBinding = NULL;
|
|
RPC_WSTR pwszBinding = NULL;
|
|
RPC_WSTR pwszMachineName = NULL;
|
|
|
|
TRACE("CreateJob(%p)\n", pPrinterHandle);
|
|
|
|
// Create a new job.
|
|
pJob = DllAllocSplMem(sizeof(LOCAL_JOB));
|
|
if (!pJob)
|
|
{
|
|
dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
ERR("DllAllocSplMem failed!\n");
|
|
goto Cleanup;
|
|
}
|
|
|
|
// Reserve an ID for this job.
|
|
if (!_GetNextJobID(&pJob->dwJobID))
|
|
{
|
|
dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto Cleanup;
|
|
}
|
|
|
|
// Copy over defaults to the LOCAL_JOB structure.
|
|
pJob->pPrinter = pPrinterHandle->pPrinter;
|
|
pJob->pPrintProcessor = pPrinterHandle->pPrinter->pPrintProcessor;
|
|
pJob->dwPriority = DEF_PRIORITY;
|
|
pJob->dwStatus = JOB_STATUS_SPOOLING;
|
|
pJob->pwszDatatype = AllocSplStr(pPrinterHandle->pwszDatatype);
|
|
pJob->pwszDocumentName = AllocSplStr(wszDefaultDocumentName);
|
|
pJob->pDevMode = DuplicateDevMode(pPrinterHandle->pDevMode);
|
|
GetSystemTime(&pJob->stSubmitted);
|
|
|
|
// Get the user name for the Job.
|
|
cchUserName = UNLEN + 1;
|
|
pJob->pwszUserName = DllAllocSplMem(cchUserName * sizeof(WCHAR));
|
|
if (!GetUserNameW(pJob->pwszUserName, &cchUserName))
|
|
{
|
|
dwErrorCode = GetLastError();
|
|
ERR("GetUserNameW failed with error %lu!\n", dwErrorCode);
|
|
goto Cleanup;
|
|
}
|
|
|
|
// FIXME: For now, pwszNotifyName equals pwszUserName.
|
|
pJob->pwszNotifyName = AllocSplStr(pJob->pwszUserName);
|
|
|
|
// Get the name of the machine that submitted the Job over RPC.
|
|
dwErrorCode = RpcBindingServerFromClient(NULL, &hServerBinding);
|
|
if (dwErrorCode != RPC_S_OK)
|
|
{
|
|
ERR("RpcBindingServerFromClient failed with status %lu!\n", dwErrorCode);
|
|
goto Cleanup;
|
|
}
|
|
|
|
dwErrorCode = RpcBindingToStringBindingW(hServerBinding, &pwszBinding);
|
|
if (dwErrorCode != RPC_S_OK)
|
|
{
|
|
ERR("RpcBindingToStringBindingW failed with status %lu!\n", dwErrorCode);
|
|
goto Cleanup;
|
|
}
|
|
|
|
dwErrorCode = RpcStringBindingParseW(pwszBinding, NULL, NULL, &pwszMachineName, NULL, NULL);
|
|
if (dwErrorCode != RPC_S_OK)
|
|
{
|
|
ERR("RpcStringBindingParseW failed with status %lu!\n", dwErrorCode);
|
|
goto Cleanup;
|
|
}
|
|
|
|
cchMachineName = wcslen(pwszMachineName);
|
|
pJob->pwszMachineName = DllAllocSplMem((cchMachineName + cchDoubleBackslash + 1) * sizeof(WCHAR));
|
|
CopyMemory(pJob->pwszMachineName, wszDoubleBackslash, cchDoubleBackslash * sizeof(WCHAR));
|
|
CopyMemory(&pJob->pwszMachineName[cchDoubleBackslash], pwszMachineName, (cchMachineName + 1) * sizeof(WCHAR));
|
|
|
|
// Add the job to the Global Job List.
|
|
if (!InsertElementSkiplist(&GlobalJobList, pJob))
|
|
{
|
|
dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
ERR("InsertElementSkiplist failed for job %lu for the GlobalJobList!\n", pJob->dwJobID);
|
|
goto Cleanup;
|
|
}
|
|
|
|
// Add the job at the end of the Printer's Job List.
|
|
// As all new jobs are created with default priority, we can be sure that it would always be inserted at the end.
|
|
if (!InsertTailElementSkiplist(&pJob->pPrinter->JobList, pJob))
|
|
{
|
|
dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
ERR("InsertTailElementSkiplist failed for job %lu for the Printer's Job List!\n", pJob->dwJobID);
|
|
goto Cleanup;
|
|
}
|
|
|
|
// We were successful!
|
|
pPrinterHandle->bStartedDoc = TRUE;
|
|
pPrinterHandle->pJob = pJob;
|
|
dwErrorCode = ERROR_SUCCESS;
|
|
|
|
// Don't let the cleanup routine free this.
|
|
pJob = NULL;
|
|
|
|
Cleanup:
|
|
if (pJob)
|
|
DllFreeSplMem(pJob);
|
|
|
|
if (pwszMachineName)
|
|
RpcStringFreeW(&pwszMachineName);
|
|
|
|
if (pwszBinding)
|
|
RpcStringFreeW(&pwszBinding);
|
|
|
|
if (hServerBinding)
|
|
RpcBindingFree(&hServerBinding);
|
|
|
|
return dwErrorCode;
|
|
}
|
|
|
|
BOOL WINAPI
|
|
LocalAddJob(HANDLE hPrinter, DWORD Level, PBYTE pData, DWORD cbBuf, PDWORD pcbNeeded)
|
|
{
|
|
ADDJOB_INFO_1W AddJobInfo1;
|
|
DWORD dwErrorCode;
|
|
PLOCAL_HANDLE pHandle = (PLOCAL_HANDLE)hPrinter;
|
|
PLOCAL_PRINTER_HANDLE pPrinterHandle;
|
|
|
|
TRACE("LocalAddJob(%p, %lu, %p, %lu, %p)\n", hPrinter, Level, pData, cbBuf, pcbNeeded);
|
|
|
|
// Check if this is a printer handle.
|
|
if (pHandle->HandleType != HandleType_Printer)
|
|
{
|
|
dwErrorCode = ERROR_INVALID_HANDLE;
|
|
goto Cleanup;
|
|
}
|
|
|
|
pPrinterHandle = (PLOCAL_PRINTER_HANDLE)pHandle->pSpecificHandle;
|
|
|
|
// This handle must not have started a job yet!
|
|
if (pPrinterHandle->pJob)
|
|
{
|
|
dwErrorCode = ERROR_INVALID_HANDLE;
|
|
goto Cleanup;
|
|
}
|
|
|
|
// Check if this is the right structure level.
|
|
if (Level != 1)
|
|
{
|
|
dwErrorCode = ERROR_INVALID_LEVEL;
|
|
goto Cleanup;
|
|
}
|
|
|
|
// Check if the printer is set to do direct printing.
|
|
// The Job List isn't used in this case.
|
|
if (pPrinterHandle->pPrinter->dwAttributes & PRINTER_ATTRIBUTE_DIRECT)
|
|
{
|
|
dwErrorCode = ERROR_INVALID_ACCESS;
|
|
goto Cleanup;
|
|
}
|
|
|
|
// Check if the supplied buffer is large enough.
|
|
*pcbNeeded = sizeof(ADDJOB_INFO_1W) + GetJobFilePath(L"SPL", 0, NULL);
|
|
if (cbBuf < *pcbNeeded)
|
|
{
|
|
dwErrorCode = ERROR_INSUFFICIENT_BUFFER;
|
|
goto Cleanup;
|
|
}
|
|
|
|
// All requirements are met - create a new job.
|
|
dwErrorCode = CreateJob(pPrinterHandle);
|
|
if (dwErrorCode != ERROR_SUCCESS)
|
|
goto Cleanup;
|
|
|
|
// Mark that this job was started with AddJob (so that it can be scheduled for printing with ScheduleJob).
|
|
pPrinterHandle->pJob->bAddedJob = TRUE;
|
|
|
|
// Return a proper ADDJOB_INFO_1W structure.
|
|
AddJobInfo1.JobId = pPrinterHandle->pJob->dwJobID;
|
|
AddJobInfo1.Path = (PWSTR)(pData + sizeof(ADDJOB_INFO_1W));
|
|
|
|
CopyMemory(pData, &AddJobInfo1, sizeof(ADDJOB_INFO_1W));
|
|
GetJobFilePath(L"SPL", AddJobInfo1.JobId, AddJobInfo1.Path);
|
|
|
|
Cleanup:
|
|
SetLastError(dwErrorCode);
|
|
return (dwErrorCode == ERROR_SUCCESS);
|
|
}
|
|
|
|
|
|
static void
|
|
_LocalGetJobLevel1(PLOCAL_JOB pJob, PJOB_INFO_1W* ppJobInfo, PBYTE* ppJobInfoEnd, PDWORD pcbNeeded)
|
|
{
|
|
DWORD cbDatatype;
|
|
DWORD cbDocumentName = 0;
|
|
DWORD cbMachineName;
|
|
DWORD cbPrinterName;
|
|
DWORD cbStatus = 0;
|
|
DWORD cbUserName = 0;
|
|
PCWSTR pwszStrings[6];
|
|
|
|
// Calculate the string lengths.
|
|
if (!ppJobInfo)
|
|
{
|
|
cbDatatype = (wcslen(pJob->pwszDatatype) + 1) * sizeof(WCHAR);
|
|
cbMachineName = (wcslen(pJob->pwszMachineName) + 1) * sizeof(WCHAR);
|
|
cbPrinterName = (wcslen(pJob->pPrinter->pwszPrinterName) + 1) * sizeof(WCHAR);
|
|
|
|
// These values are optional.
|
|
if (pJob->pwszDocumentName)
|
|
cbDocumentName = (wcslen(pJob->pwszDocumentName) + 1) * sizeof(WCHAR);
|
|
|
|
if (pJob->pwszStatus)
|
|
cbStatus = (wcslen(pJob->pwszStatus) + 1) * sizeof(WCHAR);
|
|
|
|
if (pJob->pwszUserName)
|
|
cbUserName = (wcslen(pJob->pwszUserName) + 1) * sizeof(WCHAR);
|
|
|
|
*pcbNeeded += sizeof(JOB_INFO_1W) + cbDatatype + cbDocumentName + cbMachineName + cbPrinterName + cbStatus + cbUserName;
|
|
return;
|
|
}
|
|
|
|
// Set the general fields.
|
|
(*ppJobInfo)->JobId = pJob->dwJobID;
|
|
(*ppJobInfo)->Status = pJob->dwStatus;
|
|
(*ppJobInfo)->Priority = pJob->dwPriority;
|
|
(*ppJobInfo)->TotalPages = pJob->dwTotalPages;
|
|
(*ppJobInfo)->PagesPrinted = pJob->dwPagesPrinted;
|
|
CopyMemory(&(*ppJobInfo)->Submitted, &pJob->stSubmitted, sizeof(SYSTEMTIME));
|
|
|
|
// Position in JOB_INFO_1W is the 1-based index of the job in the processing queue.
|
|
// Retrieve this through the element index of the job in the Printer's Job List.
|
|
if (!LookupElementSkiplist(&pJob->pPrinter->JobList, pJob, &(*ppJobInfo)->Position))
|
|
{
|
|
ERR("pJob could not be located in the Printer's Job List!\n");
|
|
return;
|
|
}
|
|
|
|
// Make the index 1-based.
|
|
++(*ppJobInfo)->Position;
|
|
|
|
// Set the pPrinterName field.
|
|
pwszStrings[0] = pJob->pPrinter->pwszPrinterName;
|
|
|
|
// Set the pMachineName field.
|
|
pwszStrings[1] = pJob->pwszMachineName;
|
|
|
|
// Set the pUserName field.
|
|
pwszStrings[2] = pJob->pwszUserName;
|
|
|
|
// Set the pDocument field.
|
|
pwszStrings[3] = pJob->pwszDocumentName;
|
|
|
|
// Set the pDatatype field.
|
|
pwszStrings[4] = pJob->pwszDatatype;
|
|
|
|
// Set the pStatus field.
|
|
pwszStrings[5] = pJob->pwszStatus;
|
|
|
|
// Finally copy the structure and advance to the next one in the output buffer.
|
|
*ppJobInfoEnd = PackStrings(pwszStrings, (PBYTE)(*ppJobInfo), dwJobInfo1Offsets, *ppJobInfoEnd);
|
|
(*ppJobInfo)++;
|
|
}
|
|
|
|
static void
|
|
_LocalGetJobLevel2(PLOCAL_JOB pJob, PJOB_INFO_2W* ppJobInfo, PBYTE* ppJobInfoEnd, PDWORD pcbNeeded)
|
|
{
|
|
DWORD cbDatatype;
|
|
DWORD cbDevMode;
|
|
DWORD cbDocumentName = 0;
|
|
DWORD cbDriverName;
|
|
DWORD cbMachineName;
|
|
DWORD cbNotifyName = 0;
|
|
DWORD cbPrinterName;
|
|
DWORD cbPrintProcessor;
|
|
DWORD cbPrintProcessorParameters = 0;
|
|
DWORD cbStatus = 0;
|
|
DWORD cbUserName = 0;
|
|
FILETIME ftNow;
|
|
FILETIME ftSubmitted;
|
|
PCWSTR pwszStrings[10];
|
|
ULARGE_INTEGER uliNow;
|
|
ULARGE_INTEGER uliSubmitted;
|
|
|
|
// Calculate the string lengths.
|
|
cbDevMode = pJob->pDevMode->dmSize + pJob->pDevMode->dmDriverExtra;
|
|
|
|
if (!ppJobInfo)
|
|
{
|
|
cbDatatype = (wcslen(pJob->pwszDatatype) + 1) * sizeof(WCHAR);
|
|
cbDriverName = (wcslen(pJob->pPrinter->pwszPrinterDriver) + 1) * sizeof(WCHAR);
|
|
cbMachineName = (wcslen(pJob->pwszMachineName) + 1) * sizeof(WCHAR);
|
|
cbPrinterName = (wcslen(pJob->pPrinter->pwszPrinterName) + 1) * sizeof(WCHAR);
|
|
cbPrintProcessor = (wcslen(pJob->pPrintProcessor->pwszName) + 1) * sizeof(WCHAR);
|
|
|
|
// These values are optional.
|
|
if (pJob->pwszDocumentName)
|
|
cbDocumentName = (wcslen(pJob->pwszDocumentName) + 1) * sizeof(WCHAR);
|
|
|
|
if (pJob->pwszNotifyName)
|
|
cbNotifyName = (wcslen(pJob->pwszNotifyName) + 1) * sizeof(WCHAR);
|
|
|
|
if (pJob->pwszPrintProcessorParameters)
|
|
cbPrintProcessorParameters = (wcslen(pJob->pwszPrintProcessorParameters) + 1) * sizeof(WCHAR);
|
|
|
|
if (pJob->pwszStatus)
|
|
cbStatus = (wcslen(pJob->pwszStatus) + 1) * sizeof(WCHAR);
|
|
|
|
if (pJob->pwszUserName)
|
|
cbUserName = (wcslen(pJob->pwszUserName) + 1) * sizeof(WCHAR);
|
|
|
|
*pcbNeeded += sizeof(JOB_INFO_2W) + cbDatatype + cbDevMode + cbDocumentName + cbDriverName + cbMachineName + cbNotifyName + cbPrinterName + cbPrintProcessor + cbPrintProcessorParameters + cbStatus + cbUserName;
|
|
return;
|
|
}
|
|
|
|
// Set the general fields.
|
|
(*ppJobInfo)->JobId = pJob->dwJobID;
|
|
(*ppJobInfo)->Status = pJob->dwStatus;
|
|
(*ppJobInfo)->Priority = pJob->dwPriority;
|
|
(*ppJobInfo)->StartTime = pJob->dwStartTime;
|
|
(*ppJobInfo)->UntilTime = pJob->dwUntilTime;
|
|
(*ppJobInfo)->TotalPages = pJob->dwTotalPages;
|
|
(*ppJobInfo)->PagesPrinted = pJob->dwPagesPrinted;
|
|
CopyMemory(&(*ppJobInfo)->Submitted, &pJob->stSubmitted, sizeof(SYSTEMTIME));
|
|
|
|
// Time in JOB_INFO_2W is the number of milliseconds elapsed since the job was submitted. Calculate this time.
|
|
if (!SystemTimeToFileTime(&pJob->stSubmitted, &ftSubmitted))
|
|
{
|
|
ERR("SystemTimeToFileTime failed with error %lu!\n", GetLastError());
|
|
return;
|
|
}
|
|
|
|
GetSystemTimeAsFileTime(&ftNow);
|
|
uliSubmitted.LowPart = ftSubmitted.dwLowDateTime;
|
|
uliSubmitted.HighPart = ftSubmitted.dwHighDateTime;
|
|
uliNow.LowPart = ftNow.dwLowDateTime;
|
|
uliNow.HighPart = ftNow.dwHighDateTime;
|
|
(*ppJobInfo)->Time = (DWORD)((uliNow.QuadPart - uliSubmitted.QuadPart) / 10000);
|
|
|
|
// Position in JOB_INFO_2W is the 1-based index of the job in the processing queue.
|
|
// Retrieve this through the element index of the job in the Printer's Job List.
|
|
if (!LookupElementSkiplist(&pJob->pPrinter->JobList, pJob, &(*ppJobInfo)->Position))
|
|
{
|
|
ERR("pJob could not be located in the Printer's Job List!\n");
|
|
return;
|
|
}
|
|
|
|
// Make the index 1-based.
|
|
++(*ppJobInfo)->Position;
|
|
|
|
// FIXME!
|
|
FIXME("Setting pSecurityDescriptor and Size to 0 for now!\n");
|
|
(*ppJobInfo)->pSecurityDescriptor = NULL;
|
|
(*ppJobInfo)->Size = 0;
|
|
|
|
// Set the pDevMode field (and copy the DevMode).
|
|
*ppJobInfoEnd -= cbDevMode;
|
|
CopyMemory(*ppJobInfoEnd, pJob->pDevMode, cbDevMode);
|
|
(*ppJobInfo)->pDevMode = (PDEVMODEW)(*ppJobInfoEnd);
|
|
|
|
// Set the pPrinterName field.
|
|
pwszStrings[0] = pJob->pPrinter->pwszPrinterName;
|
|
|
|
// Set the pMachineName field.
|
|
pwszStrings[1] = pJob->pwszMachineName;
|
|
|
|
// Set the pUserName field.
|
|
pwszStrings[2] = pJob->pwszUserName;
|
|
|
|
// Set the pDocument field.
|
|
pwszStrings[3] = pJob->pwszDocumentName;
|
|
|
|
// Set the pNotifyName field.
|
|
pwszStrings[4] = pJob->pwszNotifyName;
|
|
|
|
// Set the pDatatype field.
|
|
pwszStrings[5] = pJob->pwszDatatype;
|
|
|
|
// Set the pPrintProcessor field.
|
|
pwszStrings[6] = pJob->pPrintProcessor->pwszName;
|
|
|
|
// Set the pParameters field.
|
|
pwszStrings[7] = pJob->pwszPrintProcessorParameters;
|
|
|
|
// Set the pDriverName field.
|
|
pwszStrings[8] = pJob->pPrinter->pwszPrinterDriver;
|
|
|
|
// Set the pStatus field.
|
|
pwszStrings[9] = pJob->pwszStatus;
|
|
|
|
// Finally copy the structure and advance to the next one in the output buffer.
|
|
*ppJobInfoEnd = PackStrings(pwszStrings, (PBYTE)(*ppJobInfo), dwJobInfo2Offsets, *ppJobInfoEnd);
|
|
(*ppJobInfo)++;
|
|
}
|
|
|
|
BOOL WINAPI
|
|
LocalGetJob(HANDLE hPrinter, DWORD JobId, DWORD Level, PBYTE pStart, DWORD cbBuf, LPDWORD pcbNeeded)
|
|
{
|
|
DWORD dwErrorCode;
|
|
PBYTE pEnd = &pStart[cbBuf];
|
|
PLOCAL_HANDLE pHandle;
|
|
PLOCAL_JOB pJob;
|
|
PLOCAL_PRINTER_HANDLE pPrinterHandle;
|
|
|
|
TRACE("LocalGetJob(%p, %lu, %lu, %p, %lu, %p)\n", hPrinter, JobId, Level, pStart, cbBuf, pcbNeeded);
|
|
|
|
// Check if this is a printer handle.
|
|
pHandle = (PLOCAL_HANDLE)hPrinter;
|
|
if (pHandle->HandleType != HandleType_Printer)
|
|
{
|
|
dwErrorCode = ERROR_INVALID_HANDLE;
|
|
goto Cleanup;
|
|
}
|
|
|
|
pPrinterHandle = (PLOCAL_PRINTER_HANDLE)pHandle->pSpecificHandle;
|
|
|
|
// Get the desired job.
|
|
pJob = LookupElementSkiplist(&GlobalJobList, &JobId, NULL);
|
|
if (!pJob || pJob->pPrinter != pPrinterHandle->pPrinter)
|
|
{
|
|
dwErrorCode = ERROR_INVALID_PARAMETER;
|
|
goto Cleanup;
|
|
}
|
|
|
|
if (Level > 2)
|
|
{
|
|
// The caller supplied an invalid level for GetJob.
|
|
dwErrorCode = ERROR_INVALID_LEVEL;
|
|
goto Cleanup;
|
|
}
|
|
|
|
// Count the required buffer size.
|
|
*pcbNeeded = 0;
|
|
|
|
if (Level == 1)
|
|
_LocalGetJobLevel1(pJob, NULL, NULL, pcbNeeded);
|
|
else if (Level == 2)
|
|
_LocalGetJobLevel2(pJob, NULL, NULL, pcbNeeded);
|
|
|
|
// Check if the supplied buffer is large enough.
|
|
if (cbBuf < *pcbNeeded)
|
|
{
|
|
dwErrorCode = ERROR_INSUFFICIENT_BUFFER;
|
|
goto Cleanup;
|
|
}
|
|
|
|
// Copy over the Job information.
|
|
pEnd = &pStart[*pcbNeeded];
|
|
|
|
if (Level == 1)
|
|
_LocalGetJobLevel1(pJob, (PJOB_INFO_1W*)&pStart, &pEnd, NULL);
|
|
else if (Level == 2)
|
|
_LocalGetJobLevel2(pJob, (PJOB_INFO_2W*)&pStart, &pEnd, NULL);
|
|
|
|
dwErrorCode = ERROR_SUCCESS;
|
|
|
|
Cleanup:
|
|
SetLastError(dwErrorCode);
|
|
return (dwErrorCode == ERROR_SUCCESS);
|
|
}
|
|
|
|
static DWORD
|
|
_LocalSetJobLevel1(PLOCAL_PRINTER_HANDLE pPrinterHandle, PLOCAL_JOB pJob, PJOB_INFO_1W pJobInfo)
|
|
{
|
|
DWORD dwErrorCode;
|
|
|
|
// First check the validity of the input before changing anything.
|
|
if (!FindDatatype(pJob->pPrintProcessor, pJobInfo->pDatatype))
|
|
{
|
|
dwErrorCode = ERROR_INVALID_DATATYPE;
|
|
goto Cleanup;
|
|
}
|
|
|
|
// Check if the datatype has changed.
|
|
if (!_EqualStrings(pJob->pwszDatatype, pJobInfo->pDatatype))
|
|
{
|
|
// Use the new value.
|
|
if (!ReallocSplStr(&pJob->pwszDatatype, pJobInfo->pDatatype))
|
|
{
|
|
dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
// Check if the document name has changed. An empty string is permitted here!
|
|
if (!_EqualStrings(pJob->pwszDocumentName, pJobInfo->pDocument))
|
|
{
|
|
// Use the new value.
|
|
if (!ReallocSplStr(&pJob->pwszDocumentName, pJobInfo->pDocument))
|
|
{
|
|
dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
// Check if the status message has changed. An empty string is permitted here!
|
|
if (!_EqualStrings(pJob->pwszStatus, pJobInfo->pStatus))
|
|
{
|
|
// Use the new value.
|
|
if (!ReallocSplStr(&pJob->pwszStatus, pJobInfo->pStatus))
|
|
{
|
|
dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
// Check if the user name has changed. An empty string is permitted here!
|
|
if (!_EqualStrings(pJob->pwszUserName, pJobInfo->pUserName))
|
|
{
|
|
// The new user name doesn't need to exist, so no additional verification is required.
|
|
|
|
// Use the new value.
|
|
if (!ReallocSplStr(&pJob->pwszUserName, pJobInfo->pUserName))
|
|
{
|
|
dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
// Check if the priority has changed.
|
|
if (pJob->dwPriority != pJobInfo->Priority && IS_VALID_PRIORITY(pJobInfo->Priority))
|
|
{
|
|
// Set the new priority.
|
|
pJob->dwPriority = pJobInfo->Priority;
|
|
|
|
// Remove and reinsert the job in the Printer's Job List.
|
|
// The Compare function will be used to find the right position now considering the new priority.
|
|
DeleteElementSkiplist(&pJob->pPrinter->JobList, pJob);
|
|
InsertElementSkiplist(&pJob->pPrinter->JobList, pJob);
|
|
}
|
|
|
|
// Check if the status flags have changed.
|
|
if (pJob->dwStatus != pJobInfo->Status)
|
|
{
|
|
// Only add status flags that make sense.
|
|
if (pJobInfo->Status & JOB_STATUS_PAUSED)
|
|
pJob->dwStatus |= JOB_STATUS_PAUSED;
|
|
|
|
if (pJobInfo->Status & JOB_STATUS_ERROR)
|
|
pJob->dwStatus |= JOB_STATUS_ERROR;
|
|
|
|
if (pJobInfo->Status & JOB_STATUS_OFFLINE)
|
|
pJob->dwStatus |= JOB_STATUS_OFFLINE;
|
|
|
|
if (pJobInfo->Status & JOB_STATUS_PAPEROUT)
|
|
pJob->dwStatus |= JOB_STATUS_PAPEROUT;
|
|
}
|
|
|
|
dwErrorCode = ERROR_SUCCESS;
|
|
|
|
Cleanup:
|
|
return dwErrorCode;
|
|
}
|
|
|
|
static DWORD
|
|
_LocalSetJobLevel2(PLOCAL_PRINTER_HANDLE pPrinterHandle, PLOCAL_JOB pJob, PJOB_INFO_2W pJobInfo)
|
|
{
|
|
DWORD dwErrorCode;
|
|
PLOCAL_PRINT_PROCESSOR pPrintProcessor;
|
|
|
|
// First check the validity of the input before changing anything.
|
|
pPrintProcessor = FindPrintProcessor(pJobInfo->pPrintProcessor);
|
|
if (!pPrintProcessor)
|
|
{
|
|
dwErrorCode = ERROR_UNKNOWN_PRINTPROCESSOR;
|
|
goto Cleanup;
|
|
}
|
|
|
|
if (!FindDatatype(pPrintProcessor, pJobInfo->pDatatype))
|
|
{
|
|
dwErrorCode = ERROR_INVALID_DATATYPE;
|
|
goto Cleanup;
|
|
}
|
|
|
|
// Check if the datatype has changed.
|
|
if (!_EqualStrings(pJob->pwszDatatype, pJobInfo->pDatatype))
|
|
{
|
|
// Use the new value.
|
|
if (!ReallocSplStr(&pJob->pwszDatatype, pJobInfo->pDatatype))
|
|
{
|
|
dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
// Check if the document name has changed. An empty string is permitted here!
|
|
if (!_EqualStrings(pJob->pwszDocumentName, pJobInfo->pDocument))
|
|
{
|
|
// Use the new value.
|
|
if (!ReallocSplStr(&pJob->pwszDocumentName, pJobInfo->pDocument))
|
|
{
|
|
dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
// Check if the notify name has changed. An empty string is permitted here!
|
|
if (!_EqualStrings(pJob->pwszNotifyName, pJobInfo->pNotifyName))
|
|
{
|
|
// The new notify name doesn't need to exist, so no additional verification is required.
|
|
|
|
// Use the new value.
|
|
if (!ReallocSplStr(&pJob->pwszNotifyName, pJobInfo->pNotifyName))
|
|
{
|
|
dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
// Check if the Print Processor Parameters have changed. An empty string is permitted here!
|
|
if (!_EqualStrings(pJob->pwszPrintProcessorParameters, pJobInfo->pParameters))
|
|
{
|
|
// Use the new value.
|
|
if (!ReallocSplStr(&pJob->pwszPrintProcessorParameters, pJobInfo->pParameters))
|
|
{
|
|
dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
// Check if the Status Message has changed. An empty string is permitted here!
|
|
if (!_EqualStrings(pJob->pwszStatus, pJobInfo->pStatus))
|
|
{
|
|
// Use the new value.
|
|
if (!ReallocSplStr(&pJob->pwszStatus, pJobInfo->pStatus))
|
|
{
|
|
dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
// Check if the user name has changed. An empty string is permitted here!
|
|
if (!_EqualStrings(pJob->pwszUserName, pJobInfo->pUserName))
|
|
{
|
|
// The new user name doesn't need to exist, so no additional verification is required.
|
|
|
|
// Use the new value.
|
|
if (!ReallocSplStr(&pJob->pwszUserName, pJobInfo->pUserName))
|
|
{
|
|
dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
// Check if the priority has changed.
|
|
if (pJob->dwPriority != pJobInfo->Priority && IS_VALID_PRIORITY(pJobInfo->Priority))
|
|
{
|
|
// Set the new priority.
|
|
pJob->dwPriority = pJobInfo->Priority;
|
|
|
|
// Remove and reinsert the job in the Printer's Job List.
|
|
// The Compare function will be used to find the right position now considering the new priority.
|
|
DeleteElementSkiplist(&pJob->pPrinter->JobList, pJob);
|
|
InsertElementSkiplist(&pJob->pPrinter->JobList, pJob);
|
|
}
|
|
|
|
// Check if the status flags have changed.
|
|
if (pJob->dwStatus != pJobInfo->Status)
|
|
{
|
|
// Only add status flags that make sense.
|
|
if (pJobInfo->Status & JOB_STATUS_PAUSED)
|
|
pJob->dwStatus |= JOB_STATUS_PAUSED;
|
|
|
|
if (pJobInfo->Status & JOB_STATUS_ERROR)
|
|
pJob->dwStatus |= JOB_STATUS_ERROR;
|
|
|
|
if (pJobInfo->Status & JOB_STATUS_OFFLINE)
|
|
pJob->dwStatus |= JOB_STATUS_OFFLINE;
|
|
|
|
if (pJobInfo->Status & JOB_STATUS_PAPEROUT)
|
|
pJob->dwStatus |= JOB_STATUS_PAPEROUT;
|
|
}
|
|
|
|
dwErrorCode = ERROR_SUCCESS;
|
|
|
|
Cleanup:
|
|
return dwErrorCode;
|
|
}
|
|
|
|
BOOL WINAPI
|
|
LocalSetJob(HANDLE hPrinter, DWORD JobId, DWORD Level, PBYTE pJobInfo, DWORD Command)
|
|
{
|
|
DWORD dwErrorCode = ERROR_SUCCESS;
|
|
PLOCAL_HANDLE pHandle;
|
|
PLOCAL_JOB pJob;
|
|
PLOCAL_PRINTER_HANDLE pPrinterHandle;
|
|
WCHAR wszFullPath[MAX_PATH];
|
|
|
|
TRACE("LocalSetJob(%p, %lu, %lu, %p, %lu)\n", hPrinter, JobId, Level, pJobInfo, Command);
|
|
|
|
// Check if this is a printer handle.
|
|
pHandle = (PLOCAL_HANDLE)hPrinter;
|
|
if (!pHandle || pHandle->HandleType != HandleType_Printer)
|
|
{
|
|
dwErrorCode = ERROR_INVALID_HANDLE;
|
|
goto Cleanup;
|
|
}
|
|
|
|
pPrinterHandle = (PLOCAL_PRINTER_HANDLE)pHandle->pSpecificHandle;
|
|
|
|
// Get the desired job.
|
|
pJob = LookupElementSkiplist(&GlobalJobList, &JobId, NULL);
|
|
if (!pJob || pJob->pPrinter != pPrinterHandle->pPrinter)
|
|
{
|
|
dwErrorCode = ERROR_INVALID_PARAMETER;
|
|
goto Cleanup;
|
|
}
|
|
|
|
// Set new job information if a valid level was given.
|
|
if (Level == 1)
|
|
dwErrorCode = _LocalSetJobLevel1(pPrinterHandle, pJob, (PJOB_INFO_1W)pJobInfo);
|
|
else if (Level == 2)
|
|
dwErrorCode = _LocalSetJobLevel2(pPrinterHandle, pJob, (PJOB_INFO_2W)pJobInfo);
|
|
|
|
if (dwErrorCode != ERROR_SUCCESS)
|
|
goto Cleanup;
|
|
|
|
// If we do spooled printing, the job information is written down into a shadow file.
|
|
if (!(pPrinterHandle->pPrinter->dwAttributes & PRINTER_ATTRIBUTE_DIRECT))
|
|
{
|
|
// Write the job data into the shadow file.
|
|
GetJobFilePath(L"SHD", JobId, wszFullPath);
|
|
WriteJobShadowFile(wszFullPath, pJob);
|
|
}
|
|
|
|
// Perform an additional command if desired.
|
|
if (Command)
|
|
{
|
|
if (Command == JOB_CONTROL_SENT_TO_PRINTER)
|
|
{
|
|
// This indicates the end of the Print Job.
|
|
|
|
// Cancel the Job at the Print Processor.
|
|
if (pJob->hPrintProcessor)
|
|
pJob->pPrintProcessor->pfnControlPrintProcessor(pJob->hPrintProcessor, JOB_CONTROL_CANCEL);
|
|
|
|
FreeJob(pJob);
|
|
|
|
// TODO: All open handles associated with the job need to be invalidated.
|
|
// This certainly needs handle tracking...
|
|
}
|
|
else
|
|
{
|
|
ERR("Unimplemented SetJob Command: %lu!\n", Command);
|
|
}
|
|
}
|
|
|
|
Cleanup:
|
|
SetLastError(dwErrorCode);
|
|
return (dwErrorCode == ERROR_SUCCESS);
|
|
}
|
|
|
|
BOOL WINAPI
|
|
LocalEnumJobs(HANDLE hPrinter, DWORD FirstJob, DWORD NoJobs, DWORD Level, PBYTE pStart, DWORD cbBuf, LPDWORD pcbNeeded, LPDWORD pcReturned)
|
|
{
|
|
DWORD dwErrorCode;
|
|
DWORD i;
|
|
PBYTE pEnd;
|
|
PLOCAL_HANDLE pHandle;
|
|
PLOCAL_JOB pJob;
|
|
PSKIPLIST_NODE pFirstJobNode;
|
|
PSKIPLIST_NODE pNode;
|
|
PLOCAL_PRINTER_HANDLE pPrinterHandle;
|
|
|
|
TRACE("LocalEnumJobs(%p, %lu, %lu, %lu, %p, %lu, %p, %p)\n", hPrinter, FirstJob, NoJobs, Level, pStart, cbBuf, pcbNeeded, pcReturned);
|
|
|
|
// Check if this is a printer handle.
|
|
pHandle = (PLOCAL_HANDLE)hPrinter;
|
|
if (pHandle->HandleType != HandleType_Printer)
|
|
{
|
|
dwErrorCode = ERROR_INVALID_HANDLE;
|
|
goto Cleanup;
|
|
}
|
|
|
|
pPrinterHandle = (PLOCAL_PRINTER_HANDLE)pHandle->pSpecificHandle;
|
|
|
|
// Check the level.
|
|
if (Level > 2)
|
|
{
|
|
dwErrorCode = ERROR_INVALID_LEVEL;
|
|
goto Cleanup;
|
|
}
|
|
|
|
// Begin counting.
|
|
*pcbNeeded = 0;
|
|
*pcReturned = 0;
|
|
|
|
// Lookup the node of the first job requested by the caller in the Printer's Job List.
|
|
pFirstJobNode = LookupNodeByIndexSkiplist(&pPrinterHandle->pPrinter->JobList, FirstJob);
|
|
|
|
// Count the required buffer size and the number of jobs.
|
|
i = 0;
|
|
pNode = pFirstJobNode;
|
|
|
|
while (i < NoJobs && pNode)
|
|
{
|
|
pJob = (PLOCAL_JOB)pNode->Element;
|
|
|
|
if (Level == 1)
|
|
_LocalGetJobLevel1(pJob, NULL, NULL, pcbNeeded);
|
|
else if (Level == 2)
|
|
_LocalGetJobLevel2(pJob, NULL, NULL, pcbNeeded);
|
|
|
|
// We stop either when there are no more jobs in the list or when the caller didn't request more, whatever comes first.
|
|
i++;
|
|
pNode = pNode->Next[0];
|
|
}
|
|
|
|
// Check if the supplied buffer is large enough.
|
|
if (cbBuf < *pcbNeeded)
|
|
{
|
|
dwErrorCode = ERROR_INSUFFICIENT_BUFFER;
|
|
goto Cleanup;
|
|
}
|
|
|
|
// Copy over the Job information.
|
|
i = 0;
|
|
pNode = pFirstJobNode;
|
|
pEnd = &pStart[*pcbNeeded];
|
|
|
|
while (i < NoJobs && pNode)
|
|
{
|
|
pJob = (PLOCAL_JOB)pNode->Element;
|
|
|
|
if (Level == 1)
|
|
_LocalGetJobLevel1(pJob, (PJOB_INFO_1W*)&pStart, &pEnd, NULL);
|
|
else if (Level == 2)
|
|
_LocalGetJobLevel2(pJob, (PJOB_INFO_2W*)&pStart, &pEnd, NULL);
|
|
|
|
// We stop either when there are no more jobs in the list or when the caller didn't request more, whatever comes first.
|
|
i++;
|
|
pNode = pNode->Next[0];
|
|
}
|
|
|
|
*pcReturned = i;
|
|
dwErrorCode = ERROR_SUCCESS;
|
|
|
|
Cleanup:
|
|
SetLastError(dwErrorCode);
|
|
return (dwErrorCode == ERROR_SUCCESS);
|
|
}
|
|
|
|
BOOL WINAPI
|
|
LocalScheduleJob(HANDLE hPrinter, DWORD dwJobID)
|
|
{
|
|
DWORD dwAttributes;
|
|
DWORD dwErrorCode;
|
|
HANDLE hThread;
|
|
PLOCAL_JOB pJob;
|
|
PLOCAL_HANDLE pHandle = (PLOCAL_HANDLE)hPrinter;
|
|
PLOCAL_PRINTER_HANDLE pPrinterHandle;
|
|
WCHAR wszFullPath[MAX_PATH];
|
|
|
|
TRACE("LocalScheduleJob(%p, %lu)\n", hPrinter, dwJobID);
|
|
|
|
// Check if this is a printer handle.
|
|
if (pHandle->HandleType != HandleType_Printer)
|
|
{
|
|
dwErrorCode = ERROR_INVALID_HANDLE;
|
|
goto Cleanup;
|
|
}
|
|
|
|
pPrinterHandle = (PLOCAL_PRINTER_HANDLE)pHandle->pSpecificHandle;
|
|
|
|
// Check if the Job ID is valid.
|
|
pJob = LookupElementSkiplist(&GlobalJobList, &dwJobID, NULL);
|
|
if (!pJob || pJob->pPrinter != pPrinterHandle->pPrinter)
|
|
{
|
|
dwErrorCode = ERROR_INVALID_PARAMETER;
|
|
goto Cleanup;
|
|
}
|
|
|
|
// Check if this Job was started with AddJob.
|
|
if (!pJob->bAddedJob)
|
|
{
|
|
dwErrorCode = ERROR_SPL_NO_ADDJOB;
|
|
goto Cleanup;
|
|
}
|
|
|
|
// Construct the full path to the spool file.
|
|
GetJobFilePath(L"SPL", dwJobID, wszFullPath);
|
|
|
|
// Check if it exists.
|
|
dwAttributes = GetFileAttributesW(wszFullPath);
|
|
if (dwAttributes == INVALID_FILE_ATTRIBUTES || dwAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
|
{
|
|
dwErrorCode = ERROR_SPOOL_FILE_NOT_FOUND;
|
|
goto Cleanup;
|
|
}
|
|
|
|
// Spooling is finished at this point.
|
|
pJob->dwStatus &= ~JOB_STATUS_SPOOLING;
|
|
|
|
// Write the job data into the shadow file.
|
|
wcscpy(wcsrchr(wszFullPath, L'.'), L".SHD");
|
|
WriteJobShadowFile(wszFullPath, pJob);
|
|
|
|
// Create the thread for performing the printing process.
|
|
hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)PrintingThreadProc, pJob, 0, NULL);
|
|
if (!hThread)
|
|
{
|
|
dwErrorCode = GetLastError();
|
|
ERR("CreateThread failed with error %lu!\n", dwErrorCode);
|
|
goto Cleanup;
|
|
}
|
|
|
|
// We don't need the thread handle. Keeping it open blocks the thread from terminating.
|
|
CloseHandle(hThread);
|
|
|
|
// ScheduleJob has done its job. The rest happens inside the thread.
|
|
dwErrorCode = ERROR_SUCCESS;
|
|
|
|
Cleanup:
|
|
SetLastError(dwErrorCode);
|
|
return (dwErrorCode == ERROR_SUCCESS);
|
|
}
|
|
|
|
PLOCAL_JOB
|
|
ReadJobShadowFile(PCWSTR pwszFilePath)
|
|
{
|
|
DWORD cbFileSize;
|
|
DWORD cbRead;
|
|
HANDLE hFile = INVALID_HANDLE_VALUE;
|
|
PLOCAL_JOB pJob;
|
|
PLOCAL_JOB pReturnValue = NULL;
|
|
PLOCAL_PRINTER pPrinter;
|
|
PLOCAL_PRINT_PROCESSOR pPrintProcessor;
|
|
PSHD_HEADER pShadowFile = NULL;
|
|
PWSTR pwszPrinterName;
|
|
PWSTR pwszPrintProcessor;
|
|
|
|
TRACE("ReadJobShadowFile(%S)\n", pwszFilePath);
|
|
|
|
// Try to open the file.
|
|
hFile = CreateFileW(pwszFilePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
|
|
if (hFile == INVALID_HANDLE_VALUE)
|
|
{
|
|
ERR("CreateFileW failed with error %lu for file \"%S\"!\n", GetLastError(), pwszFilePath);
|
|
goto Cleanup;
|
|
}
|
|
|
|
// Get its file size (small enough for a single DWORD) and allocate memory for all of it.
|
|
cbFileSize = GetFileSize(hFile, NULL);
|
|
pShadowFile = DllAllocSplMem(cbFileSize);
|
|
if (!pShadowFile)
|
|
{
|
|
ERR("DllAllocSplMem failed for file \"%S\"!\n", pwszFilePath);
|
|
goto Cleanup;
|
|
}
|
|
|
|
// Read the entire file.
|
|
if (!ReadFile(hFile, pShadowFile, cbFileSize, &cbRead, NULL))
|
|
{
|
|
ERR("ReadFile failed with error %lu for file \"%S\"!\n", GetLastError(), pwszFilePath);
|
|
goto Cleanup;
|
|
}
|
|
|
|
// Check signature and header size.
|
|
if (pShadowFile->dwSignature != SHD_WIN2003_SIGNATURE || pShadowFile->cbHeader != sizeof(SHD_HEADER))
|
|
{
|
|
ERR("Signature or Header Size mismatch for file \"%S\"!\n", pwszFilePath);
|
|
goto Cleanup;
|
|
}
|
|
|
|
// Retrieve the associated printer from the list.
|
|
pwszPrinterName = (PWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offPrinterName);
|
|
pPrinter = LookupElementSkiplist(&PrinterList, &pwszPrinterName, NULL);
|
|
if (!pPrinter)
|
|
{
|
|
ERR("Shadow file \"%S\" references a non-existing printer \"%S\"!\n", pwszFilePath, pwszPrinterName);
|
|
goto Cleanup;
|
|
}
|
|
|
|
// Retrieve the associated Print Processor from the list.
|
|
pwszPrintProcessor = (PWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offPrintProcessor);
|
|
pPrintProcessor = FindPrintProcessor(pwszPrintProcessor);
|
|
if (!pPrintProcessor)
|
|
{
|
|
ERR("Shadow file \"%S\" references a non-existing Print Processor \"%S\"!\n", pwszFilePath, pwszPrintProcessor);
|
|
goto Cleanup;
|
|
}
|
|
|
|
// Create a new job structure and copy over the relevant fields.
|
|
pJob = DllAllocSplMem(sizeof(LOCAL_JOB));
|
|
if (!pJob)
|
|
{
|
|
ERR("DllAllocSplMem failed for file \"%S\"!\n", pwszFilePath);
|
|
goto Cleanup;
|
|
}
|
|
|
|
pJob->dwJobID = pShadowFile->dwJobID;
|
|
pJob->dwPriority = pShadowFile->dwPriority;
|
|
pJob->dwStartTime = pShadowFile->dwStartTime;
|
|
pJob->dwTotalPages = pShadowFile->dwTotalPages;
|
|
pJob->dwUntilTime = pShadowFile->dwUntilTime;
|
|
pJob->pPrinter = pPrinter;
|
|
pJob->pPrintProcessor = pPrintProcessor;
|
|
pJob->pDevMode = DuplicateDevMode((PDEVMODEW)((ULONG_PTR)pShadowFile + pShadowFile->offDevMode));
|
|
pJob->pwszDatatype = AllocSplStr((PCWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offDatatype));
|
|
pJob->pwszMachineName = AllocSplStr((PCWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offMachineName));
|
|
CopyMemory(&pJob->stSubmitted, &pShadowFile->stSubmitted, sizeof(SYSTEMTIME));
|
|
|
|
// Copy the optional values.
|
|
if (pShadowFile->offDocumentName)
|
|
pJob->pwszDocumentName = AllocSplStr((PCWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offDocumentName));
|
|
|
|
if (pShadowFile->offNotifyName)
|
|
pJob->pwszNotifyName = AllocSplStr((PCWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offNotifyName));
|
|
|
|
if (pShadowFile->offPrintProcessorParameters)
|
|
pJob->pwszPrintProcessorParameters = AllocSplStr((PCWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offPrintProcessorParameters));
|
|
|
|
if (pShadowFile->offUserName)
|
|
pJob->pwszUserName = AllocSplStr((PCWSTR)((ULONG_PTR)pShadowFile + pShadowFile->offUserName));
|
|
|
|
// Jobs read from shadow files were always added using AddJob.
|
|
pJob->bAddedJob = TRUE;
|
|
|
|
pReturnValue = pJob;
|
|
|
|
Cleanup:
|
|
if (pShadowFile)
|
|
DllFreeSplMem(pShadowFile);
|
|
|
|
if (hFile != INVALID_HANDLE_VALUE)
|
|
CloseHandle(hFile);
|
|
|
|
return pReturnValue;
|
|
}
|
|
|
|
BOOL
|
|
WriteJobShadowFile(PWSTR pwszFilePath, const PLOCAL_JOB pJob)
|
|
{
|
|
BOOL bReturnValue = FALSE;
|
|
DWORD cbDatatype = (wcslen(pJob->pwszDatatype) + 1) * sizeof(WCHAR);
|
|
DWORD cbDevMode = pJob->pDevMode->dmSize + pJob->pDevMode->dmDriverExtra;
|
|
DWORD cbDocumentName = 0;
|
|
DWORD cbFileSize;
|
|
DWORD cbMachineName = (wcslen(pJob->pwszMachineName) + 1) * sizeof(WCHAR);
|
|
DWORD cbNotifyName = 0;
|
|
DWORD cbPrinterDriver = (wcslen(pJob->pPrinter->pwszPrinterDriver) + 1) * sizeof(WCHAR);
|
|
DWORD cbPrinterName = (wcslen(pJob->pPrinter->pwszPrinterName) + 1) * sizeof(WCHAR);
|
|
DWORD cbPrintProcessor = (wcslen(pJob->pPrintProcessor->pwszName) + 1) * sizeof(WCHAR);
|
|
DWORD cbPrintProcessorParameters = 0;
|
|
DWORD cbUserName = 0;
|
|
DWORD cbWritten;
|
|
DWORD dwCurrentOffset;
|
|
HANDLE hSHDFile = INVALID_HANDLE_VALUE;
|
|
HANDLE hSPLFile = INVALID_HANDLE_VALUE;
|
|
PSHD_HEADER pShadowFile = NULL;
|
|
|
|
TRACE("WriteJobShadowFile(%S, %p)\n", pwszFilePath, pJob);
|
|
|
|
// Try to open the SHD file.
|
|
hSHDFile = CreateFileW(pwszFilePath, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, 0, NULL);
|
|
if (hSHDFile == INVALID_HANDLE_VALUE)
|
|
{
|
|
ERR("CreateFileW failed with error %lu for file \"%S\"!\n", GetLastError(), pwszFilePath);
|
|
goto Cleanup;
|
|
}
|
|
|
|
// Calculate the lengths of the optional values and the total size of the shadow file.
|
|
if (pJob->pwszDocumentName)
|
|
cbDocumentName = (wcslen(pJob->pwszDocumentName) + 1) * sizeof(WCHAR);
|
|
|
|
if (pJob->pwszNotifyName)
|
|
cbNotifyName = (wcslen(pJob->pwszNotifyName) + 1) * sizeof(WCHAR);
|
|
|
|
if (pJob->pwszPrintProcessorParameters)
|
|
cbPrintProcessorParameters = (wcslen(pJob->pwszPrintProcessorParameters) + 1) * sizeof(WCHAR);
|
|
|
|
if (pJob->pwszUserName)
|
|
cbUserName = (wcslen(pJob->pwszUserName) + 1) * sizeof(WCHAR);
|
|
|
|
cbFileSize = sizeof(SHD_HEADER) + cbDatatype + cbDocumentName + cbDevMode + cbMachineName + cbNotifyName + cbPrinterDriver + cbPrinterName + cbPrintProcessor + cbPrintProcessorParameters + cbUserName;
|
|
|
|
// Allocate memory for it.
|
|
pShadowFile = DllAllocSplMem(cbFileSize);
|
|
if (!pShadowFile)
|
|
{
|
|
ERR("DllAllocSplMem failed for file \"%S\"!\n", pwszFilePath);
|
|
goto Cleanup;
|
|
}
|
|
|
|
// Fill out the shadow file header information.
|
|
pShadowFile->dwSignature = SHD_WIN2003_SIGNATURE;
|
|
pShadowFile->cbHeader = sizeof(SHD_HEADER);
|
|
|
|
// Copy the values.
|
|
pShadowFile->dwJobID = pJob->dwJobID;
|
|
pShadowFile->dwPriority = pJob->dwPriority;
|
|
pShadowFile->dwStartTime = pJob->dwStartTime;
|
|
pShadowFile->dwTotalPages = pJob->dwTotalPages;
|
|
pShadowFile->dwUntilTime = pJob->dwUntilTime;
|
|
CopyMemory(&pShadowFile->stSubmitted, &pJob->stSubmitted, sizeof(SYSTEMTIME));
|
|
|
|
// Determine the file size of the .SPL file
|
|
wcscpy(wcsrchr(pwszFilePath, L'.'), L".SPL");
|
|
hSPLFile = CreateFileW(pwszFilePath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
|
|
if (hSPLFile != INVALID_HANDLE_VALUE)
|
|
pShadowFile->dwSPLSize = GetFileSize(hSPLFile, NULL);
|
|
|
|
// Add the extra values that are stored as offsets in the shadow file.
|
|
// The first value begins right after the shadow file header.
|
|
dwCurrentOffset = sizeof(SHD_HEADER);
|
|
|
|
CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pwszDatatype, cbDatatype);
|
|
pShadowFile->offDatatype = dwCurrentOffset;
|
|
dwCurrentOffset += cbDatatype;
|
|
|
|
CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pDevMode, cbDevMode);
|
|
pShadowFile->offDevMode = dwCurrentOffset;
|
|
dwCurrentOffset += cbDevMode;
|
|
|
|
// offDriverName is only written, but automatically determined through offPrinterName when reading.
|
|
CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pPrinter->pwszPrinterDriver, cbPrinterDriver);
|
|
pShadowFile->offDriverName = dwCurrentOffset;
|
|
dwCurrentOffset += cbPrinterDriver;
|
|
|
|
CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pwszMachineName, cbMachineName);
|
|
pShadowFile->offMachineName = dwCurrentOffset;
|
|
dwCurrentOffset += cbMachineName;
|
|
|
|
CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pPrinter->pwszPrinterName, cbPrinterName);
|
|
pShadowFile->offPrinterName = dwCurrentOffset;
|
|
dwCurrentOffset += cbPrinterName;
|
|
|
|
CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pPrintProcessor->pwszName, cbPrintProcessor);
|
|
pShadowFile->offPrintProcessor = dwCurrentOffset;
|
|
dwCurrentOffset += cbPrintProcessor;
|
|
|
|
// Copy the optional values.
|
|
if (cbDocumentName)
|
|
{
|
|
CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pwszDocumentName, cbDocumentName);
|
|
pShadowFile->offDocumentName = dwCurrentOffset;
|
|
dwCurrentOffset += cbDocumentName;
|
|
}
|
|
|
|
if (cbNotifyName)
|
|
{
|
|
CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pwszNotifyName, cbNotifyName);
|
|
pShadowFile->offNotifyName = dwCurrentOffset;
|
|
dwCurrentOffset += cbNotifyName;
|
|
}
|
|
|
|
if (cbPrintProcessorParameters)
|
|
{
|
|
CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pwszPrintProcessorParameters, cbPrintProcessorParameters);
|
|
pShadowFile->offPrintProcessorParameters = dwCurrentOffset;
|
|
dwCurrentOffset += cbPrintProcessorParameters;
|
|
}
|
|
|
|
if (cbUserName)
|
|
{
|
|
CopyMemory((PBYTE)pShadowFile + dwCurrentOffset, pJob->pwszUserName, cbUserName);
|
|
pShadowFile->offUserName = dwCurrentOffset;
|
|
dwCurrentOffset += cbUserName;
|
|
}
|
|
|
|
// Write the file.
|
|
if (!WriteFile(hSHDFile, pShadowFile, cbFileSize, &cbWritten, NULL))
|
|
{
|
|
ERR("WriteFile failed with error %lu for file \"%S\"!\n", GetLastError(), pwszFilePath);
|
|
goto Cleanup;
|
|
}
|
|
|
|
bReturnValue = TRUE;
|
|
|
|
Cleanup:
|
|
if (pShadowFile)
|
|
DllFreeSplMem(pShadowFile);
|
|
|
|
if (hSHDFile != INVALID_HANDLE_VALUE)
|
|
CloseHandle(hSHDFile);
|
|
|
|
if (hSPLFile != INVALID_HANDLE_VALUE)
|
|
CloseHandle(hSPLFile);
|
|
|
|
return bReturnValue;
|
|
}
|
|
|
|
void
|
|
FreeJob(PLOCAL_JOB pJob)
|
|
{
|
|
PWSTR pwszSHDFile;
|
|
|
|
TRACE("FreeJob(%p)\n", pJob);
|
|
|
|
// Remove the Job from both Job Lists.
|
|
DeleteElementSkiplist(&pJob->pPrinter->JobList, pJob);
|
|
DeleteElementSkiplist(&GlobalJobList, pJob);
|
|
|
|
// Try to delete the corresponding .SHD file.
|
|
pwszSHDFile = DllAllocSplMem(GetJobFilePath(L"SHD", 0, NULL));
|
|
if (pwszSHDFile && GetJobFilePath(L"SHD", pJob->dwJobID, pwszSHDFile))
|
|
DeleteFileW(pwszSHDFile);
|
|
|
|
// Free memory for the mandatory fields.
|
|
DllFreeSplMem(pJob->pDevMode);
|
|
DllFreeSplStr(pJob->pwszDatatype);
|
|
DllFreeSplStr(pJob->pwszDocumentName);
|
|
DllFreeSplStr(pJob->pwszMachineName);
|
|
DllFreeSplStr(pJob->pwszNotifyName);
|
|
DllFreeSplStr(pJob->pwszUserName);
|
|
|
|
// Free memory for the optional fields if they are present.
|
|
if (pJob->pwszOutputFile)
|
|
DllFreeSplStr(pJob->pwszOutputFile);
|
|
|
|
if (pJob->pwszPrintProcessorParameters)
|
|
DllFreeSplStr(pJob->pwszPrintProcessorParameters);
|
|
|
|
if (pJob->pwszStatus)
|
|
DllFreeSplStr(pJob->pwszStatus);
|
|
|
|
// Finally free the job structure itself.
|
|
DllFreeSplMem(pJob);
|
|
}
|