Merge "sflow" into "master".
[sliver-openvswitch.git] / xenserver / opt_xensource_libexec_interface-reconfigure
1 #!/usr/bin/python
2 #
3 # Copyright (c) 2008,2009 Citrix Systems, Inc.
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU Lesser General Public License as published
7 # by the Free Software Foundation; version 2.1 only. with the special
8 # exception on linking described in file LICENSE.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU Lesser General Public License for more details.
14 #
15 """Usage:
16
17     %(command-name)s <PIF> up
18     %(command-name)s <PIF> down
19     %(command-name)s rewrite
20     %(command-name)s --force <BRIDGE> up
21     %(command-name)s --force <BRIDGE> down
22     %(command-name)s --force <BRIDGE> rewrite --device=<INTERFACE> --mac=<MAC-ADDRESS> <CONFIG>
23
24     where <PIF> is one of:
25        --session <SESSION-REF> --pif <PIF-REF>
26        --pif-uuid <PIF-UUID>
27     and <CONFIG> is one of:
28        --mode=dhcp
29        --mode=static --ip=<IPADDR> --netmask=<NM> [--gateway=<GW>]
30
31   Options:
32     --session           A session reference to use to access the xapi DB
33     --pif               A PIF reference within the session.
34     --pif-uuid          The UUID of a PIF.
35     --force             An interface name.
36 """
37
38 # Notes:
39 # 1. Every pif belongs to exactly one network
40 # 2. Every network has zero or one pifs
41 # 3. A network may have an associated bridge, allowing vifs to be attached
42 # 4. A network may be bridgeless (there's no point having a bridge over a storage pif)
43
44 from InterfaceReconfigure import *
45
46 import os, sys, getopt
47 import syslog
48 import traceback
49 import re
50 import random
51
52 management_pif = None
53
54 dbcache_file = "/var/lib/openvswitch/dbcache"
55
56 #
57 # Logging.
58 #
59
60 def log_pif_action(action, pif):
61     pifrec = db().get_pif_record(pif)
62     rec = {}
63     rec['uuid'] = pifrec['uuid']
64     rec['ip_configuration_mode'] = pifrec['ip_configuration_mode']
65     rec['action'] = action
66     rec['pif_netdev_name'] = pif_netdev_name(pif)
67     rec['message'] = "Bring %(action)s PIF %(uuid)s" % rec
68     log("%(message)s: %(pif_netdev_name)s configured as %(ip_configuration_mode)s" % rec)
69
70 #
71 # Exceptions.
72 #
73
74 class Usage(Exception):
75     def __init__(self, msg):
76         Exception.__init__(self)
77         self.msg = msg
78
79 #
80 # Boot from Network filesystem or device.
81 #
82
83 def check_allowed(pif):
84     """Determine whether interface-reconfigure should be manipulating this PIF.
85
86     Used to prevent system PIFs (such as network root disk) from being interfered with.
87     """
88
89     pifrec = db().get_pif_record(pif)
90     try:
91         f = open("/proc/ardence")
92         macline = filter(lambda x: x.startswith("HWaddr:"), f.readlines())
93         f.close()
94         if len(macline) == 1:
95             p = re.compile(".*\s%(MAC)s\s.*" % pifrec, re.IGNORECASE)
96             if p.match(macline[0]):
97                 log("Skipping PVS device %(device)s (%(MAC)s)" % pifrec)
98                 return False
99     except IOError:
100         pass
101     return True
102
103 #
104 # Bare Network Devices -- network devices without IP configuration
105 #
106
107 def netdev_remap_name(pif, already_renamed=[]):
108     """Check whether 'pif' exists and has the correct MAC.
109     If not, try to find a device with the correct MAC and rename it.
110     'already_renamed' is used to avoid infinite recursion.
111     """
112
113     def read1(name):
114         file = None
115         try:
116             file = open(name, 'r')
117             return file.readline().rstrip('\n')
118         finally:
119             if file != None:
120                 file.close()
121
122     def get_netdev_mac(device):
123         try:
124             return read1("/sys/class/net/%s/address" % device)
125         except:
126             # Probably no such device.
127             return None
128
129     def get_netdev_tx_queue_len(device):
130         try:
131             return int(read1("/sys/class/net/%s/tx_queue_len" % device))
132         except:
133             # Probably no such device.
134             return None
135
136     def get_netdev_by_mac(mac):
137         for device in os.listdir("/sys/class/net"):
138             dev_mac = get_netdev_mac(device)
139             if (dev_mac and mac.lower() == dev_mac.lower() and
140                 get_netdev_tx_queue_len(device)):
141                 return device
142         return None
143
144     def rename_netdev(old_name, new_name):
145         log("Changing the name of %s to %s" % (old_name, new_name))
146         run_command(['/sbin/ifconfig', old_name, 'down'])
147         if not run_command(['/sbin/ip', 'link', 'set', old_name, 'name', new_name]):
148             raise Error("Could not rename %s to %s" % (old_name, new_name))
149
150     pifrec = db().get_pif_record(pif)
151     device = pifrec['device']
152     mac = pifrec['MAC']
153
154     # Is there a network device named 'device' at all?
155     device_exists = netdev_exists(device)
156     if device_exists:
157         # Yes.  Does it have MAC 'mac'?
158         found_mac = get_netdev_mac(device)
159         if found_mac and mac.lower() == found_mac.lower():
160             # Yes, everything checks out the way we want.  Nothing to do.
161             return
162     else:
163         log("No network device %s" % device)
164
165     # What device has MAC 'mac'?
166     cur_device = get_netdev_by_mac(mac)
167     if not cur_device:
168         log("No network device has MAC %s" % mac)
169         return
170
171     # First rename 'device', if it exists, to get it out of the way
172     # for 'cur_device' to replace it.
173     if device_exists:
174         rename_netdev(device, "dev%d" % random.getrandbits(24))
175
176     # Rename 'cur_device' to 'device'.
177     rename_netdev(cur_device, device)
178
179 #
180 # IP Network Devices -- network devices with IP configuration
181 #
182
183 def ifdown(netdev):
184     """Bring down a network interface"""
185     if not netdev_exists(netdev):
186         log("ifdown: device %s does not exist, ignoring" % netdev)
187         return
188     if not os.path.exists("/etc/sysconfig/network-scripts/ifcfg-%s" % netdev):
189         log("ifdown: device %s exists but ifcfg-%s does not" % (netdev,netdev))
190         run_command(["/sbin/ifconfig", netdev, 'down'])
191         return
192     run_command(["/sbin/ifdown", netdev])
193
194 def ifup(netdev):
195     """Bring up a network interface"""
196     if not os.path.exists("/etc/sysconfig/network-scripts/ifcfg-%s" % netdev):
197         raise Error("ifup: device %s exists but ifcfg-%s does not" % (netdev,netdev))
198     run_command(["/sbin/ifup", netdev])
199
200 #
201 #
202 #
203
204 def pif_rename_physical_devices(pif):
205
206     if pif_is_vlan(pif):
207         pif = pif_get_vlan_slave(pif)
208
209     if pif_is_bond(pif):
210         pifs = pif_get_bond_slaves(pif)
211     else:
212         pifs = [pif]
213
214     for pif in pifs:
215         netdev_remap_name(pif)
216
217 #
218 # IP device configuration
219 #
220
221 def ipdev_configure_static_routes(interface, oc, f):
222     """Open a route-<interface> file for static routes.
223
224     Opens the static routes configuration file for interface and writes one
225     line for each route specified in the network's other config "static-routes" value.
226     E.g. if
227            interface ( RO): xenbr1
228            other-config (MRW): static-routes: 172.16.0.0/15/192.168.0.3,172.18.0.0/16/192.168.0.4;...
229
230     Then route-xenbr1 should be
231           172.16.0.0/15 via 192.168.0.3 dev xenbr1
232           172.18.0.0/16 via 192.168.0.4 dev xenbr1
233     """
234     if oc.has_key('static-routes'):
235         # The key is present - extract comma seperates entries
236         lines = oc['static-routes'].split(',')
237     else:
238         # The key is not present, i.e. there are no static routes
239         lines = []
240
241     child = ConfigurationFile("/etc/sysconfig/network-scripts/route-%s" % interface)
242     child.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \
243             (os.path.basename(child.path()), os.path.basename(sys.argv[0])))
244
245     try:
246         for l in lines:
247             network, masklen, gateway = l.split('/')
248             child.write("%s/%s via %s dev %s\n" % (network, masklen, gateway, interface))
249
250         f.attach_child(child)
251         child.close()
252
253     except ValueError, e:
254         log("Error in other-config['static-routes'] format for network %s: %s" % (interface, e))
255
256 def ipdev_open_ifcfg(pif):
257     ipdev = pif_ipdev_name(pif)
258
259     log("Writing network configuration for %s" % ipdev)
260
261     f = ConfigurationFile("/etc/sysconfig/network-scripts/ifcfg-%s" % ipdev)
262
263     f.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \
264             (os.path.basename(f.path()), os.path.basename(sys.argv[0])))
265     f.write("XEMANAGED=yes\n")
266     f.write("DEVICE=%s\n" % ipdev)
267     f.write("ONBOOT=no\n")
268
269     return f
270
271 def ipdev_configure_network(pif, dp):
272     """Write the configuration file for a network.
273
274     Writes configuration derived from the network object into the relevant
275     ifcfg file.  The configuration file is passed in, but if the network is
276     bridgeless it will be ifcfg-<interface>, otherwise it will be ifcfg-<bridge>.
277
278     This routine may also write ifcfg files of the networks corresponding to other PIFs
279     in order to maintain consistency.
280
281     params:
282         pif:  Opaque_ref of pif
283         dp:   Datapath object
284     """
285
286     pifrec = db().get_pif_record(pif)
287     nwrec = db().get_network_record(pifrec['network'])
288
289     ipdev = pif_ipdev_name(pif)
290
291     f = ipdev_open_ifcfg(pif)
292
293     mode = pifrec['ip_configuration_mode']
294     log("Configuring %s using %s configuration" % (ipdev, mode))
295
296     oc = None
297     if pifrec.has_key('other_config'):
298         oc = pifrec['other_config']
299
300     dp.configure_ipdev(f)
301
302     if pifrec['ip_configuration_mode'] == "DHCP":
303         f.write("BOOTPROTO=dhcp\n")
304         f.write("PERSISTENT_DHCLIENT=yes\n")
305     elif pifrec['ip_configuration_mode'] == "Static":
306         f.write("BOOTPROTO=none\n")
307         f.write("NETMASK=%(netmask)s\n" % pifrec)
308         f.write("IPADDR=%(IP)s\n" % pifrec)
309         f.write("GATEWAY=%(gateway)s\n" % pifrec)
310     elif pifrec['ip_configuration_mode'] == "None":
311         f.write("BOOTPROTO=none\n")
312     else:
313         raise Error("Unknown ip-configuration-mode %s" % pifrec['ip_configuration_mode'])
314
315     if nwrec.has_key('other_config'):
316         settings,offload = ethtool_settings(nwrec['other_config'])
317         if len(settings):
318             f.write("ETHTOOL_OPTS=\"%s\"\n" % str.join(" ", settings))
319         if len(offload):
320             f.write("ETHTOOL_OFFLOAD_OPTS=\"%s\"\n" % str.join(" ", offload))
321
322         mtu = mtu_setting(nwrec['other_config'])
323         if mtu:
324             f.write("MTU=%s\n" % mtu)
325
326         ipdev_configure_static_routes(ipdev, nwrec['other_config'], f)
327
328     if pifrec.has_key('DNS') and pifrec['DNS'] != "":
329         ServerList = pifrec['DNS'].split(",")
330         for i in range(len(ServerList)): f.write("DNS%d=%s\n" % (i+1, ServerList[i]))
331     if oc and oc.has_key('domain'):
332         f.write("DOMAIN='%s'\n" % oc['domain'].replace(',', ' '))
333
334     # There can be only one DNSDEV and one GATEWAYDEV in /etc/sysconfig/network.
335     #
336     # The peerdns pif will be the one with
337     # pif::other-config:peerdns=true, or the mgmt pif if none have
338     # this set.
339     #
340     # The gateway pif will be the one with
341     # pif::other-config:defaultroute=true, or the mgmt pif if none
342     # have this set.
343
344     # Work out which pif on this host should be the DNSDEV and which
345     # should be the GATEWAYDEV
346     #
347     # Note: we prune out the bond master pif (if it exists). This is
348     # because when we are called to bring up an interface with a bond
349     # master, it is implicit that we should bring down that master.
350
351     pifs_on_host = [p for p in db().get_all_pifs() if not p in pif_get_bond_masters(pif)]
352
353     # loop through all the pifs on this host looking for one with
354     #   other-config:peerdns = true, and one with
355     #   other-config:default-route=true
356     peerdns_pif = None
357     defaultroute_pif = None
358     for __pif in pifs_on_host:
359         __pifrec = db().get_pif_record(__pif)
360         __oc = __pifrec['other_config']
361         if __oc.has_key('peerdns') and __oc['peerdns'] == 'true':
362             if peerdns_pif == None:
363                 peerdns_pif = __pif
364             else:
365                 log('Warning: multiple pifs with "peerdns=true" - choosing %s and ignoring %s' % \
366                         (db().get_pif_record(peerdns_pif)['device'], __pifrec['device']))
367         if __oc.has_key('defaultroute') and __oc['defaultroute'] == 'true':
368             if defaultroute_pif == None:
369                 defaultroute_pif = __pif
370             else:
371                 log('Warning: multiple pifs with "defaultroute=true" - choosing %s and ignoring %s' % \
372                         (db().get_pif_record(defaultroute_pif)['device'], __pifrec['device']))
373
374     # If no pif is explicitly specified then use the mgmt pif for
375     # peerdns/defaultroute.
376     if peerdns_pif == None:
377         peerdns_pif = management_pif
378     if defaultroute_pif == None:
379         defaultroute_pif = management_pif
380
381     is_dnsdev = peerdns_pif == pif
382     is_gatewaydev = defaultroute_pif == pif
383
384     if is_dnsdev or is_gatewaydev:
385         fnetwork = ConfigurationFile("/etc/sysconfig/network")
386         for line in fnetwork.readlines():
387             if is_dnsdev and line.lstrip().startswith('DNSDEV='):
388                 fnetwork.write('DNSDEV=%s\n' % ipdev)
389                 is_dnsdev = False
390             elif is_gatewaydev and line.lstrip().startswith('GATEWAYDEV='):
391                 fnetwork.write('GATEWAYDEV=%s\n' % ipdev)
392                 is_gatewaydev = False
393             else:
394                 fnetwork.write(line)
395
396         if is_dnsdev:
397             fnetwork.write('DNSDEV=%s\n' % ipdev)
398         if is_gatewaydev:
399             fnetwork.write('GATEWAYDEV=%s\n' % ipdev)
400
401         fnetwork.close()
402         f.attach_child(fnetwork)
403
404     return f
405
406 #
407 # Toplevel actions
408 #
409
410 def action_up(pif, force):
411     pifrec = db().get_pif_record(pif)
412
413     ipdev = pif_ipdev_name(pif)
414     dp = DatapathFactory(pif)
415
416     log("action_up: %s" % ipdev)
417
418     f = ipdev_configure_network(pif, dp)
419
420     dp.preconfigure(f)
421
422     f.close()
423
424     pif_rename_physical_devices(pif)
425
426     # if we are not forcing the interface up then attempt to tear down
427     # any existing devices which might interfere with brinign this one
428     # up.
429     if not force:
430         ifdown(ipdev)
431
432         dp.bring_down_existing()
433
434     try:
435         f.apply()
436
437         dp.configure()
438
439         ifup(ipdev)
440
441         dp.post()
442
443         # Update /etc/issue (which contains the IP address of the management interface)
444         os.system("/sbin/update-issue")
445
446         f.commit()
447     except Error, e:
448         log("failed to apply changes: %s" % e.msg)
449         f.revert()
450         raise
451
452 def action_down(pif):
453     ipdev = pif_ipdev_name(pif)
454     dp = DatapathFactory(pif)
455
456     log("action_down: %s" % ipdev)
457
458     ifdown(ipdev)
459
460     dp.bring_down()
461
462 # This is useful for reconfiguring the mgmt interface after having lost connectivity to the pool master
463 def action_force_rewrite(bridge, config):
464     def getUUID():
465         import subprocess
466         uuid,_ = subprocess.Popen(['uuidgen'], stdout = subprocess.PIPE).communicate()
467         return uuid.strip()
468
469     # Notes:
470     # 1. that this assumes the interface is bridged
471     # 2. If --gateway is given it will make that the default gateway for the host
472
473     # extract the configuration
474     try:
475         mode = config['mode']
476         mac = config['mac']
477         interface = config['device']
478     except:
479         raise Usage("Please supply --mode, --mac and --device")
480
481     if mode == 'static':
482         try:
483             netmask = config['netmask']
484             ip = config['ip']
485         except:
486             raise Usage("Please supply --netmask and --ip")
487         try:
488             gateway = config['gateway']
489         except:
490             gateway = None
491     elif mode != 'dhcp':
492         raise Usage("--mode must be either static or dhcp")
493
494     if config.has_key('vlan'):
495         is_vlan = True
496         vlan_slave, vlan_vid = config['vlan'].split('.')
497     else:
498         is_vlan = False
499
500     if is_vlan:
501         raise Error("Force rewrite of VLAN not implemented")
502
503     log("Configuring %s using %s configuration" % (bridge, mode))
504
505     f = ConfigurationFile(dbcache_file)
506
507     pif_uuid = getUUID()
508     network_uuid = getUUID()
509
510     f.write('<?xml version="1.0" ?>\n')
511     f.write('<xenserver-network-configuration>\n')
512     f.write('\t<pif ref="OpaqueRef:%s">\n' % pif_uuid)
513     f.write('\t\t<network>OpaqueRef:%s</network>\n' % network_uuid)
514     f.write('\t\t<management>True</management>\n')
515     f.write('\t\t<uuid>%sPif</uuid>\n' % interface)
516     f.write('\t\t<bond_slave_of>OpaqueRef:NULL</bond_slave_of>\n')
517     f.write('\t\t<bond_master_of/>\n')
518     f.write('\t\t<VLAN_slave_of/>\n')
519     f.write('\t\t<VLAN_master_of>OpaqueRef:NULL</VLAN_master_of>\n')
520     f.write('\t\t<VLAN>-1</VLAN>\n')
521     f.write('\t\t<device>%s</device>\n' % interface)
522     f.write('\t\t<MAC>%s</MAC>\n' % mac)
523     f.write('\t\t<other_config/>\n')
524     if mode == 'dhcp':
525         f.write('\t\t<ip_configuration_mode>DHCP</ip_configuration_mode>\n')
526         f.write('\t\t<IP></IP>\n')
527         f.write('\t\t<netmask></netmask>\n')
528         f.write('\t\t<gateway></gateway>\n')
529         f.write('\t\t<DNS></DNS>\n')
530     elif mode == 'static':
531         f.write('\t\t<ip_configuration_mode>Static</ip_configuration_mode>\n')
532         f.write('\t\t<IP>%s</IP>\n' % ip)
533         f.write('\t\t<netmask>%s</netmask>\n' % netmask)
534         if gateway is not None:
535             f.write('\t\t<gateway>%s</gateway>\n' % gateway)
536         f.write('\t\t<DNS></DNS>\n')
537     else:
538         raise Error("Unknown mode %s" % mode)
539     f.write('\t</pif>\n')
540
541     f.write('\t<network ref="OpaqueRef:%s">\n' % network_uuid)
542     f.write('\t\t<uuid>InitialManagementNetwork</uuid>\n')
543     f.write('\t\t<PIFs>\n')
544     f.write('\t\t\t<PIF>OpaqueRef:%s</PIF>\n' % pif_uuid)
545     f.write('\t\t</PIFs>\n')
546     f.write('\t\t<bridge>%s</bridge>\n' % bridge)
547     f.write('\t\t<other_config/>\n')
548     f.write('\t</network>\n')
549     f.write('</xenserver-network-configuration>\n')
550
551     f.close()
552
553     try:
554         f.apply()
555         f.commit()
556     except Error, e:
557         log("failed to apply changes: %s" % e.msg)
558         f.revert()
559         raise
560
561 def main(argv=None):
562     global management_pif
563
564     session = None
565     pif_uuid = None
566     pif = None
567
568     force_interface = None
569     force_management = False
570
571     if argv is None:
572         argv = sys.argv
573
574     try:
575         try:
576             shortops = "h"
577             longops = [ "pif=", "pif-uuid=",
578                         "session=",
579                         "force=",
580                         "force-interface=",
581                         "management",
582                         "mac=", "device=", "mode=", "ip=", "netmask=", "gateway=",
583                         "help" ]
584             arglist, args = getopt.gnu_getopt(argv[1:], shortops, longops)
585         except getopt.GetoptError, msg:
586             raise Usage(msg)
587
588         force_rewrite_config = {}
589
590         for o,a in arglist:
591             if o == "--pif":
592                 pif = a
593             elif o == "--pif-uuid":
594                 pif_uuid = a
595             elif o == "--session":
596                 session = a
597             elif o == "--force-interface" or o == "--force":
598                 force_interface = a
599             elif o == "--management":
600                 force_management = True
601             elif o in ["--mac", "--device", "--mode", "--ip", "--netmask", "--gateway"]:
602                 force_rewrite_config[o[2:]] = a
603             elif o == "-h" or o == "--help":
604                 print __doc__ % {'command-name': os.path.basename(argv[0])}
605                 return 0
606
607         syslog.openlog(os.path.basename(argv[0]))
608         log("Called as " + str.join(" ", argv))
609
610         if len(args) < 1:
611             raise Usage("Required option <action> not present")
612         if len(args) > 1:
613             raise Usage("Too many arguments")
614
615         action = args[0]
616
617         if not action in ["up", "down", "rewrite", "rewrite-configuration"]:
618             raise Usage("Unknown action \"%s\"" % action)
619
620         # backwards compatibility
621         if action == "rewrite-configuration": action = "rewrite"
622
623         if ( session or pif ) and pif_uuid:
624             raise Usage("--session/--pif and --pif-uuid are mutually exclusive.")
625         if ( session and not pif ) or ( not session and pif ):
626             raise Usage("--session and --pif must be used together.")
627         if force_interface and ( session or pif or pif_uuid ):
628             raise Usage("--force is mutually exclusive with --session, --pif and --pif-uuid")
629         if len(force_rewrite_config) and not (force_interface and action == "rewrite"):
630             raise Usage("\"--force rewrite\" needed for --device, --mode, --ip, --netmask, and --gateway")
631         if (action == "rewrite") and (pif or pif_uuid ):
632             raise Usage("rewrite action does not take --pif or --pif-uuid")
633         
634         global db
635         if force_interface:
636             log("Force interface %s %s" % (force_interface, action))
637
638             if action == "rewrite":
639                 action_force_rewrite(force_interface, force_rewrite_config)
640             elif action in ["up", "down"]:
641                 db_init_from_cache(dbcache_file)
642                 pif = db().get_pif_by_bridge(force_interface)
643                 management_pif = db().get_management_pif()
644
645                 if action == "up":
646                     action_up(pif, True)
647                 elif action == "down":
648                     action_down(pif)
649             else:
650                 raise Error("Unknown action %s"  % action)
651         else:
652             db_init_from_xenapi(session)
653
654             if pif_uuid:
655                 pif = db().get_pif_by_uuid(pif_uuid)
656
657             if action == "rewrite":
658                 pass
659             else:
660                 if not pif:
661                     raise Usage("No PIF given")
662
663                 if force_management:
664                     # pif is going to be the management pif
665                     management_pif = pif
666                 else:
667                     # pif is not going to be the management pif.
668                     # Search DB cache for pif on same host with management=true
669                     pifrec = db().get_pif_record(pif)
670                     management_pif = db().get_management_pif()
671
672                 log_pif_action(action, pif)
673
674                 if not check_allowed(pif):
675                     return 0
676
677                 if action == "up":
678                     action_up(pif, False)
679                 elif action == "down":
680                     action_down(pif)
681                 else:
682                     raise Error("Unknown action %s"  % action)
683
684             # Save cache.
685             db().save(dbcache_file)
686
687     except Usage, err:
688         print >>sys.stderr, err.msg
689         print >>sys.stderr, "For help use --help."
690         return 2
691     except Error, err:
692         log(err.msg)
693         return 1
694
695     return 0
696
697 if __name__ == "__main__":
698     rc = 1
699     try:
700         rc = main()
701     except:
702         ex = sys.exc_info()
703         err = traceback.format_exception(*ex)
704         for exline in err:
705             log(exline)
706
707     syslog.closelog()
708
709     sys.exit(rc)