Глава 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.

 

Рисунок 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 управляет предоставлением нам соответствующих оболочек на этих различных этапах.

Каким образом 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.

 

Рисунок 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.

rescue.service и emergency.service

В мире systemd служба спасения также носит название однопользовательского режима. Поэтому, когда наш пользователь запрашивает запуск в однопользовательском режиме, тогда systemd на самом деле выкидывает пользователя в соответствующую аварийную оболочку на соответствующем этапе rescue.service. Рисунок 8-3 отображает для вас нашу последовательность запуска на данный момент.

 

Рисунок 8-3


Блок-схема общей последовательности запуска

Для запуска в однопользовательском режиме вы можете либо передавать rescue.target, или передавать runlevel1.target либо emergency.service в systemd.unit. Как показано на Рисунке 8-4, для изучения этапов запуска мы буден на этот раз применять Ubuntu.

 

Рисунок 8-4


Параметр командной строки ядра

Это сбросит нас в некую аварийную оболочку. Этот однопользовательский режим, служба спасения и аварийная служба, все они запускают исполняемый файл 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


Аварийная оболочка

Как вы можете видеть на Рисунке 8-5, sysroot ещё пока не смонтирован, поскольку мы не достигли своего этапа запуска для монтирования.

Я надеюсь, теперь вы разбираетесь в том как systemd представляет свою аварийную оболочку пользователям на разнообразных этапах запуска. В нашей следующей главе мы продолжим свою приостановленную последовательность запуска systemd.