Я уже писал и про команду su и про команду screen. Теперь, видимо, пришло время написать про их взаимодействие. Конечно же, не без повода. А дело было так.

Я, как водится, опять попал в ситуацию, когда, из-за нестабильной работы используемого ПО, внезапно и довольно часто стало прерываться SSH соединение с удаленным компьютером[1]. Как следствие, копирование больших файлов, которое является одной из самых востребованных мною операций, вновь превратилось в азартную игру с непредсказуемым результатом. И мне ничего не оставалось, как использовать команду screen. Как раз тут меня и ждал сюрприз.

Ранее я немного описывал инфраструктуру, используемую для развертывания и эксплуатации self-hosted сервисов. Важной особенностью, с точки зрения сложившейся ситуации, является то, что сервисы запускаются при помощи docker контейнеров, каждый - из под своего специально созданного пользователя, и, при этом, данные, создаваемые и/или используемые сервисом, сохраняются в подмонтированном к контейнеру каталоге, владельцем которого является все тот же пользователь. Поясню более предметно.

Для скачивания торрентов я использую docker контейнер с transmission. Скачанные файлы сохраняются в физическом каталоге, который подмонтирован к контейнеру. Этот каталог, в свою очередь, является подкаталогом домашнего каталога пользователя, от имени которого запускается контейнер. Таким образом, для того, чтобы скопировать скачанные файлы в свою фильмотеку, или же, в музыкальную коллекцию, мне надо либо работать из под root-а, либо работать от имени пользователя, являющегося владельцем каталога. Нетрудно предположить, что чаще всего я работаю от имени пользователя-владельца - правила хорошего тона не поощряют работу от имени root-а. Для смены текущего пользователя я использую команду su:

su - username

после чего запускаю midnight commander и наслаждаюсь удобством его UI.

Теперь, чтобы копирование файлов не прерывалось из-за разрыва соединения, мне надо было запустить команду screen. Ну и так получилось, что я сначала переключился под нужного пользователя и, перед запуском mc, вызвал screen. И я увидел то, чего увидеть не ожидал:

Еще один интересный факт заключался в том, что если я переключался под root-а и потом вызывал screen, ошибки не было! Как всегда, в случаях таких вот неожиданностей, разбор причин обогатил меня знаниями, которые, наверное, следовало бы получить заранее. Итак.

Первое что мне удалось выяснить - это причина возникновения ошибки[2]. Дело в том, что, подключившись под определенным пользователем по протоколу SSH, и открыв, таким образом, терминал, я сделал используемого пользователя владельцем нового терминала. И, не смотря на то, что при помощи команды su я сменил пользователя, я, тем не менее, остался в том же самом терминале и владелец его не поменялся. Запуск же команды screen предполагает, что пользователь, запустивший ее, обладает всеми мыслимыми правами на терминальное окно, в котором все это безобразие и происходит. Обычно, такими правами обладает владелец и... еще пользователь root. А тот пользователь, под которого я переключился - не обладает. И именно поэтому, я и получал эту ошибку только в том случае, когда переключился под другого пользователя, который не является root-ом. Если же я передключился под root-а - все было в порядке!

Как же можно выкрутиться из создавшейся неприятной ситуации? Конечно, можно начать раздавать направо и налево права, включать пользователей в административную (root) группу, группу управления терминалами (tty), менять права доступа (chmod), но это, на мой взгляд, все из разряда несколько истеричной реакции - ослаблять таким образом защищенность системы можно, но не рекомендуется. Тем более, что, как правило, всегда (ну, или, почти всегда) находится какое-нибудь вполне приемлемое альтернативное решение. Не стал исключением и описываемый случай.

Довольно быстро после начала поисков в интернете был найден ответ, согласно которому, для того, чтобы исправить ситуацию надо было воспользоваться волшебной командой:

script /dev/null

