Изменения

м
Нет описания правки
Главная загвоздка — это init, первый (PID=1) и неубиваемый процесс в системе, запущенный с корневого раздела. Разделы, кроме корневого, отмонтировать обычно довольно легко — достаточно убить все процессы в системе. Однако init убить нельзя, большую часть сигналов, в том числе SIGKILL, он игнорирует (PID’у 1 можно игнорировать SIGKILL), а если у вас и получится его убить — ядро запаникует и остановит работу системы.
Так что init нужно, не выключая, «вытащить» в другой корневой раздел. Способ это сделать, по сути, один — подменить запущенный образом образ exec()ом другого, который сделает chroot и снова exec. Однако как заставить init сделать exec() того, чего нам надо? А вот как — все init’ы (даже несчастный systemd) умеют перезапускать себя. И хотя это сделано вовсе для другой цели, а именно — для применения обновлений в самом init или системных библиотеках (libc?) без перезапуска системы — это вполне можно заюзать в наших целях… достаточно поверх /sbin/init смонтировать свой файл --bind’ом. После чего команда «init U» перезапустит вместо init’а наш файл. Только сам инит надо куда-то скопировать, типа cp /sbin/init /sbin/init1, мы ж его только что перемонтировали.
А дальше нужно переключать корень. Мне изначально хотелось его переключать обратно в initramfs (то есть rootfs), чтобы «аварийная система» находилась именно там — типа, так надёжнее — загрузился сразу dropbear и всё, никуда уже не уходит и на машину можно зайти по ssh. Если не заморачиваться именно выходом в initramfs, переключить корень ещё проще — достаточно сделать pivot_root. pivot_root — это такой хитрый системный вызов, который подменяет / другой точкой монтирования, а текущий / помещает в подпапку нового, причём (!) насколько я понял, это единственный системный вызов, нагло подменяющий корень всем процессам в системе, в коде ядра прямо так и написано — «foreach (по всем задачам) { если корень == предыдущему, подменить на новый }».
* <tt>ls /<любая_папка>/..</tt> &rarr; листается новый корень.
* <tt>cd /..</tt> &rarr; всё-таки остаёмся в старом корне.
* <tt>mount --move /.. /root</tt> &rarr; самое интересное. Новый корень перемещается в подпапку rootfs /root, а остальная система становится как бы запущенной в chroot’е.Но не потому, что произошёл chroot, а потому, что сам корень переместился в новое место :)
А из chroot’а, как известно, можно вылезти — нужно только получить открытый дескриптор на директорию, находящуюся ВНЕ текущего корня. Делается это путём открытия / и потом ещё одного chroot’а в его подпапку. После этого достаточно сделать fchdir в открытый дескриптор и потом «cd ..» столько раз, сколько нужно.