2 * Task Scheduler Service
4 * Copyright 2014 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
29 #include "wine/debug.h"
31 #include "schedsvc_private.h"
33 WINE_DEFAULT_DEBUG_CHANNEL(schedsvc
);
35 static const WCHAR scheduleW
[] = {'S','c','h','e','d','u','l','e',0};
36 static SERVICE_STATUS_HANDLE schedsvc_handle
;
37 static HANDLE done_event
, hjob_queue
;
39 void add_process_to_queue(HANDLE process
)
41 if (!AssignProcessToJobObject(hjob_queue
, process
))
42 ERR("AssignProcessToJobObject failed\n");
45 static DWORD WINAPI
tasks_monitor_thread(void *arg
)
47 static const WCHAR tasksW
[] = { '\\','T','a','s','k','s','\\',0 };
49 HANDLE htasks
, hport
, htimer
;
50 JOBOBJECT_ASSOCIATE_COMPLETION_PORT job_info
;
55 FILE_NOTIFY_INFORMATION data
;
56 WCHAR name_buffer
[MAX_PATH
];
59 /* the buffer must be DWORD aligned */
60 C_ASSERT(!(sizeof(info
) & 3));
62 TRACE("Starting...\n");
65 check_missed_task_time();
67 htimer
= CreateWaitableTimerW(NULL
, FALSE
, NULL
);
70 ERR("CreateWaitableTimer failed\n");
74 GetWindowsDirectoryW(path
, MAX_PATH
);
75 lstrcatW(path
, tasksW
);
77 /* Just in case it's an old Wine prefix with missing c:\windows\tasks */
78 CreateDirectoryW(path
, NULL
);
80 htasks
= CreateFileW(path
, FILE_LIST_DIRECTORY
, FILE_SHARE_READ
| FILE_SHARE_WRITE
| FILE_SHARE_DELETE
,
81 NULL
, OPEN_EXISTING
, FILE_FLAG_BACKUP_SEMANTICS
| FILE_FLAG_OVERLAPPED
, NULL
);
82 if (htasks
== INVALID_HANDLE_VALUE
)
84 ERR("Couldn't start monitoring %s for tasks, error %lu\n", debugstr_w(path
), GetLastError());
88 hjob_queue
= CreateJobObjectW(NULL
, NULL
);
91 ERR("CreateJobObject failed\n");
95 hport
= CreateIoCompletionPort(INVALID_HANDLE_VALUE
, NULL
, 0, 1);
98 ERR("CreateIoCompletionPort failed\n");
102 job_info
.CompletionKey
= hjob_queue
;
103 job_info
.CompletionPort
= hport
;
104 if (!SetInformationJobObject(hjob_queue
, JobObjectAssociateCompletionPortInformation
, &job_info
, sizeof(job_info
)))
106 ERR("SetInformationJobObject failed\n");
110 memset(&ov
, 0, sizeof(ov
));
111 ov
.hEvent
= CreateEventW(NULL
, FALSE
, FALSE
, NULL
);
113 memset(&info
, 0, sizeof(info
));
114 ReadDirectoryChangesW(htasks
, &info
, sizeof(info
), FALSE
,
115 FILE_NOTIFY_CHANGE_FILE_NAME
| FILE_NOTIFY_CHANGE_SIZE
| FILE_NOTIFY_CHANGE_LAST_WRITE
,
123 events
[0] = done_event
;
126 events
[3] = ov
.hEvent
;
128 ret
= WaitForMultipleObjects(4, events
, FALSE
, INFINITE
);
130 if (ret
== WAIT_OBJECT_0
) break;
132 /* Next runtime timer */
133 if (ret
== WAIT_OBJECT_0
+ 1)
140 if (ret
== WAIT_OBJECT_0
+ 2)
143 ULONG_PTR dummy
, pid
;
145 if (GetQueuedCompletionStatus(hport
, &msg
, &dummy
, (OVERLAPPED
**)&pid
, 0))
147 if (msg
== JOB_OBJECT_MSG_EXIT_PROCESS
)
149 TRACE("got message: process %#Ix has terminated\n", pid
);
150 update_process_status(pid
);
153 FIXME("got message %#lx from the job\n", msg
);
158 if (info
.data
.NextEntryOffset
)
159 FIXME("got multiple entries\n");
161 /* Directory change notification */
162 info
.data
.FileName
[info
.data
.FileNameLength
/sizeof(WCHAR
)] = 0;
164 switch (info
.data
.Action
)
166 case FILE_ACTION_ADDED
:
167 TRACE("FILE_ACTION_ADDED %s\n", debugstr_w(info
.data
.FileName
));
169 GetWindowsDirectoryW(path
, MAX_PATH
);
170 lstrcatW(path
, tasksW
);
171 lstrcatW(path
, info
.data
.FileName
);
175 case FILE_ACTION_REMOVED
:
176 TRACE("FILE_ACTION_REMOVED %s\n", debugstr_w(info
.data
.FileName
));
177 GetWindowsDirectoryW(path
, MAX_PATH
);
178 lstrcatW(path
, tasksW
);
179 lstrcatW(path
, info
.data
.FileName
);
183 case FILE_ACTION_MODIFIED
:
184 TRACE("FILE_ACTION_MODIFIED %s\n", debugstr_w(info
.data
.FileName
));
186 GetWindowsDirectoryW(path
, MAX_PATH
);
187 lstrcatW(path
, tasksW
);
188 lstrcatW(path
, info
.data
.FileName
);
194 FIXME("%s: action %#lx not handled\n", debugstr_w(info
.data
.FileName
), info
.data
.Action
);
200 if (get_next_runtime(&period
))
202 if (!SetWaitableTimer(htimer
, &period
, 0, NULL
, NULL
, FALSE
))
203 ERR("SetWaitableTimer failed\n");
206 memset(&info
, 0, sizeof(info
));
207 if (!ReadDirectoryChangesW(htasks
, &info
, sizeof(info
), FALSE
,
208 FILE_NOTIFY_CHANGE_FILE_NAME
| FILE_NOTIFY_CHANGE_SIZE
| FILE_NOTIFY_CHANGE_LAST_WRITE
,
209 NULL
, &ov
, NULL
)) break;
212 CancelWaitableTimer(htimer
);
214 CloseHandle(ov
.hEvent
);
216 CloseHandle(hjob_queue
);
219 TRACE("Finished.\n");
224 void schedsvc_auto_start(void)
226 static DWORD start_type
;
227 SC_HANDLE scm
, service
;
228 QUERY_SERVICE_CONFIGW
*cfg
;
231 if (start_type
== SERVICE_AUTO_START
) return;
233 TRACE("changing service start type to SERVICE_AUTO_START\n");
235 scm
= OpenSCManagerW(NULL
, NULL
, 0);
238 WARN("failed to open SCM (%lu)\n", GetLastError());
242 service
= OpenServiceW(scm
, scheduleW
, SERVICE_QUERY_CONFIG
| SERVICE_CHANGE_CONFIG
);
245 if (!QueryServiceConfigW(service
, NULL
, 0, &cfg_size
) && GetLastError() == ERROR_INSUFFICIENT_BUFFER
)
247 cfg
= HeapAlloc(GetProcessHeap(), 0, cfg_size
);
250 if (QueryServiceConfigW(service
, cfg
, cfg_size
, &cfg_size
))
252 start_type
= cfg
->dwStartType
;
253 if (start_type
!= SERVICE_AUTO_START
)
255 if (ChangeServiceConfigW(service
, SERVICE_NO_CHANGE
, SERVICE_AUTO_START
, SERVICE_NO_CHANGE
,
256 NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
))
257 start_type
= SERVICE_AUTO_START
;
260 HeapFree(GetProcessHeap(), 0, cfg
);
264 WARN("failed to query service config (%lu)\n", GetLastError());
266 CloseServiceHandle(service
);
269 WARN("failed to open service (%lu)\n", GetLastError());
271 CloseServiceHandle(scm
);
274 static void schedsvc_update_status(DWORD state
)
276 SERVICE_STATUS status
;
278 status
.dwServiceType
= SERVICE_WIN32
;
279 status
.dwControlsAccepted
= SERVICE_ACCEPT_STOP
| SERVICE_ACCEPT_SHUTDOWN
;
280 status
.dwWin32ExitCode
= 0;
281 status
.dwServiceSpecificExitCode
= 0;
282 status
.dwCheckPoint
= 0;
283 status
.dwWaitHint
= 0;
284 status
.dwControlsAccepted
= 0;
285 status
.dwCurrentState
= state
;
287 SetServiceStatus(schedsvc_handle
, &status
);
290 static void WINAPI
schedsvc_handler(DWORD control
)
292 TRACE("%#lx\n", control
);
296 case SERVICE_CONTROL_STOP
:
297 case SERVICE_CONTROL_SHUTDOWN
:
298 schedsvc_update_status(SERVICE_STOP_PENDING
);
299 SetEvent(done_event
);
303 schedsvc_update_status(SERVICE_RUNNING
);
308 static RPC_BINDING_VECTOR
*sched_bindings
;
310 static RPC_STATUS
RPC_init(void)
312 static WCHAR ncacn_npW
[] = { 'n','c','a','c','n','_','n','p',0 };
313 static WCHAR endpoint_npW
[] = { '\\','p','i','p','e','\\','a','t','s','v','c',0 };
314 static WCHAR ncalrpcW
[] = { 'n','c','a','l','r','p','c',0 };
315 static WCHAR endpoint_lrpcW
[] = { 'a','t','s','v','c',0 };
318 status
= RpcServerRegisterIf(ITaskSchedulerService_v1_0_s_ifspec
, NULL
, NULL
);
319 if (status
!= RPC_S_OK
)
321 ERR("RpcServerRegisterIf error %#lx\n", status
);
325 status
= RpcServerRegisterIf(atsvc_v1_0_s_ifspec
, NULL
, NULL
);
326 if (status
!= RPC_S_OK
)
328 ERR("RpcServerRegisterIf error %#lx\n", status
);
329 RpcServerUnregisterIf(ITaskSchedulerService_v1_0_s_ifspec
, NULL
, FALSE
);
333 status
= RpcServerUseProtseqEpW(ncacn_npW
, RPC_C_PROTSEQ_MAX_REQS_DEFAULT
, endpoint_npW
, NULL
);
334 if (status
!= RPC_S_OK
)
336 ERR("RpcServerUseProtseqEp error %#lx\n", status
);
340 status
= RpcServerUseProtseqEpW(ncalrpcW
, RPC_C_PROTSEQ_MAX_REQS_DEFAULT
, endpoint_lrpcW
, NULL
);
341 if (status
!= RPC_S_OK
)
343 ERR("RpcServerUseProtseqEp error %#lx\n", status
);
347 status
= RpcServerInqBindings(&sched_bindings
);
348 if (status
!= RPC_S_OK
)
350 ERR("RpcServerInqBindings error %#lx\n", status
);
354 status
= RpcEpRegisterW(ITaskSchedulerService_v1_0_s_ifspec
, sched_bindings
, NULL
, NULL
);
355 if (status
!= RPC_S_OK
)
357 ERR("RpcEpRegister error %#lx\n", status
);
361 status
= RpcEpRegisterW(atsvc_v1_0_s_ifspec
, sched_bindings
, NULL
, NULL
);
362 if (status
!= RPC_S_OK
)
364 ERR("RpcEpRegister error %#lx\n", status
);
368 status
= RpcServerListen(1, RPC_C_LISTEN_MAX_CALLS_DEFAULT
, TRUE
);
369 if (status
!= RPC_S_OK
)
371 ERR("RpcServerListen error %#lx\n", status
);
377 static void RPC_finish(void)
379 RpcMgmtStopServerListening(NULL
);
380 RpcEpUnregister(ITaskSchedulerService_v1_0_s_ifspec
, sched_bindings
, NULL
);
381 RpcEpUnregister(atsvc_v1_0_s_ifspec
, sched_bindings
, NULL
);
382 RpcBindingVectorFree(&sched_bindings
);
383 RpcServerUnregisterIf(ITaskSchedulerService_v1_0_s_ifspec
, NULL
, FALSE
);
384 RpcServerUnregisterIf(atsvc_v1_0_s_ifspec
, NULL
, FALSE
);
387 void WINAPI
ServiceMain(DWORD argc
, LPWSTR
*argv
)
392 TRACE("starting Task Scheduler Service\n");
394 schedsvc_handle
= RegisterServiceCtrlHandlerW(scheduleW
, schedsvc_handler
);
395 if (!schedsvc_handle
)
397 ERR("RegisterServiceCtrlHandler error %ld\n", GetLastError());
401 schedsvc_update_status(SERVICE_START_PENDING
);
403 done_event
= CreateEventW(NULL
, TRUE
, FALSE
, NULL
);
404 thread
= CreateThread(NULL
, 0, tasks_monitor_thread
, NULL
, 0, &tid
);
406 if (thread
&& RPC_init() == RPC_S_OK
)
408 schedsvc_update_status(SERVICE_RUNNING
);
409 WaitForSingleObject(thread
, INFINITE
);
414 schedsvc_update_status(SERVICE_STOPPED
);
416 TRACE("exiting Task Scheduler Service\n");
419 void __RPC_FAR
* __RPC_USER
MIDL_user_allocate(SIZE_T len
)
421 return heap_alloc(len
);
424 void __RPC_USER
MIDL_user_free(void __RPC_FAR
* ptr
)