From 2a099e8049a2f87290e9e71146c6ac98c8446fb6 Mon Sep 17 00:00:00 2001 From: Debarshi Ray Date: Fri, 11 Oct 2019 14:05:15 +0200 Subject: [PATCH] Add a reset command The 'reset' command is meant to factory reset the local Podman and Toolbox installations. Every now and then early adopters and testers of Toolbox have to do this when their local Podman state has gotten irrecoverably broken due to some Podman bug. It's useful to have a command that encapsulates all the steps to do a factory reset, as opposed to having to spell them out separately. It's easier to document, helps with user support, and can enable less opaque error messages that suggest a way forward when nothing is working. Since this command is meant to be used when the Podman installation is completely broken, it must avoid using any Podman commands at all costs. This is why it cannot use 'podman stop' to stop any running containers, nor can it use 'podman unshare' to delete ~/.local/share/containers when running rootless. Instead, it relies on the user rebooting the machine for the former, and uses newgidmap(1), newuidmap(1) and unshare(1) to reimplement 'podman unshare' for the latter. Note that when running as root, some care has been taken to avoid removing directories that might be owned by the operating system. eg., on Fedora /var/lib/containers/sigstore is owned by the containers-common RPM. https://github.com/containers/toolbox/pull/295 --- completion/bash/toolbox | 2 +- doc/meson.build | 1 + doc/toolbox-reset.1.md | 26 +++++ doc/toolbox.1.md | 4 + toolbox | 206 +++++++++++++++++++++++++++++++++++++++- 5 files changed, 236 insertions(+), 3 deletions(-) create mode 100644 doc/toolbox-reset.1.md diff --git a/completion/bash/toolbox b/completion/bash/toolbox index 67d2785..f9ed0b2 100644 --- a/completion/bash/toolbox +++ b/completion/bash/toolbox @@ -13,7 +13,7 @@ __toolbox() { local MIN_VERSION=29 local RAWHIDE_VERSION=32 - local commands="create enter help init-container list rm rmi run" + local commands="create enter help init-container list reset rm rmi run" declare -A options local options=([create]="--candidate-registry --container --image --release" \ diff --git a/doc/meson.build b/doc/meson.build index 172aee2..bad1cee 100644 --- a/doc/meson.build +++ b/doc/meson.build @@ -11,6 +11,7 @@ manuals = [ 'toolbox-init-container.1', 'toolbox-help.1', 'toolbox-list.1', + 'toolbox-reset.1', 'toolbox-rm.1', 'toolbox-rmi.1', 'toolbox-run.1', diff --git a/doc/toolbox-reset.1.md b/doc/toolbox-reset.1.md new file mode 100644 index 0000000..d90a0bf --- /dev/null +++ b/doc/toolbox-reset.1.md @@ -0,0 +1,26 @@ +% toolbox-reset(1) + +## NAME +toolbox\-reset - Remove all local podman (and toolbox) state + +## SYNOPSIS +**toolbox reset** + +## DESCRIPTION + +Removes all existing podman (and toolbox) containers, images and configuration. +This can be used to factory reset your local Podman and Toolbox installations +when something has gone irrecoverably wrong with the `podman(1)` and +`toolbox(1)` commands. + +This command can only be used on the host, and not from within a toolbox +container, and is only expected to be used right after a fresh boot before any +other `podman(1)` or `toolbox(1)` commands have been invoked. + +## EXAMPLES + +### Reset a broken Podman and Toolbox installation + +``` +$ toolbox reset +``` diff --git a/doc/toolbox.1.md b/doc/toolbox.1.md index 1f9755d..6617fb4 100644 --- a/doc/toolbox.1.md +++ b/doc/toolbox.1.md @@ -68,6 +68,10 @@ Initialize a running container. List existing toolbox containers and images. +**toolbox-reset(1)** + +Remove all local podman (and toolbox) state. + **toolbox-rm(1)** Remove one or more toolbox containers. diff --git a/toolbox b/toolbox index 65459d1..69f3d62 100755 --- a/toolbox +++ b/toolbox @@ -690,6 +690,103 @@ pull_base_toolbox_image() ) +unshare_userns_rm() +( + path="$1" + + if ! unshare_directory=$(mktemp --directory --tmpdir "toolbox-unshare-userns-rm-XXXXXXXXXX" 2>&3); then + echo "$base_toolbox_command: failed to enter user namespace: directory couldn't be created" >&2 + return 1 + fi + + if ! touch "$unshare_directory/map" 2>&3; then + echo "$base_toolbox_command: failed to enter user namespace: file couldn't be created" >&2 + return 1 + fi + + exec 6>"$unshare_directory/map" + if ! flock 6 2>&3; then + echo "$base_toolbox_command: failed to enter user namespace: lock couldn't be acquired" >&2 + return 1 + fi + + echo "$base_toolbox_command: parsing /etc/subgid" >&3 + + if ! subgid_entry=$(grep "^$USER:" /etc/subgid 2>&3); then + echo "$base_toolbox_command: failed to enter user namespace: no entry in /etc/subgid" >&2 + return 1 + fi + + userns_gid_start=$(echo "$subgid_entry" | cut --delimiter ":" --fields 2 2>&3) + if ! is_integer "$userns_gid_start"; then + echo "$base_toolbox_command: failed to enter user namespace: cannot parse the first sub-GID" >&2 + return 1 + fi + + userns_gid_len=$(echo "$subgid_entry" | cut --delimiter ":" --fields 3 2>&3) + if ! is_integer "$userns_gid_len"; then + echo "$base_toolbox_command: failed to enter user namespace: cannot parse the sub-GID count" >&2 + return 1 + fi + + echo "$base_toolbox_command: parsing /etc/subuid" >&3 + + if ! subuid_entry=$(grep "^$USER:" /etc/subuid 2>&3); then + echo "$base_toolbox_command: failed to enter user namespace: no entry in /etc/subuid" >&2 + return 1 + fi + + userns_uid_start=$(echo "$subuid_entry" | cut --delimiter ":" --fields 2 2>&3) + if ! is_integer "$userns_uid_start"; then + echo "$base_toolbox_command: failed to enter user namespace: cannot parse the first sub-UID" >&2 + return 1 + fi + + userns_uid_len=$(echo "$subuid_entry" | cut --delimiter ":" --fields 3 2>&3) + if ! is_integer "$userns_uid_len"; then + echo "$base_toolbox_command: failed to enter user namespace: cannot parse the sub-UID count" >&2 + return 1 + fi + + echo "$base_toolbox_command: unsharing user namespace" >&3 + + unshare --user sh -c "flock $unshare_directory/map rm --force --recursive $path" 2>&3 & + unshare_pid="$!" + + echo "$base_toolbox_command: setting UID map of user namespace" >&3 + + if ! newgidmap "$unshare_pid" 0 "$user_id_real" 1 1 "$userns_gid_start" "$userns_gid_len" 2>&3; then + echo "$base_toolbox_command: failed to set GID mapping of user namespace" >&2 + kill -9 "$unshare_pid" 2>&3 + return 1 + fi + + if ! newuidmap "$unshare_pid" 0 "$user_id_real" 1 1 "$userns_uid_start" "$userns_uid_len" 2>&3; then + echo "$base_toolbox_command: failed to set UID mapping of user namespace" >&2 + kill -9 "$unshare_pid" 2>&3 + return 1 + fi + + echo "$base_toolbox_command: UID map of user namespace:" >&3 + cat /proc/$unshare_pid/uid_map 1>&3 2>&3 + + if ! flock --unlock 6 2>&3; then + echo "$base_toolbox_command: failed to remove $path: lock couldn't be unlocked" >&2 + kill -9 "$unshare_pid" 2>&3 + return 1 + fi + + if ! wait "$unshare_pid" 2>&3; then + echo "$base_toolbox_command: failed to remove $path" >&2 + return 1 + fi + + rm --force --recursive "$unshare_directory" 2>&3 + + return 0 +) + + create() ( enter_command_skip="$1" @@ -1599,6 +1696,87 @@ remove_images() ) +reset() +( + do_reset=false + prompt_for_reset=true + ret_val=0 + + if [ "$user_id_real" -eq 0 ] 2>&3; then + if [ -d /run/containers ] 2>&3; then + echo "$base_toolbox_command: The 'reset' command cannot be used after other commands" >&2 + echo "Reboot the system before using it again." >&2 + echo "Try '$base_toolbox_command --help' for more information." >&2 + return 1 + fi + else + if [ -d "$XDG_RUNTIME_DIR"/overlay-containers ] 2>&3 \ + || [ -d "$XDG_RUNTIME_DIR"/overlay-layers ] 2>&3 \ + || [ -d "$XDG_RUNTIME_DIR"/overlay-locks ] 2>&3; then + echo "$base_toolbox_command: The 'reset' command cannot be used after other commands" >&2 + echo "Reboot the system before using it again." >&2 + echo "Try '$base_toolbox_command --help' for more information." >&2 + return 1 + fi + fi + + if $assume_yes; then + do_reset=true + prompt_for_reset=false + fi + + if $prompt_for_reset; then + echo "All existing podman (and toolbox) containers and images will be removed." + + prompt=$(printf "Continue? [y/N]:") + if ask_for_confirmation "n" "$prompt"; then + do_reset=true + else + do_reset=false + fi + fi + + if ! $do_reset; then + return 1 + fi + + echo "$base_toolbox_command: resetting local state" >&3 + + if [ "$user_id_real" -eq 0 ] 2>&3; then + if ! rm --force --recursive /var/lib/containers/cache >/dev/null 2>&3; then + echo "$base_toolbox_command: failed to remove /var/lib/containers/cache" >&2 + ret_val=1 + fi + + if ! rm --force --recursive /var/lib/containers/sigstore/* >/dev/null 2>&3; then + echo "$base_toolbox_command: failed to remove the contents of /var/lib/containers/sigstore" >&2 + ret_val=1 + fi + + if ! rm --force --recursive /var/lib/containers/storage >/dev/null 2>&3; then + echo "$base_toolbox_command: failed to remove /var/lib/containers/storage" >&2 + ret_val=1 + fi + else + if ! unshare_userns_rm "$HOME/.local/share/containers"; then + ret_val=1 + fi + + if ! rm --force --recursive "$HOME/.config/containers" >/dev/null 2>&3; then + echo "$base_toolbox_command: failed to remove $HOME/.config/containers" >&2 + ret_val=1 + fi + fi + + if ! rm --force --recursive "$HOME/.config/toolbox" >/dev/null 2>&3; then + echo "$base_toolbox_command: failed to remove $HOME/.config/toolbox" >&2 + ret_val=1 + fi + + return "$ret_val" +) + + exit_if_extra_operand() { if [ "$1" != "" ]; then @@ -1910,6 +2088,11 @@ if [ -f /run/.containerenv ] 2>&3; then "$init_container_user" exit "$?" ;; + reset ) + echo "$base_toolbox_command: The 'reset' command cannot be used inside containers" >&2 + echo "Try '$base_toolbox_command --help' for more information." >&2 + exit 1 + ;; * ) echo "$base_toolbox_command: unrecognized command '$op'" >&2 echo "Try '$base_toolbox_command --help' for more information." >&2 @@ -1918,8 +2101,10 @@ if [ -f /run/.containerenv ] 2>&3; then esac fi -if ! migrate; then - exit 1 +if [ "$op" != "reset" ] 2>&3; then + if ! migrate; then + exit 1 + fi fi case $op in @@ -2082,6 +2267,23 @@ case $op in exit ;; + reset ) + while has_prefix "$1" -; do + case $1 in + -h | --help ) + help "$op" + exit + ;; + * ) + exit_if_unrecognized_option "$1" + esac + shift + done + exit_if_extra_operand "$1" + + reset + exit "$?" + ;; rm | rmi ) rm_all=false rm_force=false