start.py 9.52 KB
Newer Older
1
##############################################################################
2 3 4
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
Guido van Rossum's avatar
Guido van Rossum committed
5
#
6 7 8 9 10 11
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
Guido van Rossum's avatar
Guido van Rossum committed
12
#
13
##############################################################################
14
"""Start the ZEO storage server."""
Christopher Petrilli's avatar
Christopher Petrilli committed
15

16 17
import sys, os, getopt
import types
18 19
import errno
import socket
20
import ThreadedAsync.LoopCallback
Jim Fulton's avatar
Jim Fulton committed
21

22
def directory(p, n=1):
Florent Guillaume's avatar
Florent Guillaume committed
23
    d = os.path.abspath(p)
24
    while n:
25
        d = os.path.split(d)[0]
26
        if not d or d == '.':
27 28
            d = os.getcwd()
        n -= 1
Jim Fulton's avatar
Jim Fulton committed
29
    return d
Jim Fulton's avatar
Jim Fulton committed
30

31
def get_storage(m, n, cache={}):
32
    p = sys.path
Jim Fulton's avatar
Jim Fulton committed
33
    d, m = os.path.split(m)
34 35 36 37 38 39
    if m.endswith('.py'):
        m = m[:-3]
    im = cache.get((d, m))
    if im is None:
        if d:
            p = [d] + p
40
        import imp
41 42 43
        im = imp.find_module(m, p)
        im = imp.load_module(m, *im)
        cache[(d, m)] = im
Jim Fulton's avatar
Jim Fulton committed
44
    return getattr(im, n)
Jim Fulton's avatar
Jim Fulton committed
45

46
def set_uid(arg):
47 48 49 50 51 52 53
    """Try to set uid and gid based on -u argument.

    This will only work if this script is run by root.
    """
    try:
        import pwd
    except ImportError:
54
        LOG('ZEO/start.py', INFO, ("Can't set uid to %s."
55
                                "pwd module is not available." % arg))
56 57 58 59
        return
    try:
        gid = None
        try:
60
            arg = int(arg)
61
        except: # conversion could raise all sorts of errors
62 63
            uid = pwd.getpwnam(arg)[2]
            gid = pwd.getpwnam(arg)[3]
64
        else:
65 66
            uid = pwd.getpwuid(arg)[2]
            gid = pwd.getpwuid(arg)[3]
67 68 69 70 71 72 73 74 75 76
        if gid is not None:
            try:
                os.setgid(gid)
            except OSError:
                pass
        try:
            os.setuid(uid)
        except OSError:
            pass
    except KeyError:
77
        LOG('ZEO/start.py', ERROR, ("can't find uid %s" % arg))
78 79 80 81 82 83 84

def setup_signals(storages):
    try:
        import signal
    except ImportError:
        return

85 86 87 88 89 90 91
    if hasattr(signal, 'SIGXFSZ'):
        signal.signal(signal.SIGXFSZ, signal.SIG_IGN)
    if hasattr(signal, 'SIGTERM'):
        signal.signal(signal.SIGTERM, lambda sig, frame: shutdown(storages))
    if hasattr(signal, 'SIGHUP'):
        signal.signal(signal.SIGHUP, lambda sig, frame: shutdown(storages, 0))
    if hasattr(signal, 'SIGUSR2'):
Guido van Rossum's avatar
Guido van Rossum committed
92
        signal.signal(signal.SIGUSR2, rotate_logs_handler)
93

Jim Fulton's avatar
Jim Fulton committed
94
def main(argv):
95
    me = argv[0]
96
    sys.path.insert(0, directory(me, 2))
Jim Fulton's avatar
Jim Fulton committed
97

98
    global LOG, INFO, ERROR
99
    from zLOG import LOG, INFO, WARNING, ERROR, PANIC
100 101
    from ZEO.util import Environment
    env = Environment(me)
102

Jeremy Hylton's avatar
Jeremy Hylton committed
103
    # XXX hack for profiling support
104
    global unix, storages, asyncore
Jeremy Hylton's avatar
Jeremy Hylton committed
105

106 107
    args = []
    last = ''
Jim Fulton's avatar
Jim Fulton committed
108
    for a in argv[1:]:
109 110 111
        if (a[:1] != '-' and a.find('=') > 0 and last != '-S'): # lame, sorry
            a = a.split("=")
            os.environ[a[0]] = "=".join(a[1:])
Jim Fulton's avatar
Jim Fulton committed
112 113
            continue
        args.append(a)
114
        last = a
Jim Fulton's avatar
Jim Fulton committed
115

