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