Вот когда я вижу такие решения, я понимаю, почему люди не переходят с такой ужасной и ругаемой всеми Windows на замечательный и чрезвычайно превозносимый некоторыми Linux - ни у одного нормального пользователя не может возникнуть чувства уверенности в завтрашнем дне при виде такого очевидного решения описываемой мною проблемы. Тут, правда, есть заковырка - я (почему-то) считаю, что являюсь не совсем обычным пользователем. Поэтому я попытался понять, что же это за абракадабра написана и почему она должна помочь[3]. И вот, что мне удалось выяснить.

Во-первых, script - это специальная команда для записи производимых в терминале действий в отдельный файл, который потом можно будет просмотреть. Грубо говоря, я бы назвал это стенограммой, но официально используемый английский термин - typescript, что дословно переводится как "машинопись". Обычно script используют для того, чтобы задокументировать факт того, что какие-то определенные действия были выполнены. Эта команда может быть вызвана с различными ключами, если кому интересно, может почитать документацию, хотя бы тут.

Во-вторых, в той же документации есть фраза, которая раскрывает всю суть происходящего волшебства. Не поленюсь и приведу ее тут:

The script ends when the forked shell exits

Или вот тут тоже, более явно:

If the variable SHELL exists, the shell forked by script will be that shell.

Эти фразы дают нам понять, что команда script запускает командную оболочку (shell), активность в которой мониторится и записывается в файл. И для этого создается виртуальный терминал, в котором и работает вновь запущенная оболочка. И, что важно для нас, у этого терминала владельцем будет тот пользователь, под которого мы чуть ранее переключились при помощи команды su. Соответственно, при вызове screen проблем с правами возникать более не должно.

Осталось совсем чуть-чуть: понять что такое /dev/null? На самом деле, на фоне всего остального, это совсем не сложно. Команда script пишет все, что происходит в ее виртуальном терминале в файл с названием typescript. Но это поведение можно изменить, явно указав, в какой файл мы хотим производить запись. Например, так:

script tty-record.txt

Такая команда будет вести запись в файл с именем tty-record.txt. В нашем же случае, после команды script указано устройство /dev/null, то есть, запись будет производиться в устройство с незамысловатым названием null, а именно, "в никуда". Ну в самом деле, зачем нам сохранять запись наших действий, которые мы собираемя производить в терминале? Нет, если вам она нужна, пожалуйста, пишите, меня же вариант с null устраивает полностью.

Подведем итог наших изысканий. Переключившись под другого пользователя, мы не можем вызывать команду screen, так как терминал, в котром мы ее вызываем, на самом деле, принадлежит первоначальному пользователю, создавшему его, например, при соединении по SSH. Поэтому мы вызываем команду script /dev/null, которая создает свой виртуальный терминал и уже для этого виртуального терминала мы можем использовать команду screen, так как ее использование теперь не будет противоречить правам владения - виртуальный терминал создается под пользователем, от имени которого мы работаем в данный момент времени. Ну а чтобы не захламлять диск ненужными стенограммами наших действий, мы пишем активность в устройство с именем null. Побочным эффектом такого алгоритма работы является то, что для вовзрата к статус-кво надо будет набрать на одну команду exit больше: сначала закрыть терминал, созданный командой screen, потом завершить работу терминала, созданного командой script, и только потом вернуться к своему первоначальному пользователю.

И уж совсем в завершении. Всех этих заморочек можно было избежать, если бы я сначала запустил команду screen и уже потом, в созданном ею терминале, переключился бы под нужного пользователя - не было бы никаких проблем с правами владельца терминала. Правда, так будет работать только в простейшем случае, когда вы создаете лишь один сеанс (терминал) screen, но, если честно, этот случай - самый распространенный. Такие вот дела.


  1. об этом я тоже как-нибудь подробно расскажу ↩︎

  2. Тут надо понимать, что когда я пишу "удалось выяснить" это не значит, что я сам докопался до истины - просто умные люди посредством замечательных статей на различных ресурсах учат таких "быстрых" админов, как я ↩︎

  3. Как говаривал сын одного очень известного фокусника, тоже фокусник: "Чтобы фокус получился, надо обязательно дунуть. Потому что, если не дунуть, то никакого чуда не произойдет". ↩︎