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
26 #include "wine/list.h"
27 #include "wine/debug.h"
29 #include "schedsvc_private.h"
31 WINE_DEFAULT_DEBUG_CHANNEL(schedsvc
);
33 /* lmat.h defines those, but other types in that file conflict
34 * with generated atsvc.h typedefs.
36 #define JOB_ADD_CURRENT_DATE 0x08
37 #define JOB_NONINTERACTIVE 0x10
41 USHORT product_version
;
44 USHORT name_size_offset
;
45 USHORT trigger_offset
;
46 USHORT error_retry_count
;
47 USHORT error_retry_interval
;
55 SYSTEMTIME last_runtime
;
66 USHORT instance_count
;
68 TASK_TRIGGER
*trigger
;
79 static LONG current_jobid
= 1;
81 static struct list at_job_list
= LIST_INIT(at_job_list
);
82 static struct list running_job_list
= LIST_INIT(running_job_list
);
84 static CRITICAL_SECTION at_job_list_section
;
85 static CRITICAL_SECTION_DEBUG cs_debug
=
87 0, 0, &at_job_list_section
,
88 { &cs_debug
.ProcessLocksList
, &cs_debug
.ProcessLocksList
},
89 0, 0, { (DWORD_PTR
)(__FILE__
": at_job_list_section") }
91 static CRITICAL_SECTION at_job_list_section
= { &cs_debug
, -1, 0, 0, 0, 0 };
93 static void filetime_add_ms(FILETIME
*ft
, LONGLONG ms
)
99 } *ftll
= (union u_ftll
*)ft
;
101 ftll
->ll
+= ms
* (LONGLONG
)10000;
104 static void filetime_add_minutes(FILETIME
*ft
, LONG minutes
)
106 filetime_add_ms(ft
, (LONGLONG
)minutes
* 60 * 1000);
109 static void filetime_add_hours(FILETIME
*ft
, LONG hours
)
111 filetime_add_minutes(ft
, (LONGLONG
)hours
* 60);
114 static void filetime_add_days(FILETIME
*ft
, LONG days
)
116 filetime_add_hours(ft
, (LONGLONG
)days
* 24);
119 static void filetime_add_weeks(FILETIME
*ft
, LONG weeks
)
121 filetime_add_days(ft
, (LONGLONG
)weeks
* 7);
124 static void get_begin_time(const TASK_TRIGGER
*trigger
, FILETIME
*ft
)
128 st
.wYear
= trigger
->wBeginYear
;
129 st
.wMonth
= trigger
->wBeginMonth
;
130 st
.wDay
= trigger
->wBeginDay
;
135 st
.wMilliseconds
= 0;
136 SystemTimeToFileTime(&st
, ft
);
139 static void get_end_time(const TASK_TRIGGER
*trigger
, FILETIME
*ft
)
143 if (!(trigger
->rgFlags
& TASK_TRIGGER_FLAG_HAS_END_DATE
))
145 ft
->dwHighDateTime
= ~0u;
146 ft
->dwLowDateTime
= ~0u;
150 st
.wYear
= trigger
->wEndYear
;
151 st
.wMonth
= trigger
->wEndMonth
;
152 st
.wDay
= trigger
->wEndDay
;
157 st
.wMilliseconds
= 0;
158 SystemTimeToFileTime(&st
, ft
);
161 static BOOL
trigger_get_next_runtime(const TASK_TRIGGER
*trigger
, const FILETIME
*current_ft
, FILETIME
*rt
)
163 SYSTEMTIME st
, current_st
;
164 FILETIME begin_ft
, end_ft
, trigger_ft
;
166 if (trigger
->rgFlags
& TASK_TRIGGER_FLAG_DISABLED
)
169 FileTimeToSystemTime(current_ft
, ¤t_st
);
171 get_begin_time(trigger
, &begin_ft
);
172 if (CompareFileTime(&begin_ft
, current_ft
) < 0)
173 begin_ft
= *current_ft
;
175 get_end_time(trigger
, &end_ft
);
177 switch (trigger
->TriggerType
)
179 case TASK_EVENT_TRIGGER_ON_IDLE
:
180 case TASK_EVENT_TRIGGER_AT_SYSTEMSTART
:
181 case TASK_EVENT_TRIGGER_AT_LOGON
:
184 case TASK_TIME_TRIGGER_ONCE
:
186 st
.wHour
= trigger
->wStartHour
;
187 st
.wMinute
= trigger
->wStartMinute
;
189 st
.wMilliseconds
= 0;
190 SystemTimeToFileTime(&st
, &trigger_ft
);
191 if (CompareFileTime(&begin_ft
, &trigger_ft
) <= 0 && CompareFileTime(&trigger_ft
, &end_ft
) < 0)
198 case TASK_TIME_TRIGGER_DAILY
:
199 if (!trigger
->Type
.Daily
.DaysInterval
)
200 break; /* avoid infinite loop */
203 st
.wHour
= trigger
->wStartHour
;
204 st
.wMinute
= trigger
->wStartMinute
;
206 st
.wMilliseconds
= 0;
207 SystemTimeToFileTime(&st
, &trigger_ft
);
208 while (CompareFileTime(&trigger_ft
, &end_ft
) < 0)
210 if (CompareFileTime(&trigger_ft
, &begin_ft
) >= 0)
216 filetime_add_days(&trigger_ft
, trigger
->Type
.Daily
.DaysInterval
);
220 case TASK_TIME_TRIGGER_WEEKLY
:
221 if (!trigger
->Type
.Weekly
.rgfDaysOfTheWeek
)
222 break; /* avoid infinite loop */
225 st
.wHour
= trigger
->wStartHour
;
226 st
.wMinute
= trigger
->wStartMinute
;
228 st
.wMilliseconds
= 0;
229 SystemTimeToFileTime(&st
, &trigger_ft
);
230 while (CompareFileTime(&trigger_ft
, &end_ft
) < 0)
232 FileTimeToSystemTime(&trigger_ft
, &st
);
234 if (CompareFileTime(&trigger_ft
, &begin_ft
) >= 0)
236 if (trigger
->Type
.Weekly
.rgfDaysOfTheWeek
& (1 << st
.wDayOfWeek
))
243 if (st
.wDayOfWeek
== 0 && trigger
->Type
.Weekly
.WeeksInterval
> 1) /* Sunday, goto next week */
244 filetime_add_weeks(&trigger_ft
, trigger
->Type
.Weekly
.WeeksInterval
- 1);
245 else /* check next weekday */
246 filetime_add_days(&trigger_ft
, 1);
251 FIXME("trigger type %u is not handled\n", trigger
->TriggerType
);
258 static BOOL
job_get_next_runtime(struct job_t
*job
, const FILETIME
*current_ft
, FILETIME
*next_rt
)
261 BOOL have_next_rt
= FALSE
;
264 for (i
= 0; i
< job
->trigger_count
; i
++)
266 if (trigger_get_next_runtime(&job
->trigger
[i
], current_ft
, &trigger_rt
))
268 if (!have_next_rt
|| CompareFileTime(&trigger_rt
, next_rt
) < 0)
270 *next_rt
= trigger_rt
;
279 /* Returns next runtime in UTC */
280 BOOL
get_next_runtime(LARGE_INTEGER
*rt
)
282 FILETIME current_ft
, job_rt
, next_job_rt
;
283 BOOL have_next_rt
= FALSE
;
286 GetSystemTimeAsFileTime(¤t_ft
);
287 FileTimeToLocalFileTime(¤t_ft
, ¤t_ft
);
289 EnterCriticalSection(&at_job_list_section
);
291 LIST_FOR_EACH_ENTRY(job
, &at_job_list
, struct job_t
, entry
)
293 if (job_get_next_runtime(job
, ¤t_ft
, &job_rt
))
295 if (!have_next_rt
|| CompareFileTime(&job_rt
, &next_job_rt
) < 0)
297 next_job_rt
= job_rt
;
303 LeaveCriticalSection(&at_job_list_section
);
307 LocalFileTimeToFileTime(&next_job_rt
, &next_job_rt
);
308 rt
->u
.LowPart
= next_job_rt
.dwLowDateTime
;
309 rt
->u
.HighPart
= next_job_rt
.dwHighDateTime
;
315 static BOOL
job_runs_at(struct job_t
*job
, const FILETIME
*begin_ft
, const FILETIME
*end_ft
)
319 if (job_get_next_runtime(job
, begin_ft
, &job_ft
))
321 if (CompareFileTime(&job_ft
, end_ft
) < 0)
328 static DWORD
load_unicode_strings(const char *data
, DWORD limit
, struct job_t
*job
)
330 DWORD i
, data_size
= 0;
333 for (i
= 0; i
< 5; i
++)
335 if (limit
< sizeof(USHORT
))
337 TRACE("invalid string %lu offset\n", i
);
341 len
= *(USHORT
*)data
;
342 data
+= sizeof(USHORT
);
343 data_size
+= sizeof(USHORT
);
344 limit
-= sizeof(USHORT
);
345 if (limit
< len
* sizeof(WCHAR
))
347 TRACE("invalid string %lu size\n", i
);
351 TRACE("string %lu: %s\n", i
, wine_dbgstr_wn((const WCHAR
*)data
, len
));
356 job
->info
.Command
= wcsdup((const WCHAR
*)data
);
360 job
->params
= wcsdup((const WCHAR
*)data
);
364 job
->curdir
= wcsdup((const WCHAR
*)data
);
371 data
+= len
* sizeof(WCHAR
);
372 data_size
+= len
* sizeof(WCHAR
);
378 static BOOL
load_job_data(const char *data
, DWORD size
, struct job_t
*info
)
380 const FIXDLEN_DATA
*fixed
;
381 const SYSTEMTIME
*st
;
382 DWORD unicode_strings_size
, data_size
, triggers_size
;
384 const USHORT
*signature
;
385 const TASK_TRIGGER
*trigger
;
387 memset(info
, 0, sizeof(*info
));
389 if (size
< sizeof(*fixed
))
391 TRACE("no space for FIXDLEN_DATA\n");
395 fixed
= (const FIXDLEN_DATA
*)data
;
398 TRACE("product_version %04x\n", fixed
->product_version
);
399 TRACE("file_version %04x\n", fixed
->file_version
);
400 TRACE("uuid %s\n", wine_dbgstr_guid(&fixed
->uuid
));
402 if (fixed
->file_version
!= 0x0001)
404 TRACE("invalid file version\n");
408 TRACE("name_size_offset %04x\n", fixed
->name_size_offset
);
409 TRACE("trigger_offset %04x\n", fixed
->trigger_offset
);
410 TRACE("error_retry_count %u\n", fixed
->error_retry_count
);
411 TRACE("error_retry_interval %u\n", fixed
->error_retry_interval
);
412 TRACE("idle_deadline %u\n", fixed
->idle_deadline
);
413 TRACE("idle_wait %u\n", fixed
->idle_wait
);
414 TRACE("priority %08x\n", fixed
->priority
);
415 TRACE("maximum_runtime %u\n", fixed
->maximum_runtime
);
416 TRACE("exit_code %#x\n", fixed
->exit_code
);
417 TRACE("status %08x\n", fixed
->status
);
418 TRACE("flags %08x\n", fixed
->flags
);
419 st
= &fixed
->last_runtime
;
420 TRACE("last_runtime %d/%d/%d wday %d %d:%d:%d.%03d\n",
421 st
->wDay
, st
->wMonth
, st
->wYear
, st
->wDayOfWeek
,
422 st
->wHour
, st
->wMinute
, st
->wSecond
, st
->wMilliseconds
);
425 if (size
< sizeof(*fixed
) + sizeof(USHORT
))
427 TRACE("no space for instance count\n");
431 info
->instance_count
= *(const USHORT
*)(data
+ sizeof(*fixed
));
432 TRACE("instance count %u\n", info
->instance_count
);
434 if (fixed
->name_size_offset
+ sizeof(USHORT
) < size
)
435 unicode_strings_size
= load_unicode_strings(data
+ fixed
->name_size_offset
, size
- fixed
->name_size_offset
, info
);
438 TRACE("invalid name_size_offset\n");
441 TRACE("unicode strings end at %#lx\n", fixed
->name_size_offset
+ unicode_strings_size
);
443 if (size
< fixed
->trigger_offset
+ sizeof(USHORT
))
445 TRACE("no space for triggers count\n");
448 info
->trigger_count
= *(const USHORT
*)(data
+ fixed
->trigger_offset
);
449 TRACE("trigger_count %u\n", info
->trigger_count
);
450 triggers_size
= size
- fixed
->trigger_offset
- sizeof(USHORT
);
451 TRACE("triggers_size %lu\n", triggers_size
);
453 data
+= fixed
->name_size_offset
+ unicode_strings_size
;
454 size
-= fixed
->name_size_offset
+ unicode_strings_size
;
457 if (size
< sizeof(USHORT
))
459 TRACE("no space for user data size\n");
463 data_size
= *(const USHORT
*)data
;
464 if (size
< sizeof(USHORT
) + data_size
)
466 TRACE("no space for user data\n");
469 TRACE("User Data size %#lx\n", data_size
);
471 size
-= sizeof(USHORT
) + data_size
;
472 data
+= sizeof(USHORT
) + data_size
;
475 if (size
< sizeof(USHORT
))
477 TRACE("no space for reserved data size\n");
481 data_size
= *(const USHORT
*)data
;
482 if (size
< sizeof(USHORT
) + data_size
)
484 TRACE("no space for reserved data\n");
487 TRACE("Reserved Data size %#lx\n", data_size
);
489 size
-= sizeof(USHORT
) + data_size
;
490 data
+= sizeof(USHORT
) + data_size
;
493 TRACE("trigger_offset %04x, triggers end at %04Ix\n", fixed
->trigger_offset
,
494 fixed
->trigger_offset
+ sizeof(USHORT
) + info
->trigger_count
* sizeof(TASK_TRIGGER
));
496 info
->trigger_count
= *(const USHORT
*)data
;
497 TRACE("trigger_count %u\n", info
->trigger_count
);
498 trigger
= (const TASK_TRIGGER
*)(data
+ sizeof(USHORT
));
500 if (info
->trigger_count
* sizeof(TASK_TRIGGER
) > triggers_size
)
502 TRACE("no space for triggers data\n");
506 info
->trigger
= malloc(info
->trigger_count
* sizeof(info
->trigger
[0]));
509 TRACE("not enough memory for trigger data\n");
513 for (i
= 0; i
< info
->trigger_count
; i
++)
515 TRACE("%u: cbTriggerSize = %#x\n", i
, trigger
[i
].cbTriggerSize
);
516 if (trigger
[i
].cbTriggerSize
!= sizeof(TASK_TRIGGER
))
517 TRACE("invalid cbTriggerSize\n");
518 TRACE("Reserved1 = %#x\n", trigger
[i
].Reserved1
);
519 TRACE("wBeginYear = %u\n", trigger
[i
].wBeginYear
);
520 TRACE("wBeginMonth = %u\n", trigger
[i
].wBeginMonth
);
521 TRACE("wBeginDay = %u\n", trigger
[i
].wBeginDay
);
522 TRACE("wEndYear = %u\n", trigger
[i
].wEndYear
);
523 TRACE("wEndMonth = %u\n", trigger
[i
].wEndMonth
);
524 TRACE("wEndDay = %u\n", trigger
[i
].wEndDay
);
525 TRACE("wStartHour = %u\n", trigger
[i
].wStartHour
);
526 TRACE("wStartMinute = %u\n", trigger
[i
].wStartMinute
);
527 TRACE("MinutesDuration = %lu\n", trigger
[i
].MinutesDuration
);
528 TRACE("MinutesInterval = %lu\n", trigger
[i
].MinutesInterval
);
529 TRACE("rgFlags = %lu\n", trigger
[i
].rgFlags
);
530 TRACE("TriggerType = %u\n", trigger
[i
].TriggerType
);
531 TRACE("Reserved2 = %u\n", trigger
[i
].Reserved2
);
532 TRACE("wRandomMinutesInterval = %u\n", trigger
[i
].wRandomMinutesInterval
);
534 info
->trigger
[i
] = trigger
[i
];
537 size
-= sizeof(USHORT
) + info
->trigger_count
* sizeof(TASK_TRIGGER
);
538 data
+= sizeof(USHORT
) + info
->trigger_count
* sizeof(TASK_TRIGGER
);
540 if (size
< 2 * sizeof(USHORT
) + 64)
542 TRACE("no space for signature\n");
543 return TRUE
; /* signature is optional */
546 signature
= (const USHORT
*)data
;
547 TRACE("signature version %04x, client version %04x\n", signature
[0], signature
[1]);
552 static BOOL
load_job(const WCHAR
*name
, struct job_t
*info
)
554 HANDLE file
, mapping
;
562 file
= CreateFileW(name
, GENERIC_READ
, FILE_SHARE_READ
, NULL
, OPEN_EXISTING
, 0, 0);
563 if (file
== INVALID_HANDLE_VALUE
)
565 TRACE("Failed to open %s, error %lu\n", debugstr_w(name
), GetLastError());
566 if (GetLastError() != ERROR_SHARING_VIOLATION
|| try++ >= 3) break;
571 size
= GetFileSize(file
, NULL
);
573 mapping
= CreateFileMappingW(file
, NULL
, PAGE_READONLY
, 0, 0, 0);
576 TRACE("Failed to create file mapping %s, error %lu\n", debugstr_w(name
), GetLastError());
581 data
= MapViewOfFile(mapping
, FILE_MAP_READ
, 0, 0, 0);
584 ret
= load_job_data(data
, size
, info
);
585 UnmapViewOfFile(data
);
588 CloseHandle(mapping
);
596 static void free_job_info(AT_ENUM
*info
)
601 static void free_job(struct job_t
*job
)
603 free_job_info(&job
->info
);
611 void add_job(const WCHAR
*name
)
615 job
= calloc(1, sizeof(*job
));
618 if (!load_job(name
, job
))
624 EnterCriticalSection(&at_job_list_section
);
625 job
->name
= wcsdup(name
);
626 job
->info
.JobId
= current_jobid
++;
627 list_add_tail(&at_job_list
, &job
->entry
);
628 LeaveCriticalSection(&at_job_list_section
);
631 static inline BOOL
is_file(const WIN32_FIND_DATAW
*data
)
633 return !(data
->dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
);
636 void load_at_tasks(void)
638 static const WCHAR tasksW
[] = { '\\','T','a','s','k','s','\\',0 };
639 static const WCHAR allW
[] = { '*',0 };
640 WCHAR windir
[MAX_PATH
], path
[MAX_PATH
];
641 WIN32_FIND_DATAW data
;
644 GetWindowsDirectoryW(windir
, MAX_PATH
);
645 lstrcpyW(path
, windir
);
646 lstrcatW(path
, tasksW
);
647 lstrcatW(path
, allW
);
649 handle
= FindFirstFileW(path
, &data
);
650 if (handle
== INVALID_HANDLE_VALUE
) return;
656 lstrcpyW(path
, windir
);
657 lstrcatW(path
, tasksW
);
659 if (lstrlenW(path
) + lstrlenW(data
.cFileName
) < MAX_PATH
)
661 lstrcatW(path
, data
.cFileName
);
665 FIXME("too long file name %s\n", debugstr_w(data
.cFileName
));
667 } while (FindNextFileW(handle
, &data
));
672 static BOOL
write_signature(HANDLE hfile
)
676 USHORT SignatureVersion
;
677 USHORT ClientVersion
;
682 signature
.SignatureVersion
= 0x0001;
683 signature
.ClientVersion
= 0x0001;
684 memset(&signature
.md5
, 0, sizeof(signature
.md5
));
686 return WriteFile(hfile
, &signature
, sizeof(signature
), &size
, NULL
);
689 static BOOL
write_reserved_data(HANDLE hfile
)
695 } user
= { 8, { 0xff,0x0f,0x1d,0,0,0,0,0 } };
698 return WriteFile(hfile
, &user
, sizeof(user
), &size
, NULL
);
701 static BOOL
write_trigger(HANDLE hfile
, const AT_INFO
*info
)
706 TASK_TRIGGER trigger
;
709 if (!WriteFile(hfile
, &count
, sizeof(count
), &size
, NULL
))
713 if (!(info
->Flags
& JOB_ADD_CURRENT_DATE
))
715 /* FIXME: parse AT_INFO */
718 trigger
.cbTriggerSize
= sizeof(trigger
);
719 trigger
.Reserved1
= 0;
720 trigger
.wBeginYear
= st
.wYear
;
721 trigger
.wBeginMonth
= st
.wMonth
;
722 trigger
.wBeginDay
= st
.wDay
;
723 trigger
.wEndYear
= st
.wYear
;
724 trigger
.wEndMonth
= st
.wMonth
;
725 trigger
.wEndDay
= st
.wDay
;
726 trigger
.wStartHour
= st
.wHour
;
727 trigger
.wStartMinute
= st
.wMinute
;
728 trigger
.MinutesDuration
= 0;
729 trigger
.MinutesInterval
= 0;
731 trigger
.rgFlags
= TASK_TRIGGER_FLAG_HAS_END_DATE
;
732 trigger
.TriggerType
= TASK_TIME_TRIGGER_MONTHLYDATE
;
733 trigger
.Type
.MonthlyDate
.rgfDays
= 0;
734 trigger
.Type
.MonthlyDate
.rgfMonths
= 0xffff;
735 trigger
.Reserved2
= 0;
736 trigger
.wRandomMinutesInterval
= 0;
738 return WriteFile(hfile
, &trigger
, sizeof(trigger
), &size
, NULL
);
741 static BOOL
write_unicode_string(HANDLE hfile
, const WCHAR
*str
)
746 count
= str
? (lstrlenW(str
) + 1) : 0;
747 if (!WriteFile(hfile
, &count
, sizeof(count
), &size
, NULL
))
750 if (!str
) return TRUE
;
752 count
*= sizeof(WCHAR
);
753 return WriteFile(hfile
, str
, count
, &size
, NULL
);
756 static BOOL
create_job(const WCHAR
*job_name
, const AT_INFO
*info
)
758 static WCHAR authorW
[] = { 'W','i','n','e',0 };
759 static WCHAR commentW
[] = { 'C','r','e','a','t','e','d',' ','b','y',' ','W','i','n','e',0 };
766 TRACE("trying to create job %s\n", debugstr_w(job_name
));
767 hfile
= CreateFileW(job_name
, GENERIC_WRITE
, 0, NULL
, CREATE_NEW
, 0, 0);
768 if (hfile
== INVALID_HANDLE_VALUE
)
772 fixed
.product_version
= MAKEWORD(ver
>> 8, ver
);
773 fixed
.file_version
= 0x0001;
774 UuidCreate(&fixed
.uuid
);
775 fixed
.name_size_offset
= sizeof(fixed
) + sizeof(USHORT
); /* FIXDLEN_DATA + Instance Count */
776 fixed
.trigger_offset
= sizeof(fixed
) + sizeof(USHORT
); /* FIXDLEN_DATA + Instance Count */
777 fixed
.trigger_offset
+= sizeof(USHORT
) + (lstrlenW(info
->Command
) + 1) * sizeof(WCHAR
); /* Application Name */
778 fixed
.trigger_offset
+= sizeof(USHORT
); /* Parameters */
779 fixed
.trigger_offset
+= sizeof(USHORT
); /* Working Directory */
780 fixed
.trigger_offset
+= sizeof(USHORT
) + (lstrlenW(authorW
) + 1) * sizeof(WCHAR
); /* Author */
781 fixed
.trigger_offset
+= sizeof(USHORT
) + (lstrlenW(commentW
) + 1) * sizeof(WCHAR
); /* Comment */
782 fixed
.trigger_offset
+= sizeof(USHORT
); /* User Data */
783 fixed
.trigger_offset
+= 10; /* Reserved Data */
784 fixed
.error_retry_count
= 0;
785 fixed
.error_retry_interval
= 0;
786 fixed
.idle_deadline
= 60;
787 fixed
.idle_wait
= 10;
788 fixed
.priority
= NORMAL_PRIORITY_CLASS
;
789 fixed
.maximum_runtime
= 259200000;
791 fixed
.status
= SCHED_S_TASK_HAS_NOT_RUN
;
792 fixed
.flags
= TASK_FLAG_DELETE_WHEN_DONE
;
793 if (!(info
->Flags
& JOB_NONINTERACTIVE
))
794 fixed
.flags
|= TASK_FLAG_INTERACTIVE
;
795 /* FIXME: add other flags */
796 memset(&fixed
.last_runtime
, 0, sizeof(fixed
.last_runtime
));
798 if (!WriteFile(hfile
, &fixed
, sizeof(fixed
), &size
, NULL
))
803 if (!WriteFile(hfile
, &word
, sizeof(word
), &size
, NULL
))
805 /* Application Name */
806 if (!write_unicode_string(hfile
, info
->Command
))
809 if (!write_unicode_string(hfile
, NULL
))
811 /* Working Directory */
812 if (!write_unicode_string(hfile
, NULL
))
815 if (!write_unicode_string(hfile
, authorW
))
818 if (!write_unicode_string(hfile
, commentW
))
823 if (!WriteFile(hfile
, &word
, sizeof(word
), &size
, NULL
))
827 if (!write_reserved_data(hfile
))
831 if (!write_trigger(hfile
, info
))
835 if (!write_signature(hfile
))
842 if (!ret
) DeleteFileW(job_name
);
846 static struct job_t
*find_job(DWORD jobid
, const WCHAR
*name
, const UUID
*id
)
850 LIST_FOR_EACH_ENTRY(job
, &at_job_list
, struct job_t
, entry
)
852 if (job
->info
.JobId
== jobid
|| (name
&& !lstrcmpiW(job
->name
, name
)) || (id
&& IsEqualGUID(&job
->data
.uuid
, id
)))
859 static void update_job_status(struct job_t
*job
)
863 #include "pshpack2.h"
869 SYSTEMTIME last_runtime
;
877 hfile
= CreateFileW(job
->name
, GENERIC_READ
| GENERIC_WRITE
, FILE_SHARE_READ
, NULL
, OPEN_EXISTING
, 0, 0);
878 if (hfile
!= INVALID_HANDLE_VALUE
) break;
880 if (GetLastError() != ERROR_SHARING_VIOLATION
|| try++ >= 3)
882 TRACE("Failed to update %s, error %lu\n", debugstr_w(job
->name
), GetLastError());
888 if (SetFilePointer(hfile
, FIELD_OFFSET(FIXDLEN_DATA
, exit_code
), NULL
, FILE_BEGIN
) != INVALID_SET_FILE_POINTER
)
890 state
.exit_code
= job
->data
.exit_code
;
891 state
.status
= job
->data
.status
;
892 state
.flags
= job
->data
.flags
;
893 state
.last_runtime
= job
->data
.last_runtime
;
894 state
.instance_count
= job
->instance_count
;
895 WriteFile(hfile
, &state
, sizeof(state
), &size
, NULL
);
901 void update_process_status(DWORD pid
)
903 struct running_job_t
*runjob
;
905 EnterCriticalSection(&at_job_list_section
);
907 LIST_FOR_EACH_ENTRY(runjob
, &running_job_list
, struct running_job_t
, entry
)
909 if (runjob
->pid
== pid
)
911 struct job_t
*job
= find_job(0, NULL
, &runjob
->uuid
);
914 DWORD exit_code
= STILL_ACTIVE
;
916 GetExitCodeProcess(runjob
->process
, &exit_code
);
918 if (exit_code
!= STILL_ACTIVE
)
920 CloseHandle(runjob
->process
);
921 list_remove(&runjob
->entry
);
924 job
->data
.exit_code
= exit_code
;
925 job
->data
.status
= SCHED_S_TASK_TERMINATED
;
926 job
->data
.flags
&= ~0x0c000000;
927 job
->instance_count
= 0;
928 update_job_status(job
);
935 LeaveCriticalSection(&at_job_list_section
);
938 void check_task_state(void)
941 struct running_job_t
*runjob
;
943 EnterCriticalSection(&at_job_list_section
);
945 LIST_FOR_EACH_ENTRY(job
, &at_job_list
, struct job_t
, entry
)
947 if (job
->data
.flags
& 0x08000000)
949 TRACE("terminating process %s\n", debugstr_w(job
->info
.Command
));
951 LIST_FOR_EACH_ENTRY(runjob
, &running_job_list
, struct running_job_t
, entry
)
953 if (IsEqualGUID(&job
->data
.uuid
, &runjob
->uuid
))
955 TerminateProcess(runjob
->process
, 0);
956 update_process_status(runjob
->pid
);
961 else if (job
->data
.flags
& 0x04000000)
964 PROCESS_INFORMATION pi
;
966 TRACE("running process %s\n", debugstr_w(job
->info
.Command
));
968 if (job
->instance_count
)
969 FIXME("process %s is already running\n", debugstr_w(job
->info
.Command
));
971 runjob
= malloc(sizeof(*runjob
));
974 static WCHAR winsta0
[] = { 'W','i','n','S','t','a','0',0 };
976 memset(&si
, 0, sizeof(si
));
978 /* FIXME: if (job->data.flags & TASK_FLAG_INTERACTIVE) */
979 si
.lpDesktop
= winsta0
;
980 si
.dwFlags
= STARTF_USESHOWWINDOW
;
981 si
.wShowWindow
= SW_SHOWNORMAL
;
982 TRACE("executing %s %s at %s\n", debugstr_w(job
->info
.Command
), debugstr_w(job
->params
), debugstr_w(job
->curdir
));
983 if (CreateProcessW(job
->info
.Command
, job
->params
, NULL
, NULL
, FALSE
, 0, NULL
, job
->curdir
, &si
, &pi
))
985 CloseHandle(pi
.hThread
);
987 GetLocalTime(&job
->data
.last_runtime
);
988 job
->data
.exit_code
= 0;
989 job
->data
.status
= SCHED_S_TASK_RUNNING
;
990 job
->instance_count
= 1;
992 runjob
->uuid
= job
->data
.uuid
;
993 runjob
->process
= pi
.hProcess
;
994 runjob
->pid
= pi
.dwProcessId
;
995 list_add_tail(&running_job_list
, &runjob
->entry
);
996 add_process_to_queue(pi
.hProcess
);
1000 WARN("failed to execute %s\n", debugstr_w(job
->info
.Command
));
1001 job
->data
.status
= SCHED_S_TASK_HAS_NOT_RUN
;
1002 job
->instance_count
= 0;
1006 job
->data
.flags
&= ~0x0c000000;
1007 update_job_status(job
);
1011 LeaveCriticalSection(&at_job_list_section
);
1014 static void run_job(struct job_t
*job
)
1016 job
->data
.flags
|= 0x04000000;
1017 update_job_status(job
);
1020 void check_task_time(void)
1022 FILETIME current_ft
, begin_ft
, end_ft
;
1025 GetSystemTimeAsFileTime(¤t_ft
);
1026 FileTimeToLocalFileTime(¤t_ft
, ¤t_ft
);
1028 /* Give -1/+1 minute margin */
1029 begin_ft
= current_ft
;
1030 filetime_add_minutes(&begin_ft
, -1);
1031 end_ft
= current_ft
;
1032 filetime_add_minutes(&end_ft
, 1);
1034 EnterCriticalSection(&at_job_list_section
);
1036 LIST_FOR_EACH_ENTRY(job
, &at_job_list
, struct job_t
, entry
)
1038 if (job_runs_at(job
, &begin_ft
, &end_ft
))
1044 LeaveCriticalSection(&at_job_list_section
);
1047 void check_missed_task_time(void)
1049 FILETIME current_ft
, last_ft
;
1052 GetSystemTimeAsFileTime(¤t_ft
);
1053 FileTimeToLocalFileTime(¤t_ft
, ¤t_ft
);
1055 EnterCriticalSection(&at_job_list_section
);
1057 LIST_FOR_EACH_ENTRY(job
, &at_job_list
, struct job_t
, entry
)
1059 if (SystemTimeToFileTime(&job
->data
.last_runtime
, &last_ft
))
1061 if (job_runs_at(job
, &last_ft
, ¤t_ft
))
1068 LeaveCriticalSection(&at_job_list_section
);
1071 void remove_job(const WCHAR
*name
)
1075 EnterCriticalSection(&at_job_list_section
);
1076 job
= find_job(0, name
, NULL
);
1079 list_remove(&job
->entry
);
1082 LeaveCriticalSection(&at_job_list_section
);
1085 DWORD __cdecl
NetrJobAdd(ATSVC_HANDLE server_name
, AT_INFO
*info
, DWORD
*jobid
)
1087 WCHAR windir
[MAX_PATH
];
1089 TRACE("%s,%p,%p\n", debugstr_w(server_name
), info
, jobid
);
1091 GetWindowsDirectoryW(windir
, MAX_PATH
);
1095 static const WCHAR fmtW
[] = { '\\','T','a','s','k','s','\\','A','t','%','u','.','j','o','b',0 };
1096 WCHAR task_name
[MAX_PATH
], name
[32];
1098 lstrcpyW(task_name
, windir
);
1099 swprintf(name
, ARRAY_SIZE(name
), fmtW
, current_jobid
);
1100 lstrcatW(task_name
, name
);
1101 if (create_job(task_name
, info
))
1106 for (i
= 0; i
< 5; i
++)
1108 EnterCriticalSection(&at_job_list_section
);
1109 job
= find_job(0, task_name
, NULL
);
1110 LeaveCriticalSection(&at_job_list_section
);
1114 *jobid
= job
->info
.JobId
;
1123 ERR("couldn't find just created job %s\n", debugstr_w(task_name
));
1124 return ERROR_FILE_NOT_FOUND
;
1130 if (GetLastError() != ERROR_FILE_EXISTS
)
1133 TRACE("create_job error %lu\n", GetLastError());
1134 return GetLastError();
1137 InterlockedIncrement(¤t_jobid
);
1140 return ERROR_SUCCESS
;
1143 DWORD __cdecl
NetrJobDel(ATSVC_HANDLE server_name
, DWORD min_jobid
, DWORD max_jobid
)
1145 DWORD jobid
, ret
= APE_AT_ID_NOT_FOUND
;
1147 TRACE("%s,%lu,%lu\n", debugstr_w(server_name
), min_jobid
, max_jobid
);
1149 EnterCriticalSection(&at_job_list_section
);
1151 for (jobid
= min_jobid
; jobid
<= max_jobid
; jobid
++)
1153 struct job_t
*job
= find_job(jobid
, NULL
, NULL
);
1157 TRACE("job %lu not found\n", jobid
);
1158 ret
= APE_AT_ID_NOT_FOUND
;
1162 TRACE("deleting job %s\n", debugstr_w(job
->name
));
1163 if (!DeleteFileW(job
->name
))
1165 ret
= GetLastError();
1169 ret
= ERROR_SUCCESS
;
1172 LeaveCriticalSection(&at_job_list_section
);
1176 static void free_container(AT_ENUM_CONTAINER
*container
)
1180 for (i
= 0; i
< container
->EntriesRead
; i
++)
1181 free(container
->Buffer
[i
].Command
);
1183 free(container
->Buffer
);
1186 DWORD __cdecl
NetrJobEnum(ATSVC_HANDLE server_name
, AT_ENUM_CONTAINER
*container
,
1187 DWORD max_length
, DWORD
*total
, DWORD
*resume
)
1192 TRACE("%s,%p,%lu,%p,%p\n", debugstr_w(server_name
), container
, max_length
, total
, resume
);
1196 container
->EntriesRead
= 0;
1199 container
->Buffer
= malloc(allocated
* sizeof(AT_ENUM
));
1200 if (!container
->Buffer
) return ERROR_NOT_ENOUGH_MEMORY
;
1202 EnterCriticalSection(&at_job_list_section
);
1204 LIST_FOR_EACH_ENTRY(job
, &at_job_list
, struct job_t
, entry
)
1206 if (container
->EntriesRead
>= max_length
)
1208 *resume
= container
->EntriesRead
;
1212 if (allocated
<= container
->EntriesRead
)
1214 AT_ENUM
*new_buffer
;
1217 new_buffer
= realloc(container
->Buffer
, allocated
* sizeof(AT_ENUM
));
1220 free_container(container
);
1221 LeaveCriticalSection(&at_job_list_section
);
1222 return ERROR_NOT_ENOUGH_MEMORY
;
1224 container
->Buffer
= new_buffer
;
1227 container
->Buffer
[container
->EntriesRead
] = job
->info
;
1228 container
->Buffer
[container
->EntriesRead
].Command
= wcsdup(job
->info
.Command
);
1229 container
->EntriesRead
++;
1232 LeaveCriticalSection(&at_job_list_section
);
1234 *total
= container
->EntriesRead
;
1236 return ERROR_SUCCESS
;
1239 DWORD __cdecl
NetrJobGetInfo(ATSVC_HANDLE server_name
, DWORD jobid
, AT_INFO
**info
)
1242 DWORD ret
= APE_AT_ID_NOT_FOUND
;
1244 TRACE("%s,%lu,%p\n", debugstr_w(server_name
), jobid
, info
);
1246 EnterCriticalSection(&at_job_list_section
);
1248 job
= find_job(jobid
, NULL
, NULL
);
1251 AT_INFO
*info_ret
= malloc(sizeof(*info_ret
));
1253 ret
= ERROR_NOT_ENOUGH_MEMORY
;
1256 info_ret
->JobTime
= job
->info
.JobTime
;
1257 info_ret
->DaysOfMonth
= job
->info
.DaysOfMonth
;
1258 info_ret
->DaysOfWeek
= job
->info
.DaysOfWeek
;
1259 info_ret
->Flags
= job
->info
.Flags
;
1260 info_ret
->Command
= wcsdup(job
->info
.Command
);
1262 ret
= ERROR_SUCCESS
;
1266 LeaveCriticalSection(&at_job_list_section
);