Утилиты Linux, которые мы используем, не зная о них
Debian-LabВ OS Linux существует ряд инструментов с интересной судьбой: почти все хоть раз видели результат их работы, но мало кто знает, чем это было сделано. В данной статье мы рассмотрим несколько таких утилит.
Генератор паролей
Генераторов паролей сейчас в избытке. Особенно удивляет множество веб‑приложений для этой цели. Сервис random.org честно признается, что генерирует пароли на стороне сервера и использовать их для чего‑то серьезного противопоказано. Другие сервисы заявляют, будто генерируют пароли локально с помощью JavaScript, но стоит ли верить им на слово и гарантирует ли их подход, что утечек можно не опасаться, — вопрос непростой.
В то же время генератор случайных паролей присутствует в репозиториях всех дистрибутивов Linux еще с девяностых и называется pwgen. Многие пароли, которые ты получал в своей жизни, с большой вероятностью сгенерировал именно он.
Что интересно, его автор — Теодор Цо, тот самый, который разработал файловую систему ext2 и ее журналируемые версии ext3 и ext4.
К примеру, pwgen 16 1
сгенерирует один пароль из шестнадцати символов.
$ pwgen 16 1
iy1naeZeeNguchae
Если не указывать второй аргумент (количество паролей), то по умолчанию в интерактивном режиме pwgen сгенерирует целых восемьдесят паролей — четыре колонки по двадцать строк. По замыслу авторов, это должно защитить пользователя от любителей заглядывать в чужой экран. Пользователь генерирует целую таблицу паролей и копирует или запоминает из нее один случайный. В этом случае злоумышленник или не в меру любопытный коллега по офису никак не сможет узнать, какой пароль из восьми десятков выбрал пользователь. Во времена массовой удаленной работы этот аргумент кажется несколько натянутым, да и вызывает сомнения, что пароли так уж просто запомнить.
В современных условиях куда полезнее опция -s/--secure
, которая генерирует полностью случайные пароли без претензий на удобочитаемость. К ней же можно добавить -B/--ambiguous
, которая исключает из вывода похожие на вид символы вроде O/0
и 1/I
.
$ pwgen -sB 16 1
PiVRps3erAngsmeb
Самораспаковывающиеся архивы
Проприетарные программы для Linux нередко распространяются в виде исполняемых установщиков. Таким способом их авторы избегают необходимости собирать более чем один формат пакета. Программы на платформенно независимых языках вроде Java также могут использовать один графический установщик на всех ОС — различается способ его запуска.
Таким способом распространялись пакеты с драйверами NVIDIA, некоторые игры, значительная часть пакетов Sun Microsystems / Oracle (NetBeans, SunStudio и другие). Чаще всего у них было расширение .run
, иногда просто .sh
.
Как правило, такой установщик представляет собой самораспаковывающийся архив. На Windows монолитные установщики и самораспаковывающиеся архивы обычно содержали двоичный код программы для распаковки архива. UNIX-подобные ОС всегда включают в себя как минимум tar и gzip, как требует стандарт POSIX, поэтому можно обойтись скриптом на стандартном же Bourne shell.
Я долго думал, что инструменты для создания этих установщиков тоже проприетарные. Так бы я и думал, если бы не наткнулся на свободные проекты с такими же установщиками. Создателем этих пакетов оказался один и тот же инструмент с открытым исходным кодом — Makeself.
Makeself представляет собой сравнительно небольшой скрипт. Как ни странно, авторы до сих пор его не забросили, и в последних версиях Makeself поддерживает сжатие с помощью xz и контрольные суммы SHA-256 вместо традиционных gzip и MD5.
Можно даже не устанавливать его, а просто скопировать файлы makeself.sh
и makeself-header.sh
в каталог проекта. Продемонстрирую на простом примере. Нам потребуется целевой файл (условно test.sh
) и скрипт установки, который будет выполняться после распаковки во временный каталог.
├── makeself-header.sh
├── makeself.sh
└── my-package
├── setup.sh
└── test.sh
Процесс установки полностью на совести пользователя. Для простоты ограничимся копированием в /tmp
. Скрипт выполняется в каталоге с распакованными файлами, поэтому все пути будут относительными.
#!/bin/sh
cp test.sh /tmp/
Теперь создадим наш установщик. Синтаксис команды — makeself.sh <каталог с файлами> <имя выходного файла> <название проекта> <команда для выполнения после распаковки>
.
Путь к команде для выполнения после распаковки тоже пишется относительно каталога с распакованными файлами. Это единственный тонкий момент в работе с Makeself. Именно поэтому мы поместили скрипт setup.sh
в каталог с файлами для упаковки — Makeself воспринимает этот аргумент именно как команду, а не встраивает скрипт в заголовок установщика.
$ ./makeself.sh ./my-package/ my-package.run "My Package" "./setup.sh"
Header is 678 lines long
About to compress 12 KB of data...
Adding files to archive named "my-package.run"...
./setup.sh
./test.sh
CRC: 2448166092
MD5: f46655bb0b96ea7bee4d1f6f112eebe4
Self-extractable archive "my-package.run" successfully created.
$ ./my-package.run
Verifying archive integrity... 100% MD5 checksums are OK. All good.
Uncompressing My Package 100%
$ file /tmp/test.sh
/tmp/test.sh: POSIX shell script, ASCII text executable
Есть ли смысл использовать Makeself во времена snap, Flatpak и AppImage? Я вижу два варианта, когда Makeself все еще актуален. Первый — распространение самоустанавливающихся хотфиксов в особых случаях, когда нормальный пакет собрать невозможно или нерационально. Второй — поддержка проприетарных ОС. Скрипты Makeself с опциями по умолчанию будут работать на любой POSIX-совместимой системе, поэтому, если от тебя требуют установщик для Solaris или HP-UX, это самый простой способ их создать.
Малоизвестные форматы архивов
Некоторые форматы архивов прошли через пик популярности и остались в прошлом. В UNIX традиционно для создания архива (то есть объединения нескольких файлов в один) и для сжатия использовались разные инструменты. Из‑за этого инструменты сжатия могут появляться и устаревать, не вызывая проблем, — нет потребности оставлять поддержку устаревших алгоритмов в новых архиваторах. Если и понадобится распаковать данные старым алгоритмом, всегда можно поставить отдельную утилиту.
Если в ходе археологических раскопок тебе попадется файл .tar.Z
, можно поставить ncompress и выполнить compress -d file.tar.Z
, после чего понадобится только стандартный tar
. В других случаях оригинальный алгоритм LZW никому в голову не придет.
Иное дело с собственно форматами архивов. В отличие от алгоритмов сжатия, способы собрать один файл из нескольких сложно сравнивать между собой. Формат ZIP не поддерживает права файлов, а tar — поддерживает, в этом смысле tar.gz лучше ZIP. При этом придумать формат, который был бы объективно и бесспорно лучше tar, достаточно сложно.
Однако есть формат файла, а есть инструменты для работы с ним. И поведение tar, и формат создаваемых файлов стандартизованы в POSIX, что и делает его популярным на всех UNIX-подобных системах. К тому же утилита tar
достаточно удобна в использовании. По крайней мере tar cvf
и tar xvf
быстро запоминает каждый пользователь.
cpio
Ты удивишься, но tar — это не самый распространенный формат архивов в Linux. Среди пользователей — да, но больше всего данных хранится в формате cpio, с которым редко приходится работать вручную.
Почти на каждой машине с Linux есть файл в этом формате, поскольку именно его ядро использует для initrd (inirial RAM disk). Его же использует формат пакетов RPM. Как говорит документация ядра, определяющим фактором была простота формата.
А вот пользоваться утилитами, которые с ним работают, совсем не просто. Вот как надо вызывать утилиту cpio, чтобы создать архив:
find /path/to/dir -depth -print | cpio -o > /path/to/archive.cpio
Для скрипта сборки — сойдет, даже в чем‑то удобно. Для ручного использования... tar cvf
явно проще и покрывает большую часть потребностей пользователя.
ar
Другой распространенный формат, который никто не использует напрямую, — ar. Его реализация входит в состав пакета GNU binutils. Как он оказался в пакете с утилитами для работы с исполняемыми файлами? Дело в том, что он используется для создания статических библиотек.
Никакого особенного формата «статической библиотеки» в ELF не существует. Каждый файл с исходным кодом компилируется в отдельный объектный файл в том же формате ELF. Файлы вроде libfoo.a
— это на самом деле архивы в формате ar, которые содержат несколько объектных файлов. Таким образом, пользователь может писать gcc -o myprog myprog.o /usr/lib/foo/static/libfoo.a
.
Второй пользователь этого формата — пакетный менеджер dpkg. Если файл .rpm
— это сжатый cpio, то .deb
— сжатый ar.
Внешние отладочные символы
Один из первых уроков начинающего разработчика для UNIX: хочешь сделать исполняемый файл меньше — выполни strip myfile
. Ценой потери возможности работы с ним в отладчике, конечно.
Со временем начинающий обязательно увидит в репозиториях пакеты с внешними отладочными символами. Как они делаются? С помощью утилиты objcopy
из Binutils.
Рассмотрим на примере традиционной программы Hello world.
#include <stdio.h>
int main(void) {
printf("hello world\n");
}
Скомпилируем исполняемый файл с отладочной информацией.
$ gcc -g -o hello ./hello.c
[dmbaturin@careless makeself-test]$ file ./hello
./hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=a8e65e032c2a154eecc1ed609aac1a1687e5c2fd, for GNU/Linux 3.2.0, with debug_info, not stripped
Теперь извлечем отладочные символы во внешний файл hello.debug
и удалим их из исходного.
$ objcopy --only-keep-debug ./hello hello.debug
$ file ./hello.debug
./hello.debug: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter empty, BuildID[sha1]=a8e65e032c2a154eecc1ed609aac1a1687e5c2fd, for GNU/Linux 3.2.0, with debug_info, not stripped
$ ldd ./hello.debug
statically linked
Если запустить исходный файл в отладчике GDB, мы получим сообщение об отсутствующих отладочных символах. Но если загрузить символы командой symbol-file hello.debug
, мы сможем отлаживать его как обычно.
$ gdb ./hello
GNU gdb (GDB) Fedora 9.1-6.fc32
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./hello...
(No debugging symbols found in ./hello)
(gdb) symbol-file hello.debug
Reading symbols from hello.debug...
(gdb) b main
Breakpoint 1 at 0x40112a: file ./hello.c, line 4.
(gdb) r
Starting program: /home/dmbaturin/tmp/hello
Breakpoint 1, main () at ./hello.c:4
4 printf("hello world\n");
(gdb)
Заключение
Вполне возможно, что ни один из этих инструментов тебе не пригодится. А может, как раз их тебе и не хватало или не хватает тому, кто завтра придет к тебе с вопросом «а чем сделали вот этот файл?». В любом случае о них лучше знать, чем не знать!
Источник статьи: xakep.ru
⏳ Наш основной канал - @debian_lab