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