Jeremy Hylton's avatar
Jeremy Hylton committed
116
    usage="""%s [options] [filename]
Jim Fulton's avatar
Jim Fulton committed
117 118 119 120 121

    where options are:

       -D -- Run in debug mode

Barry Warsaw's avatar
Barry Warsaw committed
122
       -d -- Set STUPID_LOG_SEVERITY to -300
123

124
       -U -- Unix-domain socket file to listen on
Jeremy Hylton's avatar
Jeremy Hylton committed
125

126 127 128 129
       -u username or uid number

         The username to run the ZEO server as. You may want to run
         the ZEO server as 'nobody' or some other user with limited
Jeremy Hylton's avatar
Jeremy Hylton committed
130 131
         resouces. The only works under Unix, and if ZServer is
         started by root.
132

Jim Fulton's avatar
Jim Fulton committed
133
       -p port -- port to listen on
134

135
       -h address -- host address to listen on
Jim Fulton's avatar
Jim Fulton committed
136

137 138
       -s -- Don't use zdeamon

139 140 141 142 143 144 145 146 147 148
       -S storage_name=module_path:attr_name -- A storage specification

          where:

            storage_name -- is the storage name used in the ZEO protocol.
               This is the name that you give as the optional
               'storage' keyword argument to the ClientStorage constructor.

            module_path -- This is the path to a Python module
               that defines the storage object(s) to be served.
Barry Warsaw's avatar
Typo.  
Barry Warsaw committed
149
               The module path should omit the prefix (e.g. '.py').
150 151 152

            attr_name -- This is the name to which the storage object
              is assigned in the module.
Jim Fulton's avatar
Jim Fulton committed
153

Jeremy Hylton's avatar
Jeremy Hylton committed
154 155 156
       -P file -- Run under profile and dump output to file.  Implies the
          -s flag.

Jim Fulton's avatar
Jim Fulton committed
157
    if no file name is specified, then %s is used.
158
    """ % (me, env.fs)
Jim Fulton's avatar
Jim Fulton committed
159

160
    try:
Jeremy Hylton's avatar
Jeremy Hylton committed
161 162
        opts, args = getopt.getopt(args, 'p:Dh:U:sS:u:P:d')
    except getopt.error, msg:
163
        print usage
Jeremy Hylton's avatar
Jeremy Hylton committed
164
        print msg
165 166
        sys.exit(1)

Jeremy Hylton's avatar
Jeremy Hylton committed
167 168 169
    port = None
    debug = 0
    host = ''
170
    unix = None
Jeremy Hylton's avatar
Jeremy Hylton committed
171 172 173 174
    Z = 1
    UID = 'nobody'
    prof = None
    detailed = 0
175
    fs = None
Jim Fulton's avatar
Jim Fulton committed
176
    for o, v in opts:
177
        if o =='-p':
Jeremy Hylton's avatar
Jeremy Hylton committed
178
            port = int(v)
179
        elif o =='-h':
Jeremy Hylton's avatar
Jeremy Hylton committed
180
            host = v
181
        elif o =='-U':
Jeremy Hylton's avatar
Jeremy Hylton committed
182
            unix = v
183
        elif o =='-u':
Jeremy Hylton's avatar
Jeremy Hylton committed
184
            UID = v
185
        elif o =='-D':
Jeremy Hylton's avatar
Jeremy Hylton committed
186
            debug = 1
187
        elif o =='-d':
Jeremy Hylton's avatar
Jeremy Hylton committed
188
            detailed = 1
189
        elif o =='-s':
Jeremy Hylton's avatar
Jeremy Hylton committed
190
            Z = 0
191
        elif o =='-P':
Jeremy Hylton's avatar
Jeremy Hylton committed
192 193 194 195
            prof = v

    if prof:
        Z = 0
196 197

    if port is None and unix is None:
Jim Fulton's avatar
Jim Fulton committed
198 199 200 201 202 203 204
        print usage
        print 'No port specified.'
        sys.exit(1)

    if args:
        if len(args) > 1:
            print usage
205
            print 'Unrecognized arguments: ', " ".join(args[1:])
Jim Fulton's avatar
Jim Fulton committed
206
            sys.exit(1)
207
        fs = args[0]
Jim Fulton's avatar
Jim Fulton committed
208

Jeremy Hylton's avatar
Jeremy Hylton committed
209 210 211 212
    if debug:
        os.environ['Z_DEBUG_MODE'] = '1'
    if detailed:
        os.environ['STUPID_LOG_SEVERITY'] = '-300'
213
        rotate_logs() # reinitialize zLOG
214

215
    set_uid(UID)
Guido van Rossum's avatar
Guido van Rossum committed
216

217
    if Z:
218 219 220 221
        try:
            import posix
        except:
            pass
222
        else:
223 224
            import zdaemon.Daemon
            zdaemon.Daemon.run(sys.argv, env.zeo_pid)
