Изменения

Перейти к: навигация, поиск
м
Нет описания правки
Итак, задача - задача — не перезагружаясь, перейти в такой режим, в котором ваш Linux не будет использовать диски совсем. Чтобы, например, иметь возможность с ними что-то поделать.
Главная загвоздка - загвоздка — это init, первый (PID=1) и неубиваемый процесс в системе, запущенный с корневого раздела. Разделы, кроме корневого, отмонтировать обычно довольно легко - легко — достаточно убить все процессы в системе. Однако init убить нельзя, большую часть сигналов, в том числе SIGKILL, он игнорирует (PID'у PID’у 1 можно игнорировать SIGKILL), а если у вас и получится его убить - убить — ядро запаникует и остановит работу системы.
Так что init нужно, не выключая, "вытащить" «вытащить» в другой корневой раздел. Способ это сделать, по сути, один - один — подменить запущенный образом exec()омдругого. Однако как заставить init сделать exec() того, чего нам надо? А вот так - как — все init'ы init’ы (даже несчастный systemd) умеют перезапускать себя. И хотя это сделано вовсе для другой цели, а именно - именно — для применения обновлений в самом init или системных библиотеках (libc?) без перезапуска системы - системы — это вполне можно заюзать в наших целях... целях… достаточно поверх /sbin/init смонтировать свой файл --bind'омbind’ом. После чего команда "init U" «init U» перезапустит вместо init'а init’а наш файл. Только сам инит надо куда-то скопировать, типа cp /sbin/init /sbin/init1, мы ж его только что перемонтировали.
А дальше нужно переключать корень. Это, на самом деле, не такое уж и сложное действие - если нет задачи сделать корнем Мне изначально хотелось его переключать обратно rootfs в initramfs (это тоесть rootfs), куда чтобы «аварийная система» находилась именно там — типа, так надёжнее — загрузился сразу dropbear и всё, никуда уже не уходит и на машину можно зайти по ssh. Если не заморачиваться именно выходом в initramfs распаковывается), то для переключения корня переключить корень ещё проще — достаточно сделать pivot_root - . pivot_root — это такая хитрая фичатакой хитрый системный вызов, которая который подменяет корень / другой точкой монтирования, а текущий / помещает в подпапку нового, причём (!) насколько я понял, это единственный системный вызов, нагло подменяющий корень всем процессам в системе! В , в коде ядра прямо так и написано - foreach написано — «foreach (по всем задачам) { если корень == предыдущему, подменить на новый }».
Никаким другим образом подменить корень другому процессу, видимо, нельзя. pivot_root раньше использовался initrd для переключения в "реальный" корень - «реальный» корень — рамдиск при этом попадал в подпапку корня и потом отмонтировался init'омinit’ом, запускаемым exec()ом на месте "старого"«старого». Но сейчас это уже не так, initrd теперь называется initramfs, и работает из специальной НЕотмонтируемой и НЕперемещаемой файловой системы "rootfs"«rootfs», которая, по сути, представляет собой просто tmpfs (живущий в памяти), но по-другому названный, монтируемый в / автоматически при старте системы, и недоступный для монтирования потом. В него распаковывается содержимое initramfs (gzip -d | cpio -i, только внутри ядра), в качестве PID 1 оттуда запускается /init, а из-за того, что rootfs НЕотмонтируемый и НЕперемещаемый (так сделано тупо для удобства программирования) отличается метод переключения в новый корень - корень — с помощью утилиты run-init (в терминах klibc) или switch_root (в терминах busybox) initramfs удаляет всё содержимое rootfs, потом монтирует (на самом деле перемещает, mount --move) новый корень прямо поверх старого, а потом делает в него chroot и выполняет там /sbin/init.
Именно поэтому на свежих ядрах в /proc/mounts корень (/) смонтирован всегда дважды, один раз как rootfs, и второй раз как обычный корень. В системе не остаётся Просто старый rootfs пустой, процессов, видящих старый пустой rootfsего, в системе уже не остаётся, а смонтировать его куда-то ещё раз невозможно (невозможно — есть явный запрет его повторного монтирования в коде ядра), ядра — поэтому он висит и практически не отсвечивает. Пустой tmpfs, вроде как говорят, памяти почти не занимает, и оверхед нестрашный.
Однако, если Но! Если после завершения работы initramfs оставить работающий процесс, запущенный из старого корня - корня — возможность туда попасть у вас сразу же появляется, достаточно . Достаточно сделать cd (а то и chroot) /proc/<PID>/root (где PID - это PID того самого процессаили chroot туда же, если содержимое осталось). Если же это dropbear - вы этот процесс вообще можете dropbear — тогда зайдя в него зайти путём по ssh, вы тоже окажетесь в старом корне. Это, кстати, да - да — ещё одно откровение: разные процессы в Linux могут видеть файловую систему по-разному, причём при монтировании нескольких файловых систем поверх одной точки - даже в рамках одного namespace'аnamespace’а! (То есть в разных namespace'ах изоляция точек монтирования вообще полнаяnamespace’ах-то ладно, это же часть функционала контейнеров), полностью изолирующая иерархии ФС друг от друга, но в рамках одного — вообще забавно.
И если выйти Конкретно, в запущенном из старого корня процессе получается так:* cd / -> попадаем в "старый корень" (rootfs), получается очень забавно .* ls /.. - > листается новый корень будет виден в "/..", либо "* ls /<любая_папка>/..". То есть, "ls /.." покажет вам содержимое нового корня-> листается новый корень. Но при этом cd в него таким образом сделать будет нельзя - по "* cd /.." вы останетесь -> всё-таки остаёмся в старом корне (потому что, видимо, он уже "открыт"). Зато /.. можно переместить обратно в подпапку rootfs путём команды "* mount --move /.. /root"-> самое интересное. Новый корень перемещается в подпапку rootfs /root, а остальная система становится как бы запущенной в chroot’е.
ОчевидноА из chroot’а, как известно, можно вылезти — нужно только получить открытый дескриптор на директорию, находящуюся ВНЕ текущего корня. Делается это путём открытия / и потом ещё одного chroot’а в его подпапку. После этого достаточно сделать fchdir в открытый дескриптор и потом «cd ..» столько раз, сколько нужно. Отсюда у меня созрело такое вот решение. Сначала правим initramfs (Debian: /usr/share/initramfs-tools/):* Чтобы вместо перемещения /dev, /dev/pts, /proc в новый корень (/root) он их туда биндил (<tt>mount --bind /dev /root/dev и т.п.</tt>).* Чтобы вместо run-init/switch_root, удаляющего содержимое старого корня, он просто делал<br /><tt>mount --move /root /</tt><br /><tt>chroot /.. /sbin/init < dev/console > dev/console</tt>* Также сразу включаем в initramfs dropbear, и делаем, чтобы чтоон НЕ выключался при переходе в новый корень. На Debian’е это делается просто, apt-то делать get install dropbear, потом добавляем DROPBEAR=y в /etc/initramfs-tools/initramfs.conf, и потом в /usr/share/initramfs-tools/scripts/init-bottom/dropbear убираем строчку с системой kill. Заодно меняем ему порт с 22 на, скажем, 1022 — в таком режимеscripts/init-premount/dropbear добавляем ему опцию «/sbin/dropbear -p 1022». Ключи при установке dropbear сгенерятся сами и подложатся в /etc/initramfs-tools/root/.ssh — их оттуда надо утянуть, нужен какойчтобы потом иметь возможность зайти в систему. После чего можно сделать <tt>update-initramfs -u -k `uname -r`</tt> и загрузиться в новую систему (старый initramfs на всякий случай лучше скопировать и положить рядом — вдруг чего-то легковесный рамдиск с базовыми утилитами не то натворили). Ну а чтобы вытащить наружу init, я написал [http://svn.yourcmc.ru/viewvc.py/vitalif/trunk/unchroot- обычно с busybox'омinit/ пару утилиток] — unchroot-init и init-stub. Первая вышеописанным путём сбегает из chroot’а и делает там exec /sbin/init-stub, утилитами для вторая — тупо висит и игнорирует все сигналы, кроме SIGCHLD и SIGUSR1 — по первому добивает зомби-процессы, по второму — делает «chroot /..» (переход в новый смонтированный поверх старого корень) и запускает там exec’ом /sbin/init --init — это чтобы вернуться в нормальный режим работы . Наверное, ещё можно было туда прикрутить перезапуск того же dropbear’а, если он вдруг упадёт, но фиг с ФС (разные mkfsним. Итого, чтобы вытащить «наружу» init:* ssh’имся в запущенный из rootfs dropbear и делаем там: <br /fsck><tt>mount --move /.. /root</tt><br /><tt>cp /root/<путь>/init-stub /sbin/init-stub</tt>* потом в нормальной системе говорим: <br /><tt>cp /sbin/init /sbin/init1</tt><br /><tt>mount --bind /<путь>/unchroot-init /sbin/init</tt><br /><tt>/sbin/init1 U</tt> А дальше уже дело техники — находясь в новой системе или в chroot /root, останавливаем запущенные процессы — что можем, через /etc/rc0.d, что не можем — просто убиваем. Главное — не отключить сеть и не сделать случайно reboot :). Дальше есть ещё один маленький нюанс — запущенный udev держит «базу данных» текущих девайсов в /run/udev (по крайней мере в дебиане это так) — её нужно утащить (тупо mv) в rootfs — если быть точным, то в /dev/.udev, чтобы она потом не потерялась при отмонтировании tmpfs от /root/run, и чтобы при выходе из «аварийного режима» udev спокойно перезапустился, забрал базу из /dev/.udev и опять-таки пересунул её в /run/udev. И после всех этих манипуляций — всё, файловые системы, в том числе корень /root, можно отмонтировать. Единственный прикол — у меня почему-то получалось так, что dropbear'омиспользовал /root/dev/null, если нужен лёгкий ssh сервери /root/dev отмонтироваться не хотел.ХЗ, как так получилось — я до конца не понял. Но это не так страшно, /root/dev можно сделать umount -l, всё равно это devtmpfs, а не реальная ФС на диске. Соответственно, чтобы потом вернуться в нормальный режим работы, нужно:* Подмонтировать /root.* Забиндить/смонтировать в него /dev, /dev/pts и /sys.* Сделать mount --move /root /.* И сказать kill -USR1 1 — chroot в новый корень заглушка сделает уже сама. С этого момента всё будет выглядеть, как нормальная загрузка системы.{{wl-publish: 2013-12-18 16:46:20 +0400 | VitaliyFilippov }}

Навигация