dinput/tests: Don't consider extra IOCTL_HID_GET_STRING an error.
[wine.git] / dlls / schedsvc / atsvc.c
blob1625b43c888fb66bde385e297b44d91ac70cb23d
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 * (LONGLONG)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, LONG 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 if (CompareFileTime(&begin_ft, current_ft) < 0)
174 begin_ft = *current_ft;
176 get_end_time(trigger, &end_ft);
178 switch (trigger->TriggerType)
180 case TASK_EVENT_TRIGGER_ON_IDLE:
181 case TASK_EVENT_TRIGGER_AT_SYSTEMSTART:
182 case TASK_EVENT_TRIGGER_AT_LOGON:
183 return FALSE;
185 case TASK_TIME_TRIGGER_ONCE:
186 st = current_st;
187 st.wHour = trigger->wStartHour;
188 st.wMinute = trigger->wStartMinute;
189 st.wSecond = 0;
190 st.wMilliseconds = 0;
191 SystemTimeToFileTime(&st, &trigger_ft);
192 if (CompareFileTime(&begin_ft, &trigger_ft) <= 0 && CompareFileTime(&trigger_ft, &end_ft) < 0)
194 *rt = trigger_ft;
195 return TRUE;
197 break;
199 case TASK_TIME_TRIGGER_DAILY:
200 if (!trigger->Type.Daily.DaysInterval)
201 break; /* avoid infinite loop */
203 st = current_st;
204 st.wHour = trigger->wStartHour;
205 st.wMinute = trigger->wStartMinute;
206 st.wSecond = 0;
207 st.wMilliseconds = 0;
208 SystemTimeToFileTime(&st, &trigger_ft);
209 while (CompareFileTime(&trigger_ft, &end_ft) < 0)
211 if (CompareFileTime(&trigger_ft, &begin_ft) >= 0)
213 *rt = trigger_ft;
214 return TRUE;
217 filetime_add_days(&trigger_ft, trigger->Type.Daily.DaysInterval);
219 break;
221 case TASK_TIME_TRIGGER_WEEKLY:
222 if (!trigger->Type.Weekly.rgfDaysOfTheWeek)
223 break; /* avoid infinite loop */
225 st = current_st;
226 st.wHour = trigger->wStartHour;
227 st.wMinute = trigger->wStartMinute;
228 st.wSecond = 0;
229 st.wMilliseconds = 0;
230 SystemTimeToFileTime(&st, &trigger_ft);
231 while (CompareFileTime(&trigger_ft, &end_ft) < 0)
233 FileTimeToSystemTime(&trigger_ft, &st);
235 if (CompareFileTime(&trigger_ft, &begin_ft) >= 0)
237 if (trigger->Type.Weekly.rgfDaysOfTheWeek & (1 << st.wDayOfWeek))
239 *rt = trigger_ft;
240 return TRUE;
244 if (st.wDayOfWeek == 0 && trigger->Type.Weekly.WeeksInterval > 1) /* Sunday, goto next week */
245 filetime_add_weeks(&trigger_ft, trigger->Type.Weekly.WeeksInterval - 1);
246 else /* check next weekday */
247 filetime_add_days(&trigger_ft, 1);
249 break;
251 default:
252 FIXME("trigger type %u is not handled\n", trigger->TriggerType);
253 break;
256 return FALSE;
259 static BOOL job_get_next_runtime(struct job_t *job, const FILETIME *current_ft, FILETIME *next_rt)
261 FILETIME trigger_rt;
262 BOOL have_next_rt = FALSE;
263 USHORT i;
265 for (i = 0; i < job->trigger_count; i++)
267 if (trigger_get_next_runtime(&job->trigger[i], current_ft, &trigger_rt))
269 if (!have_next_rt || CompareFileTime(&trigger_rt, next_rt) < 0)
271 *next_rt = trigger_rt;
272 have_next_rt = TRUE;
277 return have_next_rt;
280 /* Returns next runtime in UTC */
281 BOOL get_next_runtime(LARGE_INTEGER *rt)
283 FILETIME current_ft, job_rt, next_job_rt;
284 BOOL have_next_rt = FALSE;
285 struct job_t *job;
287 GetSystemTimeAsFileTime(&current_ft);
288 FileTimeToLocalFileTime(&current_ft, &current_ft);
290 EnterCriticalSection(&at_job_list_section);
292 LIST_FOR_EACH_ENTRY(job, &at_job_list, struct job_t, entry)
294 if (job_get_next_runtime(job, &current_ft, &job_rt))
296 if (!have_next_rt || CompareFileTime(&job_rt, &next_job_rt) < 0)
298 next_job_rt = job_rt;
299 have_next_rt = TRUE;
304 LeaveCriticalSection(&at_job_list_section);
306 if (have_next_rt)
308 LocalFileTimeToFileTime(&next_job_rt, &next_job_rt);
309 rt->u.LowPart = next_job_rt.dwLowDateTime;
310 rt->u.HighPart = next_job_rt.dwHighDateTime;
313 return have_next_rt;
316 static BOOL job_runs_at(struct job_t *job, const FILETIME *begin_ft, const FILETIME *end_ft)
318 FILETIME job_ft;
320 if (job_get_next_runtime(job, begin_ft, &job_ft))
322 if (CompareFileTime(&job_ft, end_ft) < 0)
323 return TRUE;
326 return FALSE;
329 static DWORD load_unicode_strings(const char *data, DWORD limit, struct job_t *job)
331 DWORD i, data_size = 0;
332 USHORT len;
334 for (i = 0; i < 5; i++)
336 if (limit < sizeof(USHORT))
338 TRACE("invalid string %u offset\n", i);
339 break;
342 len = *(USHORT *)data;
343 data += sizeof(USHORT);
344 data_size += sizeof(USHORT);
345 limit -= sizeof(USHORT);
346 if (limit < len * sizeof(WCHAR))
348 TRACE("invalid string %u size\n", i);
349 break;
352 TRACE("string %u: %s\n", i, wine_dbgstr_wn((const WCHAR *)data, len));
354 switch (i)
356 case 0:
357 job->info.Command = heap_strdupW((const WCHAR *)data);
358 break;
360 case 1:
361 job->params = heap_strdupW((const WCHAR *)data);
362 break;
364 case 2:
365 job->curdir = heap_strdupW((const WCHAR *)data);
366 break;
368 default:
369 break;
372 data += len * sizeof(WCHAR);
373 data_size += len * sizeof(WCHAR);
376 return data_size;
379 static BOOL load_job_data(const char *data, DWORD size, struct job_t *info)
381 const FIXDLEN_DATA *fixed;
382 const SYSTEMTIME *st;
383 DWORD unicode_strings_size, data_size, triggers_size;
384 USHORT i;
385 const USHORT *signature;
386 const TASK_TRIGGER *trigger;
388 memset(info, 0, sizeof(*info));
390 if (size < sizeof(*fixed))
392 TRACE("no space for FIXDLEN_DATA\n");
393 return FALSE;
396 fixed = (const FIXDLEN_DATA *)data;
397 info->data = *fixed;
399 TRACE("product_version %04x\n", fixed->product_version);
400 TRACE("file_version %04x\n", fixed->file_version);
401 TRACE("uuid %s\n", wine_dbgstr_guid(&fixed->uuid));
403 if (fixed->file_version != 0x0001)
405 TRACE("invalid file version\n");
406 return FALSE;
409 TRACE("name_size_offset %04x\n", fixed->name_size_offset);
410 TRACE("trigger_offset %04x\n", fixed->trigger_offset);
411 TRACE("error_retry_count %u\n", fixed->error_retry_count);
412 TRACE("error_retry_interval %u\n", fixed->error_retry_interval);
413 TRACE("idle_deadline %u\n", fixed->idle_deadline);
414 TRACE("idle_wait %u\n", fixed->idle_wait);
415 TRACE("priority %08x\n", fixed->priority);
416 TRACE("maximum_runtime %u\n", fixed->maximum_runtime);
417 TRACE("exit_code %#x\n", fixed->exit_code);
418 TRACE("status %08x\n", fixed->status);
419 TRACE("flags %08x\n", fixed->flags);
420 st = &fixed->last_runtime;
421 TRACE("last_runtime %d/%d/%d wday %d %d:%d:%d.%03d\n",
422 st->wDay, st->wMonth, st->wYear, st->wDayOfWeek,
423 st->wHour, st->wMinute, st->wSecond, st->wMilliseconds);
425 /* Instance Count */
426 if (size < sizeof(*fixed) + sizeof(USHORT))
428 TRACE("no space for instance count\n");
429 return FALSE;
432 info->instance_count = *(const USHORT *)(data + sizeof(*fixed));
433 TRACE("instance count %u\n", info->instance_count);
435 if (fixed->name_size_offset + sizeof(USHORT) < size)
436 unicode_strings_size = load_unicode_strings(data + fixed->name_size_offset, size - fixed->name_size_offset, info);
437 else
439 TRACE("invalid name_size_offset\n");
440 return FALSE;
442 TRACE("unicode strings end at %#x\n", fixed->name_size_offset + unicode_strings_size);
444 if (size < fixed->trigger_offset + sizeof(USHORT))
446 TRACE("no space for triggers count\n");
447 return FALSE;
449 info->trigger_count = *(const USHORT *)(data + fixed->trigger_offset);
450 TRACE("trigger_count %u\n", info->trigger_count);
451 triggers_size = size - fixed->trigger_offset - sizeof(USHORT);
452 TRACE("triggers_size %u\n", triggers_size);
454 data += fixed->name_size_offset + unicode_strings_size;
455 size -= fixed->name_size_offset + unicode_strings_size;
457 /* User Data */
458 if (size < sizeof(USHORT))
460 TRACE("no space for user data size\n");
461 return FALSE;
464 data_size = *(const USHORT *)data;
465 if (size < sizeof(USHORT) + data_size)
467 TRACE("no space for user data\n");
468 return FALSE;
470 TRACE("User Data size %#x\n", data_size);
472 size -= sizeof(USHORT) + data_size;
473 data += sizeof(USHORT) + data_size;
475 /* Reserved Data */
476 if (size < sizeof(USHORT))
478 TRACE("no space for reserved data size\n");
479 return FALSE;
482 data_size = *(const USHORT *)data;
483 if (size < sizeof(USHORT) + data_size)
485 TRACE("no space for reserved data\n");
486 return FALSE;
488 TRACE("Reserved Data size %#x\n", data_size);
490 size -= sizeof(USHORT) + data_size;
491 data += sizeof(USHORT) + data_size;
493 /* Trigger Data */
494 TRACE("trigger_offset %04x, triggers end at %04x\n", fixed->trigger_offset,
495 (DWORD)(fixed->trigger_offset + sizeof(USHORT) + info->trigger_count * sizeof(TASK_TRIGGER)));
497 info->trigger_count = *(const USHORT *)data;
498 TRACE("trigger_count %u\n", info->trigger_count);
499 trigger = (const TASK_TRIGGER *)(data + sizeof(USHORT));
501 if (info->trigger_count * sizeof(TASK_TRIGGER) > triggers_size)
503 TRACE("no space for triggers data\n");
504 return FALSE;
507 info->trigger = heap_alloc(info->trigger_count * sizeof(info->trigger[0]));
508 if (!info->trigger)
510 TRACE("not enough memory for trigger data\n");
511 return FALSE;
514 for (i = 0; i < info->trigger_count; i++)
516 TRACE("%u: cbTriggerSize = %#x\n", i, trigger[i].cbTriggerSize);
517 if (trigger[i].cbTriggerSize != sizeof(TASK_TRIGGER))
518 TRACE("invalid cbTriggerSize\n");
519 TRACE("Reserved1 = %#x\n", trigger[i].Reserved1);
520 TRACE("wBeginYear = %u\n", trigger[i].wBeginYear);
521 TRACE("wBeginMonth = %u\n", trigger[i].wBeginMonth);
522 TRACE("wBeginDay = %u\n", trigger[i].wBeginDay);
523 TRACE("wEndYear = %u\n", trigger[i].wEndYear);
524 TRACE("wEndMonth = %u\n", trigger[i].wEndMonth);
525 TRACE("wEndDay = %u\n", trigger[i].wEndDay);
526 TRACE("wStartHour = %u\n", trigger[i].wStartHour);
527 TRACE("wStartMinute = %u\n", trigger[i].wStartMinute);
528 TRACE("MinutesDuration = %u\n", trigger[i].MinutesDuration);
529 TRACE("MinutesInterval = %u\n", trigger[i].MinutesInterval);
530 TRACE("rgFlags = %u\n", trigger[i].rgFlags);
531 TRACE("TriggerType = %u\n", trigger[i].TriggerType);
532 TRACE("Reserved2 = %u\n", trigger[i].Reserved2);
533 TRACE("wRandomMinutesInterval = %u\n", trigger[i].wRandomMinutesInterval);
535 info->trigger[i] = trigger[i];
538 size -= sizeof(USHORT) + info->trigger_count * sizeof(TASK_TRIGGER);
539 data += sizeof(USHORT) + info->trigger_count * sizeof(TASK_TRIGGER);
541 if (size < 2 * sizeof(USHORT) + 64)
543 TRACE("no space for signature\n");
544 return TRUE; /* signature is optional */
547 signature = (const USHORT *)data;
548 TRACE("signature version %04x, client version %04x\n", signature[0], signature[1]);
550 return TRUE;
553 static BOOL load_job(const WCHAR *name, struct job_t *info)
555 HANDLE file, mapping;
556 DWORD size, try;
557 void *data;
558 BOOL ret = FALSE;
560 try = 1;
561 for (;;)
563 file = CreateFileW(name, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, 0);
564 if (file == INVALID_HANDLE_VALUE)
566 TRACE("Failed to open %s, error %u\n", debugstr_w(name), GetLastError());
567 if (GetLastError() != ERROR_SHARING_VIOLATION || try++ >= 3) break;
568 Sleep(100);
569 continue;
572 size = GetFileSize(file, NULL);
574 mapping = CreateFileMappingW(file, NULL, PAGE_READONLY, 0, 0, 0);
575 if (!mapping)
577 TRACE("Failed to create file mapping %s, error %u\n", debugstr_w(name), GetLastError());
578 CloseHandle(file);
579 break;
582 data = MapViewOfFile(mapping, FILE_MAP_READ, 0, 0, 0);
583 if (data)
585 ret = load_job_data(data, size, info);
586 UnmapViewOfFile(data);
589 CloseHandle(mapping);
590 CloseHandle(file);
591 break;
594 return ret;
597 static void free_job_info(AT_ENUM *info)
599 heap_free(info->Command);
602 static void free_job(struct job_t *job)
604 free_job_info(&job->info);
605 heap_free(job->name);
606 heap_free(job->params);
607 heap_free(job->curdir);
608 heap_free(job->trigger);
609 heap_free(job);
612 void add_job(const WCHAR *name)
614 struct job_t *job;
616 job = heap_alloc_zero(sizeof(*job));
617 if (!job) return;
619 if (!load_job(name, job))
621 free_job(job);
622 return;
625 EnterCriticalSection(&at_job_list_section);
626 job->name = heap_strdupW(name);
627 job->info.JobId = current_jobid++;
628 list_add_tail(&at_job_list, &job->entry);
629 LeaveCriticalSection(&at_job_list_section);
632 static inline BOOL is_file(const WIN32_FIND_DATAW *data)
634 return !(data->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
637 void load_at_tasks(void)
639 static const WCHAR tasksW[] = { '\\','T','a','s','k','s','\\',0 };
640 static const WCHAR allW[] = { '*',0 };
641 WCHAR windir[MAX_PATH], path[MAX_PATH];
642 WIN32_FIND_DATAW data;
643 HANDLE handle;
645 GetWindowsDirectoryW(windir, MAX_PATH);
646 lstrcpyW(path, windir);
647 lstrcatW(path, tasksW);
648 lstrcatW(path, allW);
650 handle = FindFirstFileW(path, &data);
651 if (handle == INVALID_HANDLE_VALUE) return;
655 if (is_file(&data))
657 lstrcpyW(path, windir);
658 lstrcatW(path, tasksW);
660 if (lstrlenW(path) + lstrlenW(data.cFileName) < MAX_PATH)
662 lstrcatW(path, data.cFileName);
663 add_job(path);
665 else
666 FIXME("too long file name %s\n", debugstr_w(data.cFileName));
668 } while (FindNextFileW(handle, &data));
670 FindClose(handle);
673 static BOOL write_signature(HANDLE hfile)
675 struct
677 USHORT SignatureVersion;
678 USHORT ClientVersion;
679 BYTE md5[64];
680 } signature;
681 DWORD size;
683 signature.SignatureVersion = 0x0001;
684 signature.ClientVersion = 0x0001;
685 memset(&signature.md5, 0, sizeof(signature.md5));
687 return WriteFile(hfile, &signature, sizeof(signature), &size, NULL);
690 static BOOL write_reserved_data(HANDLE hfile)
692 static const struct
694 USHORT size;
695 BYTE data[8];
696 } user = { 8, { 0xff,0x0f,0x1d,0,0,0,0,0 } };
697 DWORD size;
699 return WriteFile(hfile, &user, sizeof(user), &size, NULL);
702 static BOOL write_trigger(HANDLE hfile, const AT_INFO *info)
704 USHORT count;
705 DWORD size;
706 SYSTEMTIME st;
707 TASK_TRIGGER trigger;
709 count = 1;
710 if (!WriteFile(hfile, &count, sizeof(count), &size, NULL))
711 return FALSE;
713 GetSystemTime(&st);
714 if (!(info->Flags & JOB_ADD_CURRENT_DATE))
716 /* FIXME: parse AT_INFO */
719 trigger.cbTriggerSize = sizeof(trigger);
720 trigger.Reserved1 = 0;
721 trigger.wBeginYear = st.wYear;
722 trigger.wBeginMonth = st.wMonth;
723 trigger.wBeginDay = st.wDay;
724 trigger.wEndYear = st.wYear;
725 trigger.wEndMonth = st.wMonth;
726 trigger.wEndDay = st.wDay;
727 trigger.wStartHour = st.wHour;
728 trigger.wStartMinute = st.wMinute;
729 trigger.MinutesDuration = 0;
730 trigger.MinutesInterval = 0;
731 /* FIXME */
732 trigger.rgFlags = TASK_TRIGGER_FLAG_HAS_END_DATE;
733 trigger.TriggerType = TASK_TIME_TRIGGER_MONTHLYDATE;
734 trigger.Type.MonthlyDate.rgfDays = 0;
735 trigger.Type.MonthlyDate.rgfMonths = 0xffff;
736 trigger.Reserved2 = 0;
737 trigger.wRandomMinutesInterval = 0;
739 return WriteFile(hfile, &trigger, sizeof(trigger), &size, NULL);
742 static BOOL write_unicode_string(HANDLE hfile, const WCHAR *str)
744 USHORT count;
745 DWORD size;
747 count = str ? (lstrlenW(str) + 1) : 0;
748 if (!WriteFile(hfile, &count, sizeof(count), &size, NULL))
749 return FALSE;
751 if (!str) return TRUE;
753 count *= sizeof(WCHAR);
754 return WriteFile(hfile, str, count, &size, NULL);
757 static BOOL create_job(const WCHAR *job_name, const AT_INFO *info)
759 static WCHAR authorW[] = { 'W','i','n','e',0 };
760 static WCHAR commentW[] = { 'C','r','e','a','t','e','d',' ','b','y',' ','W','i','n','e',0 };
761 FIXDLEN_DATA fixed;
762 USHORT word;
763 HANDLE hfile;
764 DWORD size, ver;
765 BOOL ret = FALSE;
767 TRACE("trying to create job %s\n", debugstr_w(job_name));
768 hfile = CreateFileW(job_name, GENERIC_WRITE, 0, NULL, CREATE_NEW, 0, 0);
769 if (hfile == INVALID_HANDLE_VALUE)
770 return FALSE;
772 ver = GetVersion();
773 fixed.product_version = MAKEWORD(ver >> 8, ver);
774 fixed.file_version = 0x0001;
775 UuidCreate(&fixed.uuid);
776 fixed.name_size_offset = sizeof(fixed) + sizeof(USHORT); /* FIXDLEN_DATA + Instance Count */
777 fixed.trigger_offset = sizeof(fixed) + sizeof(USHORT); /* FIXDLEN_DATA + Instance Count */
778 fixed.trigger_offset += sizeof(USHORT) + (lstrlenW(info->Command) + 1) * sizeof(WCHAR); /* Application Name */
779 fixed.trigger_offset += sizeof(USHORT); /* Parameters */
780 fixed.trigger_offset += sizeof(USHORT); /* Working Directory */
781 fixed.trigger_offset += sizeof(USHORT) + (lstrlenW(authorW) + 1) * sizeof(WCHAR); /* Author */
782 fixed.trigger_offset += sizeof(USHORT) + (lstrlenW(commentW) + 1) * sizeof(WCHAR); /* Comment */
783 fixed.trigger_offset += sizeof(USHORT); /* User Data */
784 fixed.trigger_offset += 10; /* Reserved Data */
785 fixed.error_retry_count = 0;
786 fixed.error_retry_interval = 0;
787 fixed.idle_deadline = 60;
788 fixed.idle_wait = 10;
789 fixed.priority = NORMAL_PRIORITY_CLASS;
790 fixed.maximum_runtime = 259200000;
791 fixed.exit_code = 0;
792 fixed.status = SCHED_S_TASK_HAS_NOT_RUN;
793 fixed.flags = TASK_FLAG_DELETE_WHEN_DONE;
794 if (!(info->Flags & JOB_NONINTERACTIVE))
795 fixed.flags |= TASK_FLAG_INTERACTIVE;
796 /* FIXME: add other flags */
797 memset(&fixed.last_runtime, 0, sizeof(fixed.last_runtime));
799 if (!WriteFile(hfile, &fixed, sizeof(fixed), &size, NULL))
800 goto failed;
802 /* Instance Count */
803 word = 0;
804 if (!WriteFile(hfile, &word, sizeof(word), &size, NULL))
805 goto failed;
806 /* Application Name */
807 if (!write_unicode_string(hfile, info->Command))
808 goto failed;
809 /* Parameters */
810 if (!write_unicode_string(hfile, NULL))
811 goto failed;
812 /* Working Directory */
813 if (!write_unicode_string(hfile, NULL))
814 goto failed;
815 /* Author */
816 if (!write_unicode_string(hfile, authorW))
817 goto failed;
818 /* Comment */
819 if (!write_unicode_string(hfile, commentW))
820 goto failed;
822 /* User Data */
823 word = 0;
824 if (!WriteFile(hfile, &word, sizeof(word), &size, NULL))
825 goto failed;
827 /* Reserved Data */
828 if (!write_reserved_data(hfile))
829 goto failed;
831 /* Triggers */
832 if (!write_trigger(hfile, info))
833 goto failed;
835 /* Signature */
836 if (!write_signature(hfile))
837 goto failed;
839 ret = TRUE;
841 failed:
842 CloseHandle(hfile);
843 if (!ret) DeleteFileW(job_name);
844 return ret;
847 static struct job_t *find_job(DWORD jobid, const WCHAR *name, const UUID *id)
849 struct job_t *job;
851 LIST_FOR_EACH_ENTRY(job, &at_job_list, struct job_t, entry)
853 if (job->info.JobId == jobid || (name && !lstrcmpiW(job->name, name)) || (id && IsEqualGUID(&job->data.uuid, id)))
854 return job;
857 return NULL;
860 static void update_job_status(struct job_t *job)
862 HANDLE hfile;
863 DWORD try, size;
864 #include "pshpack2.h"
865 struct
867 UINT exit_code;
868 UINT status;
869 UINT flags;
870 SYSTEMTIME last_runtime;
871 WORD instance_count;
872 } state;
873 #include "poppack.h"
875 try = 1;
876 for (;;)
878 hfile = CreateFileW(job->name, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, 0);
879 if (hfile != INVALID_HANDLE_VALUE) break;
881 if (GetLastError() != ERROR_SHARING_VIOLATION || try++ >= 3)
883 TRACE("Failed to update %s, error %u\n", debugstr_w(job->name), GetLastError());
884 return;
886 Sleep(100);
889 if (SetFilePointer(hfile, FIELD_OFFSET(FIXDLEN_DATA, exit_code), NULL, FILE_BEGIN) != INVALID_SET_FILE_POINTER)
891 state.exit_code = job->data.exit_code;
892 state.status = job->data.status;
893 state.flags = job->data.flags;
894 state.last_runtime = job->data.last_runtime;
895 state.instance_count = job->instance_count;
896 WriteFile(hfile, &state, sizeof(state), &size, NULL);
899 CloseHandle(hfile);
902 void update_process_status(DWORD pid)
904 struct running_job_t *runjob;
906 EnterCriticalSection(&at_job_list_section);
908 LIST_FOR_EACH_ENTRY(runjob, &running_job_list, struct running_job_t, entry)
910 if (runjob->pid == pid)
912 struct job_t *job = find_job(0, NULL, &runjob->uuid);
913 if (job)
915 DWORD exit_code = STILL_ACTIVE;
917 GetExitCodeProcess(runjob->process, &exit_code);
919 if (exit_code != STILL_ACTIVE)
921 CloseHandle(runjob->process);
922 list_remove(&runjob->entry);
923 heap_free(runjob);
925 job->data.exit_code = exit_code;
926 job->data.status = SCHED_S_TASK_TERMINATED;
927 job->data.flags &= ~0x0c000000;
928 job->instance_count = 0;
929 update_job_status(job);
932 break;
936 LeaveCriticalSection(&at_job_list_section);
939 void check_task_state(void)
941 struct job_t *job;
942 struct running_job_t *runjob;
944 EnterCriticalSection(&at_job_list_section);
946 LIST_FOR_EACH_ENTRY(job, &at_job_list, struct job_t, entry)
948 if (job->data.flags & 0x08000000)
950 TRACE("terminating process %s\n", debugstr_w(job->info.Command));
952 LIST_FOR_EACH_ENTRY(runjob, &running_job_list, struct running_job_t, entry)
954 if (IsEqualGUID(&job->data.uuid, &runjob->uuid))
956 TerminateProcess(runjob->process, 0);
957 update_process_status(runjob->pid);
958 break;
962 else if (job->data.flags & 0x04000000)
964 STARTUPINFOW si;
965 PROCESS_INFORMATION pi;
967 TRACE("running process %s\n", debugstr_w(job->info.Command));
969 if (job->instance_count)
970 FIXME("process %s is already running\n", debugstr_w(job->info.Command));
972 runjob = heap_alloc(sizeof(*runjob));
973 if (runjob)
975 static WCHAR winsta0[] = { 'W','i','n','S','t','a','0',0 };
977 memset(&si, 0, sizeof(si));
978 si.cb = sizeof(si);
979 /* FIXME: if (job->data.flags & TASK_FLAG_INTERACTIVE) */
980 si.lpDesktop = winsta0;
981 si.dwFlags = STARTF_USESHOWWINDOW;
982 si.wShowWindow = SW_SHOWNORMAL;
983 TRACE("executing %s %s at %s\n", debugstr_w(job->info.Command), debugstr_w(job->params), debugstr_w(job->curdir));
984 if (CreateProcessW(job->info.Command, job->params, NULL, NULL, FALSE, 0, NULL, job->curdir, &si, &pi))
986 CloseHandle(pi.hThread);
988 GetLocalTime(&job->data.last_runtime);
989 job->data.exit_code = 0;
990 job->data.status = SCHED_S_TASK_RUNNING;
991 job->instance_count = 1;
993 runjob->uuid = job->data.uuid;
994 runjob->process = pi.hProcess;
995 runjob->pid = pi.dwProcessId;
996 list_add_tail(&running_job_list, &runjob->entry);
997 add_process_to_queue(pi.hProcess);
999 else
1001 WARN("failed to execute %s\n", debugstr_w(job->info.Command));
1002 job->data.status = SCHED_S_TASK_HAS_NOT_RUN;
1003 job->instance_count = 0;
1007 job->data.flags &= ~0x0c000000;
1008 update_job_status(job);
1012 LeaveCriticalSection(&at_job_list_section);
1015 static void run_job(struct job_t *job)
1017 job->data.flags |= 0x04000000;
1018 update_job_status(job);
1021 void check_task_time(void)
1023 FILETIME current_ft, begin_ft, end_ft;
1024 struct job_t *job;
1026 GetSystemTimeAsFileTime(&current_ft);
1027 FileTimeToLocalFileTime(&current_ft, &current_ft);
1029 /* Give -1/+1 minute margin */
1030 begin_ft = current_ft;
1031 filetime_add_minutes(&begin_ft, -1);
1032 end_ft = current_ft;
1033 filetime_add_minutes(&end_ft, 1);
1035 EnterCriticalSection(&at_job_list_section);
1037 LIST_FOR_EACH_ENTRY(job, &at_job_list, struct job_t, entry)
1039 if (job_runs_at(job, &begin_ft, &end_ft))
1041 run_job(job);
1045 LeaveCriticalSection(&at_job_list_section);
1048 void check_missed_task_time(void)
1050 FILETIME current_ft, last_ft;
1051 struct job_t *job;
1053 GetSystemTimeAsFileTime(&current_ft);
1054 FileTimeToLocalFileTime(&current_ft, &current_ft);
1056 EnterCriticalSection(&at_job_list_section);
1058 LIST_FOR_EACH_ENTRY(job, &at_job_list, struct job_t, entry)
1060 if (SystemTimeToFileTime(&job->data.last_runtime, &last_ft))
1062 if (job_runs_at(job, &last_ft, &current_ft))
1064 run_job(job);
1069 LeaveCriticalSection(&at_job_list_section);
1072 void remove_job(const WCHAR *name)
1074 struct job_t *job;
1076 EnterCriticalSection(&at_job_list_section);
1077 job = find_job(0, name, NULL);
1078 if (job)
1080 list_remove(&job->entry);
1081 free_job(job);
1083 LeaveCriticalSection(&at_job_list_section);
1086 DWORD __cdecl NetrJobAdd(ATSVC_HANDLE server_name, AT_INFO *info, DWORD *jobid)
1088 WCHAR windir[MAX_PATH];
1090 TRACE("%s,%p,%p\n", debugstr_w(server_name), info, jobid);
1092 GetWindowsDirectoryW(windir, MAX_PATH);
1094 for (;;)
1096 static const WCHAR fmtW[] = { '\\','T','a','s','k','s','\\','A','t','%','u','.','j','o','b',0 };
1097 WCHAR task_name[MAX_PATH], name[32];
1099 lstrcpyW(task_name, windir);
1100 swprintf(name, ARRAY_SIZE(name), fmtW, current_jobid);
1101 lstrcatW(task_name, name);
1102 if (create_job(task_name, info))
1104 struct job_t *job;
1105 int i;
1107 for (i = 0; i < 5; i++)
1109 EnterCriticalSection(&at_job_list_section);
1110 job = find_job(0, task_name, NULL);
1111 LeaveCriticalSection(&at_job_list_section);
1113 if (job)
1115 *jobid = job->info.JobId;
1116 break;
1119 Sleep(50);
1122 if (!job)
1124 ERR("couldn't find just created job %s\n", debugstr_w(task_name));
1125 return ERROR_FILE_NOT_FOUND;
1128 break;
1131 if (GetLastError() != ERROR_FILE_EXISTS)
1134 TRACE("create_job error %u\n", GetLastError());
1135 return GetLastError();
1138 InterlockedIncrement(&current_jobid);
1141 return ERROR_SUCCESS;
1144 DWORD __cdecl NetrJobDel(ATSVC_HANDLE server_name, DWORD min_jobid, DWORD max_jobid)
1146 DWORD jobid, ret = APE_AT_ID_NOT_FOUND;
1148 TRACE("%s,%u,%u\n", debugstr_w(server_name), min_jobid, max_jobid);
1150 EnterCriticalSection(&at_job_list_section);
1152 for (jobid = min_jobid; jobid <= max_jobid; jobid++)
1154 struct job_t *job = find_job(jobid, NULL, NULL);
1156 if (!job)
1158 TRACE("job %u not found\n", jobid);
1159 ret = APE_AT_ID_NOT_FOUND;
1160 break;
1163 TRACE("deleting job %s\n", debugstr_w(job->name));
1164 if (!DeleteFileW(job->name))
1166 ret = GetLastError();
1167 break;
1170 ret = ERROR_SUCCESS;
1173 LeaveCriticalSection(&at_job_list_section);
1174 return ret;
1177 static void free_container(AT_ENUM_CONTAINER *container)
1179 DWORD i;
1181 for (i = 0; i < container->EntriesRead; i++)
1182 heap_free(container->Buffer[i].Command);
1184 heap_free(container->Buffer);
1187 DWORD __cdecl NetrJobEnum(ATSVC_HANDLE server_name, AT_ENUM_CONTAINER *container,
1188 DWORD max_length, DWORD *total, DWORD *resume)
1190 DWORD allocated;
1191 struct job_t *job;
1193 TRACE("%s,%p,%u,%p,%p\n", debugstr_w(server_name), container, max_length, total, resume);
1195 *total = 0;
1196 *resume = 0;
1197 container->EntriesRead = 0;
1199 allocated = 64;
1200 container->Buffer = heap_alloc(allocated * sizeof(AT_ENUM));
1201 if (!container->Buffer) return ERROR_NOT_ENOUGH_MEMORY;
1203 EnterCriticalSection(&at_job_list_section);
1205 LIST_FOR_EACH_ENTRY(job, &at_job_list, struct job_t, entry)
1207 if (container->EntriesRead >= max_length)
1209 *resume = container->EntriesRead;
1210 break;
1213 if (allocated <= container->EntriesRead)
1215 AT_ENUM *new_buffer;
1217 allocated *= 2;
1218 new_buffer = heap_realloc(container->Buffer, allocated * sizeof(AT_ENUM));
1219 if (!new_buffer)
1221 free_container(container);
1222 LeaveCriticalSection(&at_job_list_section);
1223 return ERROR_NOT_ENOUGH_MEMORY;
1225 container->Buffer = new_buffer;
1228 container->Buffer[container->EntriesRead] = job->info;
1229 container->Buffer[container->EntriesRead].Command = heap_strdupW(job->info.Command);
1230 container->EntriesRead++;
1233 LeaveCriticalSection(&at_job_list_section);
1235 *total = container->EntriesRead;
1237 return ERROR_SUCCESS;
1240 DWORD __cdecl NetrJobGetInfo(ATSVC_HANDLE server_name, DWORD jobid, AT_INFO **info)
1242 struct job_t *job;
1243 DWORD ret = APE_AT_ID_NOT_FOUND;
1245 TRACE("%s,%u,%p\n", debugstr_w(server_name), jobid, info);
1247 EnterCriticalSection(&at_job_list_section);
1249 job = find_job(jobid, NULL, NULL);
1250 if (job)
1252 AT_INFO *info_ret = heap_alloc(sizeof(*info_ret));
1253 if (!info_ret)
1254 ret = ERROR_NOT_ENOUGH_MEMORY;
1255 else
1257 info_ret->JobTime = job->info.JobTime;
1258 info_ret->DaysOfMonth = job->info.DaysOfMonth;
1259 info_ret->DaysOfWeek = job->info.DaysOfWeek;
1260 info_ret->Flags = job->info.Flags;
1261 info_ret->Command = heap_strdupW(job->info.Command);
1262 *info = info_ret;
1263 ret = ERROR_SUCCESS;
1267 LeaveCriticalSection(&at_job_list_section);
1268 return ret;