File: ntservice.py

package info (click to toggle)
python-jtoolkit 0.7.8-2
  • links: PTS
  • area: main
  • in suites: etch, etch-m68k
  • size: 1,436 kB
  • ctags: 2,536
  • sloc: python: 15,143; makefile: 20
file content (318 lines) | stat: -rw-r--r-- 13,021 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""A class for handling a simplewebserver instance as a service"""

# Copyright 2005 St James Software
# 
# This file is part of jToolkit.
#
# jToolkit is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
# 
# jToolkit is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with jToolkit; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

import win32serviceutil, win32service
import win32evtlogutil, win32evtlog
from win32event import *
import pywintypes, winerror, win32api
import _winreg
import sys, os
import imp

from jToolkit.web import simplewebserver
from jToolkit import errors

status_messages = {
    win32service.SERVICE_STOPPED: "Stopped",
    win32service.SERVICE_STOP_PENDING: "Stopping",
    win32service.SERVICE_RUNNING: "Running",
    win32service.SERVICE_PAUSED: "Paused",
    win32service.SERVICE_START_PENDING: "Starting",
}

# We can't have a consoleerrorhandler in a service, so we change it for something that will log Events in the EventLog
class ServiceErrorHandler(errors.ConsoleErrorHandler):
  def __init__(self, errorsource="jToolkitWebServer"):
    self.errorfile = win32evtlog.EVENTLOG_ERROR_TYPE
    self.tracefile = win32evtlog.EVENTLOG_INFORMATION_TYPE
    self.auditfile = None
    self.instance = self
    self.errorsource = errorsource
    self.registersource()
  def registersource(self):
    # this tells it to use the win32evtlog.pyd library for reading error strings - string 1 is %s, so lets us store straight log messages
    win32evtlogutil.AddSourceToRegistry(self.errorsource, win32evtlog.__file__)
  def writelog(self, f, message):
    win32evtlogutil.ReportEvent(self.errorsource, 1, 0, f, [message], None, None)

class WebServerService(win32serviceutil.ServiceFramework):
  """The service that runs the server that Jack^Wserves the Hello World Pages"""
  # These two should be changed for each subclass
  _svc_name_ = "jToolkitWebServer"
  _svc_display_name_ = "jToolkit Web Server"
  def __init__(self, args):
    win32serviceutil.ServiceFramework.__init__(self, args)
    self.errorhandler = ServiceErrorHandler(self._svc_name_)
    self.webserver = None
    self.allowstart = True

  def SvcStop(self):
    self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
    if self.webserver is None:
      self.allowstart = False
      self.errorhandler.logerror("Stop before start, will not startup")
    else:
      self.webserver.setstop(True)

  def SvcDoRun(self):
    self.ReportServiceStatus(win32service.SERVICE_START_PENDING)
    parser = self.__class__.GetParserClass()(self.errorhandler)
    self.SetDefaultsOnParser(parser)
    cmdargs = self.GetCommandArgs()
    options, args = parser.parse_args(args=cmdargs)
    self.server = parser.getserver(options)
    self.webserver = self.server.webserver
    if self.allowstart:
      self.ReportServiceStatus(win32service.SERVICE_RUNNING)
      simplewebserver.run(self.server, options)
    else:
      self.errorhandler.logtrace("Stopping before startup")
    self.errorhandler.logtrace("Shutting down")

  def GetParserClass(cls):
    # Allow override of the parser we use
    # static, so we can get a parser without initialising the class
    return simplewebserver.WebOptionParser
  GetParserClass = classmethod(GetParserClass)

  def SetDefaultsOnParser(self, parser):
    """Override to set parser defaults for specific services"""
    pass

  def GetCommandArgs(self):
    # We pull default values from HKLM\SYSTEM\CurrentControlSet\Services\_svc_name_\Parameters
    try:
      OptionsKey = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Services\\%s" % self._svc_name_)
    except:
      # This probably means the key doesn't exist, in which case we add no default values
      return

    try:
      (value, valueType) = _winreg.QueryValueEx(OptionsKey, "Parameters")
    except EnvironmentError:
      return []
     
    _winreg.CloseKey(OptionsKey)
    args = value
    self.errorhandler.logtrace("service arguments: %s" % str(args))
    return args

import optparse
def CreateServiceOptionParser(parserclass):
  class ServiceOptionParser(parserclass):
    def __init__(self, errorhandler=None):
      parserclass.__init__(self, errorhandler)
      serviceoptions = optparse.OptionGroup(self, "Service options", "These options are used for controlling the service")
      serviceoptions.add_option("", "--serviceinstall", dest="install", action="store_true", default=False, help="Install the service")
      serviceoptions.add_option("", "--serviceremove", dest="remove", action="store_true", default=False, help="Remove the service")
      serviceoptions.add_option("", "--servicestart", dest="start", action="store_true", default=False, help="Start the service")
      serviceoptions.add_option("", "--servicerestart", dest="restart", action="store_true", default=False, help="Restart the service")
      serviceoptions.add_option("", "--servicestop", dest="stop", action="store_true", default=False, help="Stop the service")
      serviceoptions.add_option("", "--servicestatus", dest="status", action="store_true", default=False, help="Show the service status")
      serviceoptions.add_option("", "--servicedebug", dest="debug", action="store_true", default=False, help="Run service commands in debug mode")
      serviceoptions.add_option("", "--serviceuser", dest="user", action="store", default=None, help="Specify the user with which to run the service")
      serviceoptions.add_option("", "--servicepassword", dest="password", action="store", default=None, help="Specify the password to use to run the service")
      startuptypes = ["manual","auto","disabled"]
      serviceoptions.add_option("", "--servicestartup", dest="startup", type="choice", choices=startuptypes, default="manual", help="Specify the startup type for the service: "+ ", ".join(startuptypes))
      serviceoptions.add_option("", "--runservice", dest="runservice", action="store_true", default=False, help="Run this executable as a service (should only be run by the Services Control Manager in Windows")
      self.add_option_group(serviceoptions)
  
  return ServiceOptionParser

def RunService(service):
  import servicemanager
  # Events are sourced from the servicemanager
  evtsrc_dll = os.path.abspath(servicemanager.__file__)
  # Tell the servicemanager what our service class is
  servicemanager.Initialize(service._svc_name_, evtsrc_dll)
  servicemanager.PrepareToHostSingle(service)
  servicemanager.StartServiceCtrlDispatcher()

def check_if_frozen():
  return (hasattr(sys, "frozen") or # new py2exe
          hasattr(sys, "importers") # old py2exe
          or imp.is_frozen("__main__")) # tools/freeze   

def InstallService(service, options, script=None, nonserviceargs=None):
  # Rebuild the rest of the command-line
  if nonserviceargs is None:
    nonserviceargs = GetNonServiceArgs(sys.argv[1:])

  if options.startup == "manual":
    startType = win32service.SERVICE_DEMAND_START
  elif options.startup == "auto":
    startType = win32service.SERVICE_AUTO_START
  else:
    startType = win32service.SERVICE_DISABLED

  # Now, are we frozen or not?
  if check_if_frozen():
    # Use sys.executable as the executable name
    exeName = sys.executable + " --runservice"
  else:
      # Run using the current command-line except without
    if script is None:
      script = sys.argv[0]
    exeName = sys.executable + " " + os.path.abspath(script) + " --runservice"

  # Run the actual service install
  svc_display_name = getattr(service, "_svc_display_name_", service._svc_name_)
  svc_deps = getattr(service, "_svc_deps_", None)
  
  hscm = win32service.OpenSCManager(None,None,win32service.SC_MANAGER_ALL_ACCESS)
  try:
    hs = win32service.CreateService(hscm,
            service._svc_name_,
            svc_display_name,
            win32service.SERVICE_ALL_ACCESS,         # desired access
            win32service.SERVICE_WIN32_OWN_PROCESS,  # service type
            startType,
            win32service.SERVICE_ERROR_NORMAL,       # error control type
            exeName,
            None,
            0,
            svc_deps,
            options.user,
            options.password)
    win32service.CloseServiceHandle(hs)
  finally:
    win32service.CloseServiceHandle(hscm)
                                              
  # Write the command line to the registry
  try:
    ServiceKey = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Services\\%s" % service._svc_name_, 0, _winreg.KEY_READ | _winreg.KEY_WRITE)
  except EnvironmentError:
    print "Error attempting to store default parameters for service: Service is not installed"
    return
    
  _winreg.SetValueEx(ServiceKey, "Parameters", 0, _winreg.REG_MULTI_SZ, nonserviceargs)

def GetNonServiceArgs(args):
  """This function returns all command-line arguments not to do with services"""
  nonserviceargs = []
  usernameoption=False
  passwordoption=False
  startupoption=False
  for arg in args:
    if usernameoption and arg == options.user:    # If this is the username specified
      continue
    if passwordoption and arg == options.password:
      continue
    if startupoption and arg == options.startup:
      continue

    usernameoption = False
    passwordoption = False
    startupoption = False
    
    if arg[:9] == "--service":
      if "user" in arg:
        usernameoption = True
      if "password" in arg:
        passwordoption = True
      if "startup" in arg:
        startupoption = True
      continue
    nonserviceargs.append(arg)
  return nonserviceargs

def HandleCommandLine(service):
  # The object of this function is to wrap win32serviceutil.HandleCommandLine, 
  # so we can install command-line options for simplewebserver as well
  
  # Build a parser for the win32serviceutil stuff, for the commands install, remove, start, stop, restart and debug
  # with the extra options user, password and startup
  serviceerrorhandler = ServiceErrorHandler(service._svc_name_)
  errorhandler = errors.TeeErrorHandler(serviceerrorhandler)
  parser = CreateServiceOptionParser(service.GetParserClass())(errorhandler)
  options, args = parser.parse_args()

  if options.runservice:
    # Start this as a service
    RunService(service)
    return
  else:
    errorhandler.errorhandlers += (simplewebserver.consoleerrorhandler,)

  if options.debug:
    # FIXME: Not sure what the right thing to do here is
    return
  
  # Store the rest of the command line (only on install)
  if options.install:
    errorhandler.logtrace("Installing Service %s" % service._svc_name_)
    InstallService(service, options)
    serviceerrorhandler.registersource()
    return
  
  elif options.remove:
    errorhandler.logtrace("Removing Service %s" % service._svc_name_)
    win32serviceutil.RemoveService(service._svc_name_)
    return

  elif options.start:
    extraargs = GetNonServiceArgs(sys.argv[1:])
    errorhandler.logtrace("Starting Service %s with args %r" % (service._svc_name_, extraargs))
    win32serviceutil.StartService(service._svc_name_, extraargs)
    return
    
  elif options.restart:
    extraargs = GetNonServiceArgs(sys.argv[1:])
    errorhandler.logtrace("Restarting Service %s with args %r:" % (service._svc_name_, extraargs))
    win32serviceutil.StopService(service._svc_name_)

    servicestarted = False
    # We need to give it a few tries to restart
    for i in range(10):
      try:
        StartService(service._svc_name_, extraargs)
        servicestarted = True
        break
      except pywintypes.error, (hr, name, msg):
        if hr != winerror.ERROR_SERVICE_ALREADY_RUNNING:
          raise win32service.error, (hr, name, msg)
        win32api.Sleep(500)
    if not servicestarted:
      errorhandler.logerror("Unable to restart Service %s after %d tries" % (service._svc_name_, i))
    return
  
  elif options.stop:
    errorhandler.logtrace("Stopping Service %s" % service._svc_name_)
    win32serviceutil.StopService(service._svc_name_)
    return

  elif options.status:
    scm = win32service.OpenSCManager(None, None, win32service.SERVICE_QUERY_CONFIG)
    handle = win32service.OpenService(scm, service._svc_name_, win32service.SERVICE_ALL_ACCESS)
    status = win32service.QueryServiceStatus(handle)[1]
    status_message = status_messages.get(status, "Unknown status %d" % status)
    print "Service %s is %s" % (service._svc_name_, status_message)
    return

  else:
    parser.print_help()
    return

if __name__ == '__main__':
  HandleCommandLine(WebServerService)