2018-01-27 23:46:39 +03:00
#!/usr/bin/env python3
2014-01-19 19:37:14 +04:00
#
2014-01-26 06:08:11 +04:00
# Copyright 2013-2014 Red Hat, Inc.
2014-01-19 19:37:14 +04:00
#
2018-04-04 16:35:41 +03:00
# This work is licensed under the GNU GPLv2 or later.
2018-03-20 22:00:02 +03:00
# See the COPYING file in the top-level directory.
2014-01-19 19:37:14 +04:00
import difflib
import logging
2017-12-14 20:02:36 +03:00
import re
2014-01-19 19:37:14 +04:00
import sys
import libvirt
import virtinst
from virtinst import cli
2019-06-08 01:21:24 +03:00
from virtinst import xmlutil
2014-01-19 19:37:14 +04:00
from virtinst.cli import fail, print_stdout, print_stderr
###################
# Utility helpers #
###################
def prompt_yes_or_no(msg):
while 1:
printmsg = msg + " (y/n): "
sys.stdout.write(printmsg)
sys.stdout.flush()
2019-06-14 19:10:00 +03:00
inp = sys.stdin.readline().lower().strip()
2015-04-23 00:35:39 +03:00
2014-01-19 19:37:14 +04:00
if inp in ["y", "yes"]:
return True
elif inp in ["n", "no"]:
return False
else:
print_stdout(_("Please enter 'yes' or 'no'."))
def get_diff(origxml, newxml):
ret = "".join(difflib.unified_diff(origxml.splitlines(1),
newxml.splitlines(1),
fromfile="Original XML",
tofile="Altered XML"))
if ret:
logging.debug("XML diff:\n%s", ret)
else:
logging.debug("No XML diff, didn't generate any change.")
return ret
2019-02-07 23:48:40 +03:00
def set_os_variant(options, guest):
if options.os_variant is None:
2018-11-09 18:36:40 +03:00
return
2019-02-08 00:26:04 +03:00
osdata = cli.parse_os_variant(options.os_variant)
2019-06-14 03:56:16 +03:00
if osdata.name:
guest.set_os_name(osdata.name)
2018-11-09 18:36:40 +03:00
2014-01-26 03:16:16 +04:00
def get_domain_and_guest(conn, domstr):
2014-01-19 19:37:14 +04:00
try:
int(domstr)
isint = True
except ValueError:
isint = False
2017-12-14 20:02:36 +03:00
uuidre = "[a-fA-F0-9]{8}[-]([a-fA-F0-9]{4}[-]){3}[a-fA-F0-9]{12}$"
isuuid = bool(re.match(uuidre, domstr))
2014-01-19 19:37:14 +04:00
try:
2019-03-21 20:34:52 +03:00
domain = None
try:
2014-01-19 19:37:14 +04:00
domain = conn.lookupByName(domstr)
2019-03-21 20:34:52 +03:00
except Exception:
2019-05-16 14:13:09 +03:00
# In case the VM has a UUID or ID for a name
2019-03-21 20:34:52 +03:00
logging.debug("Error looking up domain by name", exc_info=True)
if isint:
domain = conn.lookupByID(int(domstr))
elif isuuid:
domain = conn.lookupByUUIDString(domstr)
else:
raise
2017-05-05 19:47:21 +03:00
except libvirt.libvirtError as e:
2014-01-19 19:37:14 +04:00
fail(_("Could not find domain '%s': %s") % (domstr, e))
2014-01-26 02:18:32 +04:00
state = domain.info()[0]
active_xmlobj = None
2018-02-23 02:47:49 +03:00
inactive_xmlobj = virtinst.Guest(conn, parsexml=domain.XMLDesc(0))
2014-01-26 02:18:32 +04:00
if state != libvirt.VIR_DOMAIN_SHUTOFF:
active_xmlobj = inactive_xmlobj
2018-02-23 02:47:49 +03:00
inactive_xmlobj = virtinst.Guest(conn,
parsexml=domain.XMLDesc(libvirt.VIR_DOMAIN_XML_INACTIVE))
2014-01-19 19:37:14 +04:00
2014-01-26 02:18:32 +04:00
return (domain, inactive_xmlobj, active_xmlobj)
2014-01-19 19:37:14 +04:00
################
# Change logic #
################
2016-06-14 14:37:21 +03:00
def _find_objects_to_edit(guest, action_name, editval, parserclass):
2019-06-08 01:21:24 +03:00
objlist = xmlutil.listify(parserclass.lookup_prop(guest))
2014-01-19 19:37:14 +04:00
idx = None
2014-01-26 02:06:31 +04:00
if editval is None:
2014-01-19 19:37:14 +04:00
idx = 1
2014-01-26 02:06:31 +04:00
elif (editval.isdigit() or
editval.startswith("-") and editval[1:].isdigit()):
idx = int(editval)
2014-01-19 19:37:14 +04:00
if idx is not None:
2015-09-04 23:07:01 +03:00
# Edit device by index
2014-01-19 19:37:14 +04:00
if idx == 0:
2014-01-26 02:06:31 +04:00
fail(_("Invalid --edit option '%s'") % editval)
2014-01-19 19:37:14 +04:00
2015-09-04 23:07:01 +03:00
if not objlist:
fail(_("No --%s objects found in the XML") %
2016-06-14 14:37:21 +03:00
parserclass.cli_arg_name)
2015-09-04 23:07:01 +03:00
if len(objlist) < abs(idx):
2014-01-19 19:37:14 +04:00
fail(_("--edit %s requested but there's only %s "
2015-09-04 23:07:01 +03:00
"--%s object in the XML") %
2016-06-14 14:37:21 +03:00
(idx, len(objlist), parserclass.cli_arg_name))
2014-01-19 19:37:14 +04:00
if idx > 0:
idx -= 1
2015-09-04 23:07:01 +03:00
inst = objlist[idx]
2014-01-26 02:06:31 +04:00
elif editval == "all":
2015-09-04 23:07:01 +03:00
# Edit 'all' devices
inst = objlist[:]
2014-01-19 19:37:14 +04:00
else:
2015-09-04 23:07:01 +03:00
# Lookup device by the passed prop string
2019-05-11 00:20:21 +03:00
parserobj = parserclass(editval, guest=guest)
2016-06-14 14:37:21 +03:00
inst = parserobj.lookup_child_from_option_string()
2014-01-19 19:37:14 +04:00
if not inst:
2015-09-04 23:07:01 +03:00
fail(_("No matching objects found for --%s %s") %
2014-01-26 02:06:31 +04:00
(action_name, editval))
2014-01-19 19:37:14 +04:00
return inst
2014-01-26 02:06:31 +04:00
def check_action_collision(options):
2014-01-26 05:14:42 +04:00
actions = ["edit", "add-device", "remove-device", "build-xml"]
2014-01-19 19:37:14 +04:00
2014-01-26 02:06:31 +04:00
collisions = []
for cliname in actions:
optname = cliname.replace("-", "_")
if getattr(options, optname) not in [False, -1]:
collisions.append(cliname)
if len(collisions) == 0:
fail(_("One of %s must be specified.") %
", ".join(["--" + c for c in actions]))
if len(collisions) > 1:
fail(_("Conflicting options %s") %
", ".join(["--" + c for c in collisions]))
2016-06-14 14:37:21 +03:00
def check_xmlopt_collision(options):
2014-01-19 19:37:14 +04:00
collisions = []
2016-06-14 14:37:21 +03:00
for parserclass in cli.VIRT_PARSERS:
if getattr(options, parserclass.cli_arg_name):
collisions.append(parserclass)
2014-01-19 19:37:14 +04:00
if len(collisions) == 0:
fail(_("No change specified."))
if len(collisions) != 1:
fail(_("Only one change operation may be specified "
"(conflicting options %s)") %
2019-01-14 12:21:33 +03:00
[c.cli_flag_name() for c in collisions])
2014-01-19 19:37:14 +04:00
2014-01-26 02:06:31 +04:00
return collisions[0]
2016-06-14 14:37:21 +03:00
def action_edit(guest, options, parserclass):
2019-05-10 20:03:27 +03:00
if parserclass.guest_propname:
2016-06-14 14:37:21 +03:00
inst = _find_objects_to_edit(guest, "edit", options.edit, parserclass)
2014-01-19 19:37:14 +04:00
else:
inst = guest
if options.edit and options.edit != '1' and options.edit != 'all':
fail(_("'--edit %s' doesn't make sense with --%s, "
"just use empty '--edit'") %
2016-06-14 14:37:21 +03:00
(options.edit, parserclass.cli_arg_name))
2019-02-07 23:48:40 +03:00
if options.os_variant is not None:
2018-11-09 18:36:40 +03:00
fail(_("--os-variant is not supported with --edit"))
2014-01-19 19:37:14 +04:00
2019-06-14 00:37:26 +03:00
return cli.parse_option_strings(options, guest, inst, editing=True)
2014-01-19 19:37:14 +04:00
2016-06-14 14:37:21 +03:00
def action_add_device(guest, options, parserclass):
2018-03-21 02:38:18 +03:00
if not parserclass.prop_is_list(guest):
2016-06-14 14:37:21 +03:00
fail(_("Cannot use --add-device with --%s") % parserclass.cli_arg_name)
2019-02-07 23:48:40 +03:00
set_os_variant(options, guest)
2018-09-02 00:50:19 +03:00
devs = cli.parse_option_strings(options, guest, None)
2019-06-08 01:21:24 +03:00
devs = xmlutil.listify(devs)
2018-09-02 00:50:19 +03:00
for dev in devs:
dev.set_defaults(guest)
return devs
2014-01-26 02:06:31 +04:00
2016-06-14 14:37:21 +03:00
def action_remove_device(guest, options, parserclass):
2018-03-21 02:38:18 +03:00
if not parserclass.prop_is_list(guest):
2014-01-26 02:06:31 +04:00
fail(_("Cannot use --remove-device with --%s") %
2016-06-14 14:37:21 +03:00
parserclass.cli_arg_name)
2019-02-07 23:48:40 +03:00
if options.os_variant is not None:
2018-11-09 18:36:40 +03:00
fail(_("--os-variant is not supported with --remove-device"))
2014-01-26 02:06:31 +04:00
2015-09-04 23:07:01 +03:00
devs = _find_objects_to_edit(guest, "remove-device",
2016-06-14 14:37:21 +03:00
getattr(options, parserclass.cli_arg_name)[-1], parserclass)
2014-01-26 02:06:31 +04:00
2019-06-08 01:21:24 +03:00
devs = xmlutil.listify(devs)
2017-02-14 03:42:38 +03:00
for dev in devs:
2014-01-26 02:06:31 +04:00
guest.remove_device(dev)
2014-01-26 04:51:56 +04:00
return devs
2016-06-14 14:37:21 +03:00
def action_build_xml(conn, options, parserclass):
2019-05-10 20:03:27 +03:00
if not parserclass.guest_propname:
2016-06-14 14:37:21 +03:00
fail(_("--build-xml not supported for --%s") %
parserclass.cli_arg_name)
2019-02-07 23:48:40 +03:00
if options.os_variant is not None:
2018-11-09 18:36:40 +03:00
fail(_("--os-variant is not supported with --build-xml"))
2014-01-26 05:14:42 +04:00
2018-03-21 12:32:36 +03:00
guest = virtinst.Guest(conn)
2018-03-21 02:38:18 +03:00
inst = parserclass.lookup_prop(guest)
if parserclass.prop_is_list(guest):
inst = inst.new()
else:
inst = inst.__class__(conn)
2018-09-02 00:50:19 +03:00
devs = cli.parse_option_strings(options, guest, inst)
2019-06-08 01:21:24 +03:00
devs = xmlutil.listify(devs)
2018-09-02 00:50:19 +03:00
for dev in devs:
dev.set_defaults(guest)
return devs
2014-01-26 05:14:42 +04:00
2014-01-26 06:08:11 +04:00
def setup_device(dev):
2018-03-21 01:59:14 +03:00
if getattr(dev, "DEVICE_TYPE", None) != "disk":
2014-01-26 06:08:11 +04:00
return
logging.debug("Doing setup for disk=%s", dev)
2018-09-03 23:44:38 +03:00
dev.build_storage(cli.get_meter())
2014-01-26 06:08:11 +04:00
def define_changes(conn, inactive_xmlobj, devs, action, confirm):
2014-01-26 04:51:56 +04:00
if confirm:
if not prompt_yes_or_no(
2017-09-20 10:36:27 +03:00
_("Define '%s' with the changed XML?") % inactive_xmlobj.name):
2017-03-07 05:32:32 +03:00
return False
2014-01-26 04:51:56 +04:00
2014-01-26 06:08:11 +04:00
if action == "hotplug":
for dev in devs:
setup_device(dev)
2019-02-26 12:56:43 +03:00
dom = conn.defineXML(inactive_xmlobj.get_xml())
2015-06-02 15:21:58 +03:00
print_stdout(_("Domain '%s' defined successfully.") % inactive_xmlobj.name)
2019-02-26 12:56:43 +03:00
return dom
def start_domain_transient(conn, xmlobj, devs, action, confirm):
if confirm:
if not prompt_yes_or_no(
_("Start '%s' with the changed XML?") % xmlobj.name):
return False
if action == "hotplug":
for dev in devs:
setup_device(dev)
try:
dom = conn.createXML(xmlobj.get_xml())
except libvirt.libvirtError as e:
fail(_("Failed starting domain '%s': %s") % (xmlobj.name, e))
else:
print_stdout(_("Domain '%s' started successfully.") % xmlobj.name)
return dom
2014-01-26 04:51:56 +04:00
def update_changes(domain, devs, action, confirm):
for dev in devs:
2018-08-31 23:52:02 +03:00
xml = dev.get_xml()
2014-01-26 04:51:56 +04:00
if confirm:
2014-09-17 20:20:58 +04:00
if action == "hotplug":
prep = "to"
elif action == "hotunplug":
prep = "from"
else:
prep = "for"
msg = ("%s\n\n%s this device %s guest '%s'?" %
(xml, action.capitalize(), prep, domain.name()))
2014-01-26 04:51:56 +04:00
if not prompt_yes_or_no(msg):
continue
2014-01-26 06:08:11 +04:00
if action == "hotplug":
setup_device(dev)
2014-01-26 04:51:56 +04:00
try:
if action == "hotplug":
domain.attachDeviceFlags(xml, libvirt.VIR_DOMAIN_AFFECT_LIVE)
elif action == "hotunplug":
domain.detachDeviceFlags(xml, libvirt.VIR_DOMAIN_AFFECT_LIVE)
elif action == "update":
domain.updateDeviceFlags(xml, libvirt.VIR_DOMAIN_AFFECT_LIVE)
2017-05-05 19:47:21 +03:00
except libvirt.libvirtError as e:
2014-01-26 04:51:56 +04:00
fail(_("Error attempting device %s: %s") % (action, e))
2019-06-14 19:10:00 +03:00
# Test driver doesn't support device hotplug so we can't reach this
print_stdout(_("Device %s successful.") % action) # pragma: no cover
if confirm: # pragma: no cover
2014-09-17 20:20:58 +04:00
print_stdout("")
2014-01-26 02:06:31 +04:00
2016-06-14 14:37:21 +03:00
def prepare_changes(xmlobj, options, parserclass):
2018-08-31 23:52:02 +03:00
origxml = xmlobj.get_xml()
2015-07-02 15:09:46 +03:00
if options.edit != -1:
2016-06-14 14:37:21 +03:00
devs = action_edit(xmlobj, options, parserclass)
2015-07-02 15:09:46 +03:00
action = "update"
elif options.add_device:
2016-06-14 14:37:21 +03:00
devs = action_add_device(xmlobj, options, parserclass)
2015-07-02 15:09:46 +03:00
action = "hotplug"
elif options.remove_device:
2016-06-14 14:37:21 +03:00
devs = action_remove_device(xmlobj, options, parserclass)
2015-07-02 15:09:46 +03:00
action = "hotunplug"
2018-08-31 23:52:02 +03:00
newxml = xmlobj.get_xml()
2015-07-02 15:09:46 +03:00
diff = get_diff(origxml, newxml)
if options.print_diff:
if diff:
print_stdout(diff)
elif options.print_xml:
print_stdout(newxml)
return devs, action
2014-01-19 19:37:14 +04:00
#######################
# CLI option handling #
#######################
def parse_args():
parser = cli.setupParser(
"%(prog)s [options]",
_("Edit libvirt XML using command line options."),
introspection_epilog=True)
2014-02-12 18:57:40 +04:00
cli.add_connect_option(parser, "virt-xml")
2014-01-19 19:37:14 +04:00
2014-01-26 04:51:56 +04:00
parser.add_argument("domain", nargs='?',
2014-01-26 03:16:16 +04:00
help=_("Domain name, id, or uuid"))
2014-01-26 04:51:56 +04:00
2014-01-26 21:25:18 +04:00
actg = parser.add_argument_group(_("XML actions"))
2014-01-19 19:37:14 +04:00
actg.add_argument("--edit", nargs='?', default=-1,
2014-01-26 02:06:31 +04:00
help=_("Edit VM XML. Examples:\n"
"--edit --disk ... (edit first disk device)\n"
"--edit 2 --disk ... (edit second disk device)\n"
"--edit all --disk ... (edit all disk devices)\n"
"--edit target=hda --disk ... (edit disk 'hda')\n"))
actg.add_argument("--remove-device", action="store_true",
help=_("Remove specified device. Examples:\n"
"--remove-device --disk 1 (remove first disk)\n"
"--remove-device --disk all (remove all disks)\n"
"--remove-device --disk /some/path"))
actg.add_argument("--add-device", action="store_true",
help=_("Add specified device. Example:\n"
"--add-device --disk ..."))
2014-01-26 05:14:42 +04:00
actg.add_argument("--build-xml", action="store_true",
help=_("Just output the built device XML, no domain required."))
2014-01-26 21:25:18 +04:00
outg = parser.add_argument_group(_("Output options"))
outg.add_argument("--update", action="store_true",
2014-01-26 04:51:56 +04:00
help=_("Apply changes to the running VM.\n"
"With --add-device, this is a hotplug operation.\n"
"With --remove-device, this is a hotunplug operation.\n"
"With --edit, this is an update device operation."))
2019-02-26 12:56:41 +03:00
define_g = outg.add_mutually_exclusive_group()
define_g.add_argument("--define", action="store_true",
help=_("Force defining the domain. Only required if a --print "
"option was specified."))
define_g.add_argument("--no-define", dest='define', action="store_false",
help=_("Force not defining the domain."))
define_g.set_defaults(define=None)
2019-02-26 12:56:43 +03:00
outg.add_argument("--start", action="store_true",
help=_("Start the domain."))
2014-01-26 21:25:18 +04:00
outg.add_argument("--print-diff", action="store_true",
2014-01-26 04:51:56 +04:00
help=_("Only print the requested change, in diff format"))
2014-01-26 21:25:18 +04:00
outg.add_argument("--print-xml", action="store_true",
2014-01-26 04:51:56 +04:00
help=_("Only print the requested change, in full XML format"))
2014-01-26 21:25:18 +04:00
outg.add_argument("--confirm", action="store_true",
2014-01-26 04:51:56 +04:00
help=_("Require confirmation before saving any results."))
2014-01-19 19:37:14 +04:00
2019-01-08 20:44:53 +03:00
cli.add_os_variant_option(parser, virtinstall=False)
2018-11-09 18:36:38 +03:00
2014-01-19 19:37:14 +04:00
g = parser.add_argument_group(_("XML options"))
2014-01-26 21:25:18 +04:00
cli.add_disk_option(g, editexample=True)
2014-01-19 19:37:14 +04:00
cli.add_net_option(g)
cli.add_gfx_option(g)
2014-01-25 05:03:30 +04:00
cli.add_metadata_option(g)
2014-01-25 03:56:59 +04:00
cli.add_memory_option(g)
2014-01-26 05:20:55 +04:00
cli.vcpu_cli_options(g, editexample=True)
2014-01-19 19:37:14 +04:00
cli.add_guest_xml_options(g)
2014-02-11 03:13:42 +04:00
cli.add_boot_options(g)
2014-01-19 19:37:14 +04:00
cli.add_device_options(g)
misc = parser.add_argument_group(_("Miscellaneous Options"))
cli.add_misc_options(misc, prompt=False, printxml=False, dryrun=False)
2018-12-07 11:28:51 +03:00
cli.autocomplete(parser)
2014-01-19 19:37:14 +04:00
return parser.parse_args()
###################
# main() handling #
###################
def main(conn=None):
cli.earlyLogging()
options = parse_args()
2014-01-26 05:14:42 +04:00
if (options.confirm or options.print_xml or
options.print_diff or options.build_xml):
2014-01-19 19:37:14 +04:00
options.quiet = False
cli.setupLogging("virt-xml", options.debug, options.quiet)
2019-02-26 12:56:43 +03:00
if options.update and options.start:
fail(_("Either update or start a domain"))
2016-06-14 14:37:21 +03:00
if cli.check_option_introspection(options):
2014-01-19 19:37:14 +04:00
return 0
2014-01-26 03:16:16 +04:00
options.stdinxml = None
2014-01-26 05:14:42 +04:00
if not options.domain and not options.build_xml:
if not sys.stdin.closed and not sys.stdin.isatty():
if options.confirm:
fail(_("Can't use --confirm with stdin input."))
if options.update:
fail(_("Can't use --update with stdin input."))
options.stdinxml = sys.stdin.read()
else:
fail(_("A domain must be specified"))
2014-01-26 03:16:16 +04:00
if not options.print_xml and not options.print_diff:
if options.stdinxml:
if not options.define:
options.print_xml = True
else:
2019-02-26 12:56:41 +03:00
if options.define is None:
options.define = True
2014-01-26 03:16:16 +04:00
if options.confirm and not options.print_xml:
options.print_diff = True
2019-06-11 01:13:31 +03:00
conn = cli.getConnection(options.connect, conn)
2014-01-19 19:37:14 +04:00
2014-01-26 05:14:42 +04:00
domain = None
active_xmlobj = None
inactive_xmlobj = None
2014-01-26 03:16:16 +04:00
if options.domain:
domain, inactive_xmlobj, active_xmlobj = get_domain_and_guest(
conn, options.domain)
2014-01-26 05:14:42 +04:00
elif not options.build_xml:
2018-02-23 02:47:49 +03:00
inactive_xmlobj = virtinst.Guest(conn, options.stdinxml)
2014-01-26 03:16:16 +04:00
2014-01-26 02:06:31 +04:00
check_action_collision(options)
2016-06-14 14:37:21 +03:00
parserclass = check_xmlopt_collision(options)
2014-01-26 02:06:31 +04:00
2019-05-10 20:03:27 +03:00
if options.update and not parserclass.guest_propname:
2014-01-26 04:51:56 +04:00
fail(_("Don't know how to --update for --%s") %
2016-06-14 14:37:21 +03:00
(parserclass.cli_arg_name))
2014-01-26 04:51:56 +04:00
2015-07-02 15:09:46 +03:00
if options.build_xml:
2016-06-14 14:37:21 +03:00
devs = action_build_xml(conn, options, parserclass)
for dev in devs:
2017-06-15 15:18:26 +03:00
# pylint: disable=no-member
2018-08-31 23:52:02 +03:00
print_stdout(dev.get_xml())
2014-01-26 05:14:42 +04:00
return 0
2018-03-29 03:46:13 +03:00
if options.update:
if active_xmlobj:
devs, action = prepare_changes(active_xmlobj, options, parserclass)
update_changes(domain, devs, action, options.confirm)
else:
logging.warning(
_("The VM is not running, --update is inapplicable."))
2019-02-26 12:56:43 +03:00
if options.define or options.start:
2016-06-14 14:37:21 +03:00
devs, action = prepare_changes(inactive_xmlobj, options, parserclass)
2019-02-26 12:56:43 +03:00
if options.define:
dom = define_changes(conn, inactive_xmlobj,
2017-03-07 05:32:32 +03:00
devs, action, options.confirm)
2019-02-26 12:56:43 +03:00
if dom and options.start:
try:
dom.create()
2019-06-14 19:10:00 +03:00
except libvirt.libvirtError as e: # pragma: no cover
2019-02-26 12:56:43 +03:00
fail(_("Failed starting domain '%s': %s") % (inactive_xmlobj.name, e))
print_stdout(_("Domain '%s' started successfully.") %
inactive_xmlobj.name)
elif not options.update and active_xmlobj and dom:
print_stdout(
_("Changes will take effect after the domain is fully powered off."))
else:
dom = start_domain_transient(conn, inactive_xmlobj, devs,
action, options.confirm)
if not options.update and not options.define and not options.start:
2016-06-14 14:37:21 +03:00
prepare_changes(inactive_xmlobj, options, parserclass)
2014-01-26 02:18:32 +04:00
2014-01-19 19:37:14 +04:00
return 0
2019-06-11 01:13:31 +03:00
if __name__ == "__main__": # pragma: no cover
2014-01-19 19:37:14 +04:00
try:
sys.exit(main())
2017-05-05 19:47:21 +03:00
except SystemExit as sys_e:
2014-01-19 19:37:14 +04:00
sys.exit(sys_e.code)
except KeyboardInterrupt:
logging.debug("", exc_info=True)
print_stderr(_("Aborted at user request"))
2017-05-05 19:47:21 +03:00
except Exception as main_e:
2014-01-19 19:37:14 +04:00
fail(main_e)