xref: /reactos/base/services/schedsvc/job.c (revision 53221834)
1 /*
2  * COPYRIGHT:        See COPYING in the top level directory
3  * PROJECT:          ReactOS Services
4  * FILE:             base/services/schedsvc/job.c
5  * PURPOSE:          Scheduling service
6  * PROGRAMMER:       Eric Kohl <eric.kohl@reactos.org>
7  */
8 
9 /* INCLUDES *****************************************************************/
10 
11 #include "precomp.h"
12 
13 WINE_DEFAULT_DEBUG_CHANNEL(schedsvc);
14 
15 
16 /* GLOBALS ******************************************************************/
17 
18 typedef struct _SCHEDULE
19 {
20     DWORD JobTime;
21     DWORD DaysOfMonth;
22     UCHAR DaysOfWeek;
23     UCHAR Flags;
24     WORD Reserved;
25 } SCHEDULE, PSCHEDULE;
26 
27 DWORD dwNextJobId = 0;
28 DWORD dwJobCount = 0;
29 LIST_ENTRY JobListHead;
30 RTL_RESOURCE JobListLock;
31 
32 LIST_ENTRY StartListHead;
33 RTL_RESOURCE StartListLock;
34 
35 static WORD wDaysArray[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
36 
37 
38 /* FUNCTIONS *****************************************************************/
39 
40 DWORD
41 GetNextJobTimeout(VOID)
42 {
43     FILETIME FileTime;
44     SYSTEMTIME SystemTime;
45     ULARGE_INTEGER CurrentTime, Timeout;
46     PJOB pNextJob;
47 
48     if (IsListEmpty(&StartListHead))
49     {
50         TRACE("No job in list! Wait until next update.\n");
51         return INFINITE;
52     }
53 
54     pNextJob = CONTAINING_RECORD((&StartListHead)->Flink, JOB, StartEntry);
55 
56     FileTime.dwLowDateTime = pNextJob->StartTime.u.LowPart;
57     FileTime.dwHighDateTime = pNextJob->StartTime.u.HighPart;
58     FileTimeToSystemTime(&FileTime, &SystemTime);
59 
60     TRACE("Start next job (%lu) at %02hu:%02hu %02hu.%02hu.%hu\n",
61           pNextJob->JobId, SystemTime.wHour, SystemTime.wMinute,
62           SystemTime.wDay, SystemTime.wMonth, SystemTime.wYear);
63 
64     GetLocalTime(&SystemTime);
65     SystemTimeToFileTime(&SystemTime, &FileTime);
66 
67     CurrentTime.u.LowPart = FileTime.dwLowDateTime;
68     CurrentTime.u.HighPart = FileTime.dwHighDateTime;
69 
70     if (CurrentTime.QuadPart >= pNextJob->StartTime.QuadPart)
71     {
72         TRACE("Next event has already gone by!\n");
73         return 0;
74     }
75 
76     Timeout.QuadPart = (pNextJob->StartTime.QuadPart - CurrentTime.QuadPart) / 10000;
77     if (Timeout.u.HighPart != 0)
78     {
79         TRACE("Event happens too far in the future!\n");
80         return INFINITE;
81     }
82 
83     TRACE("Timeout: %lu\n", Timeout.u.LowPart);
84     return Timeout.u.LowPart;
85 }
86 
87 
88 static
89 VOID
90 ReScheduleJob(
91     PJOB pJob)
92 {
93     /* Remove the job from the start list */
94     RemoveEntryList(&pJob->StartEntry);
95 
96     /* Non-periodical job, remove it */
97     if ((pJob->Flags & JOB_RUN_PERIODICALLY) == 0)
98     {
99         /* Remove the job from the registry */
100         DeleteJob(pJob);
101 
102         /* Remove the job from the job list */
103         RemoveEntryList(&pJob->JobEntry);
104         dwJobCount--;
105 
106         /* Free the job object */
107         HeapFree(GetProcessHeap(), 0, pJob);
108         return;
109     }
110 
111     /* Calculate the next start time */
112     CalculateNextStartTime(pJob);
113 
114     /* Insert the job into the start list again */
115     InsertJobIntoStartList(&StartListHead, pJob);
116 #if 0
117     DumpStartList(&StartListHead);
118 #endif
119 }
120 
121 
122 VOID
123 RunNextJob(VOID)
124 {
125     PROCESS_INFORMATION ProcessInformation;
126     STARTUPINFOW StartupInfo;
127     BOOL bRet;
128     PJOB pNextJob;
129 
130     if (IsListEmpty(&StartListHead))
131     {
132         ERR("No job in list!\n");
133         return;
134     }
135 
136     pNextJob = CONTAINING_RECORD((&StartListHead)->Flink, JOB, StartEntry);
137 
138     TRACE("Run job %ld: %S\n", pNextJob->JobId, pNextJob->Command);
139 
140     ZeroMemory(&StartupInfo, sizeof(StartupInfo));
141     StartupInfo.cb = sizeof(StartupInfo);
142     StartupInfo.lpTitle = pNextJob->Command;
143     StartupInfo.dwFlags = STARTF_USESHOWWINDOW;
144     StartupInfo.wShowWindow = SW_SHOWDEFAULT;
145 
146     if ((pNextJob->Flags & JOB_NONINTERACTIVE) == 0)
147     {
148         StartupInfo.dwFlags |= STARTF_INHERITDESKTOP;
149         StartupInfo.lpDesktop = L"WinSta0\\Default";
150     }
151 
152     bRet = CreateProcessW(NULL,
153                           pNextJob->Command,
154                           NULL,
155                           NULL,
156                           FALSE,
157                           CREATE_NEW_CONSOLE,
158                           NULL,
159                           NULL,
160                           &StartupInfo,
161                           &ProcessInformation);
162     if (bRet == FALSE)
163     {
164         ERR("CreateProcessW() failed (Error %lu)\n", GetLastError());
165     }
166     else
167     {
168         CloseHandle(ProcessInformation.hThread);
169         CloseHandle(ProcessInformation.hProcess);
170     }
171 
172     ReScheduleJob(pNextJob);
173 }
174 
175 
176 static
177 VOID
178 GetJobName(
179     HKEY hJobsKey,
180     PWSTR pszJobName)
181 {
182     WCHAR szNameBuffer[JOB_NAME_LENGTH];
183     FILETIME SystemTime;
184     ULONG ulSeed, ulValue;
185     HKEY hKey;
186     LONG lError;
187 
188     GetSystemTimeAsFileTime(&SystemTime);
189     ulSeed = SystemTime.dwLowDateTime;
190     for (;;)
191     {
192         ulValue = RtlRandomEx(&ulSeed);
193         swprintf(szNameBuffer, L"%08lx", ulValue);
194 
195         hKey = NULL;
196         lError = RegOpenKeyEx(hJobsKey,
197                               szNameBuffer,
198                               0,
199                               KEY_READ,
200                               &hKey);
201         if (lError != ERROR_SUCCESS)
202         {
203             wcscpy(pszJobName, szNameBuffer);
204             return;
205         }
206 
207         RegCloseKey(hKey);
208     }
209 }
210 
211 
212 LONG
213 SaveJob(
214     _In_ PJOB pJob)
215 {
216     SCHEDULE Schedule;
217     HKEY hJobsKey = NULL, hJobKey = NULL;
218     LONG lError;
219 
220     TRACE("SaveJob()\n");
221 
222     lError = RegCreateKeyExW(HKEY_LOCAL_MACHINE,
223                              L"System\\CurrentControlSet\\Services\\Schedule\\Jobs",
224                              0,
225                              NULL,
226                              REG_OPTION_NON_VOLATILE,
227                              KEY_WRITE,
228                              NULL,
229                              &hJobsKey,
230                              NULL);
231     if (lError != ERROR_SUCCESS)
232         goto done;
233 
234     GetJobName(hJobsKey, pJob->Name);
235 
236     lError = RegCreateKeyExW(hJobsKey,
237                              pJob->Name,
238                              0,
239                              NULL,
240                              REG_OPTION_NON_VOLATILE,
241                              KEY_WRITE,
242                              NULL,
243                              &hJobKey,
244                              NULL);
245     if (lError != ERROR_SUCCESS)
246         goto done;
247 
248     Schedule.JobTime = pJob->JobTime;
249     Schedule.DaysOfMonth = pJob->DaysOfMonth;
250     Schedule.DaysOfWeek = pJob->DaysOfWeek;
251     Schedule.Flags = pJob->Flags;
252 
253     lError = RegSetValueEx(hJobKey,
254                            L"Schedule",
255                            0,
256                            REG_BINARY,
257                            (PBYTE)&Schedule,
258                            sizeof(Schedule));
259     if (lError != ERROR_SUCCESS)
260         goto done;
261 
262     lError = RegSetValueEx(hJobKey,
263                            L"Command",
264                            0,
265                            REG_SZ,
266                            (PBYTE)pJob->Command,
267                            (wcslen(pJob->Command) + 1) * sizeof(WCHAR));
268     if (lError != ERROR_SUCCESS)
269         goto done;
270 
271 done:
272     if (hJobKey != NULL)
273         RegCloseKey(hJobKey);
274 
275     if (hJobsKey != NULL)
276         RegCloseKey(hJobsKey);
277 
278     return lError;
279 }
280 
281 
282 LONG
283 DeleteJob(
284     _In_ PJOB pJob)
285 {
286     HKEY hJobsKey = NULL;
287     LONG lError;
288 
289     TRACE("DeleteJob()\n");
290 
291     lError = RegCreateKeyExW(HKEY_LOCAL_MACHINE,
292                              L"System\\CurrentControlSet\\Services\\Schedule\\Jobs",
293                              0,
294                              NULL,
295                              REG_OPTION_NON_VOLATILE,
296                              KEY_WRITE,
297                              NULL,
298                              &hJobsKey,
299                              NULL);
300     if (lError != ERROR_SUCCESS)
301         goto done;
302 
303     lError = RegDeleteKey(hJobsKey,
304                           pJob->Name);
305     if (lError != ERROR_SUCCESS)
306         goto done;
307 
308 done:
309     if (hJobsKey != NULL)
310         RegCloseKey(hJobsKey);
311 
312     return lError;
313 }
314 
315 
316 LONG
317 LoadJobs(VOID)
318 {
319     SCHEDULE Schedule;
320     WCHAR szNameBuffer[JOB_NAME_LENGTH];
321     DWORD dwNameLength, dwIndex, dwSize;
322     HKEY hJobsKey = NULL, hJobKey = NULL;
323     PJOB pJob = NULL;
324     LONG lError;
325 
326     TRACE("LoadJobs()\n");
327 
328     lError = RegCreateKeyExW(HKEY_LOCAL_MACHINE,
329                              L"System\\CurrentControlSet\\Services\\Schedule\\Jobs",
330                              0,
331                              NULL,
332                              REG_OPTION_NON_VOLATILE,
333                              KEY_READ,
334                              NULL,
335                              &hJobsKey,
336                              NULL);
337     if (lError != ERROR_SUCCESS)
338         goto done;
339 
340     for (dwIndex = 0; dwIndex < 1000; dwIndex++)
341     {
342         dwNameLength = JOB_NAME_LENGTH;
343         lError = RegEnumKeyEx(hJobsKey,
344                               dwIndex,
345                               szNameBuffer,
346                               &dwNameLength,
347                               NULL,
348                               NULL,
349                               NULL,
350                               NULL);
351         if (lError != ERROR_SUCCESS)
352         {
353             lError = ERROR_SUCCESS;
354             break;
355         }
356 
357         TRACE("KeyName: %S\n", szNameBuffer);
358 
359         lError = RegOpenKeyEx(hJobsKey,
360                               szNameBuffer,
361                               0,
362                               KEY_READ,
363                               &hJobKey);
364         if (lError != ERROR_SUCCESS)
365             break;
366 
367         dwSize = sizeof(SCHEDULE);
368         lError = RegQueryValueEx(hJobKey,
369                                  L"Schedule",
370                                  NULL,
371                                  NULL,
372                                  (PBYTE)&Schedule,
373                                  &dwSize);
374         if (lError == ERROR_SUCCESS)
375         {
376             dwSize = 0;
377             RegQueryValueEx(hJobKey,
378                             L"Command",
379                             NULL,
380                             NULL,
381                             NULL,
382                             &dwSize);
383             if (dwSize != 0)
384             {
385                 /* Allocate a new job object */
386                 pJob = HeapAlloc(GetProcessHeap(),
387                                  HEAP_ZERO_MEMORY,
388                                  sizeof(JOB) + dwSize - sizeof(WCHAR));
389                 if (pJob == NULL)
390                 {
391                     lError = ERROR_OUTOFMEMORY;
392                     break;
393                 }
394 
395                 lError = RegQueryValueEx(hJobKey,
396                                          L"Command",
397                                          NULL,
398                                          NULL,
399                                          (PBYTE)pJob->Command,
400                                          &dwSize);
401                 if (lError != ERROR_SUCCESS)
402                     break;
403 
404                 wcscpy(pJob->Name, szNameBuffer);
405                 pJob->JobTime = Schedule.JobTime;
406                 pJob->DaysOfMonth = Schedule.DaysOfMonth;
407                 pJob->DaysOfWeek = Schedule.DaysOfWeek;
408                 pJob->Flags = Schedule.Flags;
409 
410                 /* Acquire the job list lock exclusively */
411                 RtlAcquireResourceExclusive(&JobListLock, TRUE);
412 
413                 /* Assign a new job ID */
414                 pJob->JobId = dwNextJobId++;
415                 dwJobCount++;
416 
417                 /* Append the new job to the job list */
418                 InsertTailList(&JobListHead, &pJob->JobEntry);
419 
420                 /* Calculate the next start time */
421                 CalculateNextStartTime(pJob);
422 
423                 /* Insert the job into the start list */
424                 InsertJobIntoStartList(&StartListHead, pJob);
425 #if 0
426                 DumpStartList(&StartListHead);
427 #endif
428 
429                 /* Release the job list lock */
430                 RtlReleaseResource(&JobListLock);
431 
432                 pJob = NULL;
433             }
434         }
435 
436         RegCloseKey(hJobKey);
437         hJobKey = NULL;
438     }
439 
440 done:
441     if (pJob != NULL)
442         HeapFree(GetProcessHeap(), 0, pJob);
443 
444     if (hJobKey != NULL)
445         RegCloseKey(hJobKey);
446 
447     if (hJobsKey != NULL)
448         RegCloseKey(hJobsKey);
449 
450     return lError;
451 }
452 
453 
454 static
455 WORD
456 DaysOfMonth(
457     WORD wMonth,
458     WORD wYear)
459 {
460     if (wMonth == 2 && wYear % 4 == 0 && wYear % 400 != 0)
461         return 29;
462 
463     return wDaysArray[wMonth];
464 }
465 
466 
467 VOID
468 CalculateNextStartTime(
469     _In_ PJOB pJob)
470 {
471     SYSTEMTIME CurrentSystemTime, StartSystemTime;
472     FILETIME StartFileTime;
473     WORD wDaysOffset, wTempOffset, i, wJobDayOfWeek, wJobDayOfMonth;
474     DWORD_PTR CurrentTimeMs;
475     BOOL bDaysOffsetValid;
476 
477     TRACE("CalculateNextStartTime(%p)\n", pJob);
478     TRACE("JobTime: %lu\n", pJob->JobTime);
479     TRACE("DaysOfWeek: 0x%x\n", pJob->DaysOfWeek);
480     TRACE("DaysOfMonth: 0x%x\n", pJob->DaysOfMonth);
481 
482     GetLocalTime(&CurrentSystemTime);
483 
484     CurrentTimeMs = (DWORD_PTR)CurrentSystemTime.wHour * 3600000 +
485                     (DWORD_PTR)CurrentSystemTime.wMinute * 60000;
486 
487     bDaysOffsetValid = FALSE;
488     wDaysOffset = 0;
489     if ((pJob->DaysOfWeek == 0) && (pJob->DaysOfMonth == 0))
490     {
491         if (CurrentTimeMs >= pJob->JobTime)
492         {
493             TRACE("Tomorrow!\n");
494             wDaysOffset = 1;
495         }
496 
497         bDaysOffsetValid = TRUE;
498     }
499     else
500     {
501         if (pJob->DaysOfWeek != 0)
502         {
503             TRACE("DaysOfWeek!\n");
504             for (i = 0; i < 7; i++)
505             {
506                 if (pJob->DaysOfWeek & (1 << i))
507                 {
508                     /* Adjust the range */
509                     wJobDayOfWeek = (i + 1) % 7;
510                     TRACE("wJobDayOfWeek: %hu\n", wJobDayOfWeek);
511                     TRACE("CurrentSystemTime.wDayOfWeek: %hu\n", CurrentSystemTime.wDayOfWeek);
512 
513                     /* Calculate the days offset */
514                     if ((CurrentSystemTime.wDayOfWeek > wJobDayOfWeek ) ||
515                         ((CurrentSystemTime.wDayOfWeek == wJobDayOfWeek) && (CurrentTimeMs >= pJob->JobTime)))
516                     {
517                         wTempOffset = 7 - CurrentSystemTime.wDayOfWeek + wJobDayOfWeek;
518                         TRACE("wTempOffset: %hu\n", wTempOffset);
519                     }
520                     else
521                     {
522                         wTempOffset = wJobDayOfWeek - CurrentSystemTime.wDayOfWeek;
523                         TRACE("wTempOffset: %hu\n", wTempOffset);
524                     }
525 
526                     /* Use the smallest offset */
527                     if (bDaysOffsetValid == FALSE)
528                     {
529                         wDaysOffset = wTempOffset;
530                         bDaysOffsetValid = TRUE;
531                     }
532                     else
533                     {
534                         if (wTempOffset < wDaysOffset)
535                             wDaysOffset = wTempOffset;
536                     }
537                 }
538             }
539         }
540 
541         if (pJob->DaysOfMonth != 0)
542         {
543             FIXME("Support DaysOfMonth!\n");
544             for (i = 0; i < 31; i++)
545             {
546                 if (pJob->DaysOfMonth & (1 << i))
547                 {
548                     /* Adjust the range */
549                     wJobDayOfMonth = i + 1;
550                     FIXME("wJobDayOfMonth: %hu\n", wJobDayOfMonth);
551                     FIXME("CurrentSystemTime.wDay: %hu\n", CurrentSystemTime.wDay);
552 
553                     if ((CurrentSystemTime.wDay > wJobDayOfMonth) ||
554                         ((CurrentSystemTime.wDay == wJobDayOfMonth) && (CurrentTimeMs >= pJob->JobTime)))
555                     {
556                         wTempOffset = DaysOfMonth(CurrentSystemTime.wMonth, CurrentSystemTime.wYear) -
557                                       CurrentSystemTime.wDay + wJobDayOfMonth;
558                         FIXME("wTempOffset: %hu\n", wTempOffset);
559                     }
560                     else
561                     {
562                         wTempOffset = wJobDayOfMonth - CurrentSystemTime.wDay;
563                         FIXME("wTempOffset: %hu\n", wTempOffset);
564                     }
565 
566                     /* Use the smallest offset */
567                     if (bDaysOffsetValid == FALSE)
568                     {
569                         wDaysOffset = wTempOffset;
570                         bDaysOffsetValid = TRUE;
571                     }
572                     else
573                     {
574                         if (wTempOffset < wDaysOffset)
575                             wDaysOffset = wTempOffset;
576                     }
577                 }
578             }
579         }
580     }
581 
582     TRACE("wDaysOffset: %hu\n", wDaysOffset);
583 
584     CopyMemory(&StartSystemTime, &CurrentSystemTime, sizeof(SYSTEMTIME));
585 
586     StartSystemTime.wMilliseconds = 0;
587     StartSystemTime.wSecond = 0;
588     StartSystemTime.wHour = (WORD)(pJob->JobTime / 3600000);
589     StartSystemTime.wMinute = (WORD)((pJob->JobTime % 3600000) / 60000);
590 
591     SystemTimeToFileTime(&StartSystemTime, &StartFileTime);
592 
593     pJob->StartTime.u.LowPart = StartFileTime.dwLowDateTime;
594     pJob->StartTime.u.HighPart = StartFileTime.dwHighDateTime;
595     if (bDaysOffsetValid && wDaysOffset != 0)
596     {
597         pJob->StartTime.QuadPart += ((ULONGLONG)wDaysOffset * 24 * 60 * 60 * 10000);
598     }
599 }
600 
601 
602 VOID
603 InsertJobIntoStartList(
604     _In_ PLIST_ENTRY StartListHead,
605     _In_ PJOB pJob)
606 {
607     PLIST_ENTRY CurrentEntry, PreviousEntry;
608     PJOB CurrentJob;
609 
610     if (IsListEmpty(StartListHead))
611     {
612          InsertHeadList(StartListHead, &pJob->StartEntry);
613          return;
614     }
615 
616     CurrentEntry = StartListHead->Flink;
617     while (CurrentEntry != StartListHead)
618     {
619         CurrentJob = CONTAINING_RECORD(CurrentEntry, JOB, StartEntry);
620 
621         if ((CurrentEntry == StartListHead->Flink) &&
622             (pJob->StartTime.QuadPart < CurrentJob->StartTime.QuadPart))
623         {
624             /* Insert before the first entry */
625             InsertHeadList(StartListHead, &pJob->StartEntry);
626             return;
627         }
628 
629         if (pJob->StartTime.QuadPart < CurrentJob->StartTime.QuadPart)
630         {
631             /* Insert between the previous and the current entry */
632             PreviousEntry = CurrentEntry->Blink;
633             pJob->StartEntry.Blink = PreviousEntry;
634             pJob->StartEntry.Flink = CurrentEntry;
635             PreviousEntry->Flink = &pJob->StartEntry;
636             CurrentEntry->Blink = &pJob->StartEntry;
637             return;
638         }
639 
640         if ((CurrentEntry->Flink == StartListHead) &&
641             (pJob->StartTime.QuadPart >= CurrentJob->StartTime.QuadPart))
642         {
643             /* Insert after the last entry */
644             InsertTailList(StartListHead, &pJob->StartEntry);
645             return;
646         }
647 
648         CurrentEntry = CurrentEntry->Flink;
649     }
650 }
651 
652 
653 VOID
654 DumpStartList(
655     _In_ PLIST_ENTRY StartListHead)
656 {
657     PLIST_ENTRY CurrentEntry;
658     PJOB CurrentJob;
659 
660     CurrentEntry = StartListHead->Flink;
661     while (CurrentEntry != StartListHead)
662     {
663         CurrentJob = CONTAINING_RECORD(CurrentEntry, JOB, StartEntry);
664 
665         TRACE("%3lu: %016I64x\n", CurrentJob->JobId, CurrentJob->StartTime.QuadPart);
666 
667         CurrentEntry = CurrentEntry->Flink;
668     }
669 }
670 
671 /* EOF */
672