thin_service 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,109 @@
1
+ '#--
2
+ '# Copyright (c) 2006-2007 Luis Lavena, Multimedia systems
3
+ '#
4
+ '# This source code is released under the MIT License.
5
+ '# See MIT-LICENSE file for details
6
+ '#++
7
+
8
+ #if __FB_VERSION__ < "0.17"
9
+ #error ServiceFB is designed to compile with FreeBASIC version "0.17"
10
+ #else
11
+
12
+ #ifndef __FB_WIN32__
13
+ #error Platform unsupported. Compiling ServiceFB requires Windows platform.
14
+ #else
15
+
16
+ #ifndef __ServiceFB_bi__
17
+ #define __ServiceFB_bi__
18
+
19
+ #include once "windows.bi"
20
+ #inclib "advapi32"
21
+
22
+ namespace fb
23
+ namespace svc '# fb.svc
24
+ #ifdef SERVICEFB_DEBUG_LOG
25
+ '# debug print
26
+ declare sub _dprint(byref as string)
27
+ #else
28
+ #define _dprint(message)
29
+ #endif
30
+
31
+ '# service states used by end user with 'state' property
32
+ enum ServiceStateEnum
33
+ Running = SERVICE_RUNNING
34
+ Paused = SERVICE_PAUSED
35
+ Stopped = SERVICE_STOPPED
36
+ end enum
37
+
38
+
39
+ '# ServiceProcess type (object)
40
+ '# use this to create new services and reference the on*() methods to perform the related
41
+ '# tasks.
42
+ type ServiceProcess
43
+ '# ctor/dtor
44
+ declare constructor()
45
+ declare constructor(byref as string)
46
+ declare destructor()
47
+
48
+ '# methods (public)
49
+ declare sub Run()
50
+ declare sub StillAlive(byval as integer = 10)
51
+
52
+ '# helper methods (private)
53
+ declare sub UpdateState(byval as DWORD, byval as integer = 0, byval as integer = 0)
54
+
55
+ '# pseudo-events
56
+ '# for onInit you should return FALSE (0) in case you want to abort
57
+ '# service initialization.
58
+ '# If everything was ok, then return TRUE (-1)
59
+ onInit as function(byref as ServiceProcess) as integer
60
+ onStart as sub(byref as ServiceProcess)
61
+ onStop as sub(byref as ServiceProcess)
62
+ onPause as sub(byref as ServiceProcess)
63
+ onContinue as sub(byref as ServiceProcess)
64
+
65
+ '# call_* are used to avoid the warning arround ThreadCreate
66
+ declare static sub call_onStart(byval as any ptr)
67
+
68
+ '# properties (public)
69
+ name as string
70
+ description as string
71
+ state as ServiceStateEnum
72
+ commandline as string '# TODO
73
+ shared_process as integer
74
+
75
+ '# properties (private)
76
+ _svcStatus as SERVICE_STATUS
77
+ _svcHandle as SERVICE_STATUS_HANDLE
78
+ _svcStopEvent as HANDLE
79
+ _threadHandle as any ptr
80
+ end type
81
+
82
+
83
+ '# ServiceHost type (object)
84
+ '# use this, beside ServiceProcess, to manage the registration and running of
85
+ '# several services sharing the same process.
86
+ '# NOTE: ServiceHost.Run() and ServiceProcess.Run() are mutually exclusive, that
87
+ '# means don't mix single service with multiple service in the same program!
88
+ type ServiceHost
89
+ '# ctor/dtor()
90
+ declare constructor()
91
+ declare destructor()
92
+
93
+ '# methods (public)
94
+ declare sub Add(byref as ServiceProcess)
95
+ declare sub Run()
96
+
97
+ '# properties (public)
98
+ count as integer
99
+ end type
100
+ end namespace '# fb.svc
101
+ end namespace '# fb
102
+
103
+ #ifdef SERVICEFB_INCLUDE_UTILS
104
+ #include once "ServiceFB_Utils.bi"
105
+ #endif
106
+
107
+ #endif '# __ServiceFB_bi__
108
+ #endif '# __FB_WIN32__
109
+ #endif '# __FB_VERSION__
@@ -0,0 +1,495 @@
1
+ '#--
2
+ '# Copyright (c) 2006-2007 Luis Lavena, Multimedia systems
3
+ '#
4
+ '# This source code is released under the MIT License.
5
+ '# See MIT-LICENSE file for details
6
+ '#++
7
+
8
+ #include once "ServiceFB.bi"
9
+ #include once "_internals.bi"
10
+ #include once "ServiceFB_Utils.bi"
11
+ #include once "_utils_internals.bi"
12
+
13
+ namespace fb
14
+ namespace svc
15
+ namespace utils '# fb.svc.utils
16
+ '# private (internals) for ServiceProcess.Console()
17
+ dim shared _svc_stop_signal as any ptr
18
+ dim shared _svc_stop_mutex as any ptr
19
+ dim shared _svc_stopped as BOOL
20
+ dim shared _svc_in_console as ServiceProcess ptr
21
+ dim shared _svc_in_console_stop_flag as BOOL
22
+
23
+ '#####################
24
+ '# ServiceController
25
+ '# ctor()
26
+ constructor ServiceController()
27
+ with this
28
+ .product = "My Product"
29
+ .version = "v0.1"
30
+ .copyright = "my copyright goes here."
31
+ end with
32
+ end constructor
33
+
34
+
35
+ '# ctor(product)
36
+ constructor ServiceController(byref new_product as string)
37
+ this.product = new_product
38
+ end constructor
39
+
40
+
41
+ '# ctor(product, version)
42
+ constructor ServiceController(byref new_product as string, byref new_version as string)
43
+ constructor(new_product)
44
+ this.version = new_version
45
+ end constructor
46
+
47
+
48
+ '# ctor(product, version, copyright)
49
+ constructor ServiceController(byref new_product as string, byref new_version as string, byref new_copyright as string)
50
+ constructor(new_product, new_version)
51
+ this.copyright = new_copyright
52
+ end constructor
53
+
54
+
55
+ '# dtor()
56
+ destructor ServiceController()
57
+ end destructor
58
+
59
+
60
+ '# Banner() will display in the console, information regarding your program
61
+ '# using this formatting:
62
+ '# 'Product', 'Version'
63
+ '# 'Copyright'
64
+ sub ServiceController.Banner()
65
+ '# display Product and Version
66
+ print this.product; ", "; this.version
67
+ print this.copyright
68
+ print ""
69
+ '# leave a empty line between banner (header) and other info
70
+ end sub
71
+
72
+
73
+ '# RunMode() provide a simple way to get (*you*) from where this process was started
74
+ '# and do the corresponding action.
75
+ function ServiceController.RunMode() as ServiceRunMode
76
+ dim result as ServiceRunMode
77
+ dim currPID as DWORD
78
+ dim parent_pid as uinteger
79
+ dim parent_name as string
80
+ dim start_mode as string
81
+
82
+ _dprint("ServiceController.RunMode()")
83
+
84
+ '# get this process PID
85
+ currPID = GetCurrentProcessId()
86
+ _dprint("CurrentPID: " + str(currPID))
87
+
88
+ '# get the parent PID
89
+ parent_pid = _parent_pid(currPID)
90
+ _dprint("ParentPID: " + str(parent_pid))
91
+
92
+ '# now the the name
93
+ parent_name = _process_name(parent_pid)
94
+ if (parent_name = "<unknown>") then
95
+ parent_name = _process_name_dyn_psapi(parent_pid)
96
+ end if
97
+ _dprint("Parent Name: " + parent_name)
98
+
99
+ '# this process started as service?
100
+ '# that means his parent is services.exe
101
+ if (parent_name = "services.exe") then
102
+ result = RunAsService
103
+ else
104
+ '# ok, it didn't start as service, analyze command line then
105
+ start_mode = lcase(trim(command(1)))
106
+ if (start_mode = "manage") then
107
+ '# start ServiceController.Manage()
108
+ result = RunAsManager
109
+ elseif (start_mode = "console") then
110
+ '# start ServiceController.Console()
111
+ result = RunAsConsole
112
+ elseif (start_mode = "service") then
113
+ result = RunAsService
114
+ else
115
+ '# ok, the first paramenter in the commandline didn't work,
116
+ '# report back so we could send the banner!
117
+ result = RunAsUnknown
118
+ end if
119
+ end if
120
+
121
+ _dprint("ServiceController.RunMode() done")
122
+ return result
123
+ end function
124
+
125
+
126
+ '# Manage will offer the user (end-user) option in the commandline to
127
+ '# install, remove, start, stop and query the status of the installed service
128
+ '# use Manage() when you code a multi-services (ServiceHost) based programs
129
+ '# for single services, use Manage(service)
130
+ sub ServiceController.Manage()
131
+ end sub
132
+
133
+
134
+ '# this is used when you want management capabilities for your service
135
+ '# use this for single services, or call Manage() for multi services
136
+ sub ServiceController.Manage(byref service as ServiceProcess)
137
+ end sub
138
+
139
+
140
+ '# this offer the user a way to test/debug your service or run it like a normal
141
+ '# program, from the command line
142
+ '# will let you SHUTDOWN the service using CTRL+C
143
+ '# use this for multi-services (ServiceHost) based programs
144
+ sub ServiceController.Console()
145
+ dim working_thread as any ptr
146
+ dim run_mode as string
147
+ dim service_name as string
148
+ dim service as ServiceProcess ptr
149
+ dim commandline as string
150
+ dim success as integer
151
+
152
+ _dprint("ServiceController.Console()")
153
+
154
+ '# show the controller banner
155
+ this.Banner()
156
+
157
+ '# determine how many service exist in references
158
+ if (_svc_references_count > 0) then
159
+ _build_commandline(run_mode, service_name, commandline)
160
+ service = _find_in_references(service_name)
161
+
162
+ if (service = 0) then
163
+ '# no valid service reference, list available services
164
+ _list_references()
165
+ else
166
+ '# build the command line, excluding 'console' and service_name
167
+ service->commandline = commandline
168
+
169
+ '# got a service reference
170
+ '# also, set the global handler that will be used by _control_handler
171
+ _svc_in_console = service
172
+
173
+ '# create the signal used to stop the service thread.
174
+ _svc_stop_signal = condcreate()
175
+ _svc_stop_mutex = mutexcreate()
176
+
177
+ '# register the Console Handler
178
+ SetConsoleCtrlHandler(@_console_handler, TRUE)
179
+
180
+ print "Starting service '"; service_name; "' in console mode, please wait..."
181
+
182
+ '# onInit should be started inline,
183
+ '# and its result validated!
184
+ if not (service->onInit = 0) then
185
+ success = service->onInit(*service)
186
+ end if
187
+
188
+ '# only continue if success
189
+ if not (success = 0) then
190
+ '# now set service.state to running
191
+ service->state = Running
192
+
193
+ '# now, fire the main loop (onStart)
194
+ if not (service->onStart = 0) then
195
+ '# create the thread
196
+ working_thread = threadcreate(@ServiceProcess.call_onStart, service)
197
+ if (working_thread = 0) then
198
+ print "Failed to create working thread."
199
+ end if
200
+ end if
201
+
202
+ print "Service is in running state."
203
+ print "Press Ctrl-C to stop it."
204
+
205
+ '# now that onStart is running, must monitor the stop_signal
206
+ '# in case it arrives, the service state must change to exit the
207
+ '# working thread.
208
+ mutexlock(_svc_stop_mutex)
209
+ do while (_svc_stopped = FALSE)
210
+ condwait(_svc_stop_signal, _svc_stop_mutex)
211
+ loop
212
+ mutexunlock(_svc_stop_mutex)
213
+
214
+ print "Stop signal received, stopping..."
215
+
216
+ '# received the signal, so set state = Stopped
217
+ service->state = Stopped
218
+
219
+ print "Waiting for onStart() to exit..."
220
+
221
+ '# now wait for the thread to terminate
222
+ if not (working_thread = 0) then
223
+ threadwait(working_thread)
224
+ end if
225
+
226
+ else
227
+ print "Error starting the service, onInit() failed."
228
+ end if
229
+
230
+ print "Service stopped, doing cleanup."
231
+
232
+ '# remove the console handler
233
+ SetConsoleCtrlHandler(@_console_handler, FALSE)
234
+
235
+ '# now that service was stopped, destroy the references.
236
+ conddestroy(_svc_stop_signal)
237
+ mutexdestroy(_svc_stop_mutex)
238
+ print "Done."
239
+ end if
240
+ else
241
+ print "ERROR: No services could be served by this program. Exiting."
242
+ end if
243
+
244
+ _dprint("ServiceController.Console() done")
245
+ end sub
246
+
247
+
248
+ '# this offer the user a way to test/debug your service or run it like a normal
249
+ '# program, from the command line
250
+ '# will let you SHUTDOWN the service using CTRL+C
251
+ '# use this for single-services
252
+ sub ServiceController.Console(byref service as ServiceProcess)
253
+
254
+ _dprint("ServiceController.RunMode(service)")
255
+
256
+ '# register the service in the references table
257
+ _add_to_references(service)
258
+
259
+ _dprint("delegate to Console()")
260
+ '# now delegate control to Console()
261
+ this.Console()
262
+
263
+ _dprint("ServiceController.Console(service) done")
264
+ end sub
265
+
266
+
267
+ '# console_handler is used to get feedback form keyboard and allow
268
+ '# shutdown of service using Ctrl+C / Ctrl+Break from keyboard
269
+ function _console_handler(byval dwCtrlType as DWORD) as BOOL
270
+ dim result as BOOL
271
+ dim service as ServiceProcess ptr
272
+
273
+ _dprint("_console_handler()")
274
+
275
+ '# get the reference from svc_in_console
276
+ service = _svc_in_console
277
+
278
+ '# we default processing of the message to false
279
+ result = FALSE
280
+
281
+ '# avoid recursion problems
282
+ if (_svc_in_console_stop_flag = FALSE) then
283
+ _dprint("no previous signaled, process event")
284
+ '# all the CtrlType events listed will raise the onStop
285
+ '# of the service
286
+ '# here also will be raised the _svc_stop_signal
287
+ select case dwCtrlType
288
+ case CTRL_C_EVENT, CTRL_CLOSE_EVENT, CTRL_BREAK_EVENT, CTRL_LOGOFF_EVENT, CTRL_SHUTDOWN_EVENT:
289
+ _dprint("got supported CTRL_*_EVENT")
290
+ '# avoid recursion problems
291
+ _svc_in_console_stop_flag = TRUE
292
+ _dprint("set signaled to TRUE")
293
+
294
+ '# the service defined onStop?
295
+ if not (service->onStop = 0) then
296
+ _dprint("pass control to onStop()")
297
+ service->onStop(*service)
298
+ end if
299
+
300
+ '# now fire the signal
301
+ _dprint("fire stop signal")
302
+ mutexlock(_svc_stop_mutex)
303
+ condsignal(_svc_stop_signal)
304
+ result = TRUE
305
+ _svc_in_console_stop_flag = FALSE
306
+ _svc_stopped = TRUE
307
+ mutexunlock(_svc_stop_mutex)
308
+
309
+ case else:
310
+ _dprint("unsupported CTRL EVENT")
311
+ result = FALSE
312
+ end select
313
+ else
314
+ _dprint("already running onStop(), do not pass the message to other message handlers!")
315
+ result = TRUE
316
+ end if
317
+
318
+ _dprint("_console_handler() done")
319
+ return result
320
+ end function
321
+
322
+
323
+ '# helper private subs used to list the services and their descriptions
324
+ '# in _svc_references
325
+ private sub _list_references()
326
+ dim item as ServiceProcess ptr
327
+ dim idx as integer
328
+
329
+ print "Available services in this program:"
330
+
331
+ for idx = 0 to (_svc_references_count - 1)
332
+ item = _svc_references[idx]
333
+
334
+ print space(2);
335
+ print trim(item->name), , trim(item->description)
336
+ next idx
337
+
338
+ end sub
339
+
340
+
341
+ '# TODO: SimpleLogger
342
+ '# TODO: EventLogger
343
+
344
+
345
+ '#####################
346
+ '# private (internals)
347
+ '# _parent_pid is used to retrieve, based on the PID you passed by, the one of the parent
348
+ '# that launched that process.
349
+ '# on fail, it will return 0
350
+ '# Thanks to MichaelW (FreeBASIC forums) for his help about this.
351
+ private function _parent_pid(byval PID as uinteger) as uinteger
352
+ dim as uinteger result
353
+ dim as HANDLE hProcessSnap
354
+ dim as PROCESSENTRY32 pe32
355
+
356
+ '# initialize result, 0 = fail, other number, ParentPID
357
+ result = 0
358
+
359
+ hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)
360
+ if not (hProcessSnap = INVALID_HANDLE_VALUE) then
361
+ pe32.dwSize = sizeof(PROCESSENTRY32)
362
+ if (Process32First(hProcessSnap, @pe32) = TRUE) then
363
+ do
364
+ if (pe32.th32ProcessID = PID) then
365
+ result = pe32.th32ParentProcessID
366
+ exit do
367
+ end if
368
+ loop while not (Process32Next(hProcessSnap, @pe32) = 0)
369
+ end if
370
+ end if
371
+
372
+ CloseHandle(hProcessSnap)
373
+ return result
374
+ end function
375
+
376
+
377
+ '# _process_name is used to retrieve the name (ImageName, BaseModule, whatever) of the PID you
378
+ '# pass to it. if no module name was found, it should return <unknown>
379
+ private function _process_name(byval PID as uinteger) as string
380
+ dim result as string
381
+ dim hProcess as HANDLE
382
+ dim hMod as HMODULE
383
+ dim cbNeeded as DWORD
384
+
385
+ '# assign "<unknown>" to process name, allocate MAX_PATH (260 bytes)
386
+ result = "<unknown>"
387
+ result += space(MAX_PATH - len(result))
388
+
389
+ '# get a handle to the Process
390
+ hProcess = OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ, FALSE, PID)
391
+
392
+ '# if valid, get the process name
393
+ if not (hProcess = NULL) then
394
+ '# success getting Process modules
395
+ if not (EnumProcessModules(hProcess, @hMod, sizeof(hMod), @cbNeeded) = 0) then
396
+ result = space(cbNeeded)
397
+ GetModuleBaseName(hProcess, hMod, strptr(result), len(result))
398
+ end if
399
+ end if
400
+
401
+ CloseHandle(hProcess)
402
+
403
+ '# return a trimmed result
404
+ result = trim(result)
405
+ return result
406
+ end function
407
+
408
+ '# _process_name_dyn_psapi is a workaround for some issues with x64 versions of Windows.
409
+ '# by default, 32bits process can't query information from 64bits modules.
410
+ private function _process_name_dyn_psapi(byval PID as uinteger) as string
411
+ dim result as string
412
+ dim chop as uinteger
413
+ dim zresult as zstring * MAX_PATH
414
+ dim hLib as any ptr
415
+ dim hProcess as HANDLE
416
+ dim cbNeeded as DWORD
417
+ dim GetProcessImageFileName as function (byval as HANDLE, byval as LPTSTR, byval as DWORD) as DWORD
418
+
419
+ '# assign "<unknown>" to process name, allocate MAX_PATH (260 bytes)
420
+ zresult = "<unknown>" + chr(0)
421
+
422
+ '# get dynlib
423
+ hLib = dylibload("psapi.dll")
424
+ if not (hlib = 0) then
425
+ GetProcessImageFileName = dylibsymbol(hlib, "GetProcessImageFileNameA")
426
+ if not (GetProcessImageFileName = 0) then
427
+ '# get a handle to the Process
428
+ hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, PID)
429
+
430
+ '# if valid, get the process name
431
+ if not (hProcess = NULL) then
432
+ cbNeeded = sizeof(zresult)
433
+ if (GetProcessImageFileName(hProcess, @zresult, cbNeeded) = 0) then
434
+ _dprint("Error with GetProcessImageFileName")
435
+ _dprint("GetLastError: " + str(GetLastError()) + _show_error())
436
+ else
437
+ result = zresult
438
+ chop = InStrRev(0, result, "\")
439
+ if (chop > 0) then
440
+ result = mid(result, chop + 1, (len(result) - chop))
441
+ end if
442
+ end if
443
+ else
444
+ _dprint("Error with OpenProcess")
445
+ _dprint("GetLastError: " + str(GetLastError()) + _show_error())
446
+ end if
447
+
448
+ CloseHandle(hProcess)
449
+ else
450
+ _dprint("Unable to get a reference to dynamic symbol GetProcessImageFileNameA.")
451
+ end if
452
+ else
453
+ _dprint("Unable to dynamic load psapi.dll")
454
+ end if
455
+
456
+ '# return a trimmed result
457
+ 'result = trim(result)
458
+ return result
459
+ end function
460
+
461
+ private function _show_error() as string
462
+ dim buffer as string * 1024
463
+ dim p as integer
464
+
465
+ FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM,_
466
+ 0,_
467
+ GetLastError(),_
468
+ 0,_
469
+ strptr(buffer),_
470
+ 1024,_
471
+ 0 )
472
+ buffer = rtrim(buffer)
473
+ p = instr(buffer, chr(13))
474
+ if p then buffer = left(buffer, p - 1)
475
+
476
+ return buffer
477
+ end function
478
+
479
+ private function InStrRev(byval start as uinteger = 0, byref src as string, byref search as string) as uinteger
480
+ dim lensearch as uinteger = len(search)
481
+ dim as uinteger b, a = 0, exit_loop = 0
482
+
483
+ do
484
+ b = a
485
+ a += 1
486
+ a = instr(a, src, search)
487
+ if start >= lensearch then if a + lensearch > start then exit_loop = 1
488
+ loop while (a > 0) and (exit_loop = 0)
489
+
490
+ return b
491
+ end function
492
+
493
+ end namespace '# fb.svc.utils
494
+ end namespace '# fb.svc
495
+ end namespace '# fb