Pull to refresh
Selectel
IT-инфраструктура для бизнеса

Как изучать исходные тексты

Reading time 5 min
Views 14K
Бувально в тот момент, когда я (не очень успешно) вычитывал ошибки и опечатки в предыдущем посте, bobry предложил обсудить, как сделать в консоли историю (которая, Shift-PgUp).

Очевидным методом сделать что-то связанное с терминалами — посмотреть, как сделано у других и сделать так же. В процессе изучения этого мы обратили внимание на интересную особенность: некоторые программы, показывая содержимое, восстанавливают экран до запуска приложения (mc, vim, nano, less и т.д.). Кроме того, при их запуске исчезает (в xterm/gnome-terminal) скролл-бар.

Для изучения «каким образом» было решено остановиться на MC, как самом старинном (и не зависящем от ncurses) приложении.

Далее идёт роматичная история о том, как mc делает toggle_panel() с большим количеством цитат из исходного кода.

Заодно, читатель сможет посмотреть, как выглядит процесс «посмотри в исходниках».

Итак, исходный текст MC. Известно, что экран «сзади» можно увидеть по Ctrl-O. Комбинация кнопок появилась ещё в старинном Norton Commander, откуда её позаимствовали все последующие консольные клоны NC: Dos Navigator, Volkov Commander, Far Navigator и т.д.

Скачали сырцы (apt-get source mc), глянули во внутрь.

Довольно быстро мы нашли файл keybind.c, где легко увидели, что комбинации Ctrl-буква кодируются как «C-L» (в нашем случае «C-o»). Простой массив enum'ов (или это define'ы? Не важно), искомая строчка:

    { XCTRL ('o'),  CK_ShowCommandLine,               "C-o" },

Дальше (мы знать не знаем, архитектуры MC, и знать не особо хотим), мы просто используем утилиту grep для того, чтобы найти, где эта функция есть: grep CK_ShowCommandLine -A3 -B3 -r *

Искомый файл — main.c

main.c:    case CK_ShowCommandLine:
main.c-        view_other_cmd ();
main.c-        break;

и рядом же:

viewer/actions_cmd.c:    case CK_ShowCommandLine:
viewer/actions_cmd.c-        view_other_cmd ();
viewer/actions_cmd.c-        break;

И вьюер. и сам mc испольузют одну и ту же функцию: view_other_cmd. Аналогичный греп, находится файл cmd.c с искомой функцией:

void
view_other_cmd (void)
{
    static int message_flag = TRUE;

    if (!xterm_flag && !console_flag && !use_subshell && !output_starts_shell) {
        if (message_flag)
            message (D_ERROR, MSG_ERROR,
                     _(" Not an xterm or Linux console; \n"
                       " the panels cannot be toggled. "));
        message_flag = FALSE;
    } else {
        toggle_panels ();
    }
}

Глаз немного режет старинный стиль форматирования текста, но…

Итак,
  1. Искомая функция toggle_pannels(). На самом деле, мы тут же ринулись смотреть её, но сейчас будем более последовательны и внимательно прочтём случай ошибки (обратите внимание на изящную эмуляцию замыканий в Си — использование static у переменной для того, чтобы выводить сообщение об ошибке только один раз за всё время работы программы).
  2. Ошибка выводится если… если у нас НЕ xterm и НЕ консоль. Часть связанная с subshell нас не интересует, а вот насчёт отдельной проверки xterm'а поставим галочку.

Ищем toggle_pannels(). Файл execute.c. Функция большая, процитирую интересное место.

    tty_reset_screen ();
    do_exit_ca_mode ();
    tty_raw_mode ();
    if (console_flag)
        handle_console (CONSOLE_RESTORE);
  1. tty_reset_screen — допустим
  2. do_exit_ca_mode — о, интересно
  3. tty_raw_mode — это просто переключение режимов ввода символов
  4. Интересно, если у нас консоль, то делается handle_console...


Что такое ca_mode? Мелкая заминка (файл не в src, а в lib), tty/win.c. рядом же обратная ей do_enter_ca_mode:
void
do_enter_ca_mode (void)
{
    if (xterm_flag) {
        fprintf (stdout, /* ESC_STR ")0" */ ESC_STR "7" ESC_STR "[?47h");
        fflush (stdout);
    }
}

void
do_exit_ca_mode (void)
{
    if (xterm_flag) {
        fprintf (stdout, ESC_STR "[?47l" ESC_STR "8" ESC_STR "[m");
        fflush (stdout);
    }
}

Итого — переключение странного режима, которого мы не знаем. Ассоциированный с xterm. Быстрогугль подсказывает, что это «Use Alternate Screen Buffer»:

