windows-nt/Source/XPSP1/NT/printscan/print/spooler/localspl/dsupdate.cxx
2020-09-26 16:20:57 +08:00

674 lines
21 KiB
C++

/*++
Copyright (c) 1996 Microsoft Corporation
Abstract:
This module provides functionality for ADs within spooler
Author:
Steve Wilson (NT) July 1997
Revision History:
--*/
#include <precomp.h>
#pragma hdrstop
#include "dsprune.hxx"
#include "clusspl.h"
DWORD WINAPI DsUpdate(PDWORD pdwDelay);
VOID ValidateDsProperties(PINIPRINTER pIniPrinter);
HANDLE ghUpdateNow = NULL;
extern DWORD dwUpdateFlag;
extern "C" HANDLE ghDsUpdateThread;
extern "C" DWORD gdwDsUpdateThreadId;
HANDLE ghDsUpdateThread = NULL;
DWORD gdwDsUpdateThreadId;
BOOL gbInDomain;
BOOL gdwLogDsEvents = LOG_ALL_EVENTS;
DWORD
SpawnDsUpdate(
DWORD dwDelay
)
{
DWORD dwError;
PDWORD pdwDelay;
SplInSem();
if (!ghDsUpdateThread && !dwUpgradeFlag) {
if (pdwDelay = (PDWORD) AllocSplMem(sizeof(DWORD))) {
*pdwDelay = dwDelay;
if(!(ghDsUpdateThread = CreateThread(NULL,
0,
(LPTHREAD_START_ROUTINE) DsUpdate,
(PVOID) pdwDelay,
0,
&gdwDsUpdateThreadId))) {
dwError = GetLastError();
FreeSplMem(pdwDelay);
} else {
CloseHandle(ghDsUpdateThread);
dwError = ERROR_SUCCESS;
}
} else {
dwError = GetLastError();
}
} else {
if (ghUpdateNow)
SetEvent(ghUpdateNow);
dwError = ERROR_BUSY;
}
return dwError;
}
BOOL
DsUpdatePrinter(
HANDLE h,
PINIPRINTER pIniPrinter
)
{
HANDLE hPrinter;
PWSTR pszPrinterName = NULL;
PDSUPDATEDATA pData = (PDSUPDATEDATA)h;
DWORD dwAction;
PRINTER_DEFAULTS Defaults;
SplInSem();
Defaults.pDatatype = NULL;
Defaults.pDevMode = NULL;
Defaults.DesiredAccess = PRINTER_ACCESS_ADMINISTER;
// dwAction and DsKeyUpdateForeground are the foreground (client) thread's requested
// action and state. DsKeyUpdate is the background (DsUpdate) thread's state
// Foreground state always has priority over background, so sync up if needed.
// When both the foreground and background actions are 0, then the publish state
// is up to date.
DBGMSG( DBG_EXEC, ("\nBACKGROUND UPDATE: Printer \"%ws\", dwAction = %x, DsKeyUpdate = %x, DsKeyUpdateForeground = %x, Attributes = %x\n",
pIniPrinter->pName, pIniPrinter->dwAction, pIniPrinter->DsKeyUpdate, pIniPrinter->DsKeyUpdateForeground, pIniPrinter->Attributes ) );
if (dwAction = pIniPrinter->dwAction) {
pIniPrinter->dwAction = 0; // set to 0 so we know when client thread sets it
pIniPrinter->DsKeyUpdate |= pIniPrinter->DsKeyUpdateForeground;
pIniPrinter->DsKeyUpdateForeground = 0;
//
// Mask off possible conflicts in DS_KEY_PUBLISH, REPUBLISH, and UNPUBLISH actions
//
pIniPrinter->DsKeyUpdate &= ~(DS_KEY_PUBLISH | DS_KEY_REPUBLISH | DS_KEY_UNPUBLISH);
if (dwAction == DSPRINT_PUBLISH) {
pIniPrinter->DsKeyUpdate |= DS_KEY_PUBLISH;
} else if (dwAction == DSPRINT_REPUBLISH) {
pIniPrinter->DsKeyUpdate = DS_KEY_REPUBLISH;
} else if (dwAction == DSPRINT_UNPUBLISH) {
pIniPrinter->DsKeyUpdate = DS_KEY_UNPUBLISH;
}
} else {
//
// If DS_KEY_UPDATE_DRIVER is set by AddForm or DeleteForm foreground threads
// in DsUpdateDriverKeys(). We have to copy that to pIniPrinter->DsKeyUpdate
// even if dwAction is not set.
//
pIniPrinter->DsKeyUpdate |= (pIniPrinter->DsKeyUpdateForeground & DS_KEY_UPDATE_DRIVER);
pIniPrinter->DsKeyUpdateForeground &= ~DS_KEY_UPDATE_DRIVER;
}
UpdatePrinterIni(pIniPrinter, UPDATE_DS_ONLY);
if (pIniPrinter->DsKeyUpdate) {
pData->bAllUpdated = FALSE;
LeaveSplSem();
// If this printer is pending deletion, delete by GUID because OpenPrinter
// will fail.
//
// We check pIniPrinter->bDsPendingDeletion instead of
// pIniPrinter->Status ~ PRINTER_PENDING_DELETION because PRINTER_PENDING_DELETION
// is set in InternalDeletePrinter after it leaves Spooler CS. This gives the DS thread
// a chance to check PRINTER_PENDING_DELETION flag before it is set.
// The reason we cannot set PRINTER_PENDING_DELETION before we leave Spooler CS is because
// we want OpenPrinter calls that come from PrinterDriverEvent to succeed.
// See how InternalDeletePrinter sets the printer on PRINTER_NO_MORE_JOBS to reject incoming jobs
// but accept OpenPrinter calls.
//
if (pIniPrinter->bDsPendingDeletion) {
//
// This will DECPRINTERREF to match DECPRINTERREF in SplDeletePrinter.
// UnpublishByGUID won't call DeletePrinterCheck when it DECPRINTERREF.
// RunForEachPrinter will do that.
//
UnpublishByGUID(pIniPrinter);
} else {
EnterSplSem();
pszPrinterName = pszGetPrinterName( pIniPrinter, TRUE, NULL );
LeaveSplSem();
if (pszPrinterName) {
if(LocalOpenPrinter(pszPrinterName, &hPrinter, &Defaults) == ROUTER_SUCCESS) {
EnterSplSem();
if( (pIniPrinter->DsKeyUpdate & DS_KEY_UPDATE_DRIVER) &&
!(pIniPrinter->DsKeyUpdate & DS_KEY_REPUBLISH)) {
//
// We update the Registry with the Form data and then
// set DS_KEY_PUBLISH. Eventually SetPrinterDS() would
// update the DS.
UpdateDsDriverKey(hPrinter);
pIniPrinter->DsKeyUpdate &= ~DS_KEY_UPDATE_DRIVER;
if(pIniPrinter->Attributes & PRINTER_ATTRIBUTE_PUBLISHED) {
pIniPrinter->DsKeyUpdate |= DS_KEY_PUBLISH;
}
}
if (pIniPrinter->DsKeyUpdate & DS_KEY_REPUBLISH) {
SetPrinterDs(hPrinter, DSPRINT_UNPUBLISH, TRUE);
// Unpublishing & republishing printer doesn't rewrite DS keys,
// so on Republish, we should also rewrite DS keys so we know
// everything is synched up
SplDeletePrinterKey(hPrinter, SPLDS_DRIVER_KEY);
SplDeletePrinterKey(hPrinter, SPLDS_SPOOLER_KEY);
UpdateDsDriverKey(hPrinter);
UpdateDsSpoolerKey(hPrinter, 0xffffffff);
SetPrinterDs(hPrinter, DSPRINT_PUBLISH, TRUE);
} else if (pIniPrinter->DsKeyUpdate & DS_KEY_UNPUBLISH) {
SetPrinterDs(hPrinter, DSPRINT_UNPUBLISH, TRUE);
} else if (pIniPrinter->DsKeyUpdate & DS_KEY_PUBLISH) {
SetPrinterDs(hPrinter, DSPRINT_PUBLISH, TRUE);
} else {
//
// If the printer is not published and DS_KEY_UPDATE_DRIVER
// is set, then we will reach here and
// DsKeyUpdate will have the DS_KEY_DRIVER set by
// UpdateDsDriverKey(). So we just clear it here.
//
pIniPrinter->DsKeyUpdate = 0;
UpdatePrinterIni(pIniPrinter, UPDATE_DS_ONLY);
}
LeaveSplSem();
SplClosePrinter(hPrinter);
}
FreeSplStr(pszPrinterName);
}
}
EnterSplSem();
if (pIniPrinter->DsKeyUpdate) {
pData->bSleep = TRUE; // Only sleep if the DS is down
gdwLogDsEvents = LOG_INFO | LOG_SUCCESS; // Only report Warnings & Errors for first printer failures
}
}
return TRUE;
}
BOOL
DsUpdateSpooler(
HANDLE h,
PINISPOOLER pIniSpooler
)
{
//
// Only do this for local spoolers.
//
if (pIniSpooler->SpoolerFlags & SPL_TYPE_LOCAL) {
RunForEachPrinter(pIniSpooler, h, DsUpdatePrinter);
}
return TRUE;
}
DWORD
WINAPI
DsUpdate(
PDWORD pdwDelay
)
{
DWORD dwSleep;
DWORD dwError = ERROR_SUCCESS;
HRESULT hr;
DSUPDATEDATA Data;
DWORD dwWaitTime = 0;
SplOutSem();
ghUpdateNow = CreateEvent((LPSECURITY_ATTRIBUTES) NULL, FALSE, FALSE, NULL);
if (ghUpdateNow) {
if (RegisterGPNotification(ghUpdateNow, TRUE)) {
hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
if (SUCCEEDED(hr)) {
DBGMSG( DBG_EXEC, ("************** ENTER DSUPDATE\n" ) );
//
// Force initial sleep to be within 1 sec & 5 minutes
//
dwSleep = (*pdwDelay >= 1 && *pdwDelay < 300) ? *pdwDelay : 1;
FreeSplMem(pdwDelay);
if (dwSleep > 1) {
Sleep(dwSleep*1000);
}
Data.dwSleepTime = dwSleep * 1000;
gdwLogDsEvents = LOG_ALL_EVENTS;
EnterSplSem();
//
// The logic of this loop changed from between Win2K to Whistler.
// On Win2k, the DS background thread used to die if there were no DS
// actions to be made.If the "Check published state" was changed,
// Spooler couldn't reflect this change unless another DS action
// created the DS thread.
// On Whistler we keep it alive but sleeping.
//
do {
Data.bAllUpdated = TRUE;
Data.bSleep = FALSE;
//
// Run through and update each printer.
//
RunForEachSpooler(&Data, DsUpdateSpooler);
//
// If all printers are updated or the DS is not responding,
// then put the DS thread to sleep.
//
if (Data.bAllUpdated || Data.bSleep) {
dwWaitTime = GetDSSleepInterval(&Data);
//
// If the VerifyPublishedState Policy is set, then we need to verify
// we're published based on the schedule specified by the policy.
// However, if updating is failing, we should revert to the background
// updating schedule rather than the "check published state" schedule.
//
LeaveSplSem();
DBGMSG( DBG_EXEC, ("BACKGROUND UPDATE SLEEP: %d\n", dwWaitTime));
dwError = WaitForSingleObject(ghUpdateNow, dwWaitTime);
if (dwError == WAIT_FAILED) {
//
// There is one case when the DS thread can still die.
// If this wait fails, we don't want the thread indefinitely spinning.
//
DBGMSG(DBG_WARNING, ("VerifyPublishedState Wait Failed: %d\n", GetLastError()));
dwError = GetLastError();
break;
}
EnterSplSem();
//
// If the "Check published state" policy is enabled,CheckPublishedPrinters will force the DS update
// for published printers.If the object doesn't exist in DS, the printer is republished.(see GetPublishPoint)
// The call returns 0 if there is the "check published state" policy
// is disabled or it is enabled and there are no published printers.
// We could actually break the loop and kill the thread in the case when we don't have published printers,
// because we don't care for policy changes.
//
CheckPublishedPrinters();
}
} while (TRUE);
LeaveSplSem();
SplOutSem();
CoUninitialize();
} else {
dwError = HRESULT_CODE(hr);
}
UnregisterGPNotification(ghUpdateNow);
} else {
dwError = GetLastError();
}
CloseHandle(ghUpdateNow);
ghUpdateNow = NULL;
} else {
dwError = GetLastError();
}
DBGMSG(DBG_EXEC, ("************ LEAVE DSUPDATE\n"));
ghDsUpdateThread = NULL;
return dwError;
}
VOID
ValidateDsProperties(
PINIPRINTER pIniPrinter
)
{
// Properties not generated by driver, spooler, or user should be checked here.
// Currently, that means only the Server name. Note that we republish the object
// if the server name changes, so the UNCName property gets fixed as well.
DWORD dwError = ERROR_SUCCESS;
BOOL dwAction = 0;
HRESULT hr;
SplInSem();
PINISPOOLER pIniSpooler = pIniPrinter->pIniSpooler;
WCHAR pData[INTERNET_MAX_HOST_NAME_LENGTH + 3];
DWORD cbNeeded;
DWORD dwType;
struct hostent *pHostEnt;
HKEY hKey = NULL;
dwError = OpenPrinterKey(pIniPrinter, KEY_READ | KEY_WRITE, &hKey, SPLDS_SPOOLER_KEY, TRUE);
if (dwError != ERROR_SUCCESS) {
pIniPrinter->DsKeyUpdate = DS_KEY_REPUBLISH;
return;
}
INCPRINTERREF(pIniPrinter);
LeaveSplSem();
// Set to publish by default. This will verify that the printer is published, but won't
// write anything if there's nothing to update.
pIniPrinter->DsKeyUpdate |= DS_KEY_PUBLISH;
// Check Server Name
//
// If we were unable to find a DNS name for this machine, then gethostbyname failed.
// In this case, let's just treat this attribute as being correct. If other attributes
// cause us to update, we'll probably fail because the network is down or something. But
// then again, we also might succeed.
//
if (pIniSpooler->pszFullMachineName) {
cbNeeded = (INTERNET_MAX_HOST_NAME_LENGTH + 3)*sizeof *pData;
dwType = REG_SZ;
dwError = SplRegQueryValue( hKey,
SPLDS_SERVER_NAME,
&dwType,
(PBYTE) pData,
&cbNeeded,
pIniSpooler);
if (dwError != ERROR_SUCCESS || wcscmp((PWSTR) pData, pIniSpooler->pszFullMachineName)) {
pIniPrinter->DsKeyUpdate = DS_KEY_REPUBLISH;
goto error;
}
}
// Check Short Server Name
cbNeeded = (INTERNET_MAX_HOST_NAME_LENGTH + 3)*sizeof *pData;
dwType = REG_SZ;
dwError = SplRegQueryValue( hKey,
SPLDS_SHORT_SERVER_NAME,
&dwType,
(PBYTE) pData,
&cbNeeded,
pIniSpooler);
if (dwError != ERROR_SUCCESS || wcscmp((PWSTR) pData, pIniSpooler->pMachineName + 2)) {
pIniPrinter->DsKeyUpdate = DS_KEY_REPUBLISH;
goto error;
}
// Check Version Number
cbNeeded = (INTERNET_MAX_HOST_NAME_LENGTH + 3)*sizeof *pData;
dwType = REG_DWORD;
dwError = SplRegQueryValue( hKey,
SPLDS_VERSION_NUMBER,
&dwType,
(PBYTE) pData,
&cbNeeded,
pIniSpooler);
if (dwError != ERROR_SUCCESS || *((PDWORD) pData) != DS_PRINTQUEUE_VERSION_WIN2000) {
pIniPrinter->DsKeyUpdate = DS_KEY_REPUBLISH;
goto error;
}
// Check Immortal flag
cbNeeded = (INTERNET_MAX_HOST_NAME_LENGTH + 3)*sizeof *pData;
dwType = REG_DWORD;
dwError = SplRegQueryValue( hKey,
SPLDS_FLAGS,
&dwType,
(PBYTE) pData,
&cbNeeded,
pIniSpooler);
if (dwError != ERROR_SUCCESS) {
pIniPrinter->DsKeyUpdate = DS_KEY_REPUBLISH;
goto error;
} else if (*((PDWORD) pData) != (DWORD) pIniSpooler->bImmortal) {
dwError = SplRegSetValue( hKey,
SPLDS_FLAGS,
dwType,
(PBYTE) &pIniSpooler->bImmortal,
sizeof pIniSpooler->bImmortal,
pIniSpooler);
if (dwError == ERROR_SUCCESS) {
pIniPrinter->DsKeyUpdate |= DS_KEY_SPOOLER;
}
else {
pIniPrinter->DsKeyUpdate = DS_KEY_REPUBLISH;
goto error;
}
}
error:
if (hKey)
SplRegCloseKey(hKey, pIniSpooler);
EnterSplSem();
DECPRINTERREF(pIniPrinter);
SplInSem();
return;
}
extern "C" VOID
InitializeDS(
PINISPOOLER pIniSpooler
)
{
PINIPRINTER pIniPrinter = NULL;
PDSROLE_PRIMARY_DOMAIN_INFO_BASIC pDsRole = NULL;
DWORD dwError = ERROR_SUCCESS;
SYSTEMTIME SystemTime;
DWORD dwDelay = 0;
// Verify that we're in a domain
dwError = DsRoleGetPrimaryDomainInformation(NULL, DsRolePrimaryDomainInfoBasic, (PBYTE *) &pDsRole);
if (pDsRole) {
gbInDomain = (dwError == ERROR_SUCCESS &&
pDsRole->MachineRole != DsRole_RoleStandaloneServer &&
pDsRole->MachineRole != DsRole_RoleStandaloneWorkstation);
DsRoleFreeMemory((PVOID) pDsRole);
} else {
gbInDomain = FALSE;
}
if (gbInDomain) {
// Check if we need to update the ds
EnterSplSem();
// Get spooler policies
pIniSpooler->bImmortal = ImmortalPolicy();
BOOL bPublishProhibited = PrinterPublishProhibited();
// Run through all the printers and see if any need updating
for (pIniPrinter = pIniSpooler->pIniPrinter ; pIniPrinter ; pIniPrinter = pIniPrinter->pNext) {
// PublishProhibited not only prohibits new publishing, but also
// removes currently published printers
if (bPublishProhibited) {
pIniPrinter->Attributes &= ~PRINTER_ATTRIBUTE_PUBLISHED;
}
if (pIniPrinter->Attributes & PRINTER_ATTRIBUTE_PUBLISHED) {
// Verify properties not changed by driver or spooler
ValidateDsProperties(pIniPrinter);
} else if (pIniPrinter->pszObjectGUID) { // State is unpublished, but we haven't deleted
// the PrintQueue from the DS yet.
pIniPrinter->DsKeyUpdate = DS_KEY_UNPUBLISH;
} else {
pIniPrinter->DsKeyUpdate = 0;
}
if (!dwDelay && (pIniPrinter->DsKeyUpdate || pIniPrinter->DsKeyUpdateForeground)) {
// Initially sleep a random amount of time
// This keeps network traffic down if there's been a power outage
GetSystemTime(&SystemTime);
srand((unsigned) SystemTime.wMilliseconds);
// 100 different sleep times from 1 sec - 100 sec
// Typical time to publish printer is 5 seconds. Updates and deletes are just a couple seconds
dwDelay = (rand()%100) + 1;
}
}
if (dwDelay)
SpawnDsUpdate(dwDelay);
LeaveSplSem();
if (ThisMachineIsADC()) {
GetSystemTime(&SystemTime);
srand((unsigned) SystemTime.wMilliseconds);
DWORD dwPruningInterval = PruningInterval();
if (dwPruningInterval == INFINITE)
dwPruningInterval = DEFAULT_PRUNING_INTERVAL;
if (dwPruningInterval)
SpawnDsPrune(rand()%dwPruningInterval);
else
SpawnDsPrune(0);
}
}
ServerThreadPolicy(gbInDomain);
return;
}
BOOL
DsUpdateDriverKeys(
HANDLE h,
PINIPRINTER pIniPrinter
)
{
//
// For now, we need this only when a new user defined form is added/deleted.
// For this case, UpdateDsDriverKey is not called before call SetPrinterDS
// We set all pIniPrinters->DsKeyUpdateForeground with DS_KEY_UPDATE_DRIVER so that
// on DsUpdatePrinter we know that UpdateDsDriverKey must be called before
// we try to publish the printer.
//
SplInSem();
pIniPrinter->DsKeyUpdateForeground |= DS_KEY_UPDATE_DRIVER;
return TRUE;
}
BOOL
DsUpdateAllDriverKeys(
HANDLE h,
PINISPOOLER pIniSpooler
)
{
HANDLE hToken = NULL;
//
// Only do this for local spoolers.
//
if (pIniSpooler->SpoolerFlags & SPL_TYPE_LOCAL) {
RunForEachPrinter(pIniSpooler, h, DsUpdateDriverKeys);
}
hToken = RevertToPrinterSelf(); // All DS accesses are done by LocalSystem account
SpawnDsUpdate(1);
if (hToken)
ImpersonatePrinterClient(hToken);
return TRUE;
}