NEWS: Fix misindented bullet point
[libvirt.git] / run.in
blob5b89b3dcd5b6b2591258be98100c4edf8984cb0c
1 #!/usr/bin/env python3
2 # libvirt 'run' programs locally script
3 # Copyright (C) 2012-2021 Red Hat, Inc.
5 # This library is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU Lesser General Public
7 # License as published by the Free Software Foundation; either
8 # version 2.1 of the License, or (at your option) any later version.
10 # This library 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 GNU
13 # Lesser General Public License for more details.
15 # You should have received a copy of the GNU Lesser General Public
16 # License along with this library; If not, see
17 # <http://www.gnu.org/licenses/>.
19 # ----------------------------------------------------------------------
21 # With this script you can run libvirt programs without needing to
22 # install them first.  You just have to do for example:
24 #   ./run virsh [args ...]
26 # Note that this runs the locally compiled copy of virsh which
27 # is usually want you want.
29 # You can also run the C programs under valgrind like this:
31 #   ./run valgrind [valgrind opts...] ./program
33 # or under gdb:
35 #   ./run gdb --args ./program
37 # This also works with sudo (eg. if you need root access for libvirt):
39 #   sudo ./run virsh list --all
41 # ----------------------------------------------------------------------
43 import argparse
44 import os
45 import os.path
46 import random
47 import shutil
48 import signal
49 import subprocess
50 import sys
53 # Function to intelligently prepend a path to an environment variable.
54 # See https://stackoverflow.com/a/9631350
55 def prepend(env, varname, extradir):
56     if varname in os.environ:
57         env[varname] = extradir + ":" + env[varname]
58     else:
59         env[varname] = extradir
62 here = "@abs_builddir@"
64 parser = argparse.ArgumentParser(add_help=False, allow_abbrev=False)
65 parser.add_argument('--selinux',
66                     action='store_true',
67                     help='Run in the appropriate selinux context')
69 opts, args = parser.parse_known_args()
71 if len(args) < 1:
72     print("syntax: %s [--selinux] BINARY [ARGS...]" % sys.argv[0], file=sys.stderr)
73     sys.exit(1)
75 prog = args[0]
76 env = os.environ
78 prepend(env, "LD_LIBRARY_PATH", os.path.join(here, "src"))
79 prepend(env, "PKG_CONFIG_PATH", os.path.join(here, "src"))
80 prepend(env, "PATH", os.path.join(here, "tools"))
81 prepend(env, "PATH", os.path.join(here, "src"))
83 # Ensure that any 3rd party apps using libvirt.so from the build tree get
84 # files resolved to the build/source tree too. Typically useful for language
85 # bindings running tests against non-installed libvirt.
86 env["LIBVIRT_DIR_OVERRIDE"] = "1"
88 # This is a cheap way to find some use-after-free and uninitialized
89 # read problems when using glibc.
90 env["MALLOC_PERTURB_"] = "%d" % random.randint(1, 255)
92 env["abs_builddir"] = "@abs_builddir@"
93 env["abs_top_builddir"] = "@abs_top_builddir@"
95 modular_daemons = [
96     "virtinterfaced",
97     "virtlxcd",
98     "virtnetworkd",
99     "virtnodedevd",
100     "virtnwfilterd",
101     "virtproxyd",
102     "virtqemud",
103     "virtsecretd",
104     "virtstoraged",
105     "virtvboxd",
106     "virtvzd",
107     "virtxend",
111 def is_modular_daemon(name):
112     return name in modular_daemons
115 def is_monolithic_daemon(name):
116     return name == "libvirtd"
119 def is_systemd_host():
120     if os.getuid() != 0:
121         return False
122     return os.path.exists("/run/systemd/system")
125 def daemon_units(name):
126     return [name + suffix for suffix in [
127         ".service", ".socket", "-ro.socket", "-admin.socket"]]
130 def is_unit_active(name):
131     ret = subprocess.call(["systemctl", "is-active", "-q", name])
132     return ret == 0
135 def change_unit(name, action):
136     ret = subprocess.call(["systemctl", action, "-q", name])
137     return ret == 0
140 def chcon(path, user, role, type):
141     print("Setting file context of {} to u={}, r={}, t={}...".format(progpath,
142                                                                      user,
143                                                                      role,
144                                                                      type))
145     ret = subprocess.call(["chcon", "-u", user, "-r", role, "-t", type, path])
146     return ret == 0
149 def restorecon(path):
150     print("Restoring selinux context for {}...".format(path))
151     ret = subprocess.call(["restorecon", path])
152     return ret == 0
155 try_stop_units = []
156 if is_systemd_host():
157     maybe_stopped_units = []
158     for arg in args:
159         name = os.path.basename(arg)
160         if is_modular_daemon(name):
161             # Only need to stop libvirtd or this specific modular unit
162             maybe_stopped_units += daemon_units("libvirtd")
163             maybe_stopped_units += daemon_units(name)
164         elif is_monolithic_daemon(name):
165             # Need to stop libvirtd and/or all modular units
166             maybe_stopped_units += daemon_units("libvirtd")
167             for entry in modular_daemons:
168                 maybe_stopped_units += daemon_units(entry)
170     for unit in maybe_stopped_units:
171         if is_unit_active(unit):
172             try_stop_units.append(unit)
174 if len(try_stop_units) == 0 and not opts.selinux:
175     # Run the program directly, replacing ourselves
176     os.execvpe(prog, args, env)
177 else:
178     stopped_units = []
180     def sighandler(signum, frame):
181         raise OSError("Signal %d received, terminating" % signum)
183     signal.signal(signal.SIGHUP, sighandler)
184     signal.signal(signal.SIGTERM, sighandler)
185     signal.signal(signal.SIGQUIT, sighandler)
187     try:
188         dorestorecon = False
189         progpath = shutil.which(prog)
190         if len(try_stop_units):
191             print("Temporarily stopping systemd units...")
193             for unit in try_stop_units:
194                 print(" > %s" % unit)
195                 if not change_unit(unit, "stop"):
196                     raise Exception("Unable to stop '%s'" % unit)
198                 stopped_units.append(unit)
200         if opts.selinux:
201             # if using a wrapper command like 'gdb', setting the selinux
202             # context won't work because the wrapper command will not be a
203             # valid entrypoint for the virtd_t context
204             if os.path.basename(prog) not in ["libvirtd", *modular_daemons]:
205                 raise Exception("'{}' is not recognized as a valid daemon. "
206                                 "Selinux process context can only be set when "
207                                 "executing a daemon directly without wrapper "
208                                 "commands".format(prog))
210             if not progpath:
211                 raise Exception("Can't find executable {} for selinux labeling"
212                                 .format(prog))
214             if not progpath.startswith(os.path.abspath(here)):
215                 raise Exception("Refusing to change selinux context of file "
216                                 "'{}' outside build directory"
217                                 .format(progpath))
219             # selinux won't allow us to transition to the virtd_t context from
220             # e.g. the user_home_t context (the likely label of the local
221             # executable file)
222             if not chcon(progpath, "system_u", "object_r", "virtd_exec_t"):
223                 raise Exception("Failed to change selinux context of binary")
224             dorestorecon = True
226             args = ['runcon',
227                     '-u', 'system_u',
228                     '-r', 'system_r',
229                     '-t', 'virtd_t', *args]
231         print("Running '%s'..." % str(" ".join(args)))
232         ret = subprocess.call(args, env=env)
233     except KeyboardInterrupt:
234         pass
235     except Exception as e:
236         print("%s" % e, file=sys.stderr)
237     finally:
238         if len(stopped_units):
239             print("Re-starting original systemd units...")
240             stopped_units.reverse()
241             for unit in stopped_units:
242                 print(" > %s" % unit)
243                 if not change_unit(unit, "start"):
244                     print(" ! unable to restart %s" % unit, file=sys.stderr)
245         if dorestorecon:
246             restorecon(progpath)