schedsvc: Load job files at the service start up.
[wine.git] / dlls / schedsvc / atsvc.c
blobb5b8317deda8321cbd9ad6cfcd28a52b2b71d7d3
1 /*
2 * ATSvc RPC API
4 * Copyright 2018 Dmitry Timoshkov
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21 #include <stdarg.h>
23 #define NONAMELESSUNION
24 #include "windef.h"
25 #include "atsvc.h"
26 #include "mstask.h"
27 #include "wine/list.h"
28 #include "wine/debug.h"
30 #include "schedsvc_private.h"
32 WINE_DEFAULT_DEBUG_CHANNEL(schedsvc);
34 /* lmat.h defines those, but other types in that file conflict
35 * with generated atsvc.h typedefs.
37 #define JOB_ADD_CURRENT_DATE 0x08
38 #define JOB_NONINTERACTIVE 0x10
40 typedef struct
42 USHORT product_version;
43 USHORT file_version;
44 UUID uuid;
45 USHORT name_size_offset;
46 USHORT trigger_offset;
47 USHORT error_retry_count;
48 USHORT error_retry_interval;
49 USHORT idle_deadline;
50 USHORT idle_wait;
51 UINT priority;
52 UINT maximum_runtime;
53 UINT exit_code;
54 UINT status;
55 UINT flags;
56 SYSTEMTIME last_runtime;
57 } FIXDLEN_DATA;
59 struct job_t
61 struct list entry;
62 WCHAR *name;
63 WCHAR *params;
64 WCHAR *curdir;
65 AT_ENUM info;
66 FIXDLEN_DATA data;
67 USHORT instance_count;
68 USHORT trigger_count;
69 TASK_TRIGGER *trigger;
72 struct running_job_t
74 struct list entry;
75 UUID uuid;
76 HANDLE process;
77 DWORD pid;
80 static LONG current_jobid = 1;
82 static struct list at_job_list = LIST_INIT(at_job_list);
83 static struct list running_job_list = LIST_INIT(running_job_list);
85 static CRITICAL_SECTION at_job_list_section;
86 static CRITICAL_SECTION_DEBUG cs_debug =
88 0, 0, &at_job_list_section,
89 { &cs_debug.ProcessLocksList, &cs_debug.ProcessLocksList },
90 0, 0, { (DWORD_PTR)(__FILE__ ": at_job_list_section") }
92 static CRITICAL_SECTION at_job_list_section = { &cs_debug, -1, 0, 0, 0, 0 };
94 static void filetime_add_ms(FILETIME *ft, LONGLONG ms)
96 union u_ftll
98 FILETIME ft;
99 LONGLONG ll;
100 } *ftll = (union u_ftll *)ft;
102 ftll->ll += ms * (ULONGLONG)10000;
105 static void filetime_add_minutes(FILETIME *ft, LONG minutes)
107 filetime_add_ms(ft, (LONGLONG)minutes * 60 * 1000);
110 static void filetime_add_hours(FILETIME *ft, LONG hours)
112 filetime_add_minutes(ft, (LONGLONG)hours * 60);
115 static void filetime_add_days(FILETIME *ft, LONG days)
117 filetime_add_hours(ft, (LONGLONG)days * 24);
120 static void filetime_add_weeks(FILETIME *ft, ULONG weeks)
122 filetime_add_days(ft, (LONGLONG)weeks * 7);
125 static void get_begin_time(const TASK_TRIGGER *trigger, FILETIME *ft)
127 SYSTEMTIME st;
129 st.wYear = trigger->wBeginYear;
130 st.wMonth = trigger->wBeginMonth;
131 st.wDay = trigger->wBeginDay;
132 st.wDayOfWeek = 0;
133 st.wHour = 0;
134 st.wMinute = 0;
135 st.wSecond = 0;
136 st.wMilliseconds = 0;
137 SystemTimeToFileTime(&st, ft);
140 static void get_end_time(const TASK_TRIGGER *trigger, FILETIME *ft)
142 SYSTEMTIME st;
144 if (!(trigger->rgFlags & TASK_TRIGGER_FLAG_HAS_END_DATE))
146 ft->dwHighDateTime = ~0u;
147 ft->dwLowDateTime = ~0u;
148 return;
151 st.wYear = trigger->wEndYear;
152 st.wMonth = trigger->wEndMonth;
153 st.wDay = trigger->wEndDay;
154 st.wDayOfWeek = 0;
155 st.wHour = 0;
156 st.wMinute = 0;
157 st.wSecond = 0;
158 st.wMilliseconds = 0;
159 SystemTimeToFileTime(&st, ft);
162 static BOOL trigger_get_next_runtime(const TASK_TRIGGER *trigger, const FILETIME *current_ft, FILETIME *rt)
164 SYSTEMTIME st, current_st;
165 FILETIME begin_ft, end_ft, trigger_ft;
167 if (trigger->rgFlags & TASK_TRIGGER_FLAG_DISABLED)
168 return FALSE;
170 FileTimeToSystemTime(current_ft, &current_st);
172 get_begin_time(trigger, &begin_ft);
173 get_end_time(trigger, &end_ft);
175 switch (trigger->TriggerType)
177 case TASK_EVENT_TRIGGER_ON_IDLE:
178 case TASK_EVENT_TRIGGER_AT_SYSTEMSTART:
179 case TASK_EVENT_TRIGGER_AT_LOGON:
180 return FALSE;
182 case TASK_TIME_TRIGGER_ONCE:
183 st = current_st;
184 st.wHour = trigger->wStartHour;
185 st.wMinute = trigger->wStartMinute;
186 st.wSecond = 0;
187 st.wMilliseconds = 0;
188 SystemTimeToFileTime(&st, &trigger_ft);
189 if (CompareFileTime(&begin_ft, &trigger_ft) <= 0 && CompareFileTime(&trigger_ft, &end_ft) < 0)
191 *rt = trigger_ft;
192 return TRUE;
194 break;
196 case TASK_TIME_TRIGGER_DAILY:
197 st = current_st;
198 st.wHour = trigger->wStartHour;
199 st.wMinute = trigger->wStartMinute;
200 st.wSecond = 0;
201 st.wMilliseconds = 0;
202 SystemTimeToFileTime(&st, &trigger_ft);
203 while (CompareFileTime(&trigger_ft, &end_ft) < 0)
205 if (CompareFileTime(&trigger_ft, &begin_ft) >= 0)
207 *rt = trigger_ft;
208 return TRUE;
211 filetime_add_days(&trigger_ft, trigger->Type.Daily.DaysInterval);
213 break;
215 case TASK_TIME_TRIGGER_WEEKLY:
216 if (!trigger->Type.Weekly.rgfDaysOfTheWeek)
217 break; /* avoid infinite loop */
219 st = current_st;
220 st.wHour = trigger->wStartHour;
221 st.wMinute = trigger->wStartMinute;
222 st.wSecond = 0;
223 st.wMilliseconds = 0;
224 SystemTimeToFileTime(&st, &trigger_ft);
225 while (CompareFileTime(&trigger_ft, &end_ft) < 0)
227 FileTimeToSystemTime(&trigger_ft, &st);
229 if (CompareFileTime(&trigger_ft, &begin_ft) >= 0)
231 if (trigger->Type.Weekly.rgfDaysOfTheWeek & (1 << st.wDayOfWeek))
233 *rt = trigger_ft;
234 return TRUE;
238 if (st.wDayOfWeek == 0 && trigger->Type.Weekly.WeeksInterval > 1) /* Sunday, goto next week */
239 filetime_add_weeks(&trigger_ft, trigger->Type.Weekly.WeeksInterval - 1);
240 else /* check next weekday */
241 filetime_add_days(&trigger_ft, 1);
243 break;
245 default:
246 FIXME("trigger type %u is not handled\n", trigger->TriggerType);
247 break;
250 return FALSE;
253 static BOOL job_get_next_runtime(struct job_t *job, FILETIME *current_ft, FILETIME *next_rt)
255 FILETIME trigger_rt;
256 BOOL have_next_rt = FALSE;
257 USHORT i;
259 for (i = 0; i < job->trigger_count; i++)
261 if (trigger_get_next_runtime(&job->trigger[i], current_ft, &trigger_rt))
263 if (!have_next_rt || CompareFileTime(&trigger_rt, next_rt) < 0)
265 *next_rt = trigger_rt;
266 have_next_rt = TRUE;
271 return have_next_rt;
274 /* Returns next runtime in UTC */
275 BOOL get_next_runtime(LARGE_INTEGER *rt)
277 FILETIME current_ft, job_rt, next_job_rt;
278 BOOL have_next_rt = FALSE;
279 struct job_t *job;
281 GetSystemTimeAsFileTime(&current_ft);
282 FileTimeToLocalFileTime(&current_ft, &current_ft);
284 EnterCriticalSection(&at_job_list_section);
286 LIST_FOR_EACH_ENTRY(job, &at_job_list, struct job_t, entry)
288 if (job_get_next_runtime(job, &current_ft, &job_rt))
290 if (!have_next_rt || CompareFileTime(&job_rt, &next_job_rt) < 0)
292 next_job_rt = job_rt;
293 have_next_rt = TRUE;
298 LeaveCriticalSection(&at_job_list_section);
300 if (have_next_rt)
302 LocalFileTimeToFileTime(&next_job_rt, &next_job_rt);
303 rt->u.LowPart = next_job_rt.dwLowDateTime;
304 rt->u.HighPart = next_job_rt.dwHighDateTime;
307 return have_next_rt;
310 static BOOL job_runs_at(struct job_t *job, FILETIME *begin_ft, FILETIME *end_ft)
312 FILETIME job_ft;
314 if (job_get_next_runtime(job, begin_ft, &job_ft))
316 if (CompareFileTime(&job_ft, end_ft) < 0)
317 return TRUE;
320 return FALSE;
323 static DWORD load_unicode_strings(const char *data, DWORD limit, struct job_t *job)
325 DWORD i, data_size = 0;
326 USHORT len;
328 for (i = 0; i < 5; i++)
330 if (limit < sizeof(USHORT))
332 TRACE("invalid string %u offset\n", i);
333 break;
336 len = *(USHORT *)data;
337 data += sizeof(USHORT);
338 data_size += sizeof(USHORT);
339 limit -= sizeof(USHORT);
340 if (limit < len * sizeof(WCHAR))
342 TRACE("invalid string %u size\n", i);
343 break;
346 TRACE("string %u: %s\n", i, wine_dbgstr_wn((const WCHAR *)data, len));
348 switch (i)
350 case 0:
351 job->info.Command = heap_strdupW((const WCHAR *)data);
352 break;
354 case 1:
355 job->params = heap_strdupW((const WCHAR *)data);
356 break;
358 case 2:
359 job->curdir = heap_strdupW((const WCHAR *)data);
360 break;
362 default:
363 break;
366 data += len * sizeof(WCHAR);
367 data_size += len * sizeof(WCHAR);
370 return data_size;
373 static BOOL load_job_data(const char *data, DWORD size, struct job_t *info)
375 const FIXDLEN_DATA *fixed;
376 const SYSTEMTIME *st;
377 DWORD unicode_strings_size, data_size, triggers_size;
378 USHORT i;
379 const USHORT *signature;
380 const TASK_TRIGGER *trigger;
382 memset(info, 0, sizeof(*info));
384 if (size < sizeof(*fixed))
386 TRACE("no space for FIXDLEN_DATA\n");
387 return FALSE;
390 fixed = (const FIXDLEN_DATA *)data;
391 info->data = *fixed;
393 TRACE("product_version %04x\n", fixed->product_version);
394 TRACE("file_version %04x\n", fixed->file_version);
395 TRACE("uuid %s\n", wine_dbgstr_guid(&fixed->uuid));
397 if (fixed->file_version != 0x0001)
399 TRACE("invalid file version\n");
400 return FALSE;
403 TRACE("name_size_offset %04x\n", fixed->name_size_offset);
404 TRACE("trigger_offset %04x\n", fixed->trigger_offset);
405 TRACE("error_retry_count %u\n", fixed->error_retry_count);
406 TRACE("error_retry_interval %u\n", fixed->error_retry_interval);
407 TRACE("idle_deadline %u\n", fixed->idle_deadline);
408 TRACE("idle_wait %u\n", fixed->idle_wait);
409 TRACE("priority %08x\n", fixed->priority);
410 TRACE("maximum_runtime %u\n", fixed->maximum_runtime);
411 TRACE("exit_code %#x\n", fixed->exit_code);
412 TRACE("status %08x\n", fixed->status);
413 TRACE("flags %08x\n", fixed->flags);
414 st = &fixed->last_runtime;
415 TRACE("last_runtime %d/%d/%d wday %d %d:%d:%d.%03d\n",
416 st->wDay, st->wMonth, st->wYear, st->wDayOfWeek,
417 st->wHour, st->wMinute, st->wSecond, st->wMilliseconds);
419 /* Instance Count */
420 if (size < sizeof(*fixed) + sizeof(USHORT))
422 TRACE("no space for instance count\n");
423 return FALSE;
426 info->instance_count = *(const USHORT *)(data + sizeof(*fixed));
427 TRACE("instance count %u\n", info->instance_count);
429 if (fixed->name_size_offset + sizeof(USHORT) < size)
430 unicode_strings_size = load_unicode_strings(data + fixed->name_size_offset, size - fixed->name_size_offset, info);
431 else
433 TRACE("invalid name_size_offset\n");
434 return FALSE;
436 TRACE("unicode strings end at %#x\n", fixed->name_size_offset + unicode_strings_size);
438 if (size < fixed->trigger_offset + sizeof(USHORT))
440 TRACE("no space for triggers count\n");
441 return FALSE;
443 info->trigger_count = *(const USHORT *)(data + fixed->trigger_offset);
444 TRACE("trigger_count %u\n", info->trigger_count);
445 triggers_size = size - fixed->trigger_offset - sizeof(USHORT);
446 TRACE("triggers_size %u\n", triggers_size);
448 data += fixed->name_size_offset + unicode_strings_size;
449 size -= fixed->name_size_offset + unicode_strings_size;
451 /* User Data */
452 if (size < sizeof(USHORT))
454 TRACE("no space for user data size\n");
455 return FALSE;
458 data_size = *(const USHORT *)data;
459 if (size < sizeof(USHORT) + data_size)
461 TRACE("no space for user data\n");
462 return FALSE;
464 TRACE("User Data size %#x\n", data_size);
466 size -= sizeof(USHORT) + data_size;
467 data += sizeof(USHORT) + data_size;
469 /* Reserved Data */
470 if (size < sizeof(USHORT))
472 TRACE("no space for reserved data size\n");
473 return FALSE;
476 data_size = *(const USHORT *)data;
477 if (size < sizeof(USHORT) + data_size)
479 TRACE("no space for reserved data\n");
480 return FALSE;
482 TRACE("Reserved Data size %#x\n", data_size);
484 size -= sizeof(USHORT) + data_size;
485 data += sizeof(USHORT) + data_size;
487 /* Trigger Data */
488 TRACE("trigger_offset %04x, triggers end at %04x\n", fixed->trigger_offset,
489 (DWORD)(fixed->trigger_offset + sizeof(USHORT) + info->trigger_count * sizeof(TASK_TRIGGER)));
491 info->trigger_count = *(const USHORT *)data;
492 TRACE("trigger_count %u\n", info->trigger_count);
493 trigger = (const TASK_TRIGGER *)(data + sizeof(USHORT));
495 if (info->trigger_count * sizeof(TASK_TRIGGER) > triggers_size)
497 TRACE("no space for triggers data\n");
498 return FALSE;
501 info->trigger = heap_alloc(info->trigger_count * sizeof(info->trigger[0]));
502 if (!info->trigger)
504 TRACE("not enough memory for trigger data\n");
505 return FALSE;
508 for (i = 0; i < info->trigger_count; i++)
510 TRACE("%u: cbTriggerSize = %#x\n", i, trigger[i].cbTriggerSize);
511 if (trigger[i].cbTriggerSize != sizeof(TASK_TRIGGER))
512 TRACE("invalid cbTriggerSize\n");
513 TRACE("Reserved1 = %#x\n", trigger[i].Reserved1);
514 TRACE("wBeginYear = %u\n", trigger[i].wBeginYear);
515 TRACE("wBeginMonth = %u\n", trigger[i].wBeginMonth);
516 TRACE("wBeginDay = %u\n", trigger[i].wBeginDay);
517 TRACE("wEndYear = %u\n", trigger[i].wEndYear);
518 TRACE("wEndMonth = %u\n", trigger[i].wEndMonth);
519 TRACE("wEndDay = %u\n", trigger[i].wEndDay);
520 TRACE("wStartHour = %u\n", trigger[i].wStartHour);
521 TRACE("wStartMinute = %u\n", trigger[i].wStartMinute);
522 TRACE("MinutesDuration = %u\n", trigger[i].MinutesDuration);
523 TRACE("MinutesInterval = %u\n", trigger[i].MinutesInterval);
524 TRACE("rgFlags = %u\n", trigger[i].rgFlags);
525 TRACE("TriggerType = %u\n", trigger[i].TriggerType);
526 TRACE("Reserved2 = %u\n", trigger[i].Reserved2);
527 TRACE("wRandomMinutesInterval = %u\n", trigger[i].wRandomMinutesInterval);
529 info->trigger[i] = trigger[i];
532 size -= sizeof(USHORT) + info->trigger_count * sizeof(TASK_TRIGGER);
533 data += sizeof(USHORT) + info->trigger_count * sizeof(TASK_TRIGGER);
535 if (size < 2 * sizeof(USHORT) + 64)
537 TRACE("no space for signature\n");
538 return TRUE; /* signature is optional */
541 signature = (const USHORT *)data;
542 TRACE("signature version %04x, client version %04x\n", signature[0], signature[1]);
544 return TRUE;
547 static BOOL load_job(const WCHAR *name, struct job_t *info)
549 HANDLE file, mapping;
550 DWORD size, try;
551 void *data;
552 BOOL ret = FALSE;
554 try = 1;
555 for (;;)
557 file = CreateFileW(name, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, 0);
558 if (file == INVALID_HANDLE_VALUE)
560 TRACE("Failed to open %s, error %u\n", debugstr_w(name), GetLastError());
561 if (try++ >= 3) break;
562 Sleep(100);
563 continue;
566 size = GetFileSize(file, NULL);
568 mapping = CreateFileMappingW(file, NULL, PAGE_READONLY, 0, 0, 0);
569 if (!mapping)
571 TRACE("Failed to create file mapping %s, error %u\n", debugstr_w(name), GetLastError());
572 CloseHandle(file);
573 break;
576 data = MapViewOfFile(mapping, FILE_MAP_READ, 0, 0, 0);
577 if (data)
579 ret = load_job_data(data, size, info);
580 UnmapViewOfFile(data);
583 CloseHandle(mapping);
584 CloseHandle(file);
585 break;
588 return ret;
591 static void free_job_info(AT_ENUM *info)
593 heap_free(info->Command);
596 static void free_job(struct job_t *job)
598 free_job_info(&job->info);
599 heap_free(job->name);
600 heap_free(job->params);
601 heap_free(job->curdir);
602 heap_free(job->trigger);
603 heap_free(job);
606 void add_job(const WCHAR *name)
608 struct job_t *job;
610 job = heap_alloc_zero(sizeof(*job));
611 if (!job) return;
613 if (!load_job(name, job))
615 free_job(job);
616 return;
619 EnterCriticalSection(&at_job_list_section);
620 job->name = heap_strdupW(name);
621 job->info.JobId = current_jobid++;
622 list_add_tail(&at_job_list, &job->entry);
623 LeaveCriticalSection(&at_job_list_section);
626 static inline BOOL is_file(const WIN32_FIND_DATAW *data)
628 return !(data->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
631 void load_at_tasks(void)
633 static const WCHAR tasksW[] = { '\\','T','a','s','k','s','\\',0 };
634 static const WCHAR allW[] = { '*',0 };
635 WCHAR windir[MAX_PATH], path[MAX_PATH];
636 WIN32_FIND_DATAW data;
637 HANDLE handle;
639 GetWindowsDirectoryW(windir, MAX_PATH);
640 lstrcpyW(path, windir);
641 lstrcatW(path, tasksW);
642 lstrcatW(path, allW);
644 handle = FindFirstFileW(path, &data);
645 if (handle == INVALID_HANDLE_VALUE) return;
649 if (is_file(&data))
651 lstrcpyW(path, windir);
652 lstrcatW(path, tasksW);
654 if (lstrlenW(path) + lstrlenW(data.cFileName) < MAX_PATH)
656 lstrcatW(path, data.cFileName);
657 add_job(path);
659 else
660 FIXME("too long file name %s\n", debugstr_w(data.cFileName));
662 } while (FindNextFileW(handle, &data));
664 FindClose(handle);
667 static BOOL write_signature(HANDLE hfile)
669 struct
671 USHORT SignatureVersion;
672 USHORT ClientVersion;
673 BYTE md5[64];
674 } signature;
675 DWORD size;
677 signature.SignatureVersion = 0x0001;
678 signature.ClientVersion = 0x0001;
679 memset(&signature.md5, 0, sizeof(signature.md5));
681 return WriteFile(hfile, &signature, sizeof(signature), &size, NULL);
684 static BOOL write_reserved_data(HANDLE hfile)
686 static const struct
688 USHORT size;
689 BYTE data[8];
690 } user = { 8, { 0xff,0x0f,0x1d,0,0,0,0,0 } };
691 DWORD size;
693 return WriteFile(hfile, &user, sizeof(user), &size, NULL);
696 static BOOL write_trigger(HANDLE hfile, const AT_INFO *info)
698 USHORT count;
699 DWORD size;
700 SYSTEMTIME st;
701 TASK_TRIGGER trigger;
703 count = 1;
704 if (!WriteFile(hfile, &count, sizeof(count), &size, NULL))
705 return FALSE;
707 GetSystemTime(&st);
708 if (!(info->Flags & JOB_ADD_CURRENT_DATE))
710 /* FIXME: parse AT_INFO */
713 trigger.cbTriggerSize = sizeof(trigger);
714 trigger.Reserved1 = 0;
715 trigger.wBeginYear = st.wYear;
716 trigger.wBeginMonth = st.wMonth;
717 trigger.wBeginDay = st.wDay;
718 trigger.wEndYear = st.wYear;
719 trigger.wEndMonth = st.wMonth;
720 trigger.wEndDay = st.wDay;
721 trigger.wStartHour = st.wHour;
722 trigger.wStartMinute = st.wMinute;
723 trigger.MinutesDuration = 0;
724 trigger.MinutesInterval = 0;
725 /* FIXME */
726 trigger.rgFlags = TASK_TRIGGER_FLAG_HAS_END_DATE;
727 trigger.TriggerType = TASK_TIME_TRIGGER_MONTHLYDATE;
728 trigger.Type.MonthlyDate.rgfDays = 0;
729 trigger.Type.MonthlyDate.rgfMonths = 0xffff;
730 trigger.Reserved2 = 0;
731 trigger.wRandomMinutesInterval = 0;
733 return WriteFile(hfile, &trigger, sizeof(trigger), &size, NULL);
736 static BOOL write_unicode_string(HANDLE hfile, const WCHAR *str)
738 USHORT count;
739 DWORD size;
741 count = str ? (lstrlenW(str) + 1) : 0;
742 if (!WriteFile(hfile, &count, sizeof(count), &size, NULL))
743 return FALSE;
745 if (!str) return TRUE;
747 count *= sizeof(WCHAR);
748 return WriteFile(hfile, str, count, &size, NULL);
751 static BOOL create_job(const WCHAR *job_name, const AT_INFO *info)
753 static WCHAR authorW[] = { 'W','i','n','e',0 };
754 static WCHAR commentW[] = { 'C','r','e','a','t','e','d',' ','b','y',' ','W','i','n','e',0 };
755 FIXDLEN_DATA fixed;
756 USHORT word;
757 HANDLE hfile;
758 DWORD size, ver;
759 BOOL ret = FALSE;
761 TRACE("trying to create job %s\n", debugstr_w(job_name));
762 hfile = CreateFileW(job_name, GENERIC_WRITE, 0, NULL, CREATE_NEW, 0, 0);
763 if (hfile == INVALID_HANDLE_VALUE)
764 return FALSE;
766 ver = GetVersion();
767 fixed.product_version = MAKEWORD(ver >> 8, ver);
768 fixed.file_version = 0x0001;
769 UuidCreate(&fixed.uuid);
770 fixed.name_size_offset = sizeof(fixed) + sizeof(USHORT); /* FIXDLEN_DATA + Instance Count */
771 fixed.trigger_offset = sizeof(fixed) + sizeof(USHORT); /* FIXDLEN_DATA + Instance Count */
772 fixed.trigger_offset += sizeof(USHORT) + (lstrlenW(info->Command) + 1) * sizeof(WCHAR); /* Application Name */
773 fixed.trigger_offset += sizeof(USHORT); /* Parameters */
774 fixed.trigger_offset += sizeof(USHORT); /* Working Directory */
775 fixed.trigger_offset += sizeof(USHORT) + (lstrlenW(authorW) + 1) * sizeof(WCHAR); /* Author */
776 fixed.trigger_offset += sizeof(USHORT) + (lstrlenW(commentW) + 1) * sizeof(WCHAR); /* Comment */
777 fixed.trigger_offset += sizeof(USHORT); /* User Data */
778 fixed.trigger_offset += 10; /* Reserved Data */
779 fixed.error_retry_count = 0;
780 fixed.error_retry_interval = 0;
781 fixed.idle_deadline = 60;
782 fixed.idle_wait = 10;
783 fixed.priority = NORMAL_PRIORITY_CLASS;
784 fixed.maximum_runtime = 259200000;
785 fixed.exit_code = 0;
786 fixed.status = SCHED_S_TASK_HAS_NOT_RUN;
787 fixed.flags = TASK_FLAG_DELETE_WHEN_DONE;
788 if (!(info->Flags & JOB_NONINTERACTIVE))
789 fixed.flags |= TASK_FLAG_INTERACTIVE;
790 /* FIXME: add other flags */
791 memset(&fixed.last_runtime, 0, sizeof(fixed.last_runtime));
793 if (!WriteFile(hfile, &fixed, sizeof(fixed), &size, NULL))
794 goto failed;
796 /* Instance Count */
797 word = 0;
798 if (!WriteFile(hfile, &word, sizeof(word), &size, NULL))
799 goto failed;
800 /* Application Name */
801 if (!write_unicode_string(hfile, info->Command))
802 goto failed;
803 /* Parameters */
804 if (!write_unicode_string(hfile, NULL))
805 goto failed;
806 /* Working Directory */
807 if (!write_unicode_string(hfile, NULL))
808 goto failed;
809 /* Author */
810 if (!write_unicode_string(hfile, authorW))
811 goto failed;
812 /* Comment */
813 if (!write_unicode_string(hfile, commentW))
814 goto failed;
816 /* User Data */
817 word = 0;
818 if (!WriteFile(hfile, &word, sizeof(word), &size, NULL))
819 goto failed;
821 /* Reserved Data */
822 if (!write_reserved_data(hfile))
823 goto failed;
825 /* Trigegrs */
826 if (!write_trigger(hfile, info))
827 goto failed;
829 /* Signature */
830 if (!write_signature(hfile))
831 goto failed;
833 ret = TRUE;
835 failed:
836 CloseHandle(hfile);
837 if (!ret) DeleteFileW(job_name);
838 return ret;
841 static struct job_t *find_job(DWORD jobid, const WCHAR *name, const UUID *id)
843 struct job_t *job;
845 LIST_FOR_EACH_ENTRY(job, &at_job_list, struct job_t, entry)
847 if (job->info.JobId == jobid || (name && !lstrcmpiW(job->name, name)) || (id && IsEqualGUID(&job->data.uuid, id)))
848 return job;
851 return NULL;
854 static void update_job_status(struct job_t *job)
856 HANDLE hfile;
857 DWORD try, size;
858 #include "pshpack2.h"
859 struct
861 UINT exit_code;
862 UINT status;
863 UINT flags;
864 SYSTEMTIME last_runtime;
865 WORD instance_count;
866 } state;
867 #include "poppack.h"
869 try = 1;
870 for (;;)
872 hfile = CreateFileW(job->name, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, 0);
873 if (hfile != INVALID_HANDLE_VALUE) break;
875 if (try++ >= 3)
877 ERR("Failed to update %s, error %u\n", debugstr_w(job->name), GetLastError());
878 return;
880 Sleep(100);
883 if (SetFilePointer(hfile, FIELD_OFFSET(FIXDLEN_DATA, exit_code), NULL, FILE_BEGIN) != INVALID_SET_FILE_POINTER)
885 state.exit_code = job->data.exit_code;
886 state.status = job->data.status;
887 state.flags = job->data.flags;
888 state.last_runtime = job->data.last_runtime;
889 state.instance_count = job->instance_count;
890 WriteFile(hfile, &state, sizeof(state), &size, NULL);
893 CloseHandle(hfile);
896 void update_process_status(DWORD pid)
898 struct running_job_t *runjob;
900 EnterCriticalSection(&at_job_list_section);
902 LIST_FOR_EACH_ENTRY(runjob, &running_job_list, struct running_job_t, entry)
904 if (runjob->pid == pid)
906 struct job_t *job = find_job(0, NULL, &runjob->uuid);
907 if (job)
909 DWORD exit_code = STILL_ACTIVE;
911 GetExitCodeProcess(runjob->process, &exit_code);
913 if (exit_code != STILL_ACTIVE)
915 CloseHandle(runjob->process);
916 list_remove(&runjob->entry);
917 heap_free(runjob);
919 job->data.exit_code = exit_code;
920 job->data.status = SCHED_S_TASK_TERMINATED;
921 job->data.flags &= ~0x0c000000;
922 job->instance_count = 0;
923 update_job_status(job);
926 break;
930 LeaveCriticalSection(&at_job_list_section);
933 void check_task_state(void)
935 struct job_t *job;
936 struct running_job_t *runjob;
938 EnterCriticalSection(&at_job_list_section);
940 LIST_FOR_EACH_ENTRY(job, &at_job_list, struct job_t, entry)
942 if (job->data.flags & 0x08000000)
944 TRACE("terminating process %s\n", debugstr_w(job->info.Command));
946 LIST_FOR_EACH_ENTRY(runjob, &running_job_list, struct running_job_t, entry)
948 if (IsEqualGUID(&job->data.uuid, &runjob->uuid))
950 TerminateProcess(runjob->process, 0);
951 update_process_status(runjob->pid);
952 break;
956 else if (job->data.flags & 0x04000000)
958 STARTUPINFOW si;
959 PROCESS_INFORMATION pi;
961 TRACE("running process %s\n", debugstr_w(job->info.Command));
963 if (job->instance_count)
964 FIXME("process %s is already running\n", debugstr_w(job->info.Command));
966 runjob = heap_alloc(sizeof(*runjob));
967 if (runjob)
969 static WCHAR winsta0[] = { 'W','i','n','S','t','a','0',0 };
971 memset(&si, 0, sizeof(si));
972 si.cb = sizeof(si);
973 /* FIXME: if (job->data.flags & TASK_FLAG_INTERACTIVE) */
974 si.lpDesktop = winsta0;
975 si.dwFlags = STARTF_USESHOWWINDOW;
976 si.wShowWindow = SW_SHOWNORMAL;
977 TRACE("executing %s %s at %s\n", debugstr_w(job->info.Command), debugstr_w(job->params), debugstr_w(job->curdir));
978 if (CreateProcessW(job->info.Command, job->params, NULL, NULL, FALSE, 0, NULL, job->curdir, &si, &pi))
980 CloseHandle(pi.hThread);
982 GetLocalTime(&job->data.last_runtime);
983 job->data.exit_code = 0;
984 job->data.status = SCHED_S_TASK_RUNNING;
985 job->instance_count = 1;
987 runjob->uuid = job->data.uuid;
988 runjob->process = pi.hProcess;
989 runjob->pid = pi.dwProcessId;
990 list_add_tail(&running_job_list, &runjob->entry);
991 add_process_to_queue(pi.hProcess);
993 else
995 WARN("failed to execute %s\n", debugstr_w(job->info.Command));
996 job->data.status = SCHED_S_TASK_HAS_NOT_RUN;
997 job->instance_count = 0;
1001 job->data.flags &= ~0x0c000000;
1002 update_job_status(job);
1006 LeaveCriticalSection(&at_job_list_section);
1009 static void run_job(struct job_t *job)
1011 job->data.flags |= 0x04000000;
1012 update_job_status(job);
1015 void check_task_time(void)
1017 FILETIME current_ft, begin_ft, end_ft;
1018 struct job_t *job;
1020 GetSystemTimeAsFileTime(&current_ft);
1021 FileTimeToLocalFileTime(&current_ft, &current_ft);
1023 /* Give -1/+1 minute margin */
1024 begin_ft = current_ft;
1025 filetime_add_minutes(&begin_ft, -1);
1026 end_ft = current_ft;
1027 filetime_add_minutes(&end_ft, 1);
1029 EnterCriticalSection(&at_job_list_section);
1031 LIST_FOR_EACH_ENTRY(job, &at_job_list, struct job_t, entry)
1033 if (job_runs_at(job, &begin_ft, &end_ft))
1035 run_job(job);
1039 LeaveCriticalSection(&at_job_list_section);
1042 void remove_job(const WCHAR *name)
1044 struct job_t *job;
1046 EnterCriticalSection(&at_job_list_section);
1047 job = find_job(0, name, NULL);
1048 if (job)
1050 list_remove(&job->entry);
1051 free_job(job);
1053 LeaveCriticalSection(&at_job_list_section);
1056 DWORD __cdecl NetrJobAdd(ATSVC_HANDLE server_name, AT_INFO *info, DWORD *jobid)
1058 WCHAR windir[MAX_PATH];
1060 TRACE("%s,%p,%p\n", debugstr_w(server_name), info, jobid);
1062 GetWindowsDirectoryW(windir, MAX_PATH);
1064 for (;;)
1066 static const WCHAR fmtW[] = { '\\','T','a','s','k','s','\\','A','t','%','u','.','j','o','b',0 };
1067 WCHAR task_name[MAX_PATH], name[32];
1069 strcpyW(task_name, windir);
1070 sprintfW(name, fmtW, current_jobid);
1071 strcatW(task_name, name);
1072 if (create_job(task_name, info))
1074 struct job_t *job;
1075 int i;
1077 for (i = 0; i < 5; i++)
1079 EnterCriticalSection(&at_job_list_section);
1080 job = find_job(0, task_name, NULL);
1081 LeaveCriticalSection(&at_job_list_section);
1083 if (job)
1085 *jobid = job->info.JobId;
1086 break;
1089 Sleep(50);
1092 if (!job)
1094 ERR("couldn't find just created job %s\n", debugstr_w(task_name));
1095 return ERROR_FILE_NOT_FOUND;
1098 break;
1101 if (GetLastError() != ERROR_FILE_EXISTS)
1104 TRACE("create_job error %u\n", GetLastError());
1105 return GetLastError();
1108 InterlockedIncrement(&current_jobid);
1111 return ERROR_SUCCESS;
1114 DWORD __cdecl NetrJobDel(ATSVC_HANDLE server_name, DWORD min_jobid, DWORD max_jobid)
1116 DWORD jobid, ret = APE_AT_ID_NOT_FOUND;
1118 TRACE("%s,%u,%u\n", debugstr_w(server_name), min_jobid, max_jobid);
1120 EnterCriticalSection(&at_job_list_section);
1122 for (jobid = min_jobid; jobid <= max_jobid; jobid++)
1124 struct job_t *job = find_job(jobid, NULL, NULL);
1126 if (!job)
1128 TRACE("job %u not found\n", jobid);
1129 ret = APE_AT_ID_NOT_FOUND;
1130 break;
1133 TRACE("deleting job %s\n", debugstr_w(job->name));
1134 if (!DeleteFileW(job->name))
1136 ret = GetLastError();
1137 break;
1140 ret = ERROR_SUCCESS;
1143 LeaveCriticalSection(&at_job_list_section);
1144 return ret;
1147 static void free_container(AT_ENUM_CONTAINER *container)
1149 DWORD i;
1151 for (i = 0; i < container->EntriesRead; i++)
1152 heap_free(container->Buffer[i].Command);
1154 heap_free(container->Buffer);
1157 DWORD __cdecl NetrJobEnum(ATSVC_HANDLE server_name, AT_ENUM_CONTAINER *container,
1158 DWORD max_length, DWORD *total, DWORD *resume)
1160 DWORD allocated;
1161 struct job_t *job;
1163 TRACE("%s,%p,%u,%p,%p\n", debugstr_w(server_name), container, max_length, total, resume);
1165 *total = 0;
1166 *resume = 0;
1167 container->EntriesRead = 0;
1169 allocated = 64;
1170 container->Buffer = heap_alloc(allocated * sizeof(AT_ENUM));
1171 if (!container->Buffer) return ERROR_NOT_ENOUGH_MEMORY;
1173 EnterCriticalSection(&at_job_list_section);
1175 LIST_FOR_EACH_ENTRY(job, &at_job_list, struct job_t, entry)
1177 if (container->EntriesRead >= max_length)
1179 *resume = container->EntriesRead;
1180 break;
1183 if (allocated <= container->EntriesRead)
1185 AT_ENUM *new_buffer;
1187 allocated *= 2;
1188 new_buffer = heap_realloc(container->Buffer, allocated * sizeof(AT_ENUM));
1189 if (!new_buffer)
1191 free_container(container);
1192 LeaveCriticalSection(&at_job_list_section);
1193 return ERROR_NOT_ENOUGH_MEMORY;
1195 container->Buffer = new_buffer;
1198 container->Buffer[container->EntriesRead] = job->info;
1199 container->Buffer[container->EntriesRead].Command = heap_strdupW(job->info.Command);
1200 container->EntriesRead++;
1203 LeaveCriticalSection(&at_job_list_section);
1205 *total = container->EntriesRead;
1207 return ERROR_SUCCESS;
1210 DWORD __cdecl NetrJobGetInfo(ATSVC_HANDLE server_name, DWORD jobid, AT_INFO **info)
1212 struct job_t *job;
1213 DWORD ret = APE_AT_ID_NOT_FOUND;
1215 TRACE("%s,%u,%p\n", debugstr_w(server_name), jobid, info);
1217 EnterCriticalSection(&at_job_list_section);
1219 job = find_job(jobid, NULL, NULL);
1220 if (job)
1222 AT_INFO *info_ret = heap_alloc(sizeof(*info_ret));
1223 if (!info_ret)
1224 ret = ERROR_NOT_ENOUGH_MEMORY;
1225 else
1227 info_ret->JobTime = job->info.JobTime;
1228 info_ret->DaysOfMonth = job->info.DaysOfMonth;
1229 info_ret->DaysOfWeek = job->info.DaysOfWeek;
1230 info_ret->Flags = job->info.Flags;
1231 info_ret->Command = heap_strdupW(job->info.Command);
1232 *info = info_ret;
1233 ret = ERROR_SUCCESS;
1237 LeaveCriticalSection(&at_job_list_section);
1238 return ret;