Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

874 lines
23 KiB

  1. /*++
  2. Copyright (c) 1992 Microsoft Corporation
  3. Module Name:
  4. CfgAPI2.cxx
  5. Abstract:
  6. This file contains the Service Controller's extended Config API.
  7. RChangeServiceConfig2W
  8. RQueryServiceConfig2W
  9. COutBuf
  10. CUpdateOptionalString::Update
  11. CUpdateOptionalString::~CUpdateOptionalString
  12. PrintConfig2Parms
  13. Author:
  14. Anirudh Sahni (AnirudhS) 11-Oct-96
  15. Environment:
  16. User Mode - Win32
  17. Revision History:
  18. 11-Oct-1996 AnirudhS
  19. Created.
  20. --*/
  21. //
  22. // INCLUDES
  23. //
  24. #include "precomp.hxx"
  25. #include <tstr.h> // Unicode string macros
  26. #include <align.h> // COUNT_IS_ALIGNED
  27. #include <valid.h> // ACTION_TYPE_INVALID
  28. #include <sclib.h> // ScImagePathsMatch
  29. #include <scwow.h> // 32/64-bit interop structures
  30. #include "scconfig.h" // ScOpenServiceConfigKey, etc.
  31. #include "scsec.h" // ScPrivilegeCheckAndAudit
  32. #include "smartp.h" // CHeapPtr
  33. #if DBG == 1
  34. VOID
  35. PrintConfig2Parms(
  36. IN SC_RPC_HANDLE hService,
  37. IN SC_RPC_CONFIG_INFOW Info
  38. );
  39. #endif
  40. //
  41. // Class definitions
  42. //
  43. //+-------------------------------------------------------------------------
  44. //
  45. // Class: COutBuf
  46. //
  47. // Purpose: Abstraction of an output buffer that is written sequentially
  48. //
  49. // History: 22-Nov-96 AnirudhS Created.
  50. //
  51. //--------------------------------------------------------------------------
  52. class COutBuf
  53. {
  54. public:
  55. COutBuf(LPBYTE lpBuffer) :
  56. _Start(lpBuffer),
  57. _Used(0)
  58. { }
  59. LPBYTE Next() const { return (_Start + _Used); }
  60. DWORD OffsetNext() const { return _Used; }
  61. void AddUsed(DWORD Bytes) { _Used += Bytes; }
  62. void AppendBytes(void * Source, DWORD Bytes)
  63. {
  64. RtlCopyMemory(Next(), Source, Bytes);
  65. AddUsed(Bytes);
  66. }
  67. private:
  68. LPBYTE _Start;
  69. DWORD _Used;
  70. };
  71. //+-------------------------------------------------------------------------
  72. //
  73. // Class: CUpdateOptionalString
  74. //
  75. // Purpose: An object of this class represents an update of an optional
  76. // string value in the registry. The update takes place when
  77. // the Update() method is called. When the object is destroyed
  78. // the operation is undone, unless the Commit() method has been
  79. // called.
  80. //
  81. // This class simplifies the writing of APIs like
  82. // ChangeServiceConfig2.
  83. //
  84. // History: 27-Nov-96 AnirudhS Created.
  85. //
  86. //--------------------------------------------------------------------------
  87. class CUpdateOptionalString
  88. {
  89. public:
  90. CUpdateOptionalString (HKEY Key, LPCWSTR ValueName) :
  91. _Key(Key),
  92. _ValueName(ValueName),
  93. _UndoNeeded(FALSE)
  94. { }
  95. ~CUpdateOptionalString();
  96. DWORD Update (LPCWSTR NewValue);
  97. void Commit ()
  98. { _UndoNeeded = FALSE; }
  99. private:
  100. HKEY _Key;
  101. LPCWSTR _ValueName;
  102. CHeapPtr< LPWSTR > _OldValue;
  103. BOOL _UndoNeeded;
  104. };
  105. DWORD
  106. CUpdateOptionalString::Update(
  107. IN LPCWSTR NewValue
  108. )
  109. /*++
  110. Routine Description:
  111. See class definition.
  112. --*/
  113. {
  114. // This method should be called only once in the object's lifetime
  115. SC_ASSERT(_UndoNeeded == FALSE && _OldValue == NULL);
  116. //
  117. // Read the old value.
  118. //
  119. DWORD Error = ScReadOptionalString(_Key, _ValueName, &_OldValue);
  120. if (Error != ERROR_SUCCESS)
  121. {
  122. return Error;
  123. }
  124. //
  125. // Write the new value. Note that NULL means no change.
  126. //
  127. Error = ScWriteOptionalString(_Key, _ValueName, NewValue);
  128. //
  129. // Remember whether the change needs to be undone.
  130. //
  131. if (Error == ERROR_SUCCESS && NewValue != NULL)
  132. {
  133. _UndoNeeded = TRUE;
  134. }
  135. return Error;
  136. }
  137. CUpdateOptionalString::~CUpdateOptionalString(
  138. )
  139. /*++
  140. Routine Description:
  141. See class definition.
  142. --*/
  143. {
  144. if (_UndoNeeded)
  145. {
  146. DWORD Error = ScWriteOptionalString(
  147. _Key,
  148. _ValueName,
  149. _OldValue ? _OldValue : L""
  150. );
  151. if (Error != ERROR_SUCCESS)
  152. {
  153. // Nothing we can do about it
  154. SC_LOG3(ERROR, "Couldn't roll back update to %ws value, error %lu."
  155. " Old value was \"%ws\".\n",
  156. _ValueName, Error, _OldValue);
  157. }
  158. }
  159. }
  160. DWORD
  161. RChangeServiceConfig2W(
  162. IN SC_RPC_HANDLE hService,
  163. IN SC_RPC_CONFIG_INFOW Info
  164. )
  165. /*++
  166. Routine Description:
  167. Arguments:
  168. Return Value:
  169. --*/
  170. {
  171. SC_LOG(CONFIG_API, "In RChangeServiceConfig2W for service handle %#lx\n", hService);
  172. #if DBG == 1
  173. PrintConfig2Parms(hService, Info);
  174. #endif // DBG == 1
  175. if (ScShutdownInProgress)
  176. {
  177. return ERROR_SHUTDOWN_IN_PROGRESS;
  178. }
  179. //
  180. // Check the handle.
  181. //
  182. if (!ScIsValidServiceHandle(hService))
  183. {
  184. return ERROR_INVALID_HANDLE;
  185. }
  186. //
  187. // Do we have permission to do this?
  188. //
  189. if (!RtlAreAllAccessesGranted(
  190. ((LPSC_HANDLE_STRUCT) hService)->AccessGranted,
  191. SERVICE_CHANGE_CONFIG
  192. ))
  193. {
  194. return ERROR_ACCESS_DENIED;
  195. }
  196. //
  197. // Lock database, as we want to add stuff without other threads tripping
  198. // on our feet.
  199. //
  200. CServiceRecordExclusiveLock RLock;
  201. //
  202. // Find the service record for this handle.
  203. //
  204. LPSERVICE_RECORD serviceRecord =
  205. ((LPSC_HANDLE_STRUCT) hService)->Type.ScServiceObject.ServiceRecord;
  206. SC_ASSERT(serviceRecord != NULL);
  207. SC_ASSERT(serviceRecord->Signature == SERVICE_SIGNATURE);
  208. //
  209. // Disallow this call if record is marked for delete.
  210. //
  211. if (DELETE_FLAG_IS_SET(serviceRecord))
  212. {
  213. return ERROR_SERVICE_MARKED_FOR_DELETE;
  214. }
  215. //-----------------------------
  216. //
  217. // Begin Updating the Registry
  218. //
  219. //-----------------------------
  220. HKEY ServiceNameKey = NULL;
  221. DWORD ApiStatus = ScOpenServiceConfigKey(
  222. serviceRecord->ServiceName,
  223. KEY_WRITE | KEY_READ,
  224. FALSE, // don't create if missing
  225. &ServiceNameKey
  226. );
  227. if (ApiStatus != NO_ERROR)
  228. {
  229. goto Cleanup;
  230. }
  231. switch (Info.dwInfoLevel)
  232. {
  233. //-----------------------------
  234. //
  235. // Service Description
  236. //
  237. //-----------------------------
  238. case SERVICE_CONFIG_DESCRIPTION:
  239. //
  240. // NULL means no change
  241. //
  242. if (Info.psd == NULL)
  243. {
  244. ApiStatus = NO_ERROR;
  245. break;
  246. }
  247. ApiStatus = ScWriteDescription(ServiceNameKey, Info.psd->lpDescription);
  248. break;
  249. //-----------------------------
  250. //
  251. // Service Failure Actions
  252. //
  253. //-----------------------------
  254. case SERVICE_CONFIG_FAILURE_ACTIONS:
  255. {
  256. LPSERVICE_FAILURE_ACTIONSW psfa = Info.psfa;
  257. //
  258. // NULL means no change
  259. //
  260. if (psfa == NULL)
  261. {
  262. ApiStatus = NO_ERROR;
  263. break;
  264. }
  265. //
  266. // Validate the structure and permissions
  267. //
  268. if (psfa->lpsaActions != NULL &&
  269. psfa->cActions != 0)
  270. {
  271. //
  272. // These settings are only valid for Win32 services
  273. //
  274. if (! (serviceRecord->ServiceStatus.dwServiceType & SERVICE_WIN32))
  275. {
  276. ApiStatus = ERROR_CANNOT_DETECT_DRIVER_FAILURE;
  277. break;
  278. }
  279. BOOL RebootRequested = FALSE;
  280. BOOL RestartRequested = FALSE;
  281. for (DWORD i = 0; i < psfa->cActions; i++)
  282. {
  283. SC_ACTION_TYPE Type = psfa->lpsaActions[i].Type;
  284. if (Type == SC_ACTION_RESTART)
  285. {
  286. RestartRequested = TRUE;
  287. }
  288. else if (Type == SC_ACTION_REBOOT)
  289. {
  290. RebootRequested = TRUE;
  291. }
  292. else if (ACTION_TYPE_INVALID(Type))
  293. {
  294. SC_LOG(ERROR, "RChangeServiceConfig2W: invalid action type %#lx\n", Type);
  295. ApiStatus = ERROR_INVALID_PARAMETER;
  296. goto Cleanup;
  297. }
  298. }
  299. if (RestartRequested)
  300. {
  301. if (!RtlAreAllAccessesGranted(
  302. ((LPSC_HANDLE_STRUCT) hService)->AccessGranted,
  303. SERVICE_START
  304. ))
  305. {
  306. SC_LOG0(ERROR, "Service handle lacks start access\n");
  307. ApiStatus = ERROR_ACCESS_DENIED;
  308. break;
  309. }
  310. }
  311. if (RebootRequested)
  312. {
  313. NTSTATUS Status = ScPrivilegeCheckAndAudit(
  314. SE_SHUTDOWN_PRIVILEGE,
  315. hService,
  316. SERVICE_CHANGE_CONFIG
  317. );
  318. if (!NT_SUCCESS(Status))
  319. {
  320. SC_LOG0(ERROR, "Caller lacks shutdown privilege\n");
  321. ApiStatus = ERROR_ACCESS_DENIED;
  322. break;
  323. }
  324. }
  325. //
  326. // Get the service's image path
  327. //
  328. CHeapPtr<LPWSTR> ImageName;
  329. ApiStatus = ScGetImageFileName(serviceRecord->ServiceName, &ImageName);
  330. if (ApiStatus != NO_ERROR)
  331. {
  332. SC_LOG(ERROR,"RChangeServiceConfig2W: GetImageFileName failed %lu\n", ApiStatus);
  333. break;
  334. }
  335. //
  336. // If the service runs in services.exe, we certainly won't
  337. // detect if the service process dies, so don't pretend we will
  338. //
  339. if (ScImagePathsMatch(ImageName, ScGlobalThisExePath))
  340. {
  341. ApiStatus = ERROR_CANNOT_DETECT_PROCESS_ABORT;
  342. break;
  343. }
  344. }
  345. //
  346. // Write the string values, followed by the non-string values.
  347. // If anything fails, the values written up to that point will be
  348. // backed out.
  349. // (Backing out the update of the non-string values is a little
  350. // more complicated than backing out the string updates. So we
  351. // do the non-string update last, to avoid having to back it out.)
  352. //
  353. CUpdateOptionalString UpdateRebootMessage
  354. (ServiceNameKey, REBOOTMESSAGE_VALUENAME_W);
  355. CUpdateOptionalString UpdateFailureCommand
  356. (ServiceNameKey, FAILURECOMMAND_VALUENAME_W);
  357. if ((ApiStatus = UpdateRebootMessage.Update(psfa->lpRebootMsg)) == ERROR_SUCCESS &&
  358. (ApiStatus = UpdateFailureCommand.Update(psfa->lpCommand)) == ERROR_SUCCESS &&
  359. (ApiStatus = ScWriteFailureActions(ServiceNameKey, psfa)) == ERROR_SUCCESS)
  360. {
  361. UpdateRebootMessage.Commit();
  362. UpdateFailureCommand.Commit();
  363. }
  364. }
  365. break;
  366. //-----------------------------
  367. //
  368. // Other (invalid)
  369. //
  370. //-----------------------------
  371. default:
  372. ApiStatus = ERROR_INVALID_LEVEL;
  373. break;
  374. }
  375. Cleanup:
  376. if (ServiceNameKey != NULL)
  377. {
  378. ScRegFlushKey(ServiceNameKey);
  379. ScRegCloseKey(ServiceNameKey);
  380. }
  381. SC_LOG1(CONFIG_API, "RChangeServiceConfig2W returning %lu\n", ApiStatus);
  382. return ApiStatus;
  383. }
  384. DWORD
  385. RQueryServiceConfig2W(
  386. IN SC_RPC_HANDLE hService,
  387. IN DWORD dwInfoLevel,
  388. OUT LPBYTE lpBuffer,
  389. IN DWORD cbBufSize,
  390. OUT LPDWORD pcbBytesNeeded
  391. )
  392. /*++
  393. Routine Description:
  394. Arguments:
  395. Return Value:
  396. --*/
  397. {
  398. SC_LOG(CONFIG_API, "In RQueryServiceConfig2W for service handle %#lx\n", hService);
  399. if (ScShutdownInProgress)
  400. {
  401. return ERROR_SHUTDOWN_IN_PROGRESS;
  402. }
  403. //
  404. // Check the handle.
  405. //
  406. if (!ScIsValidServiceHandle(hService))
  407. {
  408. return ERROR_INVALID_HANDLE;
  409. }
  410. //
  411. // MIDL doesn't support optional [out] parameters efficiently, so
  412. // we require these parameters.
  413. //
  414. if (lpBuffer == NULL || pcbBytesNeeded == NULL)
  415. {
  416. SC_ASSERT(!"RPC passed NULL for [out] pointers");
  417. return ERROR_INVALID_PARAMETER;
  418. }
  419. //
  420. // Do we have permission to do this?
  421. //
  422. if (!RtlAreAllAccessesGranted(
  423. ((LPSC_HANDLE_STRUCT) hService)->AccessGranted,
  424. SERVICE_QUERY_CONFIG
  425. ))
  426. {
  427. return ERROR_ACCESS_DENIED;
  428. }
  429. //
  430. // Initialize *pcbBytesNeeded. It is incremented below.
  431. // (For consistency with QueryServiceConfig, it is set even on a success
  432. // return.)
  433. //
  434. *pcbBytesNeeded = 0;
  435. //
  436. // Lock database, as we want to look at stuff without other threads changing
  437. // fields at the same time.
  438. //
  439. CServiceRecordSharedLock RLock;
  440. LPSERVICE_RECORD serviceRecord =
  441. ((LPSC_HANDLE_STRUCT) hService)->Type.ScServiceObject.ServiceRecord;
  442. SC_ASSERT(serviceRecord != NULL);
  443. //
  444. // Open the service name key.
  445. //
  446. HKEY ServiceNameKey = NULL;
  447. DWORD ApiStatus = ScOpenServiceConfigKey(
  448. serviceRecord->ServiceName,
  449. KEY_READ,
  450. FALSE, // Create if missing
  451. &ServiceNameKey
  452. );
  453. if (ApiStatus != NO_ERROR)
  454. {
  455. return ApiStatus;
  456. }
  457. switch (dwInfoLevel)
  458. {
  459. //-----------------------------
  460. //
  461. // Service Description
  462. //
  463. //-----------------------------
  464. case SERVICE_CONFIG_DESCRIPTION:
  465. {
  466. *pcbBytesNeeded = sizeof(SERVICE_DESCRIPTION_WOW64);
  467. //
  468. // Read the string from the registry
  469. //
  470. CHeapPtr< LPWSTR > pszDescription;
  471. ApiStatus = ScReadDescription(
  472. ServiceNameKey,
  473. &pszDescription,
  474. pcbBytesNeeded
  475. );
  476. SC_ASSERT(ApiStatus != ERROR_INSUFFICIENT_BUFFER);
  477. if (ApiStatus != NO_ERROR)
  478. {
  479. break;
  480. }
  481. //
  482. // Check for sufficient buffer space
  483. //
  484. if (cbBufSize < *pcbBytesNeeded)
  485. {
  486. ApiStatus = ERROR_INSUFFICIENT_BUFFER;
  487. break;
  488. }
  489. //
  490. // Copy the information to the output buffer
  491. // The format is:
  492. // SERVICE_DESCRIPTION_WOW64 struct
  493. // description string
  494. // Pointers in the struct are replaced with offsets based at the
  495. // start of the buffer. NULL pointers remain NULL.
  496. //
  497. COutBuf OutBuf(lpBuffer);
  498. LPSERVICE_DESCRIPTION_WOW64 psdOut =
  499. (LPSERVICE_DESCRIPTION_WOW64) OutBuf.Next();
  500. OutBuf.AddUsed(sizeof(SERVICE_DESCRIPTION_WOW64));
  501. SC_ASSERT(COUNT_IS_ALIGNED(OutBuf.OffsetNext(), sizeof(WCHAR)));
  502. if (pszDescription != NULL)
  503. {
  504. psdOut->dwDescriptionOffset = OutBuf.OffsetNext();
  505. OutBuf.AppendBytes(pszDescription, (DWORD) WCSSIZE(pszDescription));
  506. }
  507. else
  508. {
  509. psdOut->dwDescriptionOffset = 0;
  510. }
  511. }
  512. break;
  513. //-----------------------------
  514. //
  515. // Service Failure Actions
  516. //
  517. //-----------------------------
  518. case SERVICE_CONFIG_FAILURE_ACTIONS:
  519. {
  520. //
  521. // Read the non-string info
  522. //
  523. CHeapPtr< LPSERVICE_FAILURE_ACTIONS_WOW64 > psfa;
  524. ApiStatus = ScReadFailureActions(ServiceNameKey, &psfa, pcbBytesNeeded);
  525. if (ApiStatus != NO_ERROR)
  526. {
  527. break;
  528. }
  529. if (psfa == NULL)
  530. {
  531. SC_ASSERT(*pcbBytesNeeded == 0);
  532. *pcbBytesNeeded = sizeof(SERVICE_FAILURE_ACTIONS_WOW64);
  533. }
  534. SC_ASSERT(COUNT_IS_ALIGNED(*pcbBytesNeeded, sizeof(WCHAR)));
  535. //
  536. // Read the string values
  537. //
  538. CHeapPtr< LPWSTR > RebootMessage;
  539. ApiStatus = ScReadRebootMessage(
  540. ServiceNameKey,
  541. &RebootMessage,
  542. pcbBytesNeeded
  543. );
  544. if (ApiStatus != NO_ERROR)
  545. {
  546. break;
  547. }
  548. SC_ASSERT(COUNT_IS_ALIGNED(*pcbBytesNeeded, sizeof(WCHAR)));
  549. CHeapPtr< LPWSTR > FailureCommand;
  550. ApiStatus = ScReadFailureCommand(
  551. ServiceNameKey,
  552. &FailureCommand,
  553. pcbBytesNeeded
  554. );
  555. if (ApiStatus != NO_ERROR)
  556. {
  557. break;
  558. }
  559. //
  560. // Check for sufficient buffer space
  561. //
  562. if (cbBufSize < *pcbBytesNeeded)
  563. {
  564. ApiStatus = ERROR_INSUFFICIENT_BUFFER;
  565. break;
  566. }
  567. //
  568. // Copy the information to the output buffer
  569. // The format is:
  570. // SERVICE_FAILURE_ACTIONS_WOW64 struct
  571. // SC_ACTIONS array
  572. // strings
  573. // Pointers in the struct are replaced with offsets based at the
  574. // start of the buffer. NULL pointers remain NULL.
  575. //
  576. COutBuf OutBuf(lpBuffer);
  577. LPSERVICE_FAILURE_ACTIONS_WOW64 psfaOut =
  578. (LPSERVICE_FAILURE_ACTIONS_WOW64) OutBuf.Next();
  579. if (psfa != NULL)
  580. {
  581. psfaOut->dwResetPeriod = ((LPSERVICE_FAILURE_ACTIONS_WOW64) psfa)->dwResetPeriod;
  582. psfaOut->cActions = ((LPSERVICE_FAILURE_ACTIONS_WOW64) psfa)->cActions;
  583. }
  584. else
  585. {
  586. psfaOut->dwResetPeriod = 0;
  587. psfaOut->cActions = 0;
  588. }
  589. OutBuf.AddUsed(sizeof(SERVICE_FAILURE_ACTIONS_WOW64));
  590. if (psfaOut->cActions != 0)
  591. {
  592. psfaOut->dwsaActionsOffset = OutBuf.OffsetNext();
  593. OutBuf.AppendBytes(psfa + 1,
  594. psfaOut->cActions * sizeof(SC_ACTION));
  595. }
  596. else
  597. {
  598. psfaOut->dwsaActionsOffset = 0;
  599. }
  600. SC_ASSERT(COUNT_IS_ALIGNED(OutBuf.OffsetNext(), sizeof(WCHAR)));
  601. if (RebootMessage != NULL)
  602. {
  603. psfaOut->dwRebootMsgOffset = OutBuf.OffsetNext();
  604. OutBuf.AppendBytes(RebootMessage, (DWORD) WCSSIZE(RebootMessage));
  605. }
  606. else
  607. {
  608. psfaOut->dwRebootMsgOffset = 0;
  609. }
  610. if (FailureCommand != NULL)
  611. {
  612. psfaOut->dwCommandOffset = OutBuf.OffsetNext();
  613. OutBuf.AppendBytes(FailureCommand, (DWORD) WCSSIZE(FailureCommand));
  614. }
  615. else
  616. {
  617. psfaOut->dwCommandOffset = 0;
  618. }
  619. }
  620. break;
  621. //-----------------------------
  622. //
  623. // Other (invalid)
  624. //
  625. //-----------------------------
  626. default:
  627. ApiStatus = ERROR_INVALID_LEVEL;
  628. break;
  629. }
  630. ScRegCloseKey(ServiceNameKey);
  631. SC_LOG1(CONFIG_API, "RQueryServiceConfig2W returning %lu\n", ApiStatus);
  632. return ApiStatus;
  633. }
  634. #if DBG == 1
  635. VOID
  636. PrintConfig2Parms(
  637. IN SC_RPC_HANDLE hService,
  638. IN SC_RPC_CONFIG_INFOW Info
  639. )
  640. {
  641. KdPrintEx((DPFLTR_SCSERVER_ID,
  642. DEBUG_CONFIG_API,
  643. "Parameters to RChangeServiceConfig2W:\n"));
  644. LPSTR psz;
  645. switch (Info.dwInfoLevel)
  646. {
  647. case SERVICE_CONFIG_DESCRIPTION:
  648. psz = "SERVICE_CONFIG_DESCRIPTION";
  649. break;
  650. case SERVICE_CONFIG_FAILURE_ACTIONS:
  651. psz = "SERVICE_CONFIG_FAILURE_ACTIONS";
  652. break;
  653. default:
  654. psz = "invalid";
  655. break;
  656. }
  657. KdPrintEx((DPFLTR_SCSERVER_ID,
  658. DEBUG_CONFIG_API,
  659. " dwInfoLevel = %ld (%s)\n", Info.dwInfoLevel,
  660. psz));
  661. switch (Info.dwInfoLevel)
  662. {
  663. case SERVICE_CONFIG_DESCRIPTION:
  664. if (Info.psd == NULL)
  665. {
  666. KdPrintEx((DPFLTR_SCSERVER_ID,
  667. DEBUG_CONFIG_API,
  668. " NULL information pointer -- no action requested\n\n"));
  669. break;
  670. }
  671. KdPrintEx((DPFLTR_SCSERVER_ID,
  672. DEBUG_CONFIG_API,
  673. " pszDescription = \"%ws\"\n",
  674. Info.psd->lpDescription));
  675. break;
  676. case SERVICE_CONFIG_FAILURE_ACTIONS:
  677. if (Info.psfa == NULL)
  678. {
  679. KdPrintEx((DPFLTR_SCSERVER_ID,
  680. DEBUG_CONFIG_API,
  681. " NULL information pointer -- no action requested\n\n"));
  682. break;
  683. }
  684. KdPrintEx((DPFLTR_SCSERVER_ID,
  685. DEBUG_CONFIG_API,
  686. " dwResetPeriod = %ld\n",
  687. Info.psfa->dwResetPeriod));
  688. KdPrintEx((DPFLTR_SCSERVER_ID,
  689. DEBUG_CONFIG_API,
  690. " lpRebootMsg = \"%ws\"\n",
  691. Info.psfa->lpRebootMsg));
  692. KdPrintEx((DPFLTR_SCSERVER_ID,
  693. DEBUG_CONFIG_API,
  694. " lpCommand = \"%ws\"\n",
  695. Info.psfa->lpCommand));
  696. KdPrintEx((DPFLTR_SCSERVER_ID,
  697. DEBUG_CONFIG_API,
  698. " %ld failure %s\n",
  699. Info.psfa->cActions,
  700. Info.psfa->cActions == 0 ? "actions." :
  701. Info.psfa->cActions == 1 ? "action:" : "actions:"));
  702. if (Info.psfa->lpsaActions == NULL)
  703. {
  704. KdPrintEx((DPFLTR_SCSERVER_ID,
  705. DEBUG_CONFIG_API,
  706. " NULL array pointer -- no change in fixed info\n\n"));
  707. }
  708. else
  709. {
  710. for (DWORD i = 0; i < Info.psfa->cActions; i++)
  711. {
  712. SC_ACTION& sa = Info.psfa->lpsaActions[i];
  713. KdPrintEx((DPFLTR_SCSERVER_ID,
  714. DEBUG_CONFIG_API,
  715. " %ld: Action %ld, Delay %ld\n",
  716. i,
  717. sa.Type,
  718. sa.Delay));
  719. }
  720. }
  721. break;
  722. }
  723. KdPrintEx((DPFLTR_SCSERVER_ID, DEBUG_CONFIG_API, "\n"));
  724. }
  725. #endif // DBG == 1