Глава 8. Оболочки отладки
Содержание
К данному моменту нам известно, что initramfs обладает встроенным bash и мы пользовались им время от времени
через особые точки входа rd.break
. Цель данной главы состоит в том, чтобы
разобраться как systemd снабжает нас некой оболочкой изнутри initramfs. В чём состоят те шаги, которые надлежит
предпринимать и как следует применять его более действенно? Однако перед этим давайте подведём итоги того, что
мы узнали на данный момент относительно отладочной и аварийной оболочек initramfs.
- rd.break
свалиться в оболочку в самом конце
rd.break
сбрасывает нас внутри initramfs и мы можем изучать саму
среду initramfs в ней. Такая среда initramfs также носит название аварийного
режима (emergency mode). В обычной ситуации мы сваливаемся в аварийный режим когда initramfs не
способен смонтировать некобходимую корневую файловую систему пользователя. Помните, передача
rd.break
без каких бы то ни было параметроввыбросит нас в initramfs
после монтирования необходимой корневой файловой системы пользователя в /sysroot
,
однако до выполнения в ней switch_root
. Вы всегда можете найти подробные
зарегистрированный записи в соответствующем файле /run/initramfs/rdsosreport.txt
.
Рисунок 8-1
отображает регистрационные записи из rdsosreport.txt
.
Из этих регистрационных сообщений вы ясно можете видеть, что он выбросил нас непосредственно перед выполнением
pivot_root. pivot_root
, а switch_root
будет обсуждаться в Главе 9, в то время как
chroot
будет рассматриваться в
Главе 10. Как только вы покинете свою аварийную оболочку,
systemd продолжит свою приостановленную последовательность запуска и в конечном итоге предоставит свой экран
входа в систему.
Далее мы обсудим как мы можем применять аварийные оболочки для исправления некоторых проблемных ситуаций
"can’t boot". Например, For example, initramfs ничем не хуже своей корневой файловой системы
пользователей. Итак, она обладает исполняемыми файлами lvm
,
raid
и относящимся к файловой системе, которые мы можем применить для
обнаружения, сборки, диагностирования и исправления этой утраченой корневой файловой системы пользователя. После
этого мы обсудим как мы можем смонтировать её в /sysroot
и изучим её
содержимое, скажем, для исправления неверных записей grub.cfg
.
Более того, rd.break
предоставляет нам различные варианты прерывания
своей последовательности запуска на различных этапах.
-
cmdline
: Эта особая точка входа получает установленные параметры командной строки ядра. -
pre-udev
: Прерывает нашу последовательность запуска перед обработчикомudev
. -
pre-trigger
: Вы имеете возможность установить переменные средыudev
со значением упрвленияudevadm
либо можете установить подобные--property=KEY=value
параметры или управлять своим дальнейшим исполнениемudev
сudevadm
. -
pre-mount
: Прерывает нашу последовательность перед монтированием нашей корневой файловой системы пользователя в/sysroot
. -
mount
: Прерывает исполняемую последовательность запуска после монтирования установленной корневой файловой системы пользователя в/sysroot
. -
pre-pivot
: Прерывет данную последовательность запуска непосредственно перед переключением на свою реальную корневую файловую систему.
Теперь давайте рассмотрим как в точности systemd управляет предоставлением нам соответствующих оболочек на этих различных этапах.
Давайте рассмотрим некий пример особой точки входа pre-mount
. Systemd из
initramfs выбирает параметр командной строки rd.break=pre-mount
из
dracut-cmdline.service
, и это запускает службу systemd
dracut-pre-mount.service
из местоположения initramfs
/usr/lib/systemd/system
. Эта служба запускается перед запуском
initrd-root-fs.target
, sysroot.mount
и systemd-fsck-root.service
.
# cat usr/lib/systemd/system/dracut-pre-mount.service | grep -v #'
[Unit]
Description=dracut pre-mount hook
Documentation=man:dracut-pre-mount.service(8)
DefaultDependencies=no
Before=initrd-root-fs.target sysroot.mount systemd-fsck-root.service
After=dracut-initqueue.service cryptsetup.target
ConditionPathExists=/usr/lib/initrd-release
ConditionDirectoryNotEmpty=|/lib/dracut/hooks/pre-mount
ConditionKernelCommandLine=|rd.break=pre-mount
Conflicts=shutdown.target emergency.target
[Service]
Environment=DRACUT_SYSTEMD=1
Environment=NEWROOT=/sysroot
Type=oneshot
ExecStart=-/bin/dracut-pre-mount
StandardInput=null
StandardOutput=syslog
StandardError=syslog+console
KillMode=process
RemainAfterExit=yes
KillSignal=SIGHUP
Как вы можете видеть, это всего лишь исполнение сценария /bin/dracut-pre-mount
из initramfs.
# vim bin/dracut-pre-mount
1 #!/usr/bin/sh
2
3 export DRACUT_SYSTEMD=1
4 if [ -f /dracut-state.sh ]; then
5 . /dracut-state.sh 2>/dev/null
6 fi
7 type getarg >/dev/null 2>>1 || . /lib/dracut-lib.sh
8
9 source_conf /etc/conf.d
10
11 make_trace_mem "hook pre-mount" '1:shortmem' '2+:mem' '3+:slab' '4+:komem'
12 # pre pivot scripts are sourced just before we doing cleanup and switch over
13 # to the new root.
14 getarg 'rd.break=pre-mount' 'rdbreak=pre-mount' >> emergency_shell -n pre-mount "Break pre-mount"
15 source_hook pre-mount
16
17 export -p > /dracut-state.sh
18
19 exit 0
Внутри этого сценария /bin/dracut-pre-mount
наиболее важной является
следующая строка:
getarg rd.break=pre-mount' rdbreak=pre-mount
&& emergency_shell -n pre-mount "Break pre-mount"
Мы уже обсуждали функцию getarg
, которая применяется для проверки того
какой параметр был передан в rd.break=
. Как только был передан параметр
rd.break=pre-mount
, тогда будет вызвана только функция
emergency-shell()
. Эта функция определена в
/usr/lib/dracut-lib.sh
и она передаёт в неё
pre-mount
в качестве строкового параметра
-n
означает следующее:
[ -n STRING ] или [ STRING ]: True когда длина STRING ненулевая
Эта функция emergency_shell
принимает для переменной
_rdshell_name
значение pre-mount
.
if [ "$1" = "-n" ]; then
_rdshell_name=$2
Здесь -n
рассматривается как первый аргумент
($1
), а pre-mount
это второй
аргумент ($2
). Поэтому _rdshell_name
становится pre-mount
.
#vim /usr/lib/dracut-lib.sh
1123 emergency_shell()
1124 {
1125 local _ctty
1126 set +e
1127 local _rdshell_name="dracut" action="Boot" hook="emergency"
1128 local _emergency_action
1129
1130 if [ "$1" = "-n" ]; then
1131 _rdshell_name=$2
1132 shift 2
1133 elif [ "$1" = "--shutdown" ]; then
1134 _rdshell_name=$2; action="Shutdown"; hook="shutdown-emergency"
1135 if type plymouth >/dev/null 2>&1; then
1136 plymouth --hide-splash
1137 elif [ -x /oldroot/bin/plymouth ]; then
1138 /oldroot/bin/plymouth --hide-splash
1139 fi
1140 shift 2
1141 fi
1142
1143 echo ; echo
1144 warn "$*"
1145 echo
1146
1147 _emergency_action=$(getarg rd.emergency)
1148 [ -z "$_emergency_action" ] \
1149 && [ -e /run/initramfs/.die ] \
1150 && _emergency_action=halt
1151
1152 if getargbool 1 rd.shell -d -y rdshell || getarg rd.break -d rdbreak; then
1153 _emergency_shell $_rdshell_name
1154 else
1155 source_hook "$hook"
1156 warn "$action has failed. To debug this issue add \"rd.shell rd.debug\" to the kernel command line."
1157 [ -z "$_emergency_action" ] && _emergency_action=halt
1158 fi
1159
1160 case "$_emergency_action" in
1161 reboot)
1162 reboot || exit 1;;
1163 poweroff)
1164 poweroff || exit 1;;
1165 halt)
1166 halt || exit 1;;
1167 esac
1168 }
После этого, в самом конце, это вызовет другую функцию _emergency_shell
из того же самого файла (обратите внимание на подчёркивание перед самим названием функции). Как вы можете видеть,
_rdshell_name
выступает соответствующим аргументом для такой функции
_emergency_shell
.
_emergency_shell $_rdshell_name
_emergency_shell
_name
pre-mount
local _name="$1"
#vim usr/lib/dracut-lib.sh
1081 _emergency_shell()
1082 {
1083 local _name="$1"
1084 if [ -n "$DRACUT_SYSTEMD" ]; then
1085 > /.console_lock
1086 echo "PS1=\"$_name:\\\${PWD}# \"" >/etc/profile
1087 systemctl start dracut-emergency.service
1088 rm -f -- /etc/profile
1089 rm -f -- /.console_lock
1090 else
1091 debug_off
1092 source_hook "$hook"
1093 echo
1094 /sbin/rdsosreport
1095 echo 'You might want to save "/run/initramfs/rdsosreport.txt" to a USB stick or /boot'
1096 echo 'after mounting them and attach it to a bug report.'
1097 if ! RD_DEBUG= getargbool 0 rd.debug -d -y rdinitdebug -d -y rdnetdebug; then
1098 echo
1099 echo 'To get more debug information in the report,'
1100 echo 'reboot with "rd.debug" added to the kernel command line.'
1101 fi
1102 echo
1103 echo 'Dropping to debug shell.'
1104 echo
1105 export PS1="$_name:\${PWD}# "
1106 [ -e /.profile ] || >/.profile
1107
1108 _ctty="$(RD_DEBUG= getarg rd.ctty=)" && _ctty="/dev/${_ctty##*/}"
1109 if [ -z "$_ctty" ]; then
1110 _ctty=console
1111 while [ -f /sys/class/tty/$_ctty/active ]; do
1112 _ctty=$(cat /sys/class/tty/$_ctty/active)
1113 _ctty=${_ctty##* } # last one in the list
1114 done
1115 _ctty=/dev/$_ctty
1116 fi
1117 [ -c "$_ctty" ] || _ctty=/dev/tty1
1118 case "$(/usr/bin/setsid --help 2>&1)" in *--ctty*) CTTY="--ctty";; esac
1119 setsid $CTTY /bin/sh -i -l 0<>$_ctty 1<>$_ctty 2<>$_ctty
1120 fi
Та же самая строка pre-mount
была передана в
PS1
. Давайте вначале рассмотрим чем собственно является
PS1
.
PS1
носит название псевдо
переменной. Она будет отображена bash после того как соответствующий пользователь успешно зарегистрировался. Вот
некий пример:
[root@fedora home]#
| | | |
[username]@[host][CWD][# since it is a root user]
Значениями идеальных записей, принимаемых bash являются:PS1='\u:\w\$'
.
-
u
= значение имени пользователя. -
w/code>
= Это значение рабочего каталога. -
$
= Когда UID равен 0, #; в противном случае $.
Итак, в нашем случае, когда мы получаем некую аварийную оболочку, PS1
будет выведена на печать этой оболочкой следующим образом:
'pre-mount#'
Далее в этом исходном коде вы можете наблюдать, что установленное новое значение переменной
PS1
также добавляется в /etc/profile
.
Основная причина состоит в том, что bash считывает данный файл всякий раз перед предоставлением своей оболочки
текущему пользователю. В самом конце мы просто запускаем dracut-emergency
.
systemctl start dracut-emergency.service
Ниже приводится содержимое файла dracut-emergency.service
из
usr/lib/systemd/system/
в initramfs.
# cat usr/lib/systemd/system/dracut-emergency.service | grep -v #'
[Unit]
Description=Dracut Emergency Shell
DefaultDependencies=no
After=systemd-vconsole-setup.service
Wants=systemd-vconsole-setup.service
Conflicts=shutdown.target emergency.target
[Service]
Environment=HOME=/
Environment=DRACUT_SYSTEMD=1
Environment=NEWROOT=/sysroot
WorkingDirectory=/
ExecStart=-/bin/dracut-emergency
ExecStopPost=-/bin/rm -f -- /.console_lock
Type=oneshot
StandardInput=tty-force
StandardOutput=inherit
StandardError=inherit
KillMode=process
IgnoreSIGPIPE=no
TasksMax=infinity
KillSignal=SIGHUP
Эта служба всего лишь выполняет /bin/dracut-emergency
. Данный
сценарий вначале останавливает запущенную службу plymouth
.
type plymouth >/dev/null 2>&1 && plymouth quit
Это запоминает текущим значением переменной hook
emergency
и вызывает соответствющую функцию
source_hook
с этим аргументом
emergency
.
export _rdshell_name="dracut" action="Boot" hook="emergency"
source_hook "$hook"
# vim bin/dracut-emergency
1 #!/usr/bin/sh
2
3 export DRACUT_SYSTEMD=1
4 if [ -f /dracut-state.sh ]; then
5 . /dracut-state.sh 2>/dev/null
6 fi
7 type getarg >/dev/null 2>&1 || . /lib/dracut-lib.sh
8
9 source_conf /etc/conf.d
10
11 type plymouth >/dev/null 2>&1 && plymouth quit
12
13 export _rdshell_name="dracut" action="Boot" hook="emergency"
14 _emergency_action=$(getarg rd.emergency)
15
16 if getargbool 1 rd.shell -d -y rdshell || getarg rd.break -d rdbreak; then
17 FSTXT="/run/dracut/fsck/fsck_help_$fstype.txt"
18 source_hook "$hook"
19 echo
20 rdsosreport
21 echo
22 echo
23 echo Entering emergency mode. Exit the shell to continue.'
24 echo Type "journalctl" to view system logs.'
25 echo You might want to save "/run/initramfs/rdsosreport.txt" to a USB stick or /boot'
26 echo after mounting them and attach it to a bug report.'
27 echo
28 echo
29 [ -f "$FSTXT" ] && cat "$FSTXT"
30 [ -f /etc/profile ] && . /etc/profile
31 [ -z "$PS1" ] && export PS1="$_name:\${PWD}# "
32 exec sh -i -l
33 else
34 export hook="shutdown-emergency"
35 warn "$action has failed. To debug this issue add \"rd.shell rd.debug\" to the kernel command line."
36 source_hook "$hook"
37 [ -z "$_emergency_action" ] && _emergency_action=halt
38 fi
39
40 /bin/rm -f -- /.console_lock
41
42 case "$_emergency_action" in
43 reboot)
44 reboot || exit 1;;
45 poweroff)
46 poweroff || exit 1;;
47 halt)
48 halt || exit 1;;
49 esac
50
51 exit 0
Данная функция source_hook
снова определена в
usr/lib/dracut-lib.sh
.
source_hook() {
local _dir
_dir=$1; shift
source_all "/lib/dracut/hooks/$_dir" "$@"
}
Значение переменной _dir
перехватило значение название особой точки входа,
коим выступает emergency
. Все такие особые точки входа ничто иное, как некий
пакет сценариев, хранимых и исполняемых из каталога /lib/dracut/hooks/
initramfs.
# tree usr/lib/dracut/hooks/
usr/lib/dracut/hooks/
├── cleanup
├── cmdline
│ ├── 30-parse-lvm.sh
│ ├── 91-dhcp-root.sh
│ └── 99-nm-config.sh
├── emergency
│ └── 50-plymouth-emergency.sh
├── initqueue
│ ├── finished
│ ├── online
│ ├── settled
│ │ └── 99-nm-run.sh
│ └── timeout
│ └── 99-rootfallback.sh
├── mount
├── netroot
├── pre-mount
├── pre-pivot
│ └── 85-write-ifcfg.sh
├── pre-shutdown
├── pre-trigger
├── pre-udev
│ └── 50-ifname-genrules.sh
├── shutdown
│ └── 25-dm-shutdown.sh
└── shutdown-emergency
Для некой аварийной специальной точки входа исполняется
usr/lib/dracut/hooks/emergency/50-plymouth-emergency.sh
,
который останавливает plymouth
.
#!/usr/bin/sh
plymouth --hide-splash 2>/dev/null || :
После выполнения особой точки входа и останова plymouth
, выполняется
возврат в bin/dracut-emergency
и выдаётся на печать такой баннер:
echo Entering emergency mode. Exit the shell to continue.'
echo Type "journalctl" to view system logs.'
echo You might want to save "/run/initramfs/rdsosreport.txt" to a USB stick or /boot'
echo after mounting them and attach it to a bug report.'
Итак нет значения какое rd.break=hook_name
было передано пользователем.
Systemd выполнит соответствующую особую точку входа emergency
и после вывода на печать надлежащего баннера он извлечёт тот каталог /etc/profile
,
в который мы добавили PS1=_rdshell_name/PS1=hook_name
, а затем
просто запустит свою оболочку bash.
exec sh -i –l
Когда этот сценарий начинает исполняться, он считает /etc/profile
и
будет обнаружена переменная PS1=hook_name
. В данном случае
hook_name
равен pre-mount
.
Именно по этой причине в качестве приглашающего имени bash выводится на печать
pre-mount
. Для лучшего понимания всего этого обратитесь к блок- схеме на
Рисунке 8-2.
Когда пользователь передаёт в rd.break
какой- то иной параметр, скажем,
initqueue
, тогда он будет скормлен в переменные
PS1
, _rdshell_name
и
hook
. Позднее через соответствующую авариную службу будет вызван bash.
Bash считает значение PS1
из файла /etc/profile
и отобразит в своём приглашении имя initqueue
.
Основное заключение состоит в том, что соответствующемы пользователю будет предоставляться одна и та же оболочка
bash с различными именами приглашений (cmdline
,
pre-mount
, switch_root
,
pre-udev
, emergency
и т.п.), однако
на различных этапах initramfs.
cmdline:/# pre-udev:/#
pre-trigger:/# initqueue:/#
pre-mount:/# pre-pivot:/#
switch_root:/#
Аналогично этому, systemd будет исполняться rescue.target
.
В мире systemd служба спасения также носит название однопользовательского
режима. Поэтому, когда наш пользователь запрашивает запуск в однопользовательском режиме, тогда systemd
на самом деле выкидывает пользователя в соответствующую аварийную оболочку на соответствующем этапе
rescue.service
.
Рисунок 8-3
отображает для вас нашу последовательность запуска на данный момент.
Для запуска в однопользовательском режиме вы можете либо передавать rescue.target
,
или передавать runlevel1.target
либо
emergency.service
в systemd.unit
.
Как показано на
Рисунке 8-4,
для изучения этапов запуска мы буден на этот раз применять Ubuntu.
Это сбросит нас в некую аварийную оболочку. Этот однопользовательский режим, служба спасения и аварийная служба,
все они запускают исполняемый файл dracut-emergency
. Это тот же самый исполняемый
файл, который мы запускали в особой точке входа dracut.
# cat usr/lib/systemd/system/emergency.service | grep -v ' #'
[Unit]
Description=Emergency Shell
DefaultDependencies=no
After=systemd-vconsole-setup.service
Wants=systemd-vconsole-setup.service
Conflicts=shutdown.target
Before=shutdown.target
[Service]
Environment=HOME=/
Environment=DRACUT_SYSTEMD=1
Environment=NEWROOT=/sysroot
WorkingDirectory=/
ExecStart=/bin/dracut-emergency
ExecStopPost=-/usr/bin/systemctl --fail --no-block default
Type=idle
StandardInput=tty-force
StandardOutput=inherit
StandardError=inherit
KillMode=process
IgnoreSIGPIPE=no
TasksMax=infinity
KillSignal=SIGHUP
# cat usr/lib/systemd/system/rescue.service | grep -v ' #'
[Unit]
Description=Emergency Shell
DefaultDependencies=no
After=systemd-vconsole-setup.service
Wants=systemd-vconsole-setup.service
Conflicts=shutdown.target
Before=shutdown.target
[Service]
Environment=HOME=/
Environment=DRACUT_SYSTEMD=1
Environment=NEWROOT=/sysroot
WorkingDirectory=/
ExecStart=/bin/dracut-emergency
ExecStopPost=-/usr/bin/systemctl --fail --no-block default
Type=idle
StandardInput=tty-force
StandardOutput=inherit
StandardError=inherit
KillMode=process
IgnoreSIGPIPE=no
TasksMax=infinity
KillSignal=SIGHUP
И, как нам известно, этот сценарий dracut-emergency
запускает
оболочку bash.
# vim bin/dracut-emergency
1 #!/usr/bin/sh
2
3 export DRACUT_SYSTEMD=1
4 if [ -f /dracut-state.sh ]; then
5 . /dracut-state.sh 2>/dev/null
6 fi
7 type getarg >/dev/null 2>&1 || . /lib/dracut-lib.sh
8
9 source_conf /etc/conf.d
10
11 type plymouth >/dev/null 2>&1 && plymouth quit
12
13 export _rdshell_name="dracut" action="Boot" hook="emergency"
14 _emergency_action=$(getarg rd.emergency)
15
16 if getargbool 1 rd.shell -d -y rdshell || getarg rd.break -d rdbreak; then
17 FSTXT="/run/dracut/fsck/fsck_help_$fstype.txt"
18 source_hook "$hook"
19 echo
20 rdsosreport
21 echo
22 echo
23 echo 'Entering emergency mode. Exit the shell to continue.'
24 echo 'Type "journalctl" to view system logs.'
25 echo 'You might want to save "/run/initramfs/rdsosreport.txt" to a USB stick or /boot'
26 echo 'after mounting them and attach it to a bug report.'
27 echo
28 echo
29 [ -f "$FSTXT" ] && cat "$FSTXT"
30 [ -f /etc/profile ] && . /etc/profile
31 [ -z "$PS1" ] && export PS1="$_name:\${PWD}# "
32 exec sh -i -l
33 else
34 export hook="shutdown-emergency"
35 warn "$action has failed. To debug this issue add \"rd.shell rd.debug\" to the kernel command line."
36 source_hook "$hook"
37 [ -z "$_emergency_action" ] && _emergency_action=halt
38 fi
39
40 /bin/rm -f -- /.console_lock
41
42 case "$_emergency_action" in
43 reboot)
44 reboot || exit 1;;
45 poweroff)
46 poweroff || exit 1;;
47 halt)
48 halt || exit 1;;
49 esac
50
51 exit 0
Как вы можете видеть на
Рисунке 8-5,
sysroot
ещё пока не смонтирован, поскольку мы не достигли своего этапа запуска
для монтирования.
Я надеюсь, теперь вы разбираетесь в том как systemd представляет свою аварийную оболочку пользователям на разнообразных этапах запуска. В нашей следующей главе мы продолжим свою приостановленную последовательность запуска systemd.