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.

1289 lines
34 KiB

  1. //+----------------------------------------------------------------------------
  2. //
  3. // Job Scheduler service
  4. //
  5. // Microsoft Windows
  6. // Copyright (C) Microsoft Corporation, 1992 - 1996.
  7. //
  8. // File: sch_at.cxx
  9. //
  10. // Contents: scheduler class object methods to support the NetSchedule
  11. // (AT) APIs.
  12. //
  13. // Classes: CSchedule
  14. //
  15. // History: 30-Jan-96 EricB created
  16. //
  17. //-----------------------------------------------------------------------------
  18. #include "..\pch\headers.hxx"
  19. #pragma hdrstop
  20. #include "Sched.hxx"
  21. //
  22. // Nothing here is needed for Chicago version since there is no AT support
  23. // there.
  24. //
  25. #if !defined(_CHICAGO_)
  26. //
  27. // Forward references
  28. //
  29. VOID
  30. SetDomTrigger2Days(
  31. DWORD dwDaysOfMonth,
  32. WORD wFirstDayToCheck,
  33. WORD wLastDayToCheck,
  34. SYSTEMTIME *pstStart2,
  35. SYSTEMTIME *pstEnd2);
  36. HRESULT
  37. CSchedule::AddAtJobCommon(
  38. const AT_INFO &At,
  39. DWORD *pID,
  40. CJob **ppJob,
  41. WCHAR wszName[],
  42. WCHAR wszID[]
  43. )
  44. {
  45. HRESULT hr = S_OK;
  46. //
  47. // If the next at id is > 1 but there aren't any at jobs, the id can be
  48. // reset to 1.
  49. //
  50. if (m_dwNextID > 1 && S_FALSE == _AtTaskExists())
  51. {
  52. ResetAtID();
  53. }
  54. //
  55. // Compose a name for the new AT job.
  56. //
  57. wcscpy(wszName, m_ptszFolderPath);
  58. wcscat(wszName, L"\\" TSZ_AT_JOB_PREFIX);
  59. _itow(m_dwNextID, wszID, 10);
  60. wcscat(wszName, wszID);
  61. wcscat(wszName, L"." TSZ_JOB);
  62. //
  63. // Create a new job
  64. //
  65. CJob * pJob = CJob::Create();
  66. if (pJob == NULL)
  67. {
  68. ERR_OUT("CSchedule::AddAtJob: CJob::Create", E_OUTOFMEMORY);
  69. return E_OUTOFMEMORY;
  70. }
  71. //
  72. // Convert the AT command line.
  73. //
  74. WCHAR * pwszApp, * pwszParams, wszCommand[MAX_PATH];
  75. //
  76. // net\svcdlls\atcmd\atcmd.c defines MAX_COMMAND_LEN to be 128. This
  77. // should at some point be changed to MAX_PATH.
  78. //
  79. wcscpy(wszCommand, At.Command);
  80. pwszApp = wszCommand;
  81. //
  82. // The app name and any command line params are all passed in one string,
  83. // At.Command, so separate the app name from the params. Any path to the
  84. // app plus the app name may be quoted. Otherwise, the parameters are
  85. // separated from the app name by white space.
  86. //
  87. if (*pwszApp == L'"')
  88. {
  89. //
  90. // Initial quote found, scan for end quote. The app name passed to
  91. // SetApplicationName should not be quoted.
  92. //
  93. pwszApp++;
  94. pwszParams = pwszApp + 1;
  95. while (TRUE)
  96. {
  97. if (*pwszParams == L'\0')
  98. {
  99. //
  100. // End of string found, no params.
  101. //
  102. pwszParams = NULL;
  103. break;
  104. }
  105. if (*pwszParams == L'"')
  106. {
  107. //
  108. // End quote found.
  109. //
  110. break;
  111. }
  112. pwszParams++;
  113. }
  114. }
  115. else
  116. {
  117. //
  118. // App path/name not quoted, scan for first white space for parameters.
  119. //
  120. pwszParams = wcspbrk(pwszApp, L" \t");
  121. }
  122. if (pwszParams != NULL)
  123. {
  124. // Null terminate app name string.
  125. //
  126. *pwszParams = L'\0';
  127. //
  128. // Move to first char of the parameters.
  129. //
  130. pwszParams++;
  131. //
  132. // Skip any leading white space.
  133. //
  134. while (*pwszParams != L'\0')
  135. {
  136. if (*pwszParams != L' ' && *pwszParams != L'\t')
  137. {
  138. break;
  139. }
  140. pwszParams++;
  141. }
  142. if (*pwszParams == L'\0')
  143. {
  144. //
  145. // No params.
  146. //
  147. pwszParams = NULL;
  148. }
  149. }
  150. hr = pJob->SetApplicationName(pwszApp);
  151. if (FAILED(hr))
  152. {
  153. ERR_OUT("AddAtJob: SetApplicationName", hr);
  154. pJob->Release();
  155. return hr;
  156. }
  157. if (pwszParams != NULL)
  158. {
  159. hr = pJob->SetParameters(pwszParams);
  160. if (FAILED(hr))
  161. {
  162. ERR_OUT("AddAtJob: SetParameters", hr);
  163. pJob->Release();
  164. return hr;
  165. }
  166. }
  167. pJob->m_rgFlags |= JOB_I_FLAG_NET_SCHEDULE | TASK_FLAG_DELETE_WHEN_DONE;
  168. if (!(At.Flags & JOB_NONINTERACTIVE))
  169. {
  170. pJob->m_rgFlags |= TASK_FLAG_INTERACTIVE;
  171. }
  172. pJob->m_hrStatus = SCHED_S_TASK_READY;
  173. WCHAR szComment[SCH_BUF_LEN + 1];
  174. if (LoadString(g_hInstance,
  175. IDS_NETSCHED_COMMENT,
  176. szComment,
  177. SCH_BUF_LEN) > 0)
  178. {
  179. pJob->SetComment(szComment);
  180. }
  181. //
  182. // Convert from NetSchedule representation to Job Scheduler representation
  183. // of the run dates and times.
  184. //
  185. SYSTEMTIME stNow, stStart;
  186. SYSTEMTIME stDomStart1, stDomEnd1, stDomStart2, stDomEnd2;
  187. SYSTEMTIME stDowStart, stDowEnd;
  188. stDomStart2.wDay = 0; // this serves as a flag
  189. GetLocalTime(&stNow);
  190. stStart = stNow;
  191. //
  192. // JobTime is expressed as milliseconds after midnight, so convert to
  193. // minutes.
  194. //
  195. DWORD dwMins = (DWORD)(At.JobTime / JOB_MILLISECONDS_PER_MINUTE);
  196. stStart.wHour = (WORD)(dwMins / JOB_MINS_PER_HOUR);
  197. stStart.wMinute = (WORD)(dwMins % JOB_MINS_PER_HOUR);
  198. stStart.wSecond = stStart.wMilliseconds = 0;
  199. DWORD DaysOfMonth = At.DaysOfMonth;
  200. WORD wFirstDowRunOffset = 0, wFirstDomRunOffset = 0;
  201. TASK_TRIGGER Trigger;
  202. if (At.Flags & JOB_ADD_CURRENT_DATE)
  203. {
  204. //
  205. // The flag is set, so add today as the first run date.
  206. //
  207. DaysOfMonth |= 1 << (stStart.wDay - 1);
  208. }
  209. else
  210. {
  211. if (DaysOfMonth == 0 && At.DaysOfWeek == 0)
  212. {
  213. //
  214. // Neither bitmask is set, so run at the next opportunity.
  215. //
  216. Trigger.TriggerType = TASK_TIME_TRIGGER_ONCE;
  217. if (! IsFirstTimeEarlier(&stNow, &stStart))
  218. {
  219. // Job runs tomorrow
  220. IncrementDay(&stStart);
  221. }
  222. }
  223. }
  224. //
  225. // Set the trigger values and save the new trigger(s).
  226. //
  227. // Initialize the start and end dates in case this is a periodic trigger.
  228. // If it is not periodic, then new start and end dates will overwrite
  229. // these initialization values.
  230. //
  231. Trigger.cbTriggerSize = sizeof(TASK_TRIGGER);
  232. Trigger.Reserved1 = pJob->m_Triggers.GetCount();
  233. Trigger.wBeginYear = stStart.wYear;
  234. Trigger.wBeginMonth = stStart.wMonth;
  235. Trigger.wBeginDay = stStart.wDay;
  236. Trigger.wEndYear = 0;
  237. Trigger.wEndMonth = 0;
  238. Trigger.wEndDay = 0;
  239. Trigger.wStartHour = stStart.wHour;
  240. Trigger.wStartMinute = stStart.wMinute;
  241. Trigger.Reserved2 = 0;
  242. Trigger.wRandomMinutesInterval = 0;
  243. Trigger.rgFlags = (At.Flags & JOB_RUN_PERIODICALLY)
  244. ? 0 : TASK_TRIGGER_FLAG_HAS_END_DATE;
  245. Trigger.MinutesInterval = Trigger.MinutesDuration = 0;
  246. if (DaysOfMonth == 0 && At.DaysOfWeek == 0)
  247. {
  248. // First, zero out the end date flag
  249. Trigger.rgFlags &= ~JOB_RUN_PERIODICALLY;
  250. // This is a TASK_TIME_TRIGGER_ONCE job, and we are ready to commit
  251. hr = pJob->m_Triggers.Add(Trigger);
  252. if (FAILED(hr))
  253. {
  254. ERR_OUT("AddAtJob: m_Triggers.Add Once", hr);
  255. pJob->Release();
  256. return hr;
  257. }
  258. }
  259. if (DaysOfMonth > 0)
  260. {
  261. Trigger.TriggerType = TASK_TIME_TRIGGER_MONTHLYDATE;
  262. Trigger.Type.MonthlyDate.rgfDays = DaysOfMonth;
  263. Trigger.Type.MonthlyDate.rgfMonths = JOB_RGFMONTHS_MAX;
  264. if (!(At.Flags & JOB_RUN_PERIODICALLY))
  265. {
  266. CalcDomTriggerDates(DaysOfMonth,
  267. stNow,
  268. stStart,
  269. &stDomStart1,
  270. &stDomEnd1,
  271. &stDomStart2,
  272. &stDomEnd2);
  273. Trigger.wBeginYear = stDomStart1.wYear;
  274. Trigger.wBeginMonth = stDomStart1.wMonth;
  275. Trigger.wBeginDay = stDomStart1.wDay;
  276. Trigger.wEndYear = stDomEnd1.wYear;
  277. Trigger.wEndMonth = stDomEnd1.wMonth;
  278. Trigger.wEndDay = stDomEnd1.wDay;
  279. }
  280. hr = pJob->m_Triggers.Add(Trigger);
  281. if (FAILED(hr))
  282. {
  283. ERR_OUT("AddAtJob: m_Triggers.Add Dom1", hr);
  284. pJob->Release();
  285. return hr;
  286. }
  287. if (stDomStart2.wDay != 0)
  288. {
  289. Trigger.wBeginYear = stDomStart2.wYear;
  290. Trigger.wBeginMonth = stDomStart2.wMonth;
  291. Trigger.wBeginDay = stDomStart2.wDay;
  292. Trigger.wEndYear = stDomEnd2.wYear;
  293. Trigger.wEndMonth = stDomEnd2.wMonth;
  294. Trigger.wEndDay = stDomEnd2.wDay;
  295. Trigger.Reserved1 = pJob->m_Triggers.GetCount();
  296. hr = pJob->m_Triggers.Add(Trigger);
  297. if (FAILED(hr))
  298. {
  299. ERR_OUT("AddAtJob: m_Triggers.Add Dom2", hr);
  300. pJob->Release();
  301. return hr;
  302. }
  303. }
  304. }
  305. if (At.DaysOfWeek > 0)
  306. {
  307. Trigger.Reserved1 = pJob->m_Triggers.GetCount();
  308. Trigger.TriggerType = TASK_TIME_TRIGGER_WEEKLY;
  309. Trigger.Type.Weekly.WeeksInterval = 1;
  310. //
  311. // Convert AT_INFO DOW to Scheduler DOW:
  312. // Scheduler rgfDaysOfTheWeek: Sunday = bit 0, Monday = bit 1.
  313. // AT_INFO DaysOfWeek: Monday = bit 0, Sunday = bit 6.
  314. //
  315. Trigger.Type.Weekly.rgfDaysOfTheWeek = At.DaysOfWeek << 1;
  316. if (Trigger.Type.Weekly.rgfDaysOfTheWeek & 0x0080)
  317. {
  318. Trigger.Type.Weekly.rgfDaysOfTheWeek &= ~0x0080;
  319. Trigger.Type.Weekly.rgfDaysOfTheWeek |= 1;
  320. }
  321. if (!(At.Flags & JOB_RUN_PERIODICALLY))
  322. {
  323. CalcDowTriggerDate(stNow,
  324. stStart,
  325. &stDowStart,
  326. &stDowEnd);
  327. Trigger.wBeginYear = stDowStart.wYear;
  328. Trigger.wBeginMonth = stDowStart.wMonth;
  329. Trigger.wBeginDay = stDowStart.wDay;
  330. Trigger.wEndYear = stDowEnd.wYear;
  331. Trigger.wEndMonth = stDowEnd.wMonth;
  332. Trigger.wEndDay = stDowEnd.wDay;
  333. }
  334. hr = pJob->m_Triggers.Add(Trigger);
  335. if (FAILED(hr))
  336. {
  337. ERR_OUT("AddAtJob: m_Triggers.Add", hr);
  338. pJob->Release();
  339. return hr;
  340. }
  341. }
  342. // get the AT task maximum run time from the registry
  343. // if the call fails, the default of 72 will be used
  344. DWORD dwMaxRunTime = 0;
  345. if (SUCCEEDED(GetAtTaskMaxHours(&dwMaxRunTime)))
  346. pJob->SetMaxRunTime(dwMaxRunTime);
  347. //
  348. // Set the same flags as in CJob::CreateTrigger. (We should really call
  349. // CreateTrigger in this function instead of creating the trigger ourselves.)
  350. //
  351. pJob->SetFlag(JOB_I_FLAG_PROPERTIES_DIRTY);
  352. pJob->SetTriggersDirty();
  353. //
  354. // Save the new job. Obviously this is one place we definitely don't want
  355. // the AT job flag cleared on save!
  356. //
  357. hr = pJob->SaveP(wszName,
  358. TRUE,
  359. SAVEP_VARIABLE_LENGTH_DATA |
  360. SAVEP_PRESERVE_NET_SCHEDULE);
  361. if (FAILED(hr))
  362. {
  363. if (hr == HRESULT_FROM_WIN32(ERROR_FILE_EXISTS))
  364. {
  365. //
  366. // Name collision; someone has renamed a task to match the next
  367. // AT job. Recalc the max AT job ID.
  368. //
  369. GetNextAtID(&m_dwNextID);
  370. wcscpy(wszName, m_ptszFolderPath);
  371. wcscat(wszName, L"\\" TSZ_AT_JOB_PREFIX);
  372. _itow(m_dwNextID, wszID, 10);
  373. wcscat(wszName, wszID);
  374. wcscat(wszName, TSZ_DOTJOB);
  375. //
  376. // Now, retry the save.
  377. //
  378. hr = pJob->SaveP(wszName,
  379. TRUE,
  380. SAVEP_VARIABLE_LENGTH_DATA |
  381. SAVEP_PRESERVE_NET_SCHEDULE);
  382. if (FAILED(hr))
  383. {
  384. ERR_OUT("CSchedule::AddAtJob: Save", hr);
  385. pJob->Release();
  386. return hr;
  387. }
  388. }
  389. else
  390. {
  391. ERR_OUT("CSchedule::AddAtJob: Save", hr);
  392. pJob->Release();
  393. return hr;
  394. }
  395. }
  396. *ppJob = pJob;
  397. return S_OK;
  398. }
  399. //+----------------------------------------------------------------------------
  400. //
  401. // Member: CSchedule::GetAtTaskMaxHours
  402. //
  403. // Synopsis: Check a registry setting to see what max run time value a user
  404. // has specified for AT tasks. If the key is not present or can't
  405. // be opened then interpret as the normal task default of 72.
  406. //
  407. // Arguments: none
  408. //
  409. // Returns: bool
  410. //
  411. // Notes: This method is not exposed to external clients, thus it is not
  412. // part of a public interface.
  413. //-----------------------------------------------------------------------------
  414. HRESULT CSchedule::GetAtTaskMaxHours(DWORD* pdwMaxHours)
  415. {
  416. //
  417. // Open the schedule service key
  418. //
  419. long lErr;
  420. HKEY hSchedKey;
  421. lErr = RegOpenKeyEx(HKEY_LOCAL_MACHINE, SCH_SVC_KEY, 0, KEY_READ | KEY_WRITE, &hSchedKey);
  422. if (lErr != ERROR_SUCCESS)
  423. {
  424. ERR_OUT("RegOpenKeyEx of Scheduler key", lErr);
  425. return(HRESULT_FROM_WIN32(lErr));
  426. }
  427. //
  428. // Get the AtTaskMaxHours setting
  429. //
  430. bool bNeedToUpdate = false;
  431. DWORD dwMaxHours = 0;
  432. DWORD cb = sizeof(DWORD);
  433. lErr = RegQueryValueEx(hSchedKey, SCH_ATTASKMAXHOURS_VALUE, NULL, NULL, (LPBYTE)&dwMaxHours, &cb);
  434. if (lErr != ERROR_SUCCESS)
  435. {
  436. if (lErr != ERROR_FILE_NOT_FOUND)
  437. {
  438. ERR_OUT("Read of AtTaskMaxHours registry value", lErr);
  439. RegCloseKey(hSchedKey);
  440. return(HRESULT_FROM_WIN32(lErr));
  441. }
  442. //
  443. // Need to create the missing registry entry
  444. //
  445. dwMaxHours = DEFAULT_MAXRUNTIME_HOURS;
  446. bNeedToUpdate = true;
  447. }
  448. //
  449. // Correct out-of-bounds stored registry values
  450. //
  451. if (dwMaxHours > 999)
  452. {
  453. dwMaxHours = 999;
  454. bNeedToUpdate = true;
  455. }
  456. if (bNeedToUpdate)
  457. {
  458. lErr = RegSetValueEx(hSchedKey, SCH_ATTASKMAXHOURS_VALUE, 0, REG_DWORD,(CONST BYTE *)&dwMaxHours, sizeof(DWORD));
  459. if (lErr != ERROR_SUCCESS)
  460. {
  461. ERR_OUT("Update of AtTaskMaxHours registry value", lErr);
  462. RegCloseKey(hSchedKey);
  463. return(HRESULT_FROM_WIN32(lErr));
  464. }
  465. }
  466. //
  467. // Convert the stored value to a value that has meaning to the scheduler
  468. //
  469. if (!dwMaxHours)
  470. dwMaxHours = INFINITE; // If value is zero, return infinite (-1) for max run time
  471. else
  472. dwMaxHours *= 3600000; // The stored value is in hours, but the value passed back
  473. // needs to be in milliseconds, so convert
  474. RegCloseKey(hSchedKey);
  475. // Set the value to be passed back
  476. *pdwMaxHours = dwMaxHours;
  477. return S_OK;
  478. }
  479. //+----------------------------------------------------------------------------
  480. //
  481. // Member: CSchedule::AddAtJob
  482. //
  483. // Synopsis: create a downlevel job
  484. //
  485. // Arguments: [At] - reference to an AT_INFO struct
  486. // [pID] - returns the new ID (optional, can be NULL)
  487. //
  488. // Returns: HRESULTS
  489. //
  490. // Notes: This method is not exposed to external clients, thus it is not
  491. // part of a public interface.
  492. //-----------------------------------------------------------------------------
  493. STDMETHODIMP
  494. CSchedule::AddAtJob(const AT_INFO &At, DWORD * pID)
  495. {
  496. TRACE(CSchedule, AddAtJob);
  497. HRESULT hr = S_OK;
  498. CJob *pJob;
  499. WCHAR wszName[MAX_PATH + 1];
  500. WCHAR wszID[SCH_SMBUF_LEN];
  501. hr = AddAtJobCommon(At, pID, &pJob, wszName, wszID);
  502. if (FAILED(hr))
  503. {
  504. ERR_OUT("AddAtJob: AddAtJobCommon", hr);
  505. return hr;
  506. }
  507. //
  508. // Free the job object.
  509. //
  510. pJob->Release();
  511. //
  512. // Return the new job's ID and increment the ID counter
  513. //
  514. if (pID != NULL)
  515. {
  516. *pID = m_dwNextID;
  517. }
  518. hr = IncrementAndSaveID();
  519. return hr;
  520. }
  521. //+---------------------------------------------------------------------------
  522. //
  523. // Function: IsMonthBitSet
  524. //
  525. // Synopsis: Returns nonzero if 1-based bit [wDay] in [dwDaysOfMonth] is
  526. // set.
  527. //
  528. // History: 09-26-96 DavidMun Created
  529. //
  530. //----------------------------------------------------------------------------
  531. inline BOOL
  532. IsMonthBitSet(DWORD dwDaysOfMonth, WORD wDay)
  533. {
  534. return dwDaysOfMonth & (1 << (wDay - 1));
  535. }
  536. //+---------------------------------------------------------------------------
  537. //
  538. // Function: CalcDomTriggerDates
  539. //
  540. // Synopsis: Calculate the dates for the start and end of the Day Of Month
  541. // trigger(s).
  542. //
  543. // Arguments: [dwDaysOfMonth] - bit array, bit 0=day 1, etc. At least one
  544. // bit must be set!
  545. // [stNow] - current time
  546. // [stStart] - same as [stNow] but has hour & minute
  547. // values of actual job run time
  548. // [pstStart1] - filled with start date of first trigger
  549. // [pstEnd1] - filled with end date of first trigger
  550. // [pstStart2] - filled with start date of second trigger;
  551. // wDay is 0 if second trigger not needed
  552. // [pstEnd2] - filled with end date of second trigger,
  553. // wDay is 0 if second trigger not needed
  554. //
  555. // Modifies: All output arguments.
  556. //
  557. // History: 09-26-96 DavidMun Created
  558. //
  559. // Notes: Only the month, day, and year values in the output structs
  560. // are used.
  561. //
  562. //----------------------------------------------------------------------------
  563. VOID
  564. CalcDomTriggerDates(
  565. DWORD dwDaysOfMonth,
  566. const SYSTEMTIME &stNow,
  567. const SYSTEMTIME &stStart,
  568. SYSTEMTIME *pstStart1,
  569. SYSTEMTIME *pstEnd1,
  570. SYSTEMTIME *pstStart2,
  571. SYSTEMTIME *pstEnd2)
  572. {
  573. BOOL fDone = FALSE;
  574. WORD wStart1MonthDays;
  575. // assert not all bits below 32 are zero
  576. Win4Assert(dwDaysOfMonth & JOB_RGFDAYS_MAX);
  577. //
  578. // Find the start date for the first DOM trigger.
  579. //
  580. *pstStart1 = stNow;
  581. if (IsFirstTimeEarlier(&stStart, &stNow))
  582. {
  583. // already past run time today
  584. IncrementDay(pstStart1);
  585. }
  586. HRESULT hr = MonthDays(pstStart1->wMonth, pstStart1->wYear, &wStart1MonthDays);
  587. if (FAILED(hr))
  588. {
  589. schAssert(!"Bad systemtime");
  590. return;
  591. }
  592. do
  593. {
  594. while (!IsMonthBitSet(dwDaysOfMonth, pstStart1->wDay) &&
  595. pstStart1->wDay <= wStart1MonthDays)
  596. {
  597. pstStart1->wDay++;
  598. }
  599. //
  600. // now either:
  601. // start1.wDay > wStart1MonthDays or
  602. // bit at start1.wDay is 1
  603. //
  604. if (pstStart1->wDay > wStart1MonthDays)
  605. {
  606. // have to go on to next month to get the first start date
  607. pstStart1->wDay = 1;
  608. IncrementMonth(pstStart1);
  609. MonthDays(pstStart1->wMonth, pstStart1->wYear, &wStart1MonthDays);
  610. }
  611. else
  612. {
  613. fDone = TRUE;
  614. }
  615. } while (!fDone);
  616. //
  617. // Now bit at pstStart1->wDay is on, and pstStart1->wDay is a valid day in
  618. // pstStart1->wMonth, and wStart1MonthDays is the number of days in the
  619. // month pstStart1->wMonth. Next we need to find end1.
  620. //
  621. // end1 is initialized to start1.
  622. //
  623. // If there are any days set before the start day, then end1.wMonth will
  624. // be start1.wMonth + 1, and end1.wDay will be the last of the days that
  625. // is set before start1.wDay, with the restriction that end1.wDay is not
  626. // greater than the number of days in end1.wMonth.
  627. //
  628. *pstEnd1 = *pstStart1;
  629. WORD wDay;
  630. if (pstStart1->wDay > 1)
  631. {
  632. WORD wEnd1Month;
  633. WORD wEnd1MonthDays;
  634. wEnd1Month = pstEnd1->wMonth + 1;
  635. if (wEnd1Month > 12)
  636. {
  637. wEnd1Month = 1;
  638. }
  639. MonthDays(wEnd1Month, pstEnd1->wYear, &wEnd1MonthDays);
  640. WORD wMaxDay = min(pstStart1->wDay - 1, wEnd1MonthDays);
  641. for (wDay = 1; wDay <= wMaxDay; wDay++)
  642. {
  643. if (IsMonthBitSet(dwDaysOfMonth, wDay))
  644. {
  645. pstEnd1->wDay = wDay;
  646. }
  647. }
  648. }
  649. //
  650. // If any day bits were set before start1.wDay then end1.wDay will no
  651. // longer == start1.wDay, and end1 will be referring to the next month.
  652. //
  653. // Otherwise, End1 will remain in the same month as Start1, but will
  654. // need to be set to the last day bit set in the Start1 month.
  655. //
  656. if (pstEnd1->wDay < pstStart1->wDay)
  657. {
  658. IncrementMonth(pstEnd1);
  659. }
  660. else
  661. {
  662. for (wDay = pstStart1->wDay + 1; wDay <= wStart1MonthDays; wDay++)
  663. {
  664. if (IsMonthBitSet(dwDaysOfMonth, wDay))
  665. {
  666. pstEnd1->wDay = wDay;
  667. }
  668. }
  669. }
  670. //
  671. // Now start1 and end1 are set. next, check if there's a need for the
  672. // second trigger. There are two cases where a second trigger is
  673. // required.
  674. //
  675. // Case a: second trigger must fill time between end of first trigger
  676. // and start of first trigger. for example, job is to run on next
  677. // 1, 30, 31 and start1 is 1/31. then end1 will be 2/1, and a second
  678. // trigger must go from 3/30 to 3/30. Note this case can only occur
  679. // if End1.wMonth == February.
  680. //
  681. // Case b: second trigger must fill time somewhere in the 29-31 day range.
  682. // For example, job is to run on next 1-31, and current day is 4/1. so
  683. // start1 is 4/1, end1 is 4/30, then start2 must be 5/31 to 5/31.
  684. //
  685. // As another example, job is to run on next 27, 28, 30, current day is
  686. // 2/28. then start1 is 2/28, end1 is 3/27, start2 is 3/30, end2 is 3/30.
  687. //
  688. // Case b only occurs when there are bits set for days beyond the last day
  689. // of pstStart1->wmonth.
  690. //
  691. //
  692. //
  693. // test if we need case a.
  694. //
  695. if (pstEnd1->wMonth == 2 &&
  696. pstStart1->wDay > 29 &&
  697. pstEnd1->wDay < pstStart1->wDay - 1)
  698. {
  699. //
  700. // There's a gap between end1 and start1. we need the second trigger
  701. // if there are any day bits set in that gap.
  702. //
  703. Win4Assert(pstStart1->wMonth == 1);
  704. Win4Assert(pstEnd1->wDay + 1 <= 30);
  705. *pstStart2 = *pstEnd1;
  706. pstStart2->wMonth = 3;
  707. *pstEnd2 = *pstStart2;
  708. SetDomTrigger2Days(dwDaysOfMonth,
  709. pstEnd1->wDay + 1,
  710. pstStart1->wDay - 1,
  711. pstStart2,
  712. pstEnd2);
  713. }
  714. else if (wStart1MonthDays < 31)
  715. {
  716. //
  717. // we have case b if any bits after the last day in pstStart1->wMonth
  718. // are set.
  719. //
  720. *pstStart2 = *pstEnd1;
  721. if (pstEnd1->wMonth == pstStart1->wMonth)
  722. {
  723. pstStart2->wMonth++;
  724. }
  725. *pstEnd2 = *pstStart2;
  726. SetDomTrigger2Days(dwDaysOfMonth,
  727. wStart1MonthDays + 1,
  728. 31,
  729. pstStart2,
  730. pstEnd2);
  731. }
  732. else
  733. {
  734. // no second trigger
  735. pstStart2->wDay = 0;
  736. pstEnd2->wDay = 0;
  737. }
  738. }
  739. //+---------------------------------------------------------------------------
  740. //
  741. // Function: SetDomTrigger2Days
  742. //
  743. // Synopsis: Set the start and end dates for the second DOM trigger.
  744. //
  745. // Arguments: [dwDaysOfMonth] - bit array, bit 0=day 1, etc. At least
  746. // one bit must be set!
  747. // [wFirstDayToCheck] - 1 based
  748. // [wLastDayToCheck] - 1 based, must be >= [wFirstDayToCheck]
  749. // [pstStart2] - filled with start date of second
  750. // trigger; wDay is 0 if no second
  751. // trigger is required.
  752. // [pstEnd2] - filled with end date of second trigger;
  753. // wDay is 0 if no second trigger is
  754. // required.
  755. //
  756. // Modifies: All out args.
  757. //
  758. // History: 09-26-96 DavidMun Created
  759. //
  760. // Notes: This is a helper function called only by
  761. // CalcDomTriggerDates.
  762. //
  763. //----------------------------------------------------------------------------
  764. VOID
  765. SetDomTrigger2Days(
  766. DWORD dwDaysOfMonth,
  767. WORD wFirstDayToCheck,
  768. WORD wLastDayToCheck,
  769. SYSTEMTIME *pstStart2,
  770. SYSTEMTIME *pstEnd2)
  771. {
  772. WORD wDay;
  773. pstStart2->wDay = 0;
  774. pstEnd2->wDay = 0;
  775. for (wDay = wFirstDayToCheck; wDay <= wLastDayToCheck; wDay++)
  776. {
  777. if (IsMonthBitSet(dwDaysOfMonth, wDay))
  778. {
  779. //
  780. // if the start of the second trigger hasn't been assigned
  781. // yet, assign it. otherwise update the end of the second
  782. // trigger to the current day.
  783. //
  784. if (!pstStart2->wDay)
  785. {
  786. pstStart2->wDay = wDay;
  787. }
  788. else
  789. {
  790. pstEnd2->wDay = wDay;
  791. }
  792. }
  793. }
  794. //
  795. // there may have been only one day on in the gap, so the start and
  796. // end of trigger 2 are the same day.
  797. //
  798. if (pstStart2->wDay && !pstEnd2->wDay)
  799. {
  800. pstEnd2->wDay = pstStart2->wDay;
  801. }
  802. }
  803. //+---------------------------------------------------------------------------
  804. //
  805. // Function: CalcDowTriggerDate
  806. //
  807. // Synopsis: Set the start and end dates for the Day of Week trigger.
  808. //
  809. // Arguments: [stNow] - Current time
  810. // [stStart] - same as [stNow] but with hour and minute of
  811. // actual run time
  812. // [pstStart] - filled with start date
  813. // [pstEnd] - filled with end date
  814. //
  815. // Modifies: *[pstStart], *[pstEnd]
  816. //
  817. // History: 09-26-96 DavidMun Created
  818. //
  819. //----------------------------------------------------------------------------
  820. VOID
  821. CalcDowTriggerDate(
  822. const SYSTEMTIME &stNow,
  823. const SYSTEMTIME &stStart,
  824. SYSTEMTIME *pstStart,
  825. SYSTEMTIME *pstEnd)
  826. {
  827. *pstStart = stNow;
  828. //
  829. // If it's too late for the job to run today, make the start date
  830. // tomorrow.
  831. //
  832. if (IsFirstTimeEarlier(&stStart, &stNow))
  833. {
  834. IncrementDay(pstStart);
  835. }
  836. //
  837. // Make the end date 6 days later than the start date, that way we cover a
  838. // full week and all runs will happen.
  839. //
  840. *pstEnd = *pstStart;
  841. pstEnd->wDay += 6;
  842. WORD wLastDay;
  843. HRESULT hr = MonthDays(pstEnd->wMonth, pstEnd->wYear, &wLastDay);
  844. if (FAILED(hr))
  845. {
  846. schAssert(!"Bad systemtime");
  847. }
  848. else
  849. {
  850. if (pstEnd->wDay > wLastDay)
  851. {
  852. //
  853. // Wrap to the next month.
  854. //
  855. pstEnd->wDay -= wLastDay;
  856. IncrementMonth(pstEnd);
  857. }
  858. }
  859. }
  860. //+----------------------------------------------------------------------------
  861. //
  862. // Member: CSchedule::GetAtJob, private
  863. //
  864. // Synopsis: retrieves a downlevel job's AT info
  865. //
  866. // Arguments: [pwszFileName] - job object's file name
  867. // [pAt] - pointer to an AT_INFO struct
  868. // [pwszCommand] - buffer for the command string
  869. // [pcchCommand] - on input, size of supplied buffer, on output,
  870. // size needed if supplied buffer is too small.
  871. //
  872. // Returns: HRESULTS - ERROR_INSUFFICIENT_BUFFER if too small
  873. // - SCHED_E_NOT_AN_AT_JOB if not an AT job
  874. //
  875. // Notes: This method is not exposed to external clients, thus it is not
  876. // part of a public interface.
  877. //-----------------------------------------------------------------------------
  878. STDMETHODIMP
  879. CSchedule::GetAtJob(LPCTSTR pwszFileName, AT_INFO * pAt, LPWSTR pwszCommand,
  880. DWORD * pcchCommand)
  881. {
  882. TRACE(CSchedule, GetAtJob);
  883. CJob * pJob = CJob::Create();
  884. if (pJob == NULL)
  885. {
  886. return E_OUTOFMEMORY;
  887. }
  888. HRESULT hr = pJob->LoadP(pwszFileName, 0, TRUE, TRUE);
  889. if (FAILED(hr))
  890. {
  891. ERR_OUT("GetAtJob: LoadP", hr);
  892. pJob->Release();
  893. return hr;
  894. }
  895. hr = pJob->GetAtInfo(pAt, pwszCommand, pcchCommand);
  896. if (FAILED(hr))
  897. {
  898. ERR_OUT("GetAtJob: GetAtInfo", hr);
  899. pJob->Release();
  900. return hr;
  901. }
  902. pJob->Release();
  903. return S_OK;
  904. }
  905. //+----------------------------------------------------------------------------
  906. //
  907. // Member: CSchedule::IncrementAndSaveID
  908. //
  909. // Synopsis: Increment the NextJobID value and save it to the registry.
  910. //
  911. //-----------------------------------------------------------------------------
  912. HRESULT
  913. CSchedule::IncrementAndSaveID(void)
  914. {
  915. EnterCriticalSection(&m_CriticalSection);
  916. long lErr;
  917. HKEY hSchedKey;
  918. lErr = RegOpenKeyEx(HKEY_LOCAL_MACHINE, SCH_SVC_KEY, 0, KEY_SET_VALUE,
  919. &hSchedKey);
  920. if (lErr != ERROR_SUCCESS)
  921. {
  922. ERR_OUT("RegOpenKeyEx of Scheduler key", lErr);
  923. LeaveCriticalSection(&m_CriticalSection);
  924. return(HRESULT_FROM_WIN32(lErr));
  925. }
  926. m_dwNextID++;
  927. //
  928. // update the registry entry
  929. //
  930. lErr = RegSetValueEx(hSchedKey, SCH_NEXTATJOBID_VALUE, 0, REG_DWORD,
  931. (CONST BYTE *)&m_dwNextID, sizeof(DWORD));
  932. if (lErr != ERROR_SUCCESS)
  933. {
  934. ERR_OUT("Create of NextAtJobId registry value", lErr);
  935. m_dwNextID--;
  936. RegCloseKey(hSchedKey);
  937. LeaveCriticalSection(&m_CriticalSection);
  938. return(HRESULT_FROM_WIN32(lErr));
  939. }
  940. RegCloseKey(hSchedKey);
  941. LeaveCriticalSection(&m_CriticalSection);
  942. return S_OK;
  943. }
  944. //+----------------------------------------------------------------------------
  945. //
  946. // Member: CSchedule::ResetAtID
  947. //
  948. // Synopsis: Set the next at id value in the registry to 1
  949. //
  950. //-----------------------------------------------------------------------------
  951. HRESULT
  952. CSchedule::ResetAtID(void)
  953. {
  954. HRESULT hr = S_OK;
  955. HKEY hSchedKey = NULL;
  956. EnterCriticalSection(&m_CriticalSection);
  957. m_dwNextID = 1;
  958. do
  959. {
  960. LONG lErr;
  961. //
  962. // Open the schedule service key
  963. //
  964. lErr = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
  965. SCH_SVC_KEY,
  966. 0,
  967. KEY_READ | KEY_WRITE,
  968. &hSchedKey);
  969. if (lErr != ERROR_SUCCESS)
  970. {
  971. ERR_OUT("RegOpenKeyEx of Scheduler key", lErr);
  972. hr = HRESULT_FROM_WIN32(lErr);
  973. break;
  974. }
  975. //
  976. // Set the next At Job ID value to 1. If the value is not present,
  977. // it will be created.
  978. //
  979. lErr = RegSetValueEx(hSchedKey,
  980. SCH_NEXTATJOBID_VALUE,
  981. 0,
  982. REG_DWORD,
  983. (CONST BYTE *) &m_dwNextID,
  984. sizeof(m_dwNextID));
  985. if (lErr != ERROR_SUCCESS)
  986. {
  987. ERR_OUT("Create of NextAtJobId registry value", lErr);
  988. hr = HRESULT_FROM_WIN32(lErr);
  989. }
  990. } while (0);
  991. if (hSchedKey)
  992. {
  993. RegCloseKey(hSchedKey);
  994. }
  995. LeaveCriticalSection(&m_CriticalSection);
  996. return hr;
  997. }
  998. //+----------------------------------------------------------------------------
  999. //
  1000. // Member: CSchedule::_AtTaskExists, private
  1001. //
  1002. // Synopsis: Check for existing AT tasks
  1003. //
  1004. // Returns: S_OK - an AT task was found
  1005. // S_FALSE - no AT tasks were found
  1006. // E_*
  1007. //
  1008. //-----------------------------------------------------------------------------
  1009. HRESULT
  1010. CSchedule::_AtTaskExists(void)
  1011. {
  1012. WIN32_FIND_DATA fd;
  1013. HANDLE hFileFindContext;
  1014. hFileFindContext = FindFirstFile(g_wszAtJobSearchPath, &fd);
  1015. if (hFileFindContext == INVALID_HANDLE_VALUE)
  1016. {
  1017. return S_FALSE;
  1018. }
  1019. CJob * pJob = CJob::Create();
  1020. if (pJob == NULL)
  1021. {
  1022. return E_OUTOFMEMORY;
  1023. }
  1024. HRESULT hr = S_FALSE;
  1025. DWORD rgFlags;
  1026. do
  1027. {
  1028. if (!IsValidAtFilename(fd.cFileName))
  1029. {
  1030. continue;
  1031. }
  1032. HRESULT hrLoad = LoadAtJob(pJob, fd.cFileName);
  1033. if (FAILED(hrLoad))
  1034. {
  1035. hr = hrLoad;
  1036. break;
  1037. }
  1038. pJob->GetAllFlags(&rgFlags);
  1039. if (rgFlags & JOB_I_FLAG_NET_SCHEDULE)
  1040. {
  1041. hr = S_OK;
  1042. break;
  1043. }
  1044. } while (FindNextFile(hFileFindContext, &fd));
  1045. FindClose(hFileFindContext);
  1046. pJob->Release();
  1047. return hr;
  1048. }
  1049. //+----------------------------------------------------------------------------
  1050. //
  1051. // Member: CSchedule::InitAtID
  1052. //
  1053. // Synopsis: Obtains the current AT task ID from the registry.
  1054. //
  1055. //-----------------------------------------------------------------------------
  1056. HRESULT
  1057. CSchedule::InitAtID(void)
  1058. {
  1059. //
  1060. // Open the schedule service key
  1061. //
  1062. long lErr;
  1063. HKEY hSchedKey;
  1064. lErr = RegOpenKeyEx(HKEY_LOCAL_MACHINE, SCH_SVC_KEY, 0,
  1065. KEY_READ | KEY_WRITE, &hSchedKey);
  1066. if (lErr != ERROR_SUCCESS)
  1067. {
  1068. ERR_OUT("RegOpenKeyEx of Scheduler key", lErr);
  1069. return(HRESULT_FROM_WIN32(lErr));
  1070. }
  1071. //
  1072. // Get the next At Job ID
  1073. //
  1074. DWORD cb = sizeof(DWORD);
  1075. lErr = RegQueryValueEx(hSchedKey, SCH_NEXTATJOBID_VALUE, NULL, NULL,
  1076. (LPBYTE)&m_dwNextID, &cb);
  1077. if (lErr != ERROR_SUCCESS)
  1078. {
  1079. if (lErr != ERROR_FILE_NOT_FOUND)
  1080. {
  1081. ERR_OUT("Read of NextAtJobId registry value", lErr);
  1082. RegCloseKey(hSchedKey);
  1083. return(HRESULT_FROM_WIN32(lErr));
  1084. }
  1085. //
  1086. // Scan AT jobs for value if registry entry absent
  1087. //
  1088. GetNextAtID(&m_dwNextID);
  1089. //
  1090. // Create registry entry
  1091. //
  1092. lErr = RegSetValueEx(hSchedKey, SCH_NEXTATJOBID_VALUE, 0, REG_DWORD,
  1093. (CONST BYTE *)&m_dwNextID, sizeof(DWORD));
  1094. if (lErr != ERROR_SUCCESS)
  1095. {
  1096. ERR_OUT("Create of NextAtJobId registry value", lErr);
  1097. RegCloseKey(hSchedKey);
  1098. return(HRESULT_FROM_WIN32(lErr));
  1099. }
  1100. }
  1101. RegCloseKey(hSchedKey);
  1102. return S_OK;
  1103. }
  1104. #endif // #if !defined(_CHICAGO_)