Познакомился с Capsicum, kqueue, libnv, privsep и privdrop
https://en.wikipedia.org/wiki/Capsicum_(Unix)
https://en.wikipedia.org/wiki/Kqueue
https://oshogbo.vexillium.org/blog/42/
https://utcc.utoronto.ca/~cks/space/blog/solaris/SolarisNvpairLibrary
Реализовал программу с честным (как мне кажется) полноценным privsep-ом,
когда она fork-ается, разные процессы занимаются разными вещами и имеют
доступ к разным ресурсам. Общаются между собой по Unix-сокету. Умеют
делать chroot, сбрасывать привилегии root-а после этого. Capsicum
включают, в том числе и устанавливая ограничения на каждый файловый
дескриптор выборочно. Закрывают всё лишнее и ненужное (stdin/out/err
всякие).
С Capsicum работать по сути тривиально, но архитектура программы
выстраивается вокруг использования файловых дескрипторов. Благо, в
отличии от уродского Linux, в FreeBSD даже процессы можно представлять в
виде файлов (pdfork()).
Так как системные вызовы типа waitpid() уже нельзя использовать в
Capsicum окружении, то нужно делать pdfork(), чтобы получить файл. В
man-е присутствует pdwait4(), однако его нет в исходном коде, кроме как
с пометками "ещё не реализовано". Подсмотрел как с этим живут
capsicum-изированные программы в самой ОС. Оказалось что просто
используют kqueue. Пришлось впервые и с ним поработать. Думал будет
сложно. Но... под рукой был ровно один только man kqueue и через
считанные минуты я полностью реализовал код и ожидания события когда в
Unix сокете что-то будет для чтения и когда процесс завершит свою
работу, оповещая об этом через process descriptor файл. Я очень очень
удивлён как просто работать с kqueue и как много он умеет. Можно даже
просто таймер поставить -- что я часто делал в Go языке в select-ах.
А по сокету мне нужно гонять разношёрстные данные. Точнее, не то чтобы
нужно, а хотелось бы. Как вариант можно открыть несколько сокетов и
ошибки и события отправлять по одним, а полезную нагрузку по другим (как
в FTP отдельные TCP соединения для данных и команд). Но попробовал libnv
библиотеку. У меня никогда не возникло бы мысли о том, что
разнотипизированные данные, где могут быть и вложенные словари и
массивы, могут хотя бы в теории быть просты в использовании в Си или Go.
libnv библиотека супер проста! Даже ошибку абсолютно штатно можно
проверять только во время сериализации. Даже в цикле накапливать и
доделывать (append) данные спокойно. Читать всё аналогично просто. Сам
код является схемой. Очень эффективна по использованию памяти: можно
даже брать значения из nv-пар и они сразу же будут очищать из
nv-структур, оставляя заботу о free() на самом пользователе. И добавлять
значения в nv-структуры можно тоже сразу же их очищая. Да я на Go такой
простой работы не встречал наверное нигде. Одно удовольствие работать с
данными в таком виде. Причём она и endianness блюдёт и можно спокойно
сериализовывать всё на диск. И куча типов данных: null, bool, number,
строки, списки вложенных nv структур, файловый дескриптор, бинарь,
массивы bool/number/строк/дескрипторов.
Но корни libnv растут ещё из Sun Microsystems и Solaris. В ZFS nvpairs
используются всюду и везде. libnv это функциональный аналог nvpairs. В
Solaris оно и вне ZFS встречается. Причём реализация Sun сериализует в
XDR формате всё. Который мне уже и так давно нравился, а недавно я
прочувствовал его крутость с 32-х битным выравниваем всего и вся -- это
существенно упрощает работу с ним на системах которым не нравится
невыровненный доступ к памяти. Ну и пускай что bool занимает всё равно
32-бита, но зато какой простой код получается для работы со всем этим!
Бегло поглядел на libnv и там уже не XDR, а что-то своё, без этой
alignment красоты. XDR я на практике ведь в NNCP использую. А nvpairs
сериализованные данные хранятся прямо на диске в ZFS структурах и без
всякой схемы можно выводить их содержимое.
Дальше всё аналогичное я хочу проделать в экосистеме GNU/Linux. Мне
кажется это будет настоящий ад. Seccomp вроде бы мне достаточен в его
strict режиме. Например после входа в Capsicum в FreeBSD я дополнительно
ограничиваю read/write/kqueue возможности на оставшиеся сокеты -- а в
seccomp strict режиме они сразу же будут только в read/write. Но вопросы
сериализации остаются и... как мне waitpid то там сделать? С ходу не
знаю, если в strict. Плюс kqueue там нет. Буду готовиться к страданиям.