mongrel_service 0.1 → 0.4.beta1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,70 @@
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_Utils_bi__
17
+ #define __ServiceFB_Utils_bi__
18
+
19
+ #include once "win/psapi.bi"
20
+ #include once "win/tlhelp32.bi"
21
+
22
+ namespace fb
23
+ namespace svc
24
+ namespace utils '# fb.svc.utils
25
+ '# use this to determine (using select case maybe?) the
26
+ '# mode which the service was invoked.
27
+ enum ServiceRunMode
28
+ RunAsUnknown = 0
29
+ RunAsService
30
+ RunAsManager
31
+ RunAsConsole
32
+ end enum
33
+
34
+
35
+ '# ServiceController type (object)
36
+ '# this is a helper object in case you want to implement
37
+ '# console mode (command line testing/debugging) and management (install/remove/control)
38
+ '# to your services, all from the same executable
39
+ type ServiceController
40
+ '# ctor/dtor()
41
+ declare constructor()
42
+ declare constructor(byref as string)
43
+ declare constructor(byref as string, byref as string)
44
+ declare constructor(byref as string, byref as string, byref as string)
45
+ declare destructor()
46
+
47
+ '# methods (public)
48
+ declare sub Banner()
49
+ declare function RunMode() as ServiceRunMode
50
+ declare sub Manage()
51
+ declare sub Manage(byref as ServiceProcess)
52
+ declare sub Console()
53
+ declare sub Console(byref as ServiceProcess)
54
+
55
+ '# properties (public)
56
+ '# use these properties for shwoing information on console/manager mode
57
+ '# as banner.
58
+ '# Product, version
59
+ '# copyright
60
+ product as string
61
+ version as string
62
+ copyright as string
63
+ end type
64
+ end namespace '# fb.svc.utils
65
+ end namespace '# fb.svc
66
+ end namespace '# fb
67
+
68
+ #endif '# __ServiceFB_bi__
69
+ #endif '# __FB_WIN32__
70
+ #endif '# __FB_VERSION__
@@ -0,0 +1,50 @@
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
+ '##################################################################
9
+ '#
10
+ '# DO NOT INCLUDE THIS FILE DIRECTLY!
11
+ '# it is used internaly by ServiceFB
12
+ '# use ServiceFB.bi instead
13
+ '#
14
+ '##################################################################
15
+
16
+ namespace fb
17
+ namespace svc
18
+ '# now due references locking, I needed a constructor and destructor for
19
+ '# the namespace to garantee everything is cleaned up on termination of the process
20
+ declare sub _initialize() constructor
21
+ declare sub _terminate() destructor
22
+
23
+ '# global service procedures (private)
24
+ declare sub _main(byval as DWORD, byval as LPSTR ptr)
25
+ declare function _control_ex(byval as DWORD, byval as DWORD, byval as LPVOID, byval as LPVOID) as DWORD
26
+ declare sub _run()
27
+
28
+ '# global references helper
29
+ declare function _add_to_references(byref as ServiceProcess) as integer
30
+ declare function _find_in_references(byref as string) as ServiceProcess ptr
31
+
32
+ '# command line builder (helper)
33
+ '# this is used to gather information about:
34
+ '# mode (if present)
35
+ '# valid service name (after lookup in the table)
36
+ '# command line to be passed to service
37
+ declare sub _build_commandline(byref as string, byref as string, byref as string)
38
+
39
+ '# I started this as simple, unique service served from one process
40
+ '# but the idea of share the same process space (and reduce resources use) was good.
41
+ '# to do that, I needed a references table (similar to service_table, but we will
42
+ '# hold the ServiceProcess registered by ServiceHost (the multi services host).
43
+ '# also, I needed a locking mechanism to avoid problems of two calls changing the table
44
+ '# at the same time.
45
+ extern _svc_references as ServiceProcess ptr ptr
46
+ extern _svc_references_count as integer
47
+ extern _svc_references_lock as any ptr
48
+ end namespace '# fb.svc
49
+ end namespace '# fb
50
+
@@ -0,0 +1,51 @@
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
+ '##################################################################
9
+ '#
10
+ '# DO NOT INCLUDE THIS FILE DIRECTLY!
11
+ '# it is used internaly by ServiceFB
12
+ '# use ServiceFB_Utils.bi instead
13
+ '#
14
+ '##################################################################
15
+
16
+ namespace fb
17
+ namespace svc
18
+ namespace utils '# fb.svc.utils
19
+ '# console_handler is used to get feedback form keyboard and allow
20
+ '# shutdown of service using Ctrl+C / Ctrl+Break from keyboard
21
+ declare function _console_handler(byval as DWORD) as BOOL
22
+
23
+ '# helper private subs used to list the services and their descriptions
24
+ '# in _svc_references
25
+ declare sub _list_references()
26
+
27
+ '# internals functions used to get Parent PID and Process Name
28
+ '# using this we automatically determine if the service was started by SCM
29
+ '# or by the user, from commandline or from explorer
30
+ declare function _parent_pid(byval as uinteger) as uinteger
31
+ declare function _process_name(byval as uinteger) as string
32
+ declare function _process_name_dyn_psapi(byval as uinteger) as string
33
+ declare function _show_error() as string
34
+
35
+ '# InStrRev (authored by ikkejw @ freebasic forums)
36
+ '# http://www.freebasic.net/forum/viewtopic.php?p=49315#49315
37
+ declare function InStrRev(byval as uinteger = 0, byref as string, byref as string) as uinteger
38
+
39
+ '# use a signal (condition) in the console mode to know
40
+ '# when the service should be stopped.
41
+ '# the Console() main loop will wait for it, and the console_handler
42
+ '# will raise in case Ctrl+C / Ctrl+Break or other events are
43
+ '# received.
44
+ extern _svc_stop_signal as any ptr
45
+ extern _svc_stop_mutex as any ptr
46
+ extern _svc_stopped as BOOL
47
+ extern _svc_in_console as ServiceProcess ptr
48
+ extern _svc_in_console_stop_flag as BOOL
49
+ end namespace '# fb.svc.utils
50
+ end namespace '# fb.svc
51
+ end namespace '# fb
@@ -0,0 +1,59 @@
1
+ '##################################################################
2
+ '#
3
+ '# mongrel_service: Win32 native implementation for mongrel
4
+ '# (using ServiceFB and FreeBASIC)
5
+ '#
6
+ '# Copyright (c) 2006 Multimedia systems
7
+ '# (c) and code by Luis Lavena
8
+ '#
9
+ '# mongrel_service (native) and mongrel_service gem_pluing are licensed
10
+ '# in the same terms as mongrel, please review the mongrel license at
11
+ '# http://mongrel.rubyforge.org/license.html
12
+ '#
13
+ '##################################################################
14
+
15
+ '##################################################################
16
+ '# Requirements:
17
+ '# - FreeBASIC 0.17, Win32 CVS Build (as for November 09, 2006).
18
+ '#
19
+ '##################################################################
20
+
21
+ #ifndef __Debug_bi__
22
+ #define __Debug_bi__
23
+
24
+ #ifdef DEBUG_LOG
25
+ #include once "vbcompat.bi"
26
+ #ifndef DEBUG_LOG_FILE
27
+ #define DEBUG_LOG_FILE EXEPATH + "\debug.log"
28
+ #endif
29
+
30
+ '# this procedure is only used for debugging purposed, will be removed from
31
+ '# final compilation
32
+ private sub debug_to_file(byref message as string, byref file as string, byval linenumber as uinteger, byref func as string)
33
+ dim handle as integer
34
+ static first_time as integer
35
+
36
+ handle = freefile
37
+ open DEBUG_LOG_FILE for append as #handle
38
+
39
+ if (first_time = 0) then
40
+ print #handle, "# Logfile created on "; format(now(), "dd/mm/yyyy HH:mm:ss")
41
+ print #handle, ""
42
+ first_time = 1
43
+ end if
44
+
45
+ '# src/module.bas:123, namespace.function:
46
+ '# message
47
+ '#
48
+ print #handle, file; ":"; str(linenumber); ", "; lcase(func); ":"
49
+ print #handle, space(2); message
50
+ print #handle, ""
51
+
52
+ close #handle
53
+ end sub
54
+ #define debug(message) debug_to_file(message, __FILE__, __LINE__, __FUNCTION__)
55
+ #else
56
+ #define debug(message)
57
+ #endif '# DEBUG_LOG
58
+
59
+ #endif '# __Debug_bi__
@@ -0,0 +1,18 @@
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
+ #ifndef __BOOLEAN_BI__
9
+ #define __BOOLEAN_BI__
10
+
11
+ #undef BOOLEAN
12
+ type BOOLEAN as byte
13
+ #undef FALSE
14
+ const FALSE as byte = 0
15
+ #undef TRUE
16
+ const TRUE as byte = not FALSE
17
+
18
+ #endif ' __BOOLEAN_BI__
@@ -0,0 +1,397 @@
1
+ '#--
2
+ '# Copyright (c) 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 "console_process.bi"
9
+
10
+ constructor ConsoleProcess(byref new_filename as string = "", byref new_arguments as string = "")
11
+ '# assign filename and arguments
12
+
13
+ '# if filename contains spaces, automatically quote it!
14
+ if (instr(new_filename, " ") > 0) then
15
+ _filename = !"\"" + new_filename + !"\""
16
+ else
17
+ _filename = new_filename
18
+ endif
19
+
20
+ _arguments = new_arguments
21
+ end constructor
22
+
23
+ destructor ConsoleProcess()
24
+ '# in case process still running
25
+ if (running = true) then
26
+ terminate(true)
27
+
28
+ '# close opened handles
29
+ '# ...
30
+ CloseHandle(_process_info.hProcess)
31
+ CloseHandle(_process_info.hThread)
32
+ end if
33
+ end destructor
34
+
35
+ property ConsoleProcess.filename() as string
36
+ return _filename
37
+ end property
38
+
39
+ property ConsoleProcess.filename(byref rhs as string)
40
+ if not (running = true) then
41
+ _filename = rhs
42
+ end if
43
+ end property
44
+
45
+ property ConsoleProcess.arguments() as string
46
+ return _arguments
47
+ end property
48
+
49
+ property ConsoleProcess.arguments(byref rhs as string)
50
+ if not (running = true) then
51
+ _arguments = rhs
52
+ end if
53
+ end property
54
+
55
+ property ConsoleProcess.redirected_stdout() as string
56
+ return _stdout_filename
57
+ end property
58
+
59
+ property ConsoleProcess.redirected_stderr() as string
60
+ return _stderr_filename
61
+ end property
62
+
63
+ '# running is a helper which evaluates _pid and exit_code
64
+ property ConsoleProcess.running() as boolean
65
+ dim result as boolean
66
+
67
+ '# presume not running
68
+ result = false
69
+
70
+ if not (_pid = 0) then
71
+ '# that means the process is/was running.
72
+ '# now evaluate if exit_code = STILL_ACTIVE
73
+ result = (exit_code = STILL_ACTIVE)
74
+ end if
75
+
76
+ return result
77
+ end property
78
+
79
+ property ConsoleProcess.pid() as uinteger
80
+ return _pid
81
+ end property
82
+
83
+ property ConsoleProcess.exit_code() as uinteger
84
+ static previous_code as uinteger
85
+ dim result as uinteger
86
+
87
+ result = 0
88
+
89
+ '# is _pid valid?
90
+ if not (_pid = 0) then
91
+ if not (_process_info.hProcess = NULL) then
92
+ '# the process reference is valid, get the exit_code
93
+ if not (GetExitCodeProcess(_process_info.hProcess, @result) = 0) then
94
+ previous_code = result
95
+ '# OK
96
+ '# no error in the query, get result
97
+ if not (result = STILL_ACTIVE) then
98
+ CloseHandle(_process_info.hProcess)
99
+ _process_info.hProcess = NULL
100
+ end if '# (result = STILL_ACTIVE)
101
+ end if '# not (GetExitCodeProcess() = 0)
102
+ else
103
+ result = previous_code
104
+ end if '# not (proc = NULL)
105
+ end if '# not (_pid = 0)
106
+
107
+ return result
108
+ end property
109
+
110
+ function ConsoleProcess.redirect(byval target as ProcessStdEnum, byref new_std_filename as string) as boolean
111
+ dim result as boolean
112
+
113
+ if not (running = true) then
114
+ select case target
115
+ case ProcessStdOut:
116
+ _stdout_filename = new_std_filename
117
+ result = true
118
+
119
+ case ProcessStdErr:
120
+ _stderr_filename = new_std_filename
121
+ result = true
122
+
123
+ case ProcessStdBoth:
124
+ _stdout_filename = new_std_filename
125
+ _stderr_filename = new_std_filename
126
+ result = true
127
+
128
+ end select
129
+ end if
130
+
131
+ return result
132
+ end function
133
+
134
+ function ConsoleProcess.start() as boolean
135
+ dim result as boolean
136
+ dim success as boolean
137
+
138
+ '# API
139
+ '# New Process resources
140
+ dim context as STARTUPINFO
141
+ dim proc_sa as SECURITY_ATTRIBUTES = type(sizeof(SECURITY_ATTRIBUTES), NULL, TRUE)
142
+
143
+ '# StdIn, StdOut, StdErr Read and Write Pipes.
144
+ dim as HANDLE StdInRd, StdOutRd, StdErrRd
145
+ dim as HANDLE StdInWr, StdOutWr, StdErrWr
146
+ dim merged as boolean
147
+
148
+ '# cmdline
149
+ dim cmdline as string
150
+
151
+ '# assume start will fail
152
+ result = false
153
+
154
+ if (running = false) then
155
+ '# we should create the std* for the new proc!
156
+ '# (like good parents, prepare everything!)
157
+
158
+ '# to ensure everything will work, we must allocate a console
159
+ '# using AllocConsole, even if it fails.
160
+ '# This solve the problems when running as service.
161
+ '# we discard result of AllocConsole since we ALWAYS will allocate it.
162
+ AllocConsole()
163
+
164
+ '# assume all the following steps succeed
165
+ success = true
166
+
167
+ '# StdIn is the only std that will be created using pipes always
168
+ '# StdIn
169
+ if (CreatePipe(@StdInRd, @StdInWr, @proc_sa, 0) = 0) then
170
+ success = false
171
+ end if
172
+
173
+ '# Ensure the handles to the pipe are not inherited.
174
+ if (SetHandleInformation(StdInWr, HANDLE_FLAG_INHERIT, 0) = 0) then
175
+ success = false
176
+ end if
177
+
178
+ '# StdOut and StdErr should be redirected?
179
+ if (not _stdout_filename = "") or _
180
+ (not _stderr_filename = "") then
181
+
182
+ '# out and err are the same? (merged)
183
+ if (_stdout_filename = _stderr_filename) then
184
+ merged = true
185
+ end if
186
+ end if
187
+
188
+ '# StdOut if stdout_filename
189
+ if not (_stdout_filename = "") then
190
+ StdOutWr = CreateFile(strptr(_stdout_filename), _
191
+ GENERIC_WRITE, _
192
+ FILE_SHARE_READ or FILE_SHARE_WRITE, _
193
+ @proc_sa, _
194
+ OPEN_ALWAYS, _
195
+ FILE_ATTRIBUTE_NORMAL, _
196
+ NULL)
197
+
198
+ if (StdOutWr = INVALID_HANDLE_VALUE) then
199
+ '# failed to open file
200
+ success = false
201
+ else
202
+ SetFilePointer(StdOutWr, 0, NULL, FILE_END)
203
+ end if
204
+ else
205
+ '# use pipes instead
206
+ '# StdOut
207
+ if (CreatePipe(@StdOutRd, @StdOutWr, @proc_sa, 0) = 0) then
208
+ success = false
209
+ end if
210
+
211
+ if (SetHandleInformation(StdOutRd, HANDLE_FLAG_INHERIT, 0) = 0) then
212
+ success = false
213
+ end if
214
+ end if 'not (_stdout_filename = "")
215
+
216
+ '# only create stderr if no merged.
217
+ if (merged = true) then
218
+ StdErrWr = StdOutWr
219
+ else
220
+ '# do the same for StdErr...
221
+ if not (_stderr_filename = "") then
222
+ StdErrWr = CreateFile(strptr(_stderr_filename), _
223
+ GENERIC_WRITE, _
224
+ FILE_SHARE_READ or FILE_SHARE_WRITE, _
225
+ @proc_sa, _
226
+ OPEN_ALWAYS, _
227
+ FILE_ATTRIBUTE_NORMAL, _
228
+ NULL)
229
+
230
+ if (StdErrWr = INVALID_HANDLE_VALUE) then
231
+ '# failed to open file
232
+ success = false
233
+ else
234
+ SetFilePointer(StdErrWr, 0, NULL, FILE_END)
235
+ end if
236
+ else
237
+ '# use pipes instead
238
+ '# StdOut
239
+ if (CreatePipe(@StdErrRd, @StdErrWr, @proc_sa, 0) = 0) then
240
+ success = false
241
+ end if
242
+
243
+ if (SetHandleInformation(StdErrRd, HANDLE_FLAG_INHERIT, 0) = 0) then
244
+ success = false
245
+ end if
246
+
247
+ end if 'not (_stderr_filename = "")
248
+ end if '(merged = true)
249
+
250
+ '# now we must proceed to create the process
251
+ '# without the pipes, we shouldn't continue!
252
+ if (success = true) then
253
+ '# Set the Std* handles ;-)
254
+ with context
255
+ .cb = sizeof( context )
256
+ .hStdError = StdErrWr
257
+ .hStdOutput = StdOutWr
258
+ .hStdInput = StdInRd
259
+ .dwFlags = STARTF_USESTDHANDLES or STARTF_USESHOWWINDOW
260
+ '# FIXME: .wShowWindow = iif((_show_console = true), SW_SHOW, SW_HIDE)
261
+ .wShowWindow = SW_HIDE
262
+ end with
263
+
264
+ '# build the command line
265
+ cmdline = _filename + " " + _arguments
266
+
267
+ '# now creates the process
268
+ if (CreateProcess(NULL, _
269
+ strptr(cmdline), _
270
+ NULL, _
271
+ NULL, _
272
+ 1, _ '# win32 TRUE (1)
273
+ 0, _
274
+ NULL, _
275
+ NULL, _
276
+ @context, _
277
+ @_process_info) = 0) then
278
+ result = false
279
+ else
280
+ '# set the _pid
281
+ _pid = _process_info.dwProcessId
282
+
283
+ '# OK? yeah, I think so.
284
+ result = true
285
+
286
+ '# close the Std* handles
287
+ CloseHandle(StdInRd)
288
+ CloseHandle(StdInWr)
289
+ CloseHandle(StdOutRd)
290
+ CloseHandle(StdOutWr)
291
+ CloseHandle(StdErrRd)
292
+ CloseHandle(StdErrWr)
293
+
294
+ '# close children main Thread handle and
295
+ '# NULLify to avoid issues
296
+ CloseHandle(_process_info.hThread)
297
+ _process_info.hThread = NULL
298
+ end if '# (CreateProcess() = 0)
299
+ else
300
+ result = false
301
+ end if '# (success = TRUE)
302
+ end if
303
+
304
+ return result
305
+ end function
306
+
307
+ function ConsoleProcess.terminate(byval force as boolean = false) as boolean
308
+ dim result as boolean
309
+ dim success as boolean
310
+
311
+ dim proc as HANDLE
312
+ dim code as uinteger
313
+ dim wait_code as uinteger
314
+
315
+ '# is pid valid?
316
+ if (running = true) then
317
+ '# hook our custom console handler
318
+ if not (SetConsoleCtrlHandler(@_console_handler, 1) = 0) then
319
+ success = true
320
+ end if
321
+
322
+ if (success = true) then
323
+ '# get a handle to Process
324
+ proc = _process_info.hProcess
325
+ if not (proc = NULL) then
326
+ '# process is valid, perform actions
327
+ success = false
328
+
329
+ if not (force = true) then
330
+ '# send CTRL_C_EVENT and wait for result
331
+ if not (GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0) = 0) then
332
+ '# it worked, wait 5 seconds terminates.
333
+ wait_code = WaitForSingleObject(proc, 10000)
334
+ if not (wait_code = WAIT_TIMEOUT) then
335
+ success = true
336
+ end if
337
+ else
338
+ success = false
339
+ end if
340
+
341
+ '# Ctrl-C didn't work, try Ctrl-Break
342
+ if (success = false) then
343
+ '# send CTRL_BREAK_EVENT and wait for result
344
+ if not (GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, 0) = 0) then
345
+ '# it worked, wait 5 seconds terminates.
346
+ wait_code = WaitForSingleObject(proc, 10000)
347
+ if not (wait_code = WAIT_TIMEOUT) then
348
+ success = true
349
+ end if
350
+ else
351
+ success = false
352
+ end if
353
+ end if
354
+
355
+ '# only do termination if force was set.
356
+ elseif (force = true) and (success = false) then
357
+ '# still no luck? we should do a hard kill then
358
+ if (TerminateProcess(proc, 0) = 0) then
359
+ success = false
360
+ else
361
+ success = true
362
+ end if
363
+ end if
364
+
365
+ '# now get process exit code
366
+ if (success = true) then
367
+ result = true
368
+ else
369
+ result = false
370
+ end if
371
+ else
372
+ '# invalid process handler
373
+ result = false
374
+ end if
375
+
376
+ end if '# (success = true)
377
+
378
+ '# remove hooks
379
+ if not (SetConsoleCtrlHandler(@_console_handler, 0) = 0) then
380
+ success = true
381
+ end if
382
+ end if '# not (pid = 0)
383
+
384
+ return result
385
+ end function
386
+
387
+ function ConsoleProcess._console_handler(byval dwCtrlType as DWORD) as BOOL
388
+ dim result as BOOL
389
+
390
+ if (dwCtrlType = CTRL_C_EVENT) then
391
+ result = 1
392
+ elseif (dwCtrlType = CTRL_BREAK_EVENT) then
393
+ result = 1
394
+ end if
395
+
396
+ return result
397
+ end function