mongrel_service 0.3.4-x86-mingw32

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