Успех с Aboriginal, Alpine и Debian

Три месяца назад в « Дорожной карте № 5» я написал, что OSH станет лучшей оболочкой для создания дистрибутивов Linux . Он будет запускать существующий код, включая скрипты bash, но он более строгий и легкий для отладки.

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

OSH теперь может запускать тысячи строк сценариев оболочки, которые создают три дистрибутива: Aboriginal LinuxAlpine Linux и Debian. Этот пост описывает, что я сделал, и техническую работу, которая была задействована.

Обзор недавнего прогресса

Некоторое время я не писал о дистрибутивах Linux. Что случилось?

OSH удалось запустить abuild -h в октябре, но его скорость синтаксического анализа сделала отладочные сессии неприятными. На быстрой машине потребовалось более 1600 миллисекунд, чтобы разобрать abuild!

Поэтому я поставил две задачи в стек, в общей сложности три:

  1. Запустите abuild из Alpine Linux.
  2. Оптимизируйте синтаксический анализатор так, чтобы работа над ним не была болезненной.
  3. Исправляйте ошибки в парсере перед его оптимизацией.

Два релиза с октября выбили # 3 и # 2 из стека:

  1. В OSH 0.2 я исправил ошибки, выявленные путем пыток парсера миллионными линиями оболочки. Я также представил тесты парсера.
  2. OSH 0.3 ускорил парсер на 6-7x. Я представил дополнительные тесты, в том числе те, которые измеряют скорость выполнения.

Теперь OSH может анализировать abuild примерно за 250 миллисекунд. Это все еще слишком медленно, но это не препятствует прогрессу.

Я планирую выпустить OSH 0.4 в конце этого месяца. Он сможет запускать не только abuild , но и скрипты оболочки из Aboriginal Linux и Debian.

После этого стек снова будет пустым. Мне пришлось побрить яков, но я не упустил из виду цель!

Что такое Linux Distro?

Я не понимал, как дистрибутивы Linux работали до недавнего времени. Полезно подумать о том, что они имеют (по крайней мере) эти четыре компонента:

  1. Набор исходных архивов из исходных источников: например, GNU, Linux, Apache или LLVM.
  2. Система сборки «meta», которая превращает исходные файлы в двоичные пакеты . Эта система сборки неизменно использует shell-скрипты. Иногда используется GNU make; иногда используется Python; но всегда есть сценарии оболочки.
  3. Скрипт для создания корневой файловой системы - то есть для «начальной загрузки» системы, чтобы он мог создавать свои собственные пакеты.
  4. Менеджер пакетов , что позволяет конечным пользователям устанавливать бинарные пакеты. Для Debian-производных систем это apt; для систем Red Hat (CentOS, Fedora и т. д.), это так yum.

В чем разница между Distros?

Я доволен разнообразием трех дистрибутивов, с которыми я работал, потому что это дает мне уверенность в том, что OSH работает:

  1. Debian: возможно, самый популярный дистрибутив, и один из самых старых. Он имеет множество производных, таких как Ubuntu. Сценарий debootstrap, который я запускал, обычно работает под тире , что является одной из самых несовместимых оболочек.
  2. Alpine Linux: «современный» дистрибутив для встроенных систем и контейнеров . Он запускает ядную золу.
  3. Aboriginal Linux: образовательный проект с минималистским / встроенным уклоном. Однако он работает под bash и использует много «bash-isms».

Поэтому я не только тестирую скрипты оболочки разными авторами, но и тестирую OSH на совместимость со сценариями, написанными для разных диалектов оболочки .

Вот еще некоторые сведения об этих проектах и ​​подробные сведения о том, что я сделал:

Debian

debootstrap собирает корневую файловую систему Debian из .deb пакетов. Грубо говоря, .deb - это архивы бинарных файлов, скриптов и метаданных. Я проанализировал debootstrap с OSH еще в октябре 2016 года.

Это ~ 2600 строк оболочки (выдержка). Несколько лет назад я работал с этим скриптом, и я помню, что это выглядело страшно . Были странные заклинания, которые я не понимал. Теперь это легко читать, что, я думаю, означает, что я провел слишком много времени с оболочкой :-)

