1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
|
# vi: set sw=4 ts=4 et tw=110:
# shellcheck shell=bash disable=SC2155
CONTAINER_NAME=""
CONTAINER_MACHINE_ID=""
CONTAINER_OVERLAY=""
__COREDUMPCTL_TS=""
# Prepare a systemd-nspawn container so we can test a custom polkit version without affecting the test
# machine.
#
# This function prepares a lightweight nspawn container that reuses the rootfs of the underlying test machine
# to run polkit under various tools (or a completely custom-built polkit version) without risking
# damage to the underlying test machine. The container simply combines the /etc and /usr directories from the
# host with our own additions using overlayfs, which is then bind-mounted into the container, so we do a full
# user-space boot without needing to build a custom image or restart the underlying test machine itself.
#
# The function exports/modifies three environment variables:
# - $CONTAINER_NAME - container name that can be used to identify the machine in machinectl calls (or in
# direct calls to the systemd-nspawn@.service template)
# - $CONTAINER_MACHINE_ID - machine ID of the container, which can be used to locate the container's journal
# under /var/log/journal/$CONTAINER_MACHINE_ID
# - $CONTAINER_OVERLAY - upper layer of the container overlayfs that can be used to add additional bits into
# the final container (note that only /etc and /usr subdirectores from this direcory
# are used)
#
# Once the container is ready, it can be booted up using container_start(). To execute commands inside the
# container, container_run() and container_run_user() might come in handy.
container_prepare() {
# Export a couple of env variables which can be used to track/alter the container
CONTAINER_NAME="polkit-container-$RANDOM"
CONTAINER_MACHINE_ID="$(systemd-id128 new)"
CONTAINER_OVERLAY="/var/lib/machines/$CONTAINER_NAME"
# Switch SELinux to permissive (if enabled), so it doesn't interfere with the container shenanigans below.
setenforce 0 || :
# We need persistent journal for the systemd-nspawn --link= stuff
mkdir -p /var/log/journal
journalctl --flush
# Prepare the nspawn container service
mkdir -p "/var/lib/machines/$CONTAINER_NAME"
# Notes:
# - with systemd v256+ this can be replaced by systemctl edit --stdin --runtime ..., and the
# mkdir/daemon-reload can be dropped
# - systemd-nspawn can't overlay the whole rootfs (/), so we need to cherry-pick a couple of subdirectories
# we're interested in (in this case it's pretty simple, since polkit installs everything under /usr,
# and we need /etc with our polkit.service override)
# - since the whole container is ephemeral, use --link-journal=host, so the journal directory for the
# container is created on the _host_ under /var/log/journal/<machine-id> and bind-mounted into the
# container; that way we can fetch the container journal for debugging even if something goes horribly
# wrong
mkdir -p "/run/systemd/system/systemd-nspawn@$CONTAINER_NAME.service.d"
cat >"/run/systemd/system/systemd-nspawn@$CONTAINER_NAME.service.d/override.conf" <<EOF
[Service]
# We'll handle the coredumps on the host instead
CoredumpReceive=no
ExecStart=
ExecStart=systemd-nspawn --quiet --network-veth --keep-unit --machine=%i --boot \
--link-journal=host \
--volatile=yes \
--directory=/ \
--uuid=$CONTAINER_MACHINE_ID \
--hostname=$CONTAINER_NAME \
--overlay=/etc:$CONTAINER_OVERLAY/etc:/etc \
--overlay-ro=/usr:$CONTAINER_OVERLAY/usr:/usr \
${BUILD_DIR:+"--bind=$BUILD_DIR:$BUILD_DIR"}
EOF
systemctl daemon-reload
# Prepare the nspawn container overlay
mkdir "$CONTAINER_OVERLAY"/{etc,usr}/
# Let systemd-nspawn propagate the machine ID and hostname we passed it
: >"$CONTAINER_OVERLAY/etc/machine-id"
: >"$CONTAINER_OVERLAY/etc/hostname"
}
# Start the container created by container_prepare() and wait until it boots.
container_start() {
if [[ -z "$CONTAINER_NAME" ]]; then
echo >&2 "No container to start (missing call to container_prepare()?)"
return 1
fi
machinectl start "$CONTAINER_NAME"
timeout --foreground 30s bash -ec "until systemd-run -M $CONTAINER_NAME --wait --pipe true; do sleep .5; done"
# is-system-running returns > 0 if the system is running in degraded mode, but we don't care about that, we
# just need to wait until the bootup is finished
container_run systemctl is-system-running -q --wait || :
# Create a non-root user, so we can test session bus stuff as well
#
# We can't use sysusers.d here becaues it's not (yet) designed for creating non-system users (i.e. users
# with UID >= 1000). We need a non-system user here, because since v258 systemd doesn't start user
# managers automatically for system users (which means a user D-Bus session as well), so we then couldn't
# use the test user with `systemd-run -M testuser@... --user` etc.
#
# See: https://github.com/systemd/systemd/commit/cf8f6cd0571a4f474c449d4b1fda1c4192c01824
container_run useradd -m testuser
}
container_stop() {
# Note: machinectl poweroff doesn't wait until the container shuts down completely, stop stop the service
# behind it instead which does wait
systemctl stop "systemd-nspawn@${CONTAINER_NAME:?}.service"
}
# Run a command in a container as a root.
container_run() {
systemd-run -M "${CONTAINER_NAME:?}" --wait --pipe "$@"
}
# Same as above, but run the command under a specific user.
container_run_user() {
local user="${1:?}"
shift
systemd-run -M "$user@${CONTAINER_NAME:?}" --user --wait --pipe "$@"
}
container_destroy() {
if [[ -z "$CONTAINER_NAME" ]]; then
return 0
fi
if systemctl -q is-active "systemd-nspawn@$CONTAINER_NAME.service"; then
container_stop
fi
# Export the container journal and sanitizer logs if $TMT_TEST_DATA is set, either by TMT directly or
# manually.
if [[ -n "${TMT_TEST_DATA:-}" ]]; then
mkdir -p "$TMT_TEST_DATA"
journalctl -D "/var/log/journal/$CONTAINER_MACHINE_ID" -o short-monotonic >"$TMT_TEST_DATA/$CONTAINER_NAME.log"
fi
rm -rf "/var/lib/machines/$CONTAINER_NAME"
rm -rf "/var/log/journal/$CONTAINER_MACHINE_ID"
rm -rf "/run/systemd/system/systemd-nspawn@$CONTAINER_NAME.service.d"
systemctl daemon-reload
}
coredumpctl_init() {
local ec
if ! systemctl start systemd-coredump.socket; then
echo >&2 "Failed to start systemd-coredump.socket"
return 1
fi
# Note: coredumpctl returns 1 when no coredumps are found
coredumpctl --since=now >/dev/null && ec=0 || ec=$?
if [[ $ec -ne 1 ]]; then
echo >&2 "coredumpctl is not in operative state"
return 1
fi
# Set the internal coredumpctl timestamp, so we consider coredumps only from now on
__COREDUMPCTL_TS="$(date +"%Y-%m-%d %H:%M:%S")"
return 0
}
# Attempt to dump info about relevant coredumps using the coredumpctl utility.
#
# Returns:
# 0 when no coredumps were found, 1 otherwise
coredumpctl_collect() (
set +ex
local args=(-q --no-legend --no-pager)
local tempfile="$(mktemp)"
# Register a cleanup handler
#
# Note: since this function is a technically a subshell, RETURN trap won't work here
# shellcheck disable=SC2064
trap "rm -f '$tempfile'" EXIT
if [[ -n "$__COREDUMPCTL_TS" ]]; then
args+=(--since "$__COREDUMPCTL_TS")
fi
if ! coredumpctl "${args[@]}" -F COREDUMP_EXE >"$tempfile"; then
echo "No relevant coredumps found"
return 0
fi
# For each unique executable path call 'coredumpctl info' to get the stack trace and other useful info
while read -r path; do
local exe
local gdb_cmd="set print pretty on\nbt full"
coredumpctl "${args[@]}" info "$path"
# Make sure we use the built binaries for getting gdb trace
#
# This is relevant mainly for the sanitizers run, where we don't install the just built revision, so
# `coredumpctl debug` pulls in a local binary instead of the built one, which produces useless
# results.
if [[ -v BUILD_DIR && -d $BUILD_DIR ]]; then
# The build directory layout of polkit is not flat, so we need to find the binary first
exe="$(find "$BUILD_DIR" -executable -name "${path##*/}" | head -n1)"
if [[ -n "$exe" ]]; then
gdb_cmd="file $exe\nthread apply all bt\n$gdb_cmd"
fi
fi
# Attempt to get a full stack trace for the first occurrence of the given executable path
if gdb -v >/dev/null; then
echo -e "\n"
echo "Trying to run gdb with '$gdb_cmd' for '$path'"
echo -e "$gdb_cmd" | coredumpctl "${args[@]}" debug "$path"
echo -e "\n"
fi
done < <(sort -u "$tempfile")
return 1
)
|