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
23 #define NONAMELESSUNION
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
42 USHORT product_version
;
45 USHORT name_size_offset
;
46 USHORT trigger_offset
;
47 USHORT error_retry_count
;
48 USHORT error_retry_interval
;
56 SYSTEMTIME last_runtime
;
67 USHORT instance_count
;
69 TASK_TRIGGER
*trigger
;
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
)
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
)
129 st
.wYear
= trigger
->wBeginYear
;
130 st
.wMonth
= trigger
->wBeginMonth
;
131 st
.wDay
= trigger
->wBeginDay
;
136 st
.wMilliseconds
= 0;
137 SystemTimeToFileTime(&st
, ft
);
140 static void get_end_time(const TASK_TRIGGER
*trigger
, FILETIME
*ft
)
144 if (!(trigger
->rgFlags
& TASK_TRIGGER_FLAG_HAS_END_DATE
))
146 ft
->dwHighDateTime
= ~0u;
147 ft
->dwLowDateTime
= ~0u;
151 st
.wYear
= trigger
->wEndYear
;
152 st
.wMonth
= trigger
->wEndMonth
;
153 st
.wDay
= trigger
->wEndDay
;
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
)
170 FileTimeToSystemTime(current_ft
, ¤t_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
:
185 case TASK_TIME_TRIGGER_ONCE
:
187 st
.wHour
= trigger
->wStartHour
;
188 st
.wMinute
= trigger
->wStartMinute
;
190 st
.wMilliseconds
= 0;
191 SystemTimeToFileTime(&st
, &trigger_ft
);
192 if (CompareFileTime(&begin_ft
, &trigger_ft
) <= 0 && CompareFileTime(&trigger_ft
, &end_ft
) < 0)
199 case TASK_TIME_TRIGGER_DAILY
:
200 if (!trigger
->Type
.Daily
.DaysInterval
)
201 break; /* avoid infinite loop */
204 st
.wHour
= trigger
->wStartHour
;
205 st
.wMinute
= trigger
->wStartMinute
;
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)
217 filetime_add_days(&trigger_ft
, trigger
->Type
.Daily
.DaysInterval
);
221 case TASK_TIME_TRIGGER_WEEKLY
:
222 if (!trigger
->Type
.Weekly
.rgfDaysOfTheWeek
)
223 break; /* avoid infinite loop */
226 st
.wHour
= trigger
->wStartHour
;
227 st
.wMinute
= trigger
->wStartMinute
;
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
))
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);
252 FIXME("trigger type %u is not handled\n", trigger
->TriggerType
);
259 static BOOL
job_get_next_runtime(struct job_t
*job
, const FILETIME
*current_ft
, FILETIME
*next_rt
)
262 BOOL have_next_rt
= FALSE
;
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
;
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
;
287 GetSystemTimeAsFileTime(¤t_ft
);
288 FileTimeToLocalFileTime(¤t_ft
, ¤t_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
, ¤t_ft
, &job_rt
))
296 if (!have_next_rt
|| CompareFileTime(&job_rt
, &next_job_rt
) < 0)
298 next_job_rt
= job_rt
;
304 LeaveCriticalSection(&at_job_list_section
);
308 LocalFileTimeToFileTime(&next_job_rt
, &next_job_rt
);
309 rt
->u
.LowPart
= next_job_rt
.dwLowDateTime
;
310 rt
->u
.HighPart
= next_job_rt
.dwHighDateTime
;
316 static BOOL
job_runs_at(struct job_t
*job
, const FILETIME
*begin_ft
, const FILETIME
*end_ft
)
320 if (job_get_next_runtime(job
, begin_ft
, &job_ft
))
322 if (CompareFileTime(&job_ft
, end_ft
) < 0)
329 static DWORD
load_unicode_strings(const char *data
, DWORD limit
, struct job_t
*job
)
331 DWORD i
, data_size
= 0;
334 for (i
= 0; i
< 5; i
++)
336 if (limit
< sizeof(USHORT
))
338 TRACE("invalid string %u offset\n", i
);
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
);
352 TRACE("string %u: %s\n", i
, wine_dbgstr_wn((const WCHAR
*)data
, len
));
357 job
->info
.Command
= heap_strdupW((const WCHAR
*)data
);
361 job
->params
= heap_strdupW((const WCHAR
*)data
);
365 job
->curdir
= heap_strdupW((const WCHAR
*)data
);
372 data
+= len
* sizeof(WCHAR
);
373 data_size
+= len
* sizeof(WCHAR
);
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
;
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");
396 fixed
= (const FIXDLEN_DATA
*)data
;
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");
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
);
426 if (size
< sizeof(*fixed
) + sizeof(USHORT
))
428 TRACE("no space for instance count\n");
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
);
439 TRACE("invalid name_size_offset\n");
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");
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
;
458 if (size
< sizeof(USHORT
))
460 TRACE("no space for user data size\n");
464 data_size
= *(const USHORT
*)data
;
465 if (size
< sizeof(USHORT
) + data_size
)
467 TRACE("no space for user data\n");
470 TRACE("User Data size %#x\n", data_size
);
472 size
-= sizeof(USHORT
) + data_size
;
473 data
+= sizeof(USHORT
) + data_size
;
476 if (size
< sizeof(USHORT
))
478 TRACE("no space for reserved data size\n");
482 data_size
= *(const USHORT
*)data
;
483 if (size
< sizeof(USHORT
) + data_size
)
485 TRACE("no space for reserved data\n");
488 TRACE("Reserved Data size %#x\n", data_size
);
490 size
-= sizeof(USHORT
) + data_size
;
491 data
+= sizeof(USHORT
) + data_size
;
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");
507 info
->trigger
= heap_alloc(info
->trigger_count
* sizeof(info
->trigger
[0]));
510 TRACE("not enough memory for trigger data\n");
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]);
553 static BOOL
load_job(const WCHAR
*name
, struct job_t
*info
)
555 HANDLE file
, mapping
;
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;
572 size
= GetFileSize(file
, NULL
);
574 mapping
= CreateFileMappingW(file
, NULL
, PAGE_READONLY
, 0, 0, 0);
577 TRACE("Failed to create file mapping %s, error %u\n", debugstr_w(name
), GetLastError());
582 data
= MapViewOfFile(mapping
, FILE_MAP_READ
, 0, 0, 0);
585 ret
= load_job_data(data
, size
, info
);
586 UnmapViewOfFile(data
);
589 CloseHandle(mapping
);
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
);
612 void add_job(const WCHAR
*name
)
616 job
= heap_alloc_zero(sizeof(*job
));
619 if (!load_job(name
, job
))
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
;
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;
657 lstrcpyW(path
, windir
);
658 lstrcatW(path
, tasksW
);
660 if (lstrlenW(path
) + lstrlenW(data
.cFileName
) < MAX_PATH
)
662 lstrcatW(path
, data
.cFileName
);
666 FIXME("too long file name %s\n", debugstr_w(data
.cFileName
));
668 } while (FindNextFileW(handle
, &data
));
673 static BOOL
write_signature(HANDLE hfile
)
677 USHORT SignatureVersion
;
678 USHORT ClientVersion
;
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
)
696 } user
= { 8, { 0xff,0x0f,0x1d,0,0,0,0,0 } };
699 return WriteFile(hfile
, &user
, sizeof(user
), &size
, NULL
);
702 static BOOL
write_trigger(HANDLE hfile
, const AT_INFO
*info
)
707 TASK_TRIGGER trigger
;
710 if (!WriteFile(hfile
, &count
, sizeof(count
), &size
, NULL
))
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;
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
)
747 count
= str
? (lstrlenW(str
) + 1) : 0;
748 if (!WriteFile(hfile
, &count
, sizeof(count
), &size
, NULL
))
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 };
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
)
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;
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
))
804 if (!WriteFile(hfile
, &word
, sizeof(word
), &size
, NULL
))
806 /* Application Name */
807 if (!write_unicode_string(hfile
, info
->Command
))
810 if (!write_unicode_string(hfile
, NULL
))
812 /* Working Directory */
813 if (!write_unicode_string(hfile
, NULL
))
816 if (!write_unicode_string(hfile
, authorW
))
819 if (!write_unicode_string(hfile
, commentW
))
824 if (!WriteFile(hfile
, &word
, sizeof(word
), &size
, NULL
))
828 if (!write_reserved_data(hfile
))
832 if (!write_trigger(hfile
, info
))
836 if (!write_signature(hfile
))
843 if (!ret
) DeleteFileW(job_name
);
847 static struct job_t
*find_job(DWORD jobid
, const WCHAR
*name
, const UUID
*id
)
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
)))
860 static void update_job_status(struct job_t
*job
)
864 #include "pshpack2.h"
870 SYSTEMTIME last_runtime
;
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());
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
);
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
);
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
);
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
);
936 LeaveCriticalSection(&at_job_list_section
);
939 void check_task_state(void)
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
);
962 else if (job
->data
.flags
& 0x04000000)
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
));
975 static WCHAR winsta0
[] = { 'W','i','n','S','t','a','0',0 };
977 memset(&si
, 0, 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
);
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
;
1026 GetSystemTimeAsFileTime(¤t_ft
);
1027 FileTimeToLocalFileTime(¤t_ft
, ¤t_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
))
1045 LeaveCriticalSection(&at_job_list_section
);
1048 void check_missed_task_time(void)
1050 FILETIME current_ft
, last_ft
;
1053 GetSystemTimeAsFileTime(¤t_ft
);
1054 FileTimeToLocalFileTime(¤t_ft
, ¤t_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
, ¤t_ft
))
1069 LeaveCriticalSection(&at_job_list_section
);
1072 void remove_job(const WCHAR
*name
)
1076 EnterCriticalSection(&at_job_list_section
);
1077 job
= find_job(0, name
, NULL
);
1080 list_remove(&job
->entry
);
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
);
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
))
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
);
1115 *jobid
= job
->info
.JobId
;
1124 ERR("couldn't find just created job %s\n", debugstr_w(task_name
));
1125 return ERROR_FILE_NOT_FOUND
;
1131 if (GetLastError() != ERROR_FILE_EXISTS
)
1134 TRACE("create_job error %u\n", GetLastError());
1135 return GetLastError();
1138 InterlockedIncrement(¤t_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
);
1158 TRACE("job %u not found\n", jobid
);
1159 ret
= APE_AT_ID_NOT_FOUND
;
1163 TRACE("deleting job %s\n", debugstr_w(job
->name
));
1164 if (!DeleteFileW(job
->name
))
1166 ret
= GetLastError();
1170 ret
= ERROR_SUCCESS
;
1173 LeaveCriticalSection(&at_job_list_section
);
1177 static void free_container(AT_ENUM_CONTAINER
*container
)
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
)
1193 TRACE("%s,%p,%u,%p,%p\n", debugstr_w(server_name
), container
, max_length
, total
, resume
);
1197 container
->EntriesRead
= 0;
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
;
1213 if (allocated
<= container
->EntriesRead
)
1215 AT_ENUM
*new_buffer
;
1218 new_buffer
= heap_realloc(container
->Buffer
, allocated
* sizeof(AT_ENUM
));
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
)
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
);
1252 AT_INFO
*info_ret
= heap_alloc(sizeof(*info_ret
));
1254 ret
= ERROR_NOT_ENOUGH_MEMORY
;
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
);
1263 ret
= ERROR_SUCCESS
;
1267 LeaveCriticalSection(&at_job_list_section
);