Exemple #1
0
void
FreeJob(PLOCAL_JOB pJob)
{
    PWSTR pwszSHDFile;
    
    // 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);
}
Exemple #2
0
BOOL WINAPI
OpenPrinterW(PWSTR pPrinterName, PHANDLE phPrinter, PPRINTER_DEFAULTSW pDefault)
{
    BOOL bReturnValue;
    HANDLE hPrinter;
    PLIST_ENTRY pEntry;
    PSPOOLSS_PRINTER_HANDLE pHandle;
    PSPOOLSS_PRINT_PROVIDER pPrintProvider;

    // Sanity checks.
    if (!pPrinterName || !phPrinter)
    {
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }

    // Loop through all Print Providers to find one able to open this Printer.
    for (pEntry = PrintProviderList.Flink; pEntry != &PrintProviderList; pEntry = pEntry->Flink)
    {
        pPrintProvider = CONTAINING_RECORD(pEntry, SPOOLSS_PRINT_PROVIDER, Entry);

        bReturnValue = pPrintProvider->PrintProvider.fpOpenPrinter(pPrinterName, &hPrinter, pDefault);
        if (bReturnValue == ROUTER_SUCCESS)
        {
            // This Print Provider has opened this Printer.
            // Store this information and return a handle.
            pHandle = DllAllocSplMem(sizeof(SPOOLSS_PRINTER_HANDLE));
            if (!pHandle)
            {
                SetLastError(ERROR_NOT_ENOUGH_MEMORY);
                ERR("DllAllocSplMem failed with error %lu!\n", GetLastError());
                return FALSE;
            }

            pHandle->pPrintProvider = pPrintProvider;
            pHandle->hPrinter = hPrinter;
            *phPrinter = (HANDLE)pHandle;

            SetLastError(ERROR_SUCCESS);
            return TRUE;
        }
        else if (bReturnValue == ROUTER_STOP_ROUTING)
        {
            ERR("A Print Provider returned ROUTER_STOP_ROUTING for Printer \"%S\"!\n", pPrinterName);
            return FALSE;
        }
    }

    // We found no Print Provider able to open this Printer.
    return FALSE;
}
Exemple #3
0
BOOL WINAPI
LocalmonXcvOpenPort(HANDLE hMonitor, PCWSTR pwszObject, ACCESS_MASK GrantedAccess, PHANDLE phXcv)
{
    DWORD cbObject = 0;
    DWORD dwErrorCode;
    PLOCALMON_HANDLE pLocalmon = (PLOCALMON_HANDLE)hMonitor;
    PLOCALMON_XCV pXcv;

    TRACE("LocalmonXcvOpenPort(%p, %S, %lu, %p)\n", hMonitor, pwszObject, GrantedAccess, phXcv);

    // Sanity checks
    if (!pLocalmon || !phXcv)
    {
        dwErrorCode = ERROR_INVALID_PARAMETER;
        goto Cleanup;
    }

    if (pwszObject)
        cbObject = (wcslen(pwszObject) + 1) * sizeof(WCHAR);

    // Create a new LOCALMON_XCV structure and fill the relevant fields.
    pXcv = DllAllocSplMem(sizeof(LOCALMON_XCV) + cbObject);
    if (!pXcv)
    {
        dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
        ERR("DllAllocSplMem failed with error %lu!\n", GetLastError());
        goto Cleanup;
    }

    pXcv->pLocalmon = pLocalmon;
    pXcv->GrantedAccess = GrantedAccess;

    if (cbObject)
    {
        pXcv->pwszObject = (PWSTR)((PBYTE)pXcv + sizeof(LOCALMON_XCV));
        CopyMemory(pXcv->pwszObject, pwszObject, cbObject);
    }

    InsertTailList(&pLocalmon->XcvHandles, &pXcv->Entry);

    // Return it as the Xcv handle.
    *phXcv = (HANDLE)pXcv;
    dwErrorCode = ERROR_SUCCESS;

Cleanup:
    SetLastError(dwErrorCode);
    return (dwErrorCode == ERROR_SUCCESS);
}
Exemple #4
0
BOOL
InitializePortList()
{
    BOOL bReturnValue;
    DWORD cbNeeded;
    DWORD cbPortName;
    DWORD dwErrorCode;
    DWORD dwReturned;
    DWORD i;
    PLOCAL_PORT pPort;
    PLOCAL_PRINT_MONITOR pPrintMonitor;
    PLIST_ENTRY pEntry;
    PPORT_INFO_1W p;
    PPORT_INFO_1W pPortInfo1 = NULL;

    // Initialize an empty list for our Ports.
    InitializeListHead(&_PortList);

    // Loop through all Print Monitors.
    for (pEntry = PrintMonitorList.Flink; pEntry != &PrintMonitorList; pEntry = pEntry->Flink)
    {
        // Cleanup from the previous run.
        if (pPortInfo1)
        {
            DllFreeSplMem(pPortInfo1);
            pPortInfo1 = NULL;
        }

        pPrintMonitor = CONTAINING_RECORD(pEntry, LOCAL_PRINT_MONITOR, Entry);

        // Determine the required buffer size for EnumPorts.
        if (pPrintMonitor->bIsLevel2)
            bReturnValue = ((PMONITOR2)pPrintMonitor->pMonitor)->pfnEnumPorts(pPrintMonitor->hMonitor, NULL, 1, NULL, 0, &cbNeeded, &dwReturned);
        else
            bReturnValue = ((LPMONITOREX)pPrintMonitor->pMonitor)->Monitor.pfnEnumPorts(NULL, 1, NULL, 0, &cbNeeded, &dwReturned);

        // Check the returned error code.
        if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
        {
            ERR("Print Monitor \"%S\" failed with error %lu on EnumPorts!\n", pPrintMonitor->pwszName, GetLastError());
            continue;
        }

        // Allocate a buffer large enough.
        pPortInfo1 = DllAllocSplMem(cbNeeded);
        if (!pPortInfo1)
        {
            dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
            ERR("DllAllocSplMem failed with error %lu!\n", GetLastError());
            goto Cleanup;
        }

        // Get the ports handled by this monitor.
        if (pPrintMonitor->bIsLevel2)
            bReturnValue = ((PMONITOR2)pPrintMonitor->pMonitor)->pfnEnumPorts(pPrintMonitor->hMonitor, NULL, 1, (PBYTE)pPortInfo1, cbNeeded, &cbNeeded, &dwReturned);
        else
            bReturnValue = ((LPMONITOREX)pPrintMonitor->pMonitor)->Monitor.pfnEnumPorts(NULL, 1, (PBYTE)pPortInfo1, cbNeeded, &cbNeeded, &dwReturned);

        // Check the return value.
        if (!bReturnValue)
        {
            ERR("Print Monitor \"%S\" failed with error %lu on EnumPorts!\n", pPrintMonitor->pwszName, GetLastError());
            continue;
        }

        // Loop through all returned ports.
        p = pPortInfo1;

        for (i = 0; i < dwReturned; i++)
        {
            cbPortName = (wcslen(p->pName) + 1) * sizeof(WCHAR);

            // Create a new LOCAL_PORT structure for it.
            pPort = DllAllocSplMem(sizeof(LOCAL_PORT) + cbPortName);
            if (!pPort)
            {
                dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
                ERR("DllAllocSplMem failed with error %lu!\n", GetLastError());
                goto Cleanup;
            }

            pPort->pPrintMonitor = pPrintMonitor;
            pPort->pwszName = (PWSTR)((PBYTE)pPort + sizeof(LOCAL_PORT));
            CopyMemory(pPort->pwszName, p->pName, cbPortName);

            // Insert it into the list and advance to the next port.
            InsertTailList(&_PortList, &pPort->Entry);
            p++;
        }
    }

    dwErrorCode = ERROR_SUCCESS;

Cleanup:
    // Inside the loop
    if (pPortInfo1)
        DllFreeSplMem(pPortInfo1);

    SetLastError(dwErrorCode);
    return (dwErrorCode == ERROR_SUCCESS);
}
Exemple #5
0
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;

    // Create a new job.
    pJob = DllAllocSplMem(sizeof(LOCAL_JOB));
    if (!pJob)
    {
        dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
        ERR("DllAllocSplMem failed with error %lu!\n", GetLastError());
        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;
}
Exemple #6
0
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;

    // 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 with error %lu for file \"%S\"!\n", GetLastError(), 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;
}
Exemple #7
0
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;

    // 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 with error %lu for file \"%S\"!\n", GetLastError(), 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 with error %lu for file \"%S\"!\n", GetLastError(), 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;
}