mongrel_service 0.3.4-i386-mswin32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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