Что теперь работает: я использовал OSH для создания изображения Xbio Ubuntu, chroot в нем и запускал команды. В приведенных ниже разделах описаны исправления, необходимые для выполнения этой работы.

Alpine Linux

Alpine Linux начинался как дистрибутив для встроенных систем, таких как маршрутизаторы, но теперь он также используется для контейнеров в облаке. Docker, Inc. спонсирует его, и на нем основан postmarketOS .

  • Он использует musl libc и busybox, а не GNU libc и GNUcoreutils / findutils / etc. Первые проекты пользуются популярностью во встроенных системах.
  • В целом, Alpine и Debian имеют аналогичную архитектуру, но Alpine меньше, ориентирована на безопасность и имеет более последовательный стиль.
  • abuild составляет ~ 2700 строк оболочки, которая создает .apkпакеты и метаданные. (выдержка).
  • alpine-chroot-install выполняет ту же задачу, что и debootstrap. Он собирает системный образ из набора бинарных пакетов.

Что теперь работает:

  1. Я запускал alpine-chroot-install с OSH и успешно создавал системный образ.
  2. Я ввел изображение с помощью chroot и создал OSH в этой среде. Эта сборка OSH связана с musl libc.
  3. Я построил три .apkпакета с abuild, работающими под OSH-musl.
  4. Я побежал abuild verifyпроверить, что пакеты выглядят разумно.

Aboriginal Linux

Аборигенная Linux - это не дистрибутив, как таковой. Это образовательный проект, который выглядит как дистрибутив. Он отвечает на вопрос: каково наименьшее количество пакетов, которые создадут систему Linux, которая сможет перестроить себя ?

Проект теперь не функционирует . Но код по-прежнему работает, и я все же считаю его интересным, например, с точки зрения безопасности.

Это ~ 3700 строк bash (выдержка). Это был первый проект, который я проанализировал с OSH .

Что теперь работает:

  1. Я создал i686 цель с помощью OSH. Это создает полное изображение системы из исходного кода . Напротив, debootstrapсобирает изображение из двоичных пакетов.
  2. Я загрузил полученный образ в QEMU и получил приглашение оболочки!

Итак, я тестировал OSH на разнообразном наборе сценариев оболочки, найденных в дикой природе, и исправил то, что было необходимо для их запуска.

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

Добавлены функции

Какие функции отсутствовали?

Поддержка трассировки

У некоторых ошибок, с которыми я столкнулся, были очевидные причины. Например, OSH будет бросать, NotImplementedErrorкогда используется программа ${s:1:2}(резка строк). Прохождение этой ошибки с помощью нарезки было простым.

Другие ошибки требовали отладки тысяч строк сценариев оболочки других людей . Поэтому мне нужно было узнать больше о bash и отладке. Этот совет по использованию xxtrace помог мне. В bash вы можете установить $PS4переменную так, чтобы трассы включали имя файла и номер строки.

Поэтому я имитировал эти функции отладки в OSH:

  • Внедрите set -x / xtraceс $PS4поддержкой.
  • Добавьте поддержку $SHELLOPTS, чтобы вы могли наследовать xtrace. Сценарии оболочки часто вызывают другие сценарии оболочки, и это способ bash сохранить -x все вызовы.
  • Добавьте переменные, которые полезны в PS4строке:, $LINENOи мои собственные $SOURCE_NAME.

Обратите внимание, что bash на самом деле имеет отладчик bashdb! Описывать, как это работает, будет другой пост. Короче говоря, он использует крючки, указанные со trapвстроенным, а также несколько $BASH_* переменных.

Параметры Shell для строгого поведения

Повторяющейся темой стало расслабление строгого поведения OSH, чтобы разместить обычное использование оболочки. Тем не менее, я добавил возможность отказаться в строгое поведение, с set -o strict-control-flow, strict-array и strict-errexit.

Я затрону эту тему в другом блоге, но не стесняйтесь оставлять комментарии, если вам интересно.

Капитальный ремонт разделения и оценки слов

POSIX имеет причудливые правила для $IFSпеременной, которая определяет:

  1. Как неупомянутые слова разделяются, и
  2. Как read встроенное разделяет поля.

