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.
- data/.gitignore +19 -0
- data/Gemfile +4 -0
- data/LICENSE +53 -0
- data/README.md +26 -0
- data/Rakefile +17 -0
- data/bin/thin_service +17 -0
- data/lib/thin_service.rb +15 -0
- data/lib/thin_service/command.rb +343 -0
- data/lib/thin_service/logger.rb +74 -0
- data/lib/thin_service/service_manager.rb +78 -0
- data/lib/thin_service/version.rb +3 -0
- data/resource/thin_service_wrapper.exe +0 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/thin_service_spec.rb +43 -0
- data/src/ServiceFB/ServiceFB.bas +651 -0
- data/src/ServiceFB/ServiceFB.bi +109 -0
- data/src/ServiceFB/ServiceFB_Utils.bas +495 -0
- data/src/ServiceFB/ServiceFB_Utils.bi +70 -0
- data/src/ServiceFB/_internals.bi +50 -0
- data/src/ServiceFB/_utils_internals.bi +51 -0
- data/src/thin_service/_debug.bi +61 -0
- data/src/thin_service/boolean.bi +18 -0
- data/src/thin_service/console_process.bas +397 -0
- data/src/thin_service/console_process.bi +75 -0
- data/src/thin_service/thin_service.bas +199 -0
- data/src/thin_service/thin_service.bi +61 -0
- data/tasks/native_lib.rake +40 -0
- data/tasks/native_service.rake +43 -0
- data/tasks/tests.rake +55 -0
- data/tests/all_tests.bas +18 -0
- data/tests/fixtures/mock_process.bas +92 -0
- data/tests/test_console_process.bas +402 -0
- data/tests/test_helpers.bas +35 -0
- data/tests/test_helpers.bi +8 -0
- data/thin_service.gemspec +21 -0
- data/tools/freebasic.rb +359 -0
- metadata +106 -0
@@ -0,0 +1,74 @@
|
|
1
|
+
# Note: Logger concepts are from a combination of:
|
2
|
+
# AlogR: http://alogr.rubyforge.org
|
3
|
+
# Merb: http://merbivore.com
|
4
|
+
module ThinService
|
5
|
+
|
6
|
+
class Log
|
7
|
+
attr_accessor :logger
|
8
|
+
attr_accessor :log_level
|
9
|
+
|
10
|
+
Levels = {
|
11
|
+
:name => { :emergency => 0, :alert => 1, :critical => 2, :error => 3,
|
12
|
+
:warning => 4, :notice => 5, :info => 6, :debug => 7 },
|
13
|
+
:id => { 0 => :emergency, 1 => :alert, 2 => :critical, 3 => :error,
|
14
|
+
4 => :warning, 5 => :notice, 6 => :info, 7 => :debug }
|
15
|
+
}
|
16
|
+
|
17
|
+
def initialize(log, log_level = :debug)
|
18
|
+
@logger = initialize_io(log)
|
19
|
+
@log_level = Levels[:name][log_level] || 7
|
20
|
+
|
21
|
+
if !RUBY_PLATFORM.match(/java|mswin/) && !(@log == STDOUT) &&
|
22
|
+
@log.respond_to?(:write_nonblock)
|
23
|
+
@aio = true
|
24
|
+
end
|
25
|
+
$ThinServiceLogger = self
|
26
|
+
end
|
27
|
+
|
28
|
+
# Writes a string to the logger. Writing of the string is skipped if the string's log level is
|
29
|
+
# higher than the logger's log level. If the logger responds to write_nonblock and is not on
|
30
|
+
# the java or windows platforms then the logger will use non-blocking asynchronous writes.
|
31
|
+
def log(*args)
|
32
|
+
if args[0].is_a?(String)
|
33
|
+
level, string = 6, args[0]
|
34
|
+
else
|
35
|
+
level = (args[0].is_a?(Fixnum) ? args[0] : Levels[:name][args[0]]) || 6
|
36
|
+
string = args[1]
|
37
|
+
end
|
38
|
+
|
39
|
+
return if (level > log_level)
|
40
|
+
|
41
|
+
if @aio
|
42
|
+
@log.write_nonblock("#{Time.now} | #{Levels[:id][level]} | #{string}\n")
|
43
|
+
else
|
44
|
+
@log.write("#{Time.now} | #{Levels[:id][level]} | #{string}\n")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def initialize_io(log)
|
51
|
+
if log.respond_to?(:write)
|
52
|
+
@log = log
|
53
|
+
@log.sync if log.respond_to?(:sync)
|
54
|
+
elsif File.exist?(log)
|
55
|
+
@log = open(log, (File::WRONLY | File::APPEND))
|
56
|
+
@log.sync = true
|
57
|
+
else
|
58
|
+
FileUtils.mkdir_p(File.dirname(log)) unless File.exist?(File.dirname(log))
|
59
|
+
@log = open(log, (File::WRONLY | File::APPEND | File::CREAT))
|
60
|
+
@log.sync = true
|
61
|
+
@log.write("#{Time.now} | info | Logfile created\n")
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
# Convenience wrapper for logging, allows us to use ThinService.log
|
68
|
+
def self.log(*args)
|
69
|
+
# If no logger has been defined yet at this point, log to STDOUT.
|
70
|
+
$ThinServiceLogger ||= ThinService::Log.new(STDOUT, :debug)
|
71
|
+
$ThinServiceLogger.log(*args)
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module ServiceManager
|
2
|
+
class CreateError < StandardError; end
|
3
|
+
class ServiceNotFound < StandardError; end
|
4
|
+
class ServiceError < StandardError; end
|
5
|
+
|
6
|
+
class ServiceStruct < Struct.new(:service_name, :display_name, :binary_path_name)
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.create(service_name, display_name, binary_path_name)
|
10
|
+
cmd = ['create']
|
11
|
+
cmd << quote(service_name)
|
12
|
+
cmd << "DisplayName=" << display_name.inspect
|
13
|
+
cmd << "binPath=" << binary_path_name.inspect
|
14
|
+
status, out = sc(*cmd)
|
15
|
+
raise CreateError.new(out) unless status == 0
|
16
|
+
|
17
|
+
return true
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.exist?(service_name)
|
21
|
+
status, out = sc('query', quote(service_name))
|
22
|
+
out =~ /#{service_name}/i
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.open(service_name)
|
26
|
+
status, out = sc('qc', quote(service_name), 4096)
|
27
|
+
raise ServiceNotFound.new(out) unless status == 0
|
28
|
+
|
29
|
+
out =~ /BINARY\_PATH\_NAME.*\: (.*)$/
|
30
|
+
binary_path_name = $1.strip
|
31
|
+
|
32
|
+
out =~ /DISPLAY\_NAME.*\: (.*)$/
|
33
|
+
display_name = $1.strip
|
34
|
+
|
35
|
+
svc = ServiceStruct.new(service_name, display_name, binary_path_name)
|
36
|
+
|
37
|
+
yield svc if block_given?
|
38
|
+
svc
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.getdisplayname(service_name)
|
42
|
+
status, out = sc('GetDisplayName', quote(service_name))
|
43
|
+
raise ServiceNotFound.new(out) unless status == 0
|
44
|
+
|
45
|
+
out =~ /\=(.*)$/
|
46
|
+
$1.strip
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.stop(service_name)
|
50
|
+
status, out = net('stop', quote(service_name))
|
51
|
+
raise ServiceError.new(out) unless status == 0
|
52
|
+
|
53
|
+
return true
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.delete(service_name)
|
57
|
+
status, out = sc('delete', quote(service_name))
|
58
|
+
raise ServiceError.new(out) unless status == 0
|
59
|
+
|
60
|
+
return true
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def self.sc(*args)
|
66
|
+
output = `sc #{args.join(' ')} 2>&1`
|
67
|
+
return [$?.exitstatus, output]
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.net(*args)
|
71
|
+
output = `net #{args.join(' ')} 2>&1`
|
72
|
+
return [$?.exitstatus, output]
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.quote(text)
|
76
|
+
text.inspect
|
77
|
+
end
|
78
|
+
end
|
Binary file
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ThinService do
|
4
|
+
it 'should print version' do
|
5
|
+
ThinService::Service.new( ["--version"]).run!
|
6
|
+
end
|
7
|
+
it 'should print help' do
|
8
|
+
ThinService::Service.new( ["--help"]).run!
|
9
|
+
end
|
10
|
+
it 'should print help too' do
|
11
|
+
ThinService::Service.new( ["-h"]).run!
|
12
|
+
end
|
13
|
+
it 'should fail to remove due to missing name' do
|
14
|
+
ThinService::Service.new( ["remove"]).run!
|
15
|
+
end
|
16
|
+
it 'should print remove help' do
|
17
|
+
ThinService::Service.new( ["remove", "-h"]).run!
|
18
|
+
end
|
19
|
+
it 'should print remove help too' do
|
20
|
+
ThinService::Service.new( ["remove", "--help"]).run!
|
21
|
+
end
|
22
|
+
it 'should fail to install due to missing name' do
|
23
|
+
ThinService::Service.new( ["install"]).run!
|
24
|
+
end
|
25
|
+
it 'should print install help' do
|
26
|
+
ThinService::Service.new( ["install", "-h"]).run!
|
27
|
+
end
|
28
|
+
it 'should print install help too' do
|
29
|
+
ThinService::Service.new( ["install", "--help"]).run!
|
30
|
+
end
|
31
|
+
it 'should fail to install due to no rails app' do
|
32
|
+
ThinService::Service.new( ["install", "-N", "test_thin_service"]).run!
|
33
|
+
end
|
34
|
+
it 'should fail to install due to ' do
|
35
|
+
ThinService::Service.new( ["install", "-N", "test_thin_service", "-c", "C:/Users/Owner/Documents/NetBeansProjects/hgcs"]).run!
|
36
|
+
end
|
37
|
+
it 'should fail to remove due to no rails app' do
|
38
|
+
ThinService::Service.new( ["remove", "-N", "test_thin_service"]).run!
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
|
43
|
+
end
|
@@ -0,0 +1,651 @@
|
|
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
|
+
|
11
|
+
namespace fb
|
12
|
+
namespace svc
|
13
|
+
'# I started this as simple, unique service served from one process
|
14
|
+
'# but the idea of share the same process space (and reduce resources use) was good.
|
15
|
+
'# to do that, I needed a references table (similar to service_table, but we will
|
16
|
+
'# hold the ServiceProcess registered by ServiceHost (the multi services host).
|
17
|
+
'# also, I needed a locking mechanism to avoid problems of two calls changing the table
|
18
|
+
'# at the same time.
|
19
|
+
dim shared _svc_references as ServiceProcess ptr ptr
|
20
|
+
dim shared _svc_references_count as integer
|
21
|
+
dim shared _svc_references_lock as any ptr
|
22
|
+
|
23
|
+
|
24
|
+
'#####################
|
25
|
+
'# ServiceProcess
|
26
|
+
'# ctor()
|
27
|
+
constructor ServiceProcess()
|
28
|
+
constructor("NewServiceProcess")
|
29
|
+
end constructor
|
30
|
+
|
31
|
+
|
32
|
+
'# ctor(name)
|
33
|
+
constructor ServiceProcess(byref new_name as string)
|
34
|
+
_dprint("ServiceProcess(new_name)")
|
35
|
+
'# assign the service name
|
36
|
+
this.name = new_name
|
37
|
+
|
38
|
+
'# initialize the status structure
|
39
|
+
with this._svcStatus
|
40
|
+
.dwServiceType = SERVICE_WIN32_OWN_PROCESS
|
41
|
+
.dwCurrentState = SERVICE_STOPPED
|
42
|
+
.dwControlsAccepted = (SERVICE_ACCEPT_STOP or SERVICE_ACCEPT_SHUTDOWN)
|
43
|
+
.dwWin32ExitCode = NO_ERROR
|
44
|
+
.dwServiceSpecificExitCode = NO_ERROR
|
45
|
+
.dwCheckPoint = 0
|
46
|
+
.dwWaitHint = 0
|
47
|
+
end with
|
48
|
+
|
49
|
+
'# use a state placeholder
|
50
|
+
this.state = this._svcStatus.dwCurrentState
|
51
|
+
|
52
|
+
'# disable shared process by default
|
53
|
+
this.shared_process = FALSE
|
54
|
+
|
55
|
+
'# create the stop event
|
56
|
+
this._svcStopEvent = CreateEvent( 0, FALSE, FALSE, 0 )
|
57
|
+
_dprint("ServiceProcess(new_name) done")
|
58
|
+
end constructor
|
59
|
+
|
60
|
+
|
61
|
+
'# dtor()
|
62
|
+
destructor ServiceProcess()
|
63
|
+
_dprint("ServiceProcess() destructor")
|
64
|
+
'# safe to destroy it. anyway, just checking
|
65
|
+
with this
|
66
|
+
.onInit = 0
|
67
|
+
.onStart = 0
|
68
|
+
.onStop = 0
|
69
|
+
.onPause = 0
|
70
|
+
.onContinue = 0
|
71
|
+
._threadHandle = 0
|
72
|
+
CloseHandle(._svcStopEvent)
|
73
|
+
end with
|
74
|
+
_dprint("ServiceProcess() destructor done")
|
75
|
+
end destructor
|
76
|
+
|
77
|
+
|
78
|
+
'# for single process, here I create the references table and then
|
79
|
+
'# delegate control to _run() which will call the service control dispatcher
|
80
|
+
sub ServiceProcess.Run()
|
81
|
+
_dprint("ServiceProcess.Run()")
|
82
|
+
|
83
|
+
'# add the unique reference
|
84
|
+
_add_to_references(this)
|
85
|
+
|
86
|
+
'# delegate control to _run()
|
87
|
+
_run()
|
88
|
+
|
89
|
+
_dprint("ServiceProcess.Run() done")
|
90
|
+
end sub
|
91
|
+
|
92
|
+
|
93
|
+
'# I use this method to simplify changing the service state
|
94
|
+
'# notification to the service manager.
|
95
|
+
'# is needed to set dwControlsAccepted = 0 if state is SERVICE_*_PENDING
|
96
|
+
'# also, StillAlive() call it to set the checkpoint and waithint
|
97
|
+
'# to avoid SCM shut us down.
|
98
|
+
'# is not for the the end-user (*you*) to access it, but implemented in this
|
99
|
+
'# way to reduce needed to pass the right service reference each time
|
100
|
+
sub ServiceProcess.UpdateState(byval state as DWORD, byval checkpoint as integer = 0, byval waithint as integer = 0)
|
101
|
+
_dprint("ServiceProcess.UpdateState()")
|
102
|
+
'# set the state
|
103
|
+
select case state
|
104
|
+
'# if the service is starting or stopping, I must disable the option to accept
|
105
|
+
'# other controls form SCM.
|
106
|
+
case SERVICE_START_PENDING, SERVICE_STOP_PENDING:
|
107
|
+
this._svcStatus.dwControlsAccepted = 0
|
108
|
+
|
109
|
+
'# in this case, running or paused, stop and shutdown must be available
|
110
|
+
'# also, we must check here if our service is capable of pause/continue �
|
111
|
+
'# functionality and allow them (or not).
|
112
|
+
case SERVICE_RUNNING, SERVICE_PAUSED:
|
113
|
+
this._svcStatus.dwControlsAccepted = (SERVICE_ACCEPT_STOP or SERVICE_ACCEPT_SHUTDOWN)
|
114
|
+
|
115
|
+
'# from start, the service accept stop and shutdown (see ctor(name)).
|
116
|
+
'# configure the accepted controls.
|
117
|
+
'# Pause and Continue only will be enabled if you setup onPause and onContinue
|
118
|
+
if not (this.onPause = 0) and _
|
119
|
+
not (this.onContinue = 0) then
|
120
|
+
this._svcStatus.dwControlsAccepted or= SERVICE_ACCEPT_PAUSE_CONTINUE
|
121
|
+
end if
|
122
|
+
|
123
|
+
end select
|
124
|
+
|
125
|
+
'# set the structure status
|
126
|
+
'# also the property
|
127
|
+
this._svcStatus.dwCurrentState = state
|
128
|
+
this.state = state
|
129
|
+
|
130
|
+
'# set checkpoint and waithint
|
131
|
+
this._svcStatus.dwCheckPoint = checkpoint
|
132
|
+
this._svcStatus.dwWaitHint = waithint
|
133
|
+
|
134
|
+
'# call the API
|
135
|
+
'# only we will call is _svcHandle is valid
|
136
|
+
'# this will allow use of UpdateState (and StillAlive) from console
|
137
|
+
if not (this._svcHandle = 0) then
|
138
|
+
_dprint("SetServiceStatus() API")
|
139
|
+
SetServiceStatus(this._svcHandle, @this._svcStatus)
|
140
|
+
end if
|
141
|
+
_dprint("ServiceProcess.UpdateState() done")
|
142
|
+
end sub
|
143
|
+
|
144
|
+
|
145
|
+
'# use StillAlive() method when performing lengthly tasks during onInit or onStop
|
146
|
+
'# (if they take too much time).
|
147
|
+
'# by default we set a wait hint gap of 10 seconds, but you could specify how many
|
148
|
+
'# you could specify how many seconds more will require your *work*
|
149
|
+
sub ServiceProcess.StillAlive(byval waithint as integer = 10)
|
150
|
+
dim as integer checkpoint
|
151
|
+
|
152
|
+
_dprint("ServiceProcess.StillAlive()")
|
153
|
+
'# start or stop pending?
|
154
|
+
if (this._svcStatus.dwCurrentState = SERVICE_START_PENDING) or _
|
155
|
+
(this._svcStatus.dwCurrentState = SERVICE_STOP_PENDING) then
|
156
|
+
with this
|
157
|
+
checkpoint = this._svcStatus.dwCheckPoint
|
158
|
+
checkpoint += 1
|
159
|
+
.UpdateState(._svcStatus.dwCurrentState, checkpoint, (waithint * 1000))
|
160
|
+
end with
|
161
|
+
end if
|
162
|
+
_dprint("ServiceProcess.StillAlive() done")
|
163
|
+
end sub
|
164
|
+
|
165
|
+
|
166
|
+
'# call_onStart() is a wrapper around the new limitation of threadcreate
|
167
|
+
'# sub used as pointers in threadcreate must conform the signature
|
168
|
+
sub ServiceProcess.call_onStart(byval any_service as any ptr)
|
169
|
+
var service = cast(ServiceProcess ptr, any_service)
|
170
|
+
service->onStart(*service)
|
171
|
+
end sub
|
172
|
+
|
173
|
+
'#####################
|
174
|
+
'# ServiceHost
|
175
|
+
'# ctor()
|
176
|
+
'# currently isn't needed, why I defined it?
|
177
|
+
constructor ServiceHost()
|
178
|
+
_dprint("ServiceHost()")
|
179
|
+
_dprint("ServiceHost() done")
|
180
|
+
end constructor
|
181
|
+
|
182
|
+
|
183
|
+
'# dtor()
|
184
|
+
'# currently isn't needed, why I defined it?
|
185
|
+
destructor ServiceHost()
|
186
|
+
_dprint("ServiceHost() destructor")
|
187
|
+
_dprint("ServiceHost() destructor done")
|
188
|
+
end destructor
|
189
|
+
|
190
|
+
|
191
|
+
'# using Add() will register an already initialized service into the references
|
192
|
+
'# table, which will be used later to launch and control the different services
|
193
|
+
'# we should be careful when handling references, so for that reference_lock is
|
194
|
+
'# provided ;-)
|
195
|
+
sub ServiceHost.Add(byref service as ServiceProcess)
|
196
|
+
_dprint("ServiceHost.Add()")
|
197
|
+
|
198
|
+
'# add the service reference to the references table
|
199
|
+
'# get the new count as result, so
|
200
|
+
'# increment the local counter
|
201
|
+
this.count = _add_to_references(service)
|
202
|
+
|
203
|
+
_dprint("ServiceHost.Add() done")
|
204
|
+
end sub
|
205
|
+
|
206
|
+
|
207
|
+
'# ServiceHost.Run() is just a placeholder, it delegates control to _run()
|
208
|
+
'# pretty simple, but still must be present to simplify user interaction.
|
209
|
+
sub ServiceHost.Run()
|
210
|
+
_dprint("ServiceHost.Run()")
|
211
|
+
|
212
|
+
'# the longest, hard coded function in the world!
|
213
|
+
'# just kidding
|
214
|
+
_run()
|
215
|
+
|
216
|
+
_dprint("ServiceHost.Run() done")
|
217
|
+
end sub
|
218
|
+
|
219
|
+
|
220
|
+
'# the purpose of this sub is provide a generic service creation and running
|
221
|
+
'# this is be called from exisitng ServiceProcess and ServiceHost.
|
222
|
+
'# this construct the SERVICE_TABLE_ENTRY based on the the references table,
|
223
|
+
'# which will be sent to StartServiceCtrlDispatcher()
|
224
|
+
private sub _run()
|
225
|
+
dim ServiceTable(_svc_references_count) as SERVICE_TABLE_ENTRY
|
226
|
+
dim idx as integer
|
227
|
+
|
228
|
+
_dprint("_run()")
|
229
|
+
|
230
|
+
_dprint("creating service table for " + str(_svc_references_count) + " services")
|
231
|
+
for idx = 0 to (_svc_references_count - 1)
|
232
|
+
'# we take the service name from the references and set as ServiceMain the same
|
233
|
+
'# _main() routine for all the services
|
234
|
+
ServiceTable(idx) = type<SERVICE_TABLE_ENTRY>(strptr(_svc_references[idx]->name), @_main)
|
235
|
+
_dprint(str(idx) + ": " + _svc_references[idx]->name)
|
236
|
+
next idx
|
237
|
+
'# last member of the table must be null
|
238
|
+
ServiceTable(_svc_references_count) = type<SERVICE_TABLE_ENTRY>(0, 0)
|
239
|
+
_dprint("service table created")
|
240
|
+
|
241
|
+
'# start the dispatcher
|
242
|
+
_dprint("start service dispatcher")
|
243
|
+
StartServiceCtrlDispatcher( @ServiceTable(0) )
|
244
|
+
|
245
|
+
_dprint("_run() done")
|
246
|
+
end sub
|
247
|
+
|
248
|
+
|
249
|
+
'# this sub is fired by StartServiceCtrlDispatcher in another thread.
|
250
|
+
'# because it is a global _main for all the services in the table, looking up
|
251
|
+
'# in the references for the right service is needed prior registering its
|
252
|
+
'# control handler.
|
253
|
+
private sub _main(byval argc as DWORD, byval argv as LPSTR ptr)
|
254
|
+
dim success as integer
|
255
|
+
dim service as ServiceProcess ptr
|
256
|
+
dim run_mode as string
|
257
|
+
dim service_name as string
|
258
|
+
dim commandline as string
|
259
|
+
dim param_line as string
|
260
|
+
dim temp as string
|
261
|
+
|
262
|
+
_dprint("_main()")
|
263
|
+
|
264
|
+
'# debug dump of argc and argv
|
265
|
+
dim idx as integer = 0
|
266
|
+
for idx = 0 to (argc - 1)
|
267
|
+
_dprint(str(idx) + ": " + *argv[idx])
|
268
|
+
next idx
|
269
|
+
|
270
|
+
'# retrieve all the information (mode, service name and command line
|
271
|
+
_build_commandline(run_mode, service_name, commandline)
|
272
|
+
service = _find_in_references(service_name)
|
273
|
+
|
274
|
+
'# build parameter line (passed from SCM)
|
275
|
+
if (argc > 1) then
|
276
|
+
param_line = ""
|
277
|
+
for idx = 1 to (argc - 1)
|
278
|
+
temp = *argv[idx]
|
279
|
+
if (instr(temp, chr(32)) > 0) then
|
280
|
+
param_line += """" + temp + """"
|
281
|
+
else
|
282
|
+
param_line += temp
|
283
|
+
end if
|
284
|
+
param_line += " "
|
285
|
+
next idx
|
286
|
+
end if
|
287
|
+
|
288
|
+
'# parameters passed using SCM have priority over ImagePath ones
|
289
|
+
if not (len(param_line) = 0) then
|
290
|
+
commandline = param_line
|
291
|
+
end if
|
292
|
+
|
293
|
+
'# a philosofical question: to run or not to run?
|
294
|
+
if not (service = 0) then
|
295
|
+
_dprint("got a valid service reference")
|
296
|
+
_dprint("real service name: " + service->name)
|
297
|
+
|
298
|
+
'# pass to the service the commandline
|
299
|
+
_dprint("passing service commandline: " + commandline)
|
300
|
+
service->commandline = commandline
|
301
|
+
|
302
|
+
'# ok, its a service!, its alive!
|
303
|
+
'# register his ControlHandlerEx
|
304
|
+
_dprint("register control handler ex")
|
305
|
+
service->_svcHandle = RegisterServiceCtrlHandlerEx(strptr(service_name), @_control_ex, cast(LPVOID, service))
|
306
|
+
|
307
|
+
'# check if evething is done right
|
308
|
+
if not (service->_svcHandle = 0) then
|
309
|
+
'# now, we are a single service or a bunch, like the bradys?
|
310
|
+
if (_svc_references_count > 1) then
|
311
|
+
'# determine if we share or not the process
|
312
|
+
if (service->shared_process = FALSE) then
|
313
|
+
service->_svcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS
|
314
|
+
else
|
315
|
+
'# this mean we will be sharing... hope neighbors don't crash the house!
|
316
|
+
service->_svcStatus.dwServiceType = SERVICE_WIN32_SHARE_PROCESS
|
317
|
+
end if
|
318
|
+
else
|
319
|
+
'# ok, we have a full house (ehem, process) for us only!
|
320
|
+
service->_svcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS
|
321
|
+
end if
|
322
|
+
|
323
|
+
'# START_PENDING
|
324
|
+
_dprint("service start pending")
|
325
|
+
service->UpdateState(SERVICE_START_PENDING)
|
326
|
+
|
327
|
+
'# now delegate to the long running initialization if it exist.
|
328
|
+
if not (service->onInit = 0) then
|
329
|
+
_dprint("pass control to lengthly initialization")
|
330
|
+
success = service->onInit(*service)
|
331
|
+
else
|
332
|
+
'# if no onInit was defined (maybe you don't need it?)
|
333
|
+
'# we should simulate it was successful to proceed
|
334
|
+
success = (-1)
|
335
|
+
end if
|
336
|
+
_dprint("onInit result: " + str(success))
|
337
|
+
|
338
|
+
'# check if everything is ok
|
339
|
+
'# if onInit showed problems, 0 was returned and service must not continue
|
340
|
+
if not (success = 0) then
|
341
|
+
'# SERVICE_RUNNING
|
342
|
+
'# we must launch the onStart as thread, but first setting state as running
|
343
|
+
service->UpdateState(SERVICE_RUNNING)
|
344
|
+
if not (service->onStart = 0) then
|
345
|
+
_dprint("dispatch onStart() as new thread")
|
346
|
+
service->_threadHandle = threadcreate(@ServiceProcess.call_onStart, service)
|
347
|
+
'# my guess? was a hit!
|
348
|
+
end if
|
349
|
+
|
350
|
+
'# now that we are out of onStart thread, check if actually hit the stop sign
|
351
|
+
_dprint("waiting for stop signal")
|
352
|
+
do
|
353
|
+
'# do nothing ...
|
354
|
+
'# but not too often!
|
355
|
+
loop while (WaitForSingleObject(service->_svcStopEvent, 100) = WAIT_TIMEOUT)
|
356
|
+
|
357
|
+
'# now, wait for the thread (anyway, I hope it will be checking this.state, right?)
|
358
|
+
'# we should do this, or actualy jump and wait for StopEvent?
|
359
|
+
_dprint("waiting for onStart() thread to finish")
|
360
|
+
threadwait(service->_threadHandle)
|
361
|
+
end if
|
362
|
+
|
363
|
+
'# if we reach here, that means the service is not running, and the onStop was performed
|
364
|
+
'# so no more chat, stop it one and for all!
|
365
|
+
'# set SERVICE_STOPPED (just checking)
|
366
|
+
_dprint("service stopped")
|
367
|
+
service->UpdateState(SERVICE_STOPPED)
|
368
|
+
end if
|
369
|
+
|
370
|
+
'# ok, we are done!
|
371
|
+
end if
|
372
|
+
|
373
|
+
_dprint("_main() done")
|
374
|
+
end sub
|
375
|
+
|
376
|
+
|
377
|
+
'# this sub is used by _main when registering the ControlHandler for this service
|
378
|
+
'# (as callback from service manager).
|
379
|
+
'# we process each control codes and perform the actions using the pseudo-events (callbacks)
|
380
|
+
'# also we use lpContext to get the right reference when _main registered the control handler.
|
381
|
+
private function _control_ex(byval dwControl as DWORD, byval dwEventType as DWORD, byval lpEventData as LPVOID, byval lpContext as LPVOID) as DWORD
|
382
|
+
dim result as DWORD
|
383
|
+
dim service as ServiceProcess ptr
|
384
|
+
|
385
|
+
_dprint("_control_ex()")
|
386
|
+
|
387
|
+
'# we get a reference form the context
|
388
|
+
service = cast(ServiceProcess ptr, lpContext)
|
389
|
+
|
390
|
+
'# show if the service reference is valid?
|
391
|
+
_dprint("service name: " + service->name)
|
392
|
+
|
393
|
+
select case dwControl
|
394
|
+
case SERVICE_CONTROL_INTERROGATE:
|
395
|
+
'# we are running, so what we should do here?
|
396
|
+
_dprint("interrogation signal received")
|
397
|
+
'# in case we get a interrogation, we always should answer this way.
|
398
|
+
result = NO_ERROR
|
399
|
+
|
400
|
+
case SERVICE_CONTROL_SHUTDOWN, SERVICE_CONTROL_STOP:
|
401
|
+
_dprint("stop signal received")
|
402
|
+
'# ok, service manager requested us to stop.
|
403
|
+
'# we must call onStop if was defined.
|
404
|
+
service->UpdateState(SERVICE_STOP_PENDING)
|
405
|
+
if not (service->onStop = 0) then
|
406
|
+
_dprint("pass control to onStop()")
|
407
|
+
service->onStop(*service)
|
408
|
+
end if
|
409
|
+
'# now signal the stop event so _main could take care of the rest.
|
410
|
+
_dprint("signal stop event")
|
411
|
+
SetEvent(service->_svcStopEvent)
|
412
|
+
|
413
|
+
case SERVICE_CONTROL_PAUSE:
|
414
|
+
_dprint("pause signal received")
|
415
|
+
'# we must check if we could answer to the request.
|
416
|
+
if not (service->onPause = 0) and _
|
417
|
+
not (service->onContinue = 0) then
|
418
|
+
|
419
|
+
'# just to be sure
|
420
|
+
if not (service->onPause = 0) then
|
421
|
+
service->UpdateState(SERVICE_PAUSE_PENDING)
|
422
|
+
|
423
|
+
_dprint("pass control to onPause()")
|
424
|
+
service->onPause(*service)
|
425
|
+
|
426
|
+
service->UpdateState(SERVICE_PAUSED)
|
427
|
+
_dprint("service paused")
|
428
|
+
end if
|
429
|
+
result = NO_ERROR
|
430
|
+
|
431
|
+
else
|
432
|
+
'# ok, our service didn't support pause or continue
|
433
|
+
'# tell the service manager about that!
|
434
|
+
result = ERROR_CALL_NOT_IMPLEMENTED
|
435
|
+
end if
|
436
|
+
|
437
|
+
case SERVICE_CONTROL_CONTINUE:
|
438
|
+
_dprint("continue signal received")
|
439
|
+
'# we should resume from a paused state
|
440
|
+
'# we must check if we could answer to the request.
|
441
|
+
if not (service->onPause = 0) and _
|
442
|
+
not (service->onContinue = 0) then
|
443
|
+
|
444
|
+
'# just to be sure
|
445
|
+
if not (service->onPause = 0) then
|
446
|
+
service->UpdateState(SERVICE_CONTINUE_PENDING)
|
447
|
+
|
448
|
+
_dprint("pass control to onContinue()")
|
449
|
+
service->onContinue(*service)
|
450
|
+
|
451
|
+
service->UpdateState(SERVICE_RUNNING)
|
452
|
+
_dprint("service running")
|
453
|
+
end if
|
454
|
+
result = NO_ERROR
|
455
|
+
|
456
|
+
else
|
457
|
+
'# ok, our service didn't support pause or continue
|
458
|
+
'# tell the service manager about that!
|
459
|
+
result = ERROR_CALL_NOT_IMPLEMENTED
|
460
|
+
end if
|
461
|
+
|
462
|
+
case else:
|
463
|
+
result = NO_ERROR
|
464
|
+
end select
|
465
|
+
|
466
|
+
_dprint("_control_ex() done")
|
467
|
+
return result
|
468
|
+
end function
|
469
|
+
|
470
|
+
|
471
|
+
'# add_to_references is a helper used to reduce code duplication (DRY).
|
472
|
+
'# here is used a lock around _svc_references to avoid two threads try change the
|
473
|
+
'# reference count (just in case).
|
474
|
+
function _add_to_references(byref service as ServiceProcess) as integer
|
475
|
+
_dprint("_add_to_references()")
|
476
|
+
|
477
|
+
'# get a lock before even think touch references!
|
478
|
+
mutexlock(_svc_references_lock)
|
479
|
+
|
480
|
+
'# now, reallocate space
|
481
|
+
_svc_references_count += 1
|
482
|
+
_svc_references = reallocate(_svc_references, sizeof(ServiceProcess ptr) * _svc_references_count)
|
483
|
+
|
484
|
+
'# put the reference of this service into the table
|
485
|
+
_svc_references[(_svc_references_count - 1)] = @service
|
486
|
+
|
487
|
+
'# ok, done, unlock our weapons! ;-)
|
488
|
+
mutexunlock(_svc_references_lock)
|
489
|
+
|
490
|
+
_dprint("_add_to_references() done")
|
491
|
+
'# return the new references count
|
492
|
+
return _svc_references_count
|
493
|
+
end function
|
494
|
+
|
495
|
+
|
496
|
+
'# find_in_references is used by _main to lookup for the specified service in
|
497
|
+
'# references table.
|
498
|
+
function _find_in_references(byref service_name as string) as ServiceProcess ptr
|
499
|
+
dim result as ServiceProcess ptr
|
500
|
+
dim item as ServiceProcess ptr
|
501
|
+
dim idx as integer
|
502
|
+
|
503
|
+
_dprint("_find_in_references()")
|
504
|
+
|
505
|
+
'# we start with a pesimistic idea ;-)
|
506
|
+
result = 0
|
507
|
+
|
508
|
+
for idx = 0 to (_svc_references_count - 1)
|
509
|
+
'# hold a reference to the item
|
510
|
+
item = _svc_references[idx]
|
511
|
+
|
512
|
+
'# compare if we have a match
|
513
|
+
if (service_name = item->name) then
|
514
|
+
result = item
|
515
|
+
exit for
|
516
|
+
end if
|
517
|
+
next idx
|
518
|
+
|
519
|
+
_dprint("_find_in_references() done")
|
520
|
+
'# return the found (or not) reference
|
521
|
+
return result
|
522
|
+
end function
|
523
|
+
|
524
|
+
|
525
|
+
'# namespace constructor
|
526
|
+
'# first we must create the mutex to be used with references
|
527
|
+
private sub _initialize() constructor
|
528
|
+
_dprint("_initialize() constructor")
|
529
|
+
'# we do this in case was already defined... don't know the situation,
|
530
|
+
'# just to be sure
|
531
|
+
if (_svc_references_lock = 0) then
|
532
|
+
_svc_references_lock = mutexcreate()
|
533
|
+
|
534
|
+
'# also initialize our count :-)
|
535
|
+
_svc_references_count = 0
|
536
|
+
end if
|
537
|
+
|
538
|
+
_dprint("_initialize() constructor done")
|
539
|
+
end sub
|
540
|
+
|
541
|
+
|
542
|
+
'# namespace destructor
|
543
|
+
private sub _terminate() destructor
|
544
|
+
_dprint("_terminate() destructor")
|
545
|
+
'# to avoid removing everything, we must lock to the references
|
546
|
+
mutexlock(_svc_references_lock)
|
547
|
+
|
548
|
+
'# destroy our refernces allocated memory!
|
549
|
+
deallocate(_svc_references)
|
550
|
+
|
551
|
+
'# unlock the mutex and destroy it too.
|
552
|
+
mutexunlock(_svc_references_lock)
|
553
|
+
mutexdestroy(_svc_references_lock)
|
554
|
+
|
555
|
+
_dprint("_terminate() destructor done")
|
556
|
+
end sub
|
557
|
+
|
558
|
+
|
559
|
+
'# command line builder (helper)
|
560
|
+
'# this is used to gather information about:
|
561
|
+
'# mode (if present)
|
562
|
+
'# valid service name (after lookup in the table)
|
563
|
+
'# command line to be passed to service
|
564
|
+
sub _build_commandline(byref mode as string, byref service_name as string, byref commandline as string)
|
565
|
+
dim result_mode as string
|
566
|
+
dim result_name as string
|
567
|
+
dim result_cmdline as string
|
568
|
+
dim service as ServiceProcess ptr
|
569
|
+
dim idx as integer
|
570
|
+
dim temp as string
|
571
|
+
|
572
|
+
idx = 1
|
573
|
+
'# first, determine if mode is pressent in commandline, must me command(1)
|
574
|
+
temp = lcase(command(idx))
|
575
|
+
|
576
|
+
if (temp = "console") or _
|
577
|
+
(temp = "manage") or _
|
578
|
+
(temp = "service") then
|
579
|
+
result_mode = temp
|
580
|
+
idx += 1
|
581
|
+
end if
|
582
|
+
|
583
|
+
'# now, check if service name is present
|
584
|
+
temp = command(idx)
|
585
|
+
|
586
|
+
'# its present?
|
587
|
+
if (len(temp) > 0) then
|
588
|
+
'# lookup in references table
|
589
|
+
service = _find_in_references(temp)
|
590
|
+
if not (service = 0) then
|
591
|
+
'# was found, so must be valid
|
592
|
+
result_name = temp
|
593
|
+
'# adjust start index for cmdline
|
594
|
+
idx += 1
|
595
|
+
end if
|
596
|
+
end if
|
597
|
+
|
598
|
+
'# is service valid?
|
599
|
+
'# its really needed?
|
600
|
+
if (service = 0) then
|
601
|
+
if (_svc_references_count = 1) then
|
602
|
+
'# no, get the first one
|
603
|
+
service = _svc_references[0]
|
604
|
+
result_name = service->name
|
605
|
+
'# adjust start index for cmdline
|
606
|
+
else
|
607
|
+
'# this is needed!
|
608
|
+
result_name = ""
|
609
|
+
end if
|
610
|
+
end if
|
611
|
+
|
612
|
+
result_cmdline = ""
|
613
|
+
|
614
|
+
temp = command(idx)
|
615
|
+
do while (len(temp) > 0)
|
616
|
+
if (instr(temp, chr(32)) > 0) then
|
617
|
+
'# properly quote parameters with spaces
|
618
|
+
result_cmdline += """" + temp + """"
|
619
|
+
else
|
620
|
+
result_cmdline += temp
|
621
|
+
end if
|
622
|
+
result_cmdline += " "
|
623
|
+
idx += 1
|
624
|
+
|
625
|
+
temp = command(idx)
|
626
|
+
loop
|
627
|
+
|
628
|
+
'# now, return the results
|
629
|
+
mode = result_mode
|
630
|
+
service_name = result_name
|
631
|
+
commandline = result_cmdline
|
632
|
+
end sub
|
633
|
+
|
634
|
+
|
635
|
+
'# ### DEBUG ###
|
636
|
+
'# just for debuging purposes
|
637
|
+
'# (will be removed in the future when Loggers get implemented)
|
638
|
+
#ifdef SERVICEFB_DEBUG_LOG
|
639
|
+
sub _dprint(byref message as string)
|
640
|
+
dim handle as integer
|
641
|
+
|
642
|
+
handle = freefile
|
643
|
+
open EXEPATH + "\servicefb.log" for append as #handle
|
644
|
+
|
645
|
+
print #handle, message
|
646
|
+
|
647
|
+
close #handle
|
648
|
+
end sub
|
649
|
+
#endif
|
650
|
+
end namespace '# fb.svc
|
651
|
+
end namespace '# fb
|