import mLib as M
import tripe as T
import dbus as D
+import re as RX
for i in ['mainloop', 'mainloop.glib']:
__import__('dbus.%s' % i)
try: from gi.repository import GLib as G
def __init__(me, **kw):
me.__dict__.update(kw)
-def toposort(cmp, things):
- """
- Generate the THINGS in an order consistent with a given partial order.
-
- The function CMP(X, Y) should return true if X must precede Y, and false if
- it doesn't care. If X and Y are equal then it should return false.
-
- The THINGS may be any finite iterable; it is converted to a list
- internally.
- """
-
- ## Make sure we can index the THINGS, and prepare an ordering table.
- ## What's going on? The THINGS might not have a helpful equality
- ## predicate, so it's easier to work with indices. The ordering table will
- ## remember which THINGS (by index) are considered greater than other
- ## things.
- things = list(things)
- n = len(things)
- order = [{} for i in xrange(n)]
- rorder = [{} for i in xrange(n)]
- for i in xrange(n):
- for j in xrange(n):
- if i != j and cmp(things[i], things[j]):
- order[j][i] = True
- rorder[i][j] = True
-
- ## Now we can do the sort.
- out = []
- while True:
- done = True
- for i in xrange(n):
- if order[i] is not None:
- done = False
- if len(order[i]) == 0:
- for j in rorder[i]:
- del order[j][i]
- yield things[i]
- order[i] = None
- if done:
- break
-
###--------------------------------------------------------------------------
### Address manipulation.
TESTADDR = InetAddress('1.2.3.4')
+CONFSYNTAX = [
+ ('COMMENT', RX.compile(r'^\s*($|[;#])')),
+ ('GRPHDR', RX.compile(r'^\s*\[(.*)\]\s*$')),
+ ('ASSGN', RX.compile(r'\s*([\w.-]+)\s*[:=]\s*(|\S|\S.*\S)\s*$'))]
+
+class ConfigError (Exception):
+ def __init__(me, file, lno, msg):
+ me.file = file
+ me.lno = lno
+ me.msg = msg
+ def __str__(me):
+ return '%s:%d: %s' % (me.file, me.lno, me.msg)
+
class Config (object):
"""
Represents a configuration file.
Internal function to update the configuration from the underlying file.
"""
- ## Read the configuration. We have no need of the fancy substitutions,
- ## so turn them all off.
- cp = RawConfigParser()
- cp.read(me._file)
if T._debug: print '# reread config'
- ## Save the test address. Make sure it's vaguely sensible. The default
- ## is probably good for most cases, in fact, since that address isn't
- ## actually in use. Note that we never send packets to the test address;
- ## we just use it to discover routing information.
- if cp.has_option('DEFAULT', 'test-addr'):
- testaddr = InetAddress(cp.get('DEFAULT', 'test-addr'))
- else:
- testaddr = TESTADDR
-
- ## Scan the configuration file and build the groups structure.
+ ## Initial state.
+ testaddr = None
groups = {}
- for sec in cp.sections():
- pats = []
- for tag in cp.options(sec):
- spec = cp.get(sec, tag).split()
-
- ## Parse the entry into peer and network.
- if len(spec) == 1:
- peer = None
- net = spec[0]
+ grpname = None
+ grplist = []
+
+ ## Open the file and start reading.
+ with open(me._file) as f:
+ lno = 0
+ for line in f:
+ lno += 1
+ for tag, rx in CONFSYNTAX:
+ m = rx.match(line)
+ if m: break
else:
- peer = InetAddress(spec[0])
- net = spec[1]
-
- ## Syntax of a net is ADDRESS/MASK, where ADDRESS is a dotted-quad,
- ## and MASK is either a dotted-quad or a single integer N indicating
- ## a mask with N leading ones followed by trailing zeroes.
- net = parse_net(net)
- pats.append((tag, peer, net))
-
- ## Annoyingly, RawConfigParser doesn't preserve the order of options.
- ## In order to make things vaguely sane, we topologically sort the
- ## patterns so that more specific patterns are checked first.
- pats = list(toposort(lambda (t, p, n), (tt, pp, nn): \
- (p and not pp) or \
- (p == pp and n.withinp(nn)),
- pats))
- groups[sec] = pats
+ raise ConfigError(me._file, lno, 'failed to parse line: %r' % line)
+
+ if tag == 'COMMENT':
+ ## A comment. Ignore it and hope it goes away.
+
+ continue
+
+ elif tag == 'GRPHDR':
+ ## A group header. Flush the old group and start a new one.
+ newname = m.group(1)
+
+ if grpname is not None: groups[grpname] = grplist
+ if newname in groups:
+ raise ConfigError(me._file, lno,
+ "duplicate group name `%s'" % newname)
+ grpname = newname
+ grplist = []
+
+ elif tag == 'ASSGN':
+ ## An assignment. Deal with it.
+ name, value = m.group(1), m.group(2)
+
+ if grpname is None:
+ ## We're outside of any group, so this is a global configuration
+ ## tweak.
+
+ if name == 'test-addr':
+ for astr in value.split():
+ try:
+ a = parse_address(astr)
+ except Exception, e:
+ raise ConfigError(me._file, lno,
+ "invalid IP address `%s': %s" %
+ (astr, e))
+ if testaddr is not None:
+ raise ConfigError(me._file, lno, 'duplicate test-address')
+ testaddr = a
+ else:
+ raise ConfigError(me._file, lno,
+ "unknown global option `%s'" % name)
+
+ else:
+ ## Parse a pattern and add it to the group.
+ spec = value.split()
+ i = 0
+
+ ## Check for an explicit target address.
+ if i >= len(spec) or spec[i].find('/') >= 0:
+ peer = None
+ else:
+ try:
+ peer = parse_address(spec[i])
+ except Exception, e:
+ raise ConfigError(me._file, lno,
+ "invalid IP address `%s': %s" %
+ (spec[i], e))
+ i += 1
+
+ ## Parse the local network.
+ if len(spec) != i + 1:
+ raise ConfigError(me._file, lno, 'no network defined')
+ try:
+ net = parse_net(spec[i])
+ except Exception, e:
+ raise ConfigError(me._file, lno,
+ "invalid IP network `%s': %s" %
+ (spec[i], e))
+
+ ## Add this entry to the list.
+ grplist.append((name, peer, net))
+
+ ## Fill in the default test address if necessary.
+ if testaddr is None: testaddr = TESTADDR
## Done.
+ if grpname is not None: groups[grpname] = grplist
me.testaddr = testaddr
me.groups = groups