thin_service 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1,3 @@
1
+ module ThinService
2
+ VERSION = "0.0.1"
3
+ end
Binary file
@@ -0,0 +1,7 @@
1
+ require 'rspec'
2
+ require 'thin_service'
3
+
4
+ RSpec.configure do |config|
5
+ config.color_enabled = true
6
+ config.formatter = 'documentation'
7
+ end
@@ -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