Я переписал багги-IFS-расщепление на основе регулярных выражений с помощью явного конечного автомата . Это интересный фрагмент кода, который я могу объяснить в другом сообщении в блоге. Он находится в core / legacy.py. Было много красных зеленых тестов.

Два типа С-экранированных строк

echo -e 'foo\n'и $'foo\n' - оба способа писать строки с экранированным С. Их связь такая же, как и связь между [и [[ - первая динамически анализируется, а последняя статически анализируется .

(Например, динамический синтаксический анализ позволяет это:, char=n; echo -e "1\\${char}2" но статический разбор не делает.)

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

Удаление префиксов Glob и суффиксов с помощью API POSIX

Это еще одна особенность, которая затрагивает некоторые компьютерные науки. Я обнаружил, что семантика, возникающая с помощью ksh, не может быть эффективно выражена с помощью POSIX API:

  • POSIX fnmatch()выполняет сопоставление строк в стиле glob, но не возвращает позицию совпадения.
  • POSIX regexec()возвращает позиции соответствия, но не поддерживает не-жадное сопоставление, как это делает API регулярных выражений Python.

Теоретически API Python должен иметь возможность эффективно выражать семантику ${s%suffix} vs. ${s%%suffix}, поэтому OSH использовала стратегию перевода глобусов в регулярные выражения Python . Например, выражение ${s%%*suffix}может быть реализовано с помощью регулярного выражения .*?(suffix).

Однако abuild использует классы символов в глобусах, например ${i%%[<>=]*}, которые не просто переводить.

Поэтому я переопределял эти операторы, используя обычный, неэффективный алгоритм: линейное число вызовов fnmatch(), по одному для каждой позиции в строке! (в худшем случае)

Это делает общий алгоритм квадратичным. Если fnmatch() это не линейно, чего часто нет, то отбрасывание glob-префиксов и суффиксов будет еще медленнее, чем квадратично.

Однако эта проблема не возникает на практике, так как все оболочки используют медленный алгоритм. Конечно, Масло будет предоставлять функции манипуляции струнами, которые не являются теоретически медленными. Я хочу, чтобы язык был безопасным для использования в состязательных контекстах.

Незначительные особенности

Для запуска сценариев дистрибутива потребовалось несколько других функций оболочки. В большинстве случаев я уже сделал сложную часть: представляю код с деревом синтаксиса без потерь. Реализация часто «выпадает» после выбора хорошего представления.

  • Нарезка струн и массивов: ${s:1:2}и ${a[@]:1:2}.
  • Подстановка процессов: diff <(sort left.txt) <(sort right.txt). Эта функция по своей сути является отслаивающейся, поскольку она не относится wait()к разветвленному процессу, и она не была установлена $!до bash 4.4.
  • typeВстроенный без -t. К сожалению, abuild соответствует выходу typeс регулярным выражением.
  • Больше testвстроенных:
    • -Lи -hявляются псевдонимами, чтобы проверить, является ли путь символической ссылкой.
    • [ -t 1 ]чтобы проверить, является ли stdout TTY. Там нет цвета в abuild без этого!
    • -ntи -otсравнить временные метки файлов.

Shell WTF

Повторное выполнение этих причуд оболочки было забавным и удручающим. Как покаяние, я поддерживал wiki-страницу Shell WTF(что плохо организовано).

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

Исправлены ошибки

В дополнение к реализации функций я также обнаружил и исправил ошибки в OSH.

Использование дескриптора файла

Насколько мне известно, оболочка должна обрабатывать дескрипторы файлов иначе, чем любая другая программа Unix. Он не может открыть любые файлы в диапазоне дескрипторов 3-9, потому что скрипты оболочки могут использовать их напрямую.

  • Основная программа и source«d-скрипты» теперь сразу же сбиты open()с пути dup2().
  • Я исправил сбой в таких заявлениях echo hi 6>&1, которые использует debootstrap.

Чтобы отладить эти проблемы, я использовал /proc/$$/fd/механизм, упомянутый в OSH Runs Real Shell Programs. Это хороший способ показать состояние дескриптора файла процесса.

Ошибки, связанные с буферизацией CPython

В самой опасной части проекта я упомянул о нескольких трудностях с использованием CPython для написания оболочки Unix.

Я столкнулся с другой проблемой: Python выполняет собственную буферизацию ввода-вывода файлов . Я считаю, что это на вершине буферизации libc, хотя я не заглядывал в нее глубоко.

  • sys.stdout.flush()требуется после typeпечати его вывода; в противном случае $()может быть неправильно оценена. Наконечник шляпы, чтобы timetoplatypusупомянуть об этом в отношении dirsвстроенного.
  • readВстроенный нельзя использовать в Python f.readline(). Дескриптор, который лежит в основе sys.stdinобъекта файла, изменяется при перенаправлении, что плохо взаимодействует с буферизацией.

Вместо этого я должен читать байты за раз из файлового дескриптора 0. Это кажется неэффективным, но я заметил, что тире , mksh и zshвсе делают то же самое (в C). Например, попробуйте:

$ strace zsh -c 'читать x <<< "hello world"'

Другие ошибки

  • Исправить приоритет &&и ||. Смутно, они имеют равный приоритет в командном языке, но нормальный неравныйприоритет в языке [[ выражений.
  • Исправьте диапазон переменных, заданных с помощью FOO=bar myfunc. Оболочки здесь отличаются поведением!
  • Исправить, ${x/pat/replace}когда xне определено. (Этот случай выявил ошибку в mksh.)
  • Исправьте сбой при cdудалении из удаленного каталога.
  • readonly R; unset Rдолжны возвращаться 1и уважать errexit, а не безоговорочно терпеть неудачу. Хотя я думаю, что ошибки программирования отличаются от ошибок во время выполнения, даже на динамически типизированных языках, errexitпо умолчанию будет использоваться в Oil. (Также было бы неплохо сделать это статически обнаруженной ошибкой.)

Что не было сделано

Я набросился на несколько вещей, которые не были обязательно необходимы для создания дистрибутивов или у которых были легкие обходные пути:

  • trap Встроенный в невыполненным; предупреждения печатаются stderr.
  • alias также не реализуется. Я изменил пару псевдонимов вalpine-chroot-install на функции. Общая информация: bash - единственная оболочка, которая по умолчанию не расширяет псевдонимы; это требует shopt -s expand_aliases.
  • set -h / hashall это заглушка, которая ничего не делает. Этот вариант используется Aboriginal и влияет на $PATHкеш bash , чего я еще не понимаю.

Также обратите внимание, что эти сборки OSH в некотором смысле «мелкие». Я изменил строки shebang сценариев верхнего уровня, длина которых составляет тысячи строк, но они часто вызывают больше сценариев оболочки с линией #!/bin/bash или #!/bin/sh shebang.

Например, для создания любого дистрибутива Linux потребуется несколько десятков configureскриптов. К счастью, OSH уже может их запустить.

Что дальше?

Как уже упоминалось, предстоящий выпуск OSH 0.4 будет включать всю эту работу.

После того, как я сконцентрировался на коде, у меня теперь несколько резервных копий:

  • Зачем писать новую оболочку? После каждого выпуска я получаю вопросы о мотивах проекта. Мне нужно кратко объяснить каждую цель и связать их со всем одним местом.
  • Ретроспектива проекта. Работа, описанная в этом сообщении, является важной вехой. Стоит пересмотреть, как мы сюда попали. А что осталось?
  • Лексинг сообщений. У меня есть неопубликованные черновики сообщений в этой серии (см. Тег lexing ).
  • Теперь, когда OSH находится в лучшей форме, я хотел бы возобновить запись о оболочке-хороших частях . Первые две должности теперь год назад!
  • Если у меня есть время: обзор научных статей о оболочке.nickpsecurity привлек интересную бумагу к моему вниманию, и я последовал за цитатами и прочитал еще две статьи. Я ответил в комментариях на lobste.rs и reddit . Больше о них говорить!

Было бы неплохо oil-dev@ снова отправиться. Если вы хотите внести свой вклад, напишите мне или оставьте комментарий.