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
This commit is contained in:
Debarshi Ray 2019-10-11 14:05:15 +02:00
parent 4481769182
commit 2a099e8049
5 changed files with 236 additions and 3 deletions

View File

@ -13,7 +13,7 @@ __toolbox() {
local MIN_VERSION=29 local MIN_VERSION=29
local RAWHIDE_VERSION=32 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 declare -A options
local options=([create]="--candidate-registry --container --image --release" \ local options=([create]="--candidate-registry --container --image --release" \

View File

@ -11,6 +11,7 @@ manuals = [
'toolbox-init-container.1', 'toolbox-init-container.1',
'toolbox-help.1', 'toolbox-help.1',
'toolbox-list.1', 'toolbox-list.1',
'toolbox-reset.1',
'toolbox-rm.1', 'toolbox-rm.1',
'toolbox-rmi.1', 'toolbox-rmi.1',
'toolbox-run.1', 'toolbox-run.1',

26
doc/toolbox-reset.1.md Normal file
View File

@ -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
```

View File

@ -68,6 +68,10 @@ Initialize a running container.
List existing toolbox containers and images. List existing toolbox containers and images.
**toolbox-reset(1)**
Remove all local podman (and toolbox) state.
**toolbox-rm(1)** **toolbox-rm(1)**
Remove one or more toolbox containers. Remove one or more toolbox containers.

202
toolbox
View File

@ -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() create()
( (
enter_command_skip="$1" 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() exit_if_extra_operand()
{ {
if [ "$1" != "" ]; then if [ "$1" != "" ]; then
@ -1910,6 +2088,11 @@ if [ -f /run/.containerenv ] 2>&3; then
"$init_container_user" "$init_container_user"
exit "$?" 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 "$base_toolbox_command: unrecognized command '$op'" >&2
echo "Try '$base_toolbox_command --help' for more information." >&2 echo "Try '$base_toolbox_command --help' for more information." >&2
@ -1918,9 +2101,11 @@ if [ -f /run/.containerenv ] 2>&3; then
esac esac
fi fi
if [ "$op" != "reset" ] 2>&3; then
if ! migrate; then if ! migrate; then
exit 1 exit 1
fi fi
fi
case $op in case $op in
create ) create )
@ -2082,6 +2267,23 @@ case $op in
exit 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 | rmi )
rm_all=false rm_all=false
rm_force=false rm_force=false