2021-03-18 20:51:49 +03:00
#!/usr/bin/env python3
2012-09-14 13:08:54 +04:00
# libvirt 'run' programs locally script
2021-03-18 20:51:49 +03:00
# Copyright (C) 2012-2021 Red Hat, Inc.
2012-09-14 13:08:54 +04:00
#
2013-02-23 04:10:48 +04:00
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
2012-09-14 13:08:54 +04:00
#
2013-02-23 04:10:48 +04:00
# This library is distributed in the hope that it will be useful,
2012-09-14 13:08:54 +04:00
# but WITHOUT ANY WARRANTY; without even the implied warranty of
2013-02-23 04:10:48 +04:00
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
2012-09-14 13:08:54 +04:00
#
2013-02-23 04:10:48 +04:00
# You should have received a copy of the GNU Lesser General Public
# License along with this library; If not, see
# <http://www.gnu.org/licenses/>.
2012-09-14 13:08:54 +04:00
2021-04-01 17:10:33 +03:00
# ----------------------------------------------------------------------
2012-09-14 13:08:54 +04:00
#
# With this script you can run libvirt programs without needing to
# install them first. You just have to do for example:
#
2020-01-16 20:15:42 +03:00
# ./run virsh [args ...]
2012-09-14 13:08:54 +04:00
#
2020-01-16 20:15:42 +03:00
# Note that this runs the locally compiled copy of virsh which
# is usually want you want.
2012-09-14 13:08:54 +04:00
#
# You can also run the C programs under valgrind like this:
#
# ./run valgrind [valgrind opts...] ./program
#
# or under gdb:
#
# ./run gdb --args ./program
#
# This also works with sudo (eg. if you need root access for libvirt):
#
2020-01-16 20:15:42 +03:00
# sudo ./run virsh list --all
2012-09-14 13:08:54 +04:00
#
2021-04-01 17:10:33 +03:00
# ----------------------------------------------------------------------
2012-09-14 13:08:54 +04:00
2023-04-21 21:38:10 +03:00
import argparse
2021-03-18 20:51:49 +03:00
import os
import os.path
import random
2023-04-21 21:38:10 +03:00
import shutil
2022-03-09 13:52:12 +03:00
import signal
2021-03-18 21:22:32 +03:00
import subprocess
2023-06-05 10:40:13 +03:00
import sys
2021-03-18 20:51:49 +03:00
2021-04-01 17:10:33 +03:00
2020-01-16 20:15:41 +03:00
# Function to intelligently prepend a path to an environment variable.
2020-08-26 01:44:00 +03:00
# See https://stackoverflow.com/a/9631350
2021-03-18 20:51:49 +03:00
def prepend(env, varname, extradir):
if varname in os.environ:
env[varname] = extradir + ":" + env[varname]
else:
env[varname] = extradir
2021-04-01 17:10:33 +03:00
2021-03-18 20:51:49 +03:00
here = "@abs_builddir@"
2020-01-16 20:15:41 +03:00
2023-04-21 21:38:10 +03:00
parser = argparse.ArgumentParser(add_help=False, allow_abbrev=False)
parser.add_argument('--selinux',
action='store_true',
help='Run in the appropriate selinux context')
opts, args = parser.parse_known_args()
if len(args) < 1:
print("syntax: %s [--selinux] BINARY [ARGS...]" % sys.argv[0], file=sys.stderr)
2021-03-18 20:51:49 +03:00
sys.exit(1)
2012-09-14 13:08:54 +04:00
2023-04-21 21:38:10 +03:00
prog = args[0]
2021-03-18 20:51:49 +03:00
env = os.environ
2012-09-14 13:08:54 +04:00
2021-03-18 20:51:49 +03:00
prepend(env, "LD_LIBRARY_PATH", os.path.join(here, "src"))
prepend(env, "PKG_CONFIG_PATH", os.path.join(here, "src"))
prepend(env, "PATH", os.path.join(here, "tools"))
2022-03-09 13:51:36 +03:00
prepend(env, "PATH", os.path.join(here, "src"))
2020-01-16 20:15:42 +03:00
2019-08-29 13:52:08 +03:00
# Ensure that any 3rd party apps using libvirt.so from the build tree get
# files resolved to the build/source tree too. Typically useful for language
# bindings running tests against non-installed libvirt.
2021-03-18 20:51:49 +03:00
env["LIBVIRT_DIR_OVERRIDE"] = "1"
2019-08-29 13:52:08 +03:00
2012-09-14 13:08:54 +04:00
# This is a cheap way to find some use-after-free and uninitialized
# read problems when using glibc.
2021-03-18 20:51:49 +03:00
env["MALLOC_PERTURB_"] = "%d" % random.randint(1, 255)
2012-09-14 13:08:54 +04:00
2021-08-11 10:41:02 +03:00
env["abs_builddir"] = "@abs_builddir@"
env["abs_top_builddir"] = "@abs_top_builddir@"
2021-03-18 21:22:32 +03:00
modular_daemons = [
"virtinterfaced",
"virtlxcd",
"virtnetworkd",
"virtnodedevd",
"virtnwfilterd",
"virtproxyd",
"virtqemud",
"virtsecretd",
"virtstoraged",
"virtvboxd",
"virtvzd",
"virtxend",
]
2021-04-01 17:10:33 +03:00
2021-03-18 21:22:32 +03:00
def is_modular_daemon(name):
return name in modular_daemons
2021-04-01 17:10:33 +03:00
2021-03-18 21:22:32 +03:00
def is_monolithic_daemon(name):
return name == "libvirtd"
2021-04-01 17:10:33 +03:00
2021-03-18 21:22:32 +03:00
def is_systemd_host():
if os.getuid() != 0:
return False
return os.path.exists("/run/systemd/system")
2021-04-01 17:10:33 +03:00
2021-03-18 21:22:32 +03:00
def daemon_units(name):
return [name + suffix for suffix in [
".service", ".socket", "-ro.socket", "-admin.socket"]]
2021-04-01 17:10:33 +03:00
2021-03-18 21:22:32 +03:00
def is_unit_active(name):
ret = subprocess.call(["systemctl", "is-active", "-q", name])
return ret == 0
2021-04-01 17:10:33 +03:00
2021-03-18 21:22:32 +03:00
def change_unit(name, action):
ret = subprocess.call(["systemctl", action, "-q", name])
return ret == 0
2021-04-01 17:10:33 +03:00
2023-04-21 21:38:10 +03:00
def chcon(path, user, role, type):
print("Setting file context of {} to u={}, r={}, t={}...".format(progpath,
user,
role,
type))
ret = subprocess.call(["chcon", "-u", user, "-r", role, "-t", type, path])
return ret == 0
def restorecon(path):
print("Restoring selinux context for {}...".format(path))
ret = subprocess.call(["restorecon", path])
return ret == 0
2021-03-18 21:22:32 +03:00
try_stop_units = []
if is_systemd_host():
maybe_stopped_units = []
2023-04-21 21:38:10 +03:00
for arg in args:
2021-11-24 15:02:20 +03:00
name = os.path.basename(arg)
if is_modular_daemon(name):
# Only need to stop libvirtd or this specific modular unit
maybe_stopped_units += daemon_units("libvirtd")
maybe_stopped_units += daemon_units(name)
elif is_monolithic_daemon(name):
# Need to stop libvirtd and/or all modular units
maybe_stopped_units += daemon_units("libvirtd")
for entry in modular_daemons:
maybe_stopped_units += daemon_units(entry)
2021-03-18 21:22:32 +03:00
for unit in maybe_stopped_units:
if is_unit_active(unit):
try_stop_units.append(unit)
2023-04-21 21:38:10 +03:00
if len(try_stop_units) == 0 and not opts.selinux:
2021-03-18 21:22:32 +03:00
# Run the program directly, replacing ourselves
os.execvpe(prog, args, env)
else:
stopped_units = []
2022-03-09 13:52:12 +03:00
def sighandler(signum, frame):
raise OSError("Signal %d received, terminating" % signum)
signal.signal(signal.SIGHUP, sighandler)
signal.signal(signal.SIGTERM, sighandler)
signal.signal(signal.SIGQUIT, sighandler)
2021-03-18 21:22:32 +03:00
try:
2023-04-21 21:38:10 +03:00
dorestorecon = False
progpath = shutil.which(prog)
if len(try_stop_units):
print("Temporarily stopping systemd units...")
for unit in try_stop_units:
print(" > %s" % unit)
if not change_unit(unit, "stop"):
raise Exception("Unable to stop '%s'" % unit)
stopped_units.append(unit)
if opts.selinux:
# if using a wrapper command like 'gdb', setting the selinux
# context won't work because the wrapper command will not be a
# valid entrypoint for the virtd_t context
if os.path.basename(prog) not in ["libvirtd", *modular_daemons]:
raise Exception("'{}' is not recognized as a valid daemon. "
"Selinux process context can only be set when "
"executing a daemon directly without wrapper "
"commands".format(prog))
if not progpath:
raise Exception("Can't find executable {} for selinux labeling"
.format(prog))
if not progpath.startswith(os.path.abspath(here)):
raise Exception("Refusing to change selinux context of file "
"'{}' outside build directory"
.format(progpath))
# selinux won't allow us to transition to the virtd_t context from
# e.g. the user_home_t context (the likely label of the local
# executable file)
if not chcon(progpath, "system_u", "object_r", "virtd_exec_t"):
raise Exception("Failed to change selinux context of binary")
dorestorecon = True
args = ['runcon',
'-u', 'system_u',
'-r', 'system_r',
'-t', 'virtd_t', *args]
2021-03-18 21:22:32 +03:00
2021-04-06 17:40:53 +03:00
print("Running '%s'..." % str(" ".join(args)))
ret = subprocess.call(args, env=env)
2021-04-01 17:10:33 +03:00
except KeyboardInterrupt:
2021-03-18 21:22:32 +03:00
pass
2022-03-09 13:52:12 +03:00
except Exception as e:
print("%s" % e, file=sys.stderr)
2021-03-18 21:22:32 +03:00
finally:
2023-04-21 21:38:10 +03:00
if len(stopped_units):
print("Re-starting original systemd units...")
stopped_units.reverse()
for unit in stopped_units:
print(" > %s" % unit)
if not change_unit(unit, "start"):
print(" ! unable to restart %s" % unit, file=sys.stderr)
if dorestorecon:
restorecon(progpath)