thin_service 0.0.1

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,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,61 @@
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
+ '# Adapted for Thin by Garth Smedley
14
+ '#
15
+ '##################################################################
16
+
17
+ '##################################################################
18
+ '# Requirements:
19
+ '# - FreeBASIC 0.17, Win32 CVS Build (as for November 09, 2006).
20
+ '#
21
+ '##################################################################
22
+
23
+ #ifndef __Debug_bi__
24
+ #define __Debug_bi__
25
+
26
+ #ifdef DEBUG_LOG
27
+ #include once "vbcompat.bi"
28
+ #ifndef DEBUG_LOG_FILE
29
+ #define DEBUG_LOG_FILE EXEPATH + "\debug.log"
30
+ #endif
31
+
32
+ '# this procedure is only used for debugging purposed, will be removed from
33
+ '# final compilation
34
+ private sub debug_to_file(byref message as string, byref file as string, byval linenumber as uinteger, byref func as string)
35
+ dim handle as integer
36
+ static first_time as integer
37
+
38
+ handle = freefile
39
+ open DEBUG_LOG_FILE for append as #handle
40
+
41
+ if (first_time = 0) then
42
+ print #handle, "# Logfile created on "; format(now(), "dd/mm/yyyy HH:mm:ss")
43
+ print #handle, ""
44
+ first_time = 1
45
+ end if
46
+
47
+ '# src/module.bas:123, namespace.function:
48
+ '# message
49
+ '#
50
+ print #handle, file; ":"; str(linenumber); ", "; lcase(func); ":"
51
+ print #handle, space(2); message
52
+ print #handle, ""
53
+
54
+ close #handle
55
+ end sub
56
+ #define debug(message) debug_to_file(message, __FILE__, __LINE__, __FUNCTION__)
57
+ #else
58
+ #define debug(message)
59
+ #endif '# DEBUG_LOG
60
+
61
+ #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