Xterm maintains two screen buffers. The normal screen buffer allows you to scroll back to view saved lines of output up to the maximum set by the saveLines resource. The alternate screen buffer is exactly as large as the display, contains no additional saved lines. When the alternate screen buffer is active, you cannot scroll back to view saved lines. Xterm provides control sequences and menu entries for switching between the two.


Ок. Понятно. У xterm'а есть специальный esc-код.

А у linux? Идём в консоль linux (настоящую, Ctrl-Alt-F1), пробуем vim — при выходе мы видим предыдущее содержимое на экране. less… видим. nano… видим. То есть linux (согласно console_codes) этого не поддерживает. Ага, ясно. А mc? А mc работает, засранец! Как? Почему? Идём в исходный текст mc обратно, смотрим… А что делает handle_console?

Ищем… (cons.handler.c):

void
handle_console (unsigned char action)
{
    (void) action;

    if (look_for_rxvt_extensions ())
        return;

#ifdef __linux__
    handle_console_linux (action);
#elif defined (__FreeBSD__)
    handle_console_freebsd (action);
#endif

Хм… Оказывается, в linux и в FreeBSD это обрабатывается по-разному. (А как же solaris? Нету больше вашего solaris, Oracle на пульт валенок бросил.)

Смотрим handle_console_linux, через него console_save. Что мы видим? MC форкает отдельный процесс с названием cons.saver, который делает ioctl, чтение и запись в странные устройства /dev/vcsa*. Что за устройства?

О, новый, дивный мир


Оказывается, в linux есть псевдоустройства, которые соответствуют памяти CGA (VGA) адаптера в текстовом режиме, где данные хранятся в виде массива

struct {
char Char;
char Attrib;
}

Да-да, тот старый давно забытый DOS, с прямым доступом в видео-память. Проверяем — действительно, hexdump файлов вполне показывает нам содержимое экрана. Заметим, для доступа к устройству нужно иметь права на это. Обычно это делается так: на файл устройства выставляют группу, на файл выставляют owner'а, входящего в эту группу и выставляют ему sgid-бит.

В Debian cons.saver имеет sgid бит для группы tty (общая группа для доступа к псевдотерминалам), а в centos — для специальной группы vcsa. В принципе, соглашусь с подходом RHEL/CentOS, это более безопасно.

Итак, специальный файл… То есть MC с помощью хитрого хака читает содержимое экрана, а когда нужно, пишет его обратно…

Но у виртуальной машины консоль не на VGA-адаптере! Это чистой воды последовательный порт. И простая проверка подверждает подозрения — на всех виртуалках vcsa девственно чистый, потому что у нас нет VGA-адаптера. То бишь, видеокарты.

Что делать? Ну, для начала проверить, что будет делать mc в ситуации с последовательным портом. Как мы знаем, нет никакой разницы между псевдотерминалом UNIX98 и последовательным портом. Идём обратно в настоящий linux (консоль), логинимся по ssh на любой сервер, запускам mc, и, вуаля — not supported.

Таким образом, мы, делая консоль на последовательном порту, в принципе не можем предоставить аналог vcsa — и хак в mc насчёт ctrl-o не может работать. В принципе.

А как же xterm?


Реализовать ESC-код для xterm'а было бы просто. Но… Мы не имеем доступа к содержимому виртуальных машин, мы не можем менять среду окружения пользователям — а значит, по-умолчанию, linux ожидает, что на последовательном порту у него тип терминала linux (TERM=linux) и реализовывать функции xterm/xrvt нет смысла — их всё равно не будут использовать.

На всякий случай проверяем, каким образом mc узнаёт про существование xterm-расширений…

    const char *termvalue;

    termvalue = getenv ("TERM");


Увы — переменная среды окружения (а я надеялся на query string). Ну точно нельзя. Увы.

Кстати, ровно тот же хак (но с поправкой на особенности архитектуры) используется и в FreeBSD. Увы.

Мораль?


У нас не будет полноценно работать Ctrl-O в нашей замечательной консоли в линуксе. Увы, никогда. А в остальном было интересно.

… Кстати, если у вас есть живой сервер, и он жив, вы можете удалённо посмотреть, что у него там на консолях происходит — cat /dev/vcsa*. А особые фанаты могут сделать watch и наблюдать, как на консолях работают другие.
Tags:
Hubs:
+46
Comments 47
Comments Comments 47

Articles

Information

Website
selectel.ru
Registered
Founded
Employees
501–1,000 employees
Location
Россия
Representative
Влад Ефименко