xref: /reactos/base/services/schedsvc/job.c (revision fa607733)
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 FILETIME NextJobStartTime;
35 BOOL bValidNextJobStartTime = FALSE;
36 
37 
38 static WORD wDaysArray[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
39 
40 
41 /* FUNCTIONS *****************************************************************/
42 
43 VOID
GetNextJobTimeout(HANDLE hTimer)44 GetNextJobTimeout(HANDLE hTimer)
45 {
46     PLIST_ENTRY CurrentEntry;
47     FILETIME DueTime;
48     PJOB CurrentJob;
49 
50     bValidNextJobStartTime = FALSE;
51     CurrentEntry = JobListHead.Flink;
52     while (CurrentEntry != &JobListHead)
53     {
54         CurrentJob = CONTAINING_RECORD(CurrentEntry, JOB, JobEntry);
55 
56         if (bValidNextJobStartTime == FALSE)
57         {
58             CopyMemory(&NextJobStartTime, &CurrentJob->StartTime, sizeof(FILETIME));
59             bValidNextJobStartTime = TRUE;
60         }
61         else
62         {
63             if (CompareFileTime(&NextJobStartTime, &CurrentJob->StartTime) > 0)
64                 CopyMemory(&NextJobStartTime, &CurrentJob->StartTime, sizeof(FILETIME));
65         }
66 
67         CurrentEntry = CurrentEntry->Flink;
68     }
69 
70     if (bValidNextJobStartTime == FALSE)
71     {
72         TRACE("No valid job!\n");
73         return;
74     }
75 
76     LocalFileTimeToFileTime(&DueTime, &NextJobStartTime);
77 
78     SetWaitableTimer(hTimer,
79                      (PLARGE_INTEGER)&DueTime,
80                      0,
81                      NULL,
82                      NULL,
83                      TRUE);
84 }
85 
86 #if 0
87 static
88 VOID
89 ReScheduleJob(
90     PJOB pJob)
91 {
92     /* Remove the job from the start list */
93     RemoveEntryList(&pJob->StartEntry);
94 
95     /* Non-periodical job, remove it */
96     if ((pJob->Flags & JOB_RUN_PERIODICALLY) == 0)
97     {
98         /* Remove the job from the registry */
99         DeleteJob(pJob);
100 
101         /* Remove the job from the job list */
102         RemoveEntryList(&pJob->JobEntry);
103         dwJobCount--;
104 
105         /* Free the job object */
106         HeapFree(GetProcessHeap(), 0, pJob);
107         return;
108     }
109 
110     /* Calculate the next start time */
111     CalculateNextStartTime(pJob);
112 
113     /* Insert the job into the start list again */
114     InsertJobIntoStartList(&StartListHead, pJob);
115 #if 0
116     DumpStartList(&StartListHead);
117 #endif
118 }
119 #endif
120 
121 VOID
RunCurrentJobs(VOID)122 RunCurrentJobs(VOID)
123 {
124     PROCESS_INFORMATION ProcessInformation;
125     STARTUPINFOW StartupInfo;
126     PLIST_ENTRY CurrentEntry;
127     PJOB CurrentJob;
128     BOOL bRet;
129 
130     CurrentEntry = JobListHead.Flink;
131     while (CurrentEntry != &JobListHead)
132     {
133         CurrentJob = CONTAINING_RECORD(CurrentEntry, JOB, JobEntry);
134 
135         if (CompareFileTime(&NextJobStartTime, &CurrentJob->StartTime) == 0)
136         {
137             TRACE("Run job %ld: %S\n", CurrentJob->JobId, CurrentJob->Command);
138 
139             ZeroMemory(&StartupInfo, sizeof(StartupInfo));
140             StartupInfo.cb = sizeof(StartupInfo);
141             StartupInfo.lpTitle = CurrentJob->Command;
142             StartupInfo.dwFlags = STARTF_USESHOWWINDOW;
143             StartupInfo.wShowWindow = SW_SHOWDEFAULT;
144 
145             if ((CurrentJob->Flags & JOB_NONINTERACTIVE) == 0)
146             {
147                 StartupInfo.dwFlags |= STARTF_INHERITDESKTOP;
148                 StartupInfo.lpDesktop = L"WinSta0\\Default";
149             }
150 
151             bRet = CreateProcessW(NULL,
152                                   CurrentJob->Command,
153                                   NULL,
154                                   NULL,
155                                   FALSE,
156                                   CREATE_NEW_CONSOLE,
157                                   NULL,
158                                   NULL,
159                                   &StartupInfo,
160                                   &ProcessInformation);
161             if (bRet == FALSE)
162             {
163                 ERR("CreateProcessW() failed (Error %lu)\n", GetLastError());
164             }
165             else
166             {
167                 CloseHandle(ProcessInformation.hThread);
168                 CloseHandle(ProcessInformation.hProcess);
169             }
170         }
171 
172         CurrentEntry = CurrentEntry->Flink;
173     }
174 }
175 
176 
177 static
178 VOID
GetJobName(HKEY hJobsKey,PWSTR pszJobName)179 GetJobName(
180     HKEY hJobsKey,
181     PWSTR pszJobName)
182 {
183     WCHAR szNameBuffer[JOB_NAME_LENGTH];
184     FILETIME SystemTime;
185     ULONG ulSeed, ulValue;
186     HKEY hKey;
187     LONG lError;
188 
189     GetSystemTimeAsFileTime(&SystemTime);
190     ulSeed = SystemTime.dwLowDateTime;
191     for (;;)
192     {
193         ulValue = RtlRandomEx(&ulSeed);
194         swprintf(szNameBuffer, L"%08lx", ulValue);
195 
196         hKey = NULL;
197         lError = RegOpenKeyEx(hJobsKey,
198                               szNameBuffer,
199                               0,
200                               KEY_READ,
201                               &hKey);
202         if (lError != ERROR_SUCCESS)
203         {
204             wcscpy(pszJobName, szNameBuffer);
205             return;
206         }
207 
208         RegCloseKey(hKey);
209     }
210 }
211 
212 
213 LONG
SaveJob(_In_ PJOB pJob)214 SaveJob(
215     _In_ PJOB pJob)
216 {
217     SCHEDULE Schedule;
218     HKEY hJobsKey = NULL, hJobKey = NULL;
219     LONG lError;
220 
221     TRACE("SaveJob()\n");
222 
223     lError = RegCreateKeyExW(HKEY_LOCAL_MACHINE,
224                              L"System\\CurrentControlSet\\Services\\Schedule\\Jobs",
225                              0,
226                              NULL,
227                              REG_OPTION_NON_VOLATILE,
228                              KEY_WRITE,
229                              NULL,
230                              &hJobsKey,
231                              NULL);
232     if (lError != ERROR_SUCCESS)
233         goto done;
234 
235     GetJobName(hJobsKey, pJob->Name);
236 
237     lError = RegCreateKeyExW(hJobsKey,
238                              pJob->Name,
239                              0,
240                              NULL,
241                              REG_OPTION_NON_VOLATILE,
242                              KEY_WRITE,
243                              NULL,
244                              &hJobKey,
245                              NULL);
246     if (lError != ERROR_SUCCESS)
247         goto done;
248 
249     Schedule.JobTime = pJob->JobTime;
250     Schedule.DaysOfMonth = pJob->DaysOfMonth;
251     Schedule.DaysOfWeek = pJob->DaysOfWeek;
252     Schedule.Flags = pJob->Flags;
253 
254     lError = RegSetValueEx(hJobKey,
255                            L"Schedule",
256                            0,
257                            REG_BINARY,
258                            (PBYTE)&Schedule,
259                            sizeof(Schedule));
260     if (lError != ERROR_SUCCESS)
261         goto done;
262 
263     lError = RegSetValueEx(hJobKey,
264                            L"Command",
265                            0,
266                            REG_SZ,
267                            (PBYTE)pJob->Command,
268                            (wcslen(pJob->Command) + 1) * sizeof(WCHAR));
269     if (lError != ERROR_SUCCESS)
270         goto done;
271 
272 done:
273     if (hJobKey != NULL)
274         RegCloseKey(hJobKey);
275 
276     if (hJobsKey != NULL)
277         RegCloseKey(hJobsKey);
278 
279     return lError;
280 }
281 
282 
283 LONG
DeleteJob(_In_ PJOB pJob)284 DeleteJob(
285     _In_ PJOB pJob)
286 {
287     HKEY hJobsKey = NULL;
288     LONG lError;
289 
290     TRACE("DeleteJob()\n");
291 
292     lError = RegCreateKeyExW(HKEY_LOCAL_MACHINE,
293                              L"System\\CurrentControlSet\\Services\\Schedule\\Jobs",
294                              0,
295                              NULL,
296                              REG_OPTION_NON_VOLATILE,
297                              KEY_WRITE,
298                              NULL,
299                              &hJobsKey,
300                              NULL);
301     if (lError != ERROR_SUCCESS)
302         goto done;
303 
304     lError = RegDeleteKey(hJobsKey,
305                           pJob->Name);
306     if (lError != ERROR_SUCCESS)
307         goto done;
308 
309 done:
310     if (hJobsKey != NULL)
311         RegCloseKey(hJobsKey);
312 
313     return lError;
314 }
315 
316 
317 LONG
LoadJobs(VOID)318 LoadJobs(VOID)
319 {
320     SCHEDULE Schedule;
321     WCHAR szNameBuffer[JOB_NAME_LENGTH];
322     DWORD dwNameLength, dwIndex, dwSize;
323     HKEY hJobsKey = NULL, hJobKey = NULL;
324     PJOB pJob = NULL;
325     LONG lError;
326 
327     TRACE("LoadJobs()\n");
328 
329     lError = RegCreateKeyExW(HKEY_LOCAL_MACHINE,
330                              L"System\\CurrentControlSet\\Services\\Schedule\\Jobs",
331                              0,
332                              NULL,
333                              REG_OPTION_NON_VOLATILE,
334                              KEY_READ,
335                              NULL,
336                              &hJobsKey,
337                              NULL);
338     if (lError != ERROR_SUCCESS)
339         goto done;
340 
341     for (dwIndex = 0; dwIndex < 1000; dwIndex++)
342     {
343         dwNameLength = JOB_NAME_LENGTH;
344         lError = RegEnumKeyEx(hJobsKey,
345                               dwIndex,
346                               szNameBuffer,
347                               &dwNameLength,
348                               NULL,
349                               NULL,
350                               NULL,
351                               NULL);
352         if (lError != ERROR_SUCCESS)
353         {
354             lError = ERROR_SUCCESS;
355             break;
356         }
357 
358         TRACE("KeyName: %S\n", szNameBuffer);
359 
360         lError = RegOpenKeyEx(hJobsKey,
361                               szNameBuffer,
362                               0,
363                               KEY_READ,
364                               &hJobKey);
365         if (lError != ERROR_SUCCESS)
366             break;
367 
368         dwSize = sizeof(SCHEDULE);
369         lError = RegQueryValueEx(hJobKey,
370                                  L"Schedule",
371                                  NULL,
372                                  NULL,
373                                  (PBYTE)&Schedule,
374                                  &dwSize);
375         if (lError == ERROR_SUCCESS)
376         {
377             dwSize = 0;
378             RegQueryValueEx(hJobKey,
379                             L"Command",
380                             NULL,
381                             NULL,
382                             NULL,
383                             &dwSize);
384             if (dwSize != 0)
385             {
386                 /* Allocate a new job object */
387                 pJob = HeapAlloc(GetProcessHeap(),
388                                  HEAP_ZERO_MEMORY,
389                                  sizeof(JOB) + dwSize - sizeof(WCHAR));
390                 if (pJob == NULL)
391                 {
392                     lError = ERROR_OUTOFMEMORY;
393                     break;
394                 }
395 
396                 lError = RegQueryValueEx(hJobKey,
397                                          L"Command",
398                                          NULL,
399                                          NULL,
400                                          (PBYTE)pJob->Command,
401                                          &dwSize);
402                 if (lError != ERROR_SUCCESS)
403                     break;
404 
405                 wcscpy(pJob->Name, szNameBuffer);
406                 pJob->JobTime = Schedule.JobTime;
407                 pJob->DaysOfMonth = Schedule.DaysOfMonth;
408                 pJob->DaysOfWeek = Schedule.DaysOfWeek;
409                 pJob->Flags = Schedule.Flags;
410 
411                 /* Acquire the job list lock exclusively */
412                 RtlAcquireResourceExclusive(&JobListLock, TRUE);
413 
414                 /* Assign a new job ID */
415                 pJob->JobId = dwNextJobId++;
416                 dwJobCount++;
417 
418                 /* Append the new job to the job list */
419                 InsertTailList(&JobListHead, &pJob->JobEntry);
420 
421                 /* Calculate the next start time */
422                 CalculateNextStartTime(pJob);
423 
424 #if 0
425                 DumpStartList(&StartListHead);
426 #endif
427 
428                 /* Release the job list lock */
429                 RtlReleaseResource(&JobListLock);
430 
431                 pJob = NULL;
432             }
433         }
434 
435         RegCloseKey(hJobKey);
436         hJobKey = NULL;
437     }
438 
439 done:
440     if (pJob != NULL)
441         HeapFree(GetProcessHeap(), 0, pJob);
442 
443     if (hJobKey != NULL)
444         RegCloseKey(hJobKey);
445 
446     if (hJobsKey != NULL)
447         RegCloseKey(hJobsKey);
448 
449     return lError;
450 }
451 
452 
453 static
454 WORD
DaysOfMonth(WORD wMonth,WORD wYear)455 DaysOfMonth(
456     WORD wMonth,
457     WORD wYear)
458 {
459     if (wMonth == 2 && wYear % 4 == 0 && wYear % 400 != 0)
460         return 29;
461 
462     return wDaysArray[wMonth];
463 }
464 
465 
466 VOID
CalculateNextStartTime(_In_ PJOB pJob)467 CalculateNextStartTime(
468     _In_ PJOB pJob)
469 {
470     SYSTEMTIME CurrentSystemTime, StartSystemTime;
471     FILETIME StartFileTime;
472     WORD wDaysOffset, wTempOffset, i, wJobDayOfWeek, wJobDayOfMonth;
473     DWORD_PTR CurrentTimeMs;
474     BOOL bDaysOffsetValid;
475     ULARGE_INTEGER LocalStartTime;
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     LocalStartTime.u.LowPart = StartFileTime.dwLowDateTime;
594     LocalStartTime.u.HighPart = StartFileTime.dwHighDateTime;
595     if (bDaysOffsetValid && wDaysOffset != 0)
596     {
597         LocalStartTime.QuadPart += ((ULONGLONG)wDaysOffset * 24 * 60 * 60 * 10000);
598     }
599 
600     pJob->StartTime.dwLowDateTime = LocalStartTime.u.LowPart;
601     pJob->StartTime.dwHighDateTime = LocalStartTime.u.HighPart;
602 }
603 
604 #if 0
605 VOID
606 DumpStartList(
607     _In_ PLIST_ENTRY StartListHead)
608 {
609     PLIST_ENTRY CurrentEntry;
610     PJOB CurrentJob;
611 
612     CurrentEntry = JobListHead->Flink;
613     while (CurrentEntry != &JobListHead)
614     {
615         CurrentJob = CONTAINING_RECORD(CurrentEntry, JOB, StartEntry);
616 
617         TRACE("%3lu: %016I64x\n", CurrentJob->JobId, CurrentJob->StartTime.QuadPart);
618 
619         CurrentEntry = CurrentEntry->Flink;
620     }
621 }
622 #endif
623 /* EOF */
624