Jim Fulton's avatar
Jim Fulton committed
225

226
    try:
Jim Fulton's avatar
Jim Fulton committed
227

228 229 230 231 232 233 234 235 236
        if Z:
            # Change current directory (for core dumps etc.)
            try:
                os.chdir(env.var)
            except os.error:
                LOG('ZEO/start.py', WARNING, "Couldn't chdir to %s" % env.var)
            else:
                LOG('ZEO/start.py', INFO, "Changed directory to %s" % env.var)

237
        import ZEO.StorageServer, asyncore
Guido van Rossum's avatar
Guido van Rossum committed
238

239
        storages = {}
240
        for o, v in opts:
241 242 243
            if o == '-S':
                n, m = v.split("=", 1)
                if m.find(":") >= 0:
244
                    # we got an attribute name
245
                    m, a = m.split(':')
246 247 248 249 250 251
                else:
                    # attribute name must be same as storage name
                    a=n
                storages[n]=get_storage(m,a)

        if not storages:
252 253
            from ZODB.FileStorage import FileStorage
            storages['1'] = FileStorage(fs or env.fs)
254 255

        # Try to set up a signal handler
256
        setup_signals(storages)
257

258
        items = storages.items()
259 260
        items.sort()
        for kv in items:
261
            LOG('ZEO/start.py', INFO, 'Serving %s:\t%s' % kv)
262

263 264
        if not unix:
            unix = host, port
265

266
        ZEO.StorageServer.StorageServer(unix, storages)
Guido van Rossum's avatar
Guido van Rossum committed
267

Guido van Rossum's avatar
Guido van Rossum committed
268 269 270 271 272 273 274 275 276
        if not Z:
            try:
                pid = os.getpid()
            except:
                pass # getpid not supported
            else:
                f = open(env.zeo_pid, 'w')
                f.write("%s\n" % pid)
                f.close()
Guido van Rossum's avatar
Guido van Rossum committed
277

278 279
    except:
        # Log startup exception and tell zdaemon not to restart us.
280
        info = sys.exc_info()
281
        try:
282
            LOG("ZEO/start.py", PANIC, "Startup exception", error=info)
283 284 285 286
        except:
            pass

        import traceback
287
        traceback.print_exception(*info)
Guido van Rossum's avatar
Guido van Rossum committed
288

289
        sys.exit(0)
Guido van Rossum's avatar
Guido van Rossum committed
290

291
    try:
292
        try:
293
            ThreadedAsync.LoopCallback.loop()
294 295 296
        finally:
            if os.path.isfile(env.zeo_pid):
                os.unlink(env.zeo_pid)
297 298 299 300 301
    except SystemExit:
        raise
    except:
        info = sys.exc_info()
        try:
302
            LOG("ZEO/start.py", PANIC, "Unexpected error", error=info)
303 304 305 306 307
        except:
            pass
        import traceback
        traceback.print_exception(*info)
        sys.exit(1)
308

309 310
def rotate_logs():
    import zLOG
311 312 313
    init = getattr(zLOG, 'initialize', None)
    if init is not None:
        init()
314 315 316 317 318 319 320 321 322 323 324 325
    else:
        # This will work if the minimal logger is in use, but not if some
        # other logger is active.  MinimalLogger exists only in Zopes
        # pre-2.7.
        try:
            import zLOG.MinimalLogger
            zLOG.MinimalLogger._log.initialize()
        except ImportError:
            zLOG.LOG("ZEO/start.py", zLOG.WARNING,
                     "Caught USR2, could not rotate logs")
            return
    zLOG.LOG("ZEO/start.py", zLOG.INFO, "Caught USR2, logs rotated")
326 327 328 329 330

def rotate_logs_handler(signum, frame):
    rotate_logs()

def shutdown(storages, die=1):
331
    LOG("ZEO/start.py", INFO, "Received signal")
332 333 334 335 336 337 338
    import asyncore

    # Do this twice, in case we got some more connections
    # while going through the loop.  This is really sort of
    # unnecessary, since we now use so_reuseaddr.
    for ignored in 1,2:
        for socket in asyncore.socket_map.values():
339 340 341 342
            try:
                socket.close()
            except:
                pass
343 344

    for storage in storages.values():
345 346 347 348
        try:
            storage.close()
        finally:
            pass
349 350

    try:
351
        s = die and "shutdown" or "restart"
352
        LOG('ZEO/start.py', INFO, "Shutting down (%s)" % s)
353 354
    except:
        pass
Jeremy Hylton's avatar
Jeremy Hylton committed
355

356 357 358 359
    if die:
        sys.exit(0)
    else:
        sys.exit(1)
360

361 362
if __name__=='__main__':
    main(sys.argv)