summaryrefslogtreecommitdiffstats
path: root/ldap/admin/src/scripts/ds-logpipe.py
diff options
context:
space:
mode:
Diffstat (limited to 'ldap/admin/src/scripts/ds-logpipe.py')
-rw-r--r--ldap/admin/src/scripts/ds-logpipe.py313
1 files changed, 313 insertions, 0 deletions
diff --git a/ldap/admin/src/scripts/ds-logpipe.py b/ldap/admin/src/scripts/ds-logpipe.py
new file mode 100644
index 00000000..5c605570
--- /dev/null
+++ b/ldap/admin/src/scripts/ds-logpipe.py
@@ -0,0 +1,313 @@
+#!/usr/bin/env python
+
+import sys
+import os, os.path
+import errno
+import signal
+import pprint
+import types
+import time
+import fcntl
+import pwd
+
+maxlines = 1000 # set on command line
+S_IFIFO = 0010000
+
+buffer = [] # default circular buffer used by default plugin
+totallines = 0
+logfname = "" # name of log pipe
+
+# default plugin just keeps a circular buffer
+def defaultplugin(line):
+ global totallines
+ buffer.append(line)
+ totallines = totallines + 1
+ if len(buffer) > maxlines:
+ del buffer[0]
+ return True
+
+def printbuffer():
+ sys.stdout.writelines(buffer)
+ print "Read %d total lines" % totallines
+ print logfname, "=" * 60
+
+def defaultpost(): printbuffer()
+
+plgfuncs = [] # list of plugin functions
+plgpostfuncs = [] # list of post plugin funcs
+
+def finish():
+ for postfunc in plgpostfuncs: postfunc()
+ sys.exit(0)
+
+def sighandler(signum, frame):
+ if signum != signal.SIGHUP: finish()
+ else: printbuffer()
+
+def isvalidpluginfile(plg):
+ return os.path.isfile(plg)
+
+def my_import(plgfile):
+ '''import plgfile as a python module and return
+ an error string if error - also return the prefunc if any'''
+ if not isvalidpluginfile(plgfile):
+ return ("%s is not a valid plugin filename" % plgfile, None, None)
+ # __import__ searches for the file in sys.path - we cannot
+ # __import__ a file by the full path
+ # __import__('basename') looks for basename.py in sys.path
+ (dir, fname) = os.path.split(plgfile)
+ base = os.path.splitext(fname)[0]
+ if not dir: dir = "."
+ sys.path.insert(0, dir) # put our path first so it will find our file
+ mod = __import__(base) # will throw exception if problem with python file
+ sys.path.pop(0) # remove our path
+
+ # check for the plugin functions
+ plgfunc = getattr(mod, 'plugin', None)
+ if not plgfunc:
+ return ('%s does not specify a plugin function' % plgfile, None, base)
+ if not isinstance(plgfunc, types.FunctionType):
+ return ('the symbol "plugin" in %s is not a function' % plgfile, None, base)
+ plgfuncs.append(plgfunc) # add to list in cmd line order
+
+ # check for 'post' func
+ plgpostfunc = getattr(mod, 'post', None)
+ if plgpostfunc:
+ if not isinstance(plgpostfunc, types.FunctionType):
+ return ('the symbol "post" in %s is not a function' % plgfile, None, base)
+ else:
+ plgpostfuncs.append(plgpostfunc) # add to list in cmd line order
+
+ prefunc = getattr(mod, 'pre', None)
+ # check for 'pre' func
+ if prefunc and not isinstance(prefunc, types.FunctionType):
+ return ('the symbol "pre" in %s is not a function' % plgfile, None, base)
+
+ return ('', prefunc, base)
+
+def parse_plugins(parser, options, args):
+ '''Each plugin in the plugins list may have additional
+ arguments, specified on the command line like this:
+ --plugin=foo.py foo.bar=1 foo.baz=2 ...
+ that is, each argument to plugin X will be specified as X.arg=value'''
+ if not options.plugins: return args
+
+ for plgfile in options.plugins:
+ (errstr, prefunc, base) = my_import(plgfile)
+ if errstr:
+ parser.error(errstr)
+ return args
+
+ # parse the arguments to the plugin given on the command line
+ bvals = {} # holds plugin args and values, if any
+ newargs = []
+ for arg in args:
+ if arg.startswith(base + '.'):
+ argval = arg.replace(base + '.', '')
+ (plgarg, plgval) = argval.split('=', 1) # split at first =
+ if not plgarg in bvals:
+ bvals[plgarg] = plgval
+ elif isinstance(bvals[plgarg],list):
+ bvals[plgarg].append(plgval)
+ else: # convert to list
+ bvals[plgarg] = [bvals[plgarg], plgval]
+ else:
+ newargs.append(arg)
+ if prefunc:
+ print 'Calling "pre" function in', plgfile
+ if not prefunc(bvals):
+ parser.error('the "pre" function in %s returned an error' % plgfile)
+ args = newargs
+
+ return args
+
+def open_pipe(logfname):
+ opencompleted = False
+ logf = None
+ while not opencompleted:
+ try:
+ logf = open(logfname, 'r') # blocks until there is some input
+ # set pipe to non-blocking
+# oldflags = fcntl.fcntl(logf, fcntl.F_GETFL)
+# fcntl.fcntl(logf, fcntl.F_SETFL, oldflags | os.O_NONBLOCK)
+ opencompleted = True
+ except IOError, e:
+ if e.errno == errno.EINTR:
+ continue # open was interrupted, try again
+ else: # hard error
+ raise Exception, "%s [%d]" % (e.strerror, e.errno)
+ return logf
+
+def get_server_pid(serverpidfile, servertimeout):
+ endtime = int(time.time()) + servertimeout
+ serverpid = 0
+ if serverpidfile:
+ line = None
+ try:
+ spfd = open(serverpidfile, 'r')
+ line = spfd.readline()
+ spfd.close()
+ except IOError, e:
+ if e.errno != errno.ENOENT: # may not exist yet - that's ok
+ raise Exception, "%s [%d]" % (e.strerror, e.errno)
+ if line:
+ serverpid = int(line)
+ return serverpid
+
+def is_server_alive(serverpid):
+ retval = False
+ try:
+ os.kill(serverpid, 0) # sig 0 is a "ping"
+ retval = True
+ except OSError, e:
+ pass # no such process, or EPERM/EACCES
+ return retval
+
+def read_and_process_line(logf, plgfuncs):
+ line = None
+ done = False
+ readcompleted = False
+ while not readcompleted:
+ try:
+ line = logf.readline()
+ readcompleted = True # read completed
+ except IOError, e:
+ if e.errno == errno.EINTR:
+ continue # read was interrupted, try again
+ else: # hard error
+ raise Exception, "%s [%d]" % (e.strerror, e.errno)
+ if line: # read something
+ for plgfunc in plgfuncs:
+ if not plgfunc(line):
+ print "Aborting processing due to function %s" % str(plgfunc)
+ finish()
+ done = True
+ break
+ else: # EOF
+ done = True
+ return done
+
+def parse_options():
+ from optparse import OptionParser
+ usage = "%prog <name of pipe> [options]"
+ parser = OptionParser(usage)
+ parser.add_option("-m", "--maxlines", dest="maxlines", type='int',
+ help="maximum number of lines to keep in the buffer", default=1000)
+ parser.add_option("-d", "--debug", dest="debug", action="store_true",
+ default=False, help="gather extra debugging information")
+ parser.add_option("-p", "--plugin", type='string', dest='plugins', action='append',
+ help='filename of a plugin to use with this log')
+ parser.add_option("-s", "--serverpidfile", type='string', dest='serverpidfile',
+ help='name of file containing the pid of the server to monitor')
+ parser.add_option("-t", "--servertimeout", dest="servertimeout", type='int',
+ help="timeout in seconds to wait for the serverpid to be alive", default=60)
+ parser.add_option("--serverpid", dest="serverpid", type='int',
+ help="process id of server to monitor", default=0)
+ parser.add_option("-u", "--user", type='string', dest='user',
+ help='name of user to set effective uid to')
+
+ options, args = parser.parse_args()
+
+ args = parse_plugins(parser, options, args)
+
+ if len(args) < 1:
+ parser.error("You must specify the name of the pipe to use")
+ if len(args) > 1:
+ parser.error("error - unhandled command line arguments: %s" % args.join(' '))
+
+ return options, args[0]
+
+options, logfname = parse_options()
+
+if len(plgfuncs) == 0:
+ plgfuncs.append(defaultplugin)
+if len(plgpostfuncs) == 0:
+ plgpostfuncs.append(defaultpost)
+
+if options.user:
+ try: userid = int(options.user)
+ except ValueError: # not a numeric userid - look it up
+ userid = pwd.getpwnam(options.user)[2]
+ os.seteuid(userid)
+
+serverpid = options.serverpid
+if serverpid:
+ if not is_server_alive(serverpid):
+ print "Server pid [%d] is not alive - exiting" % serverpid
+ sys.exit(1)
+
+try:
+ if os.stat(logfname).st_mode & S_IFIFO:
+ print "Using existing log pipe", logfname
+ else:
+ print "Error:", logfname, "exists and is not a log pipe"
+ print "use a filename other than", logfname
+ sys.exit(1)
+except OSError, e:
+ if e.errno == errno.ENOENT:
+ print "Creating log pipe", logfname
+ os.mkfifo(logfname)
+ os.chmod(logfname, 0600)
+ else:
+ raise Exception, "%s [%d]" % (e.strerror, e.errno)
+
+print "Listening to log pipe", logfname, "number of lines", maxlines
+
+# set up our signal handlers
+signal.signal(signal.SIGHUP, sighandler)
+signal.signal(signal.SIGINT, sighandler)
+#signal.signal(signal.SIGPIPE, sighandler)
+signal.signal(signal.SIGTERM, sighandler)
+signal.signal(signal.SIGALRM, sighandler)
+
+if options.serverpidfile:
+ # start the timer to wait for the pid file to be available
+ signal.alarm(options.servertimeout)
+
+done = False
+while not done:
+ # open the pipe - will hang until
+ # 1. something opens the other end
+ # 2. alarm goes off
+ logf = open_pipe(logfname)
+
+ if serverpid:
+ if not is_server_alive(serverpid):
+ print "Server pid [%d] is not alive - exiting" % serverpid
+ sys.exit(1)
+ else: # cancel the timer
+ signal.alarm(0) # cancel timer - got pid
+
+ innerdone = False
+ while not innerdone and not done:
+ # read and process the next line in the pipe
+ # if server exits while we are reading, we will get
+ # EOF and innerdone will be True
+ # we may have to read some number of lines until
+ # we can get the pid file
+ innerdone = read_and_process_line(logf, plgfuncs)
+ if not serverpid and options.serverpidfile:
+ serverpid = get_server_pid(options.serverpidfile, options.servertimeout)
+ if serverpid:
+ signal.alarm(0) # cancel timer - got pid
+
+ if logf:
+ logf.close()
+ logf = None
+
+ if not done:
+ if serverpid:
+ if not is_server_alive(serverpid):
+ print "Server pid [%d] is not alive - exiting" % serverpid
+ sys.exit(1)
+ else: # the server will sometimes close the log and reopen it
+ # however, at shutdown, the server will close the log before
+ # the process has exited - so is_server_alive will return
+ # true for a short time - if we then attempt to open the
+ # pipe, the open will hang forever - to avoid this situation
+ # we set the alarm again to wake up the open
+ signal.alarm(options.servertimeout)
+
+ print "log pipe", logfname, "closed - reopening"
+
+finish()