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