Репозиторий с кодом.
Сначала избавился от нескольких багов. Потом решил добавить сетевой функционал. Ну, а коль сеть - то и кольцевой буфер нужен. В общем, теперь там - не просто макросы и функционал, который в 99% случаев нужен (тот же разбор параметров командной строки), но и дополнительные облегчалки.
В релизе 0.2.1 код подвергся рефакторингу, многие функции переименованы, чтобы было единообразие: почти все функции и т.п. имеют префикс sl_; типы-перечисления имеют суффикс _e, типы данных - суффикс _t. Без префикса я оставил лишь функции red и green для цветного вывода сообщений. И многие макросы лень было переименовывать.
"Старое":
Для использования функционала в самом начале main() нужно выполнить функцию sl_init. Для работы с терминалом ввода в режиме "без эха" - sl_setup_con() (и не забыть в конце и в функции signals() вызвать sl_restore_con(), иначе по завершению придется ручками вводить reset). Функции sl_read_con() и sl_getchar() дают, соответственно, неблокирующее и блокирующее чтение символа, введенного пользователем.
Осталось и упрощение работы с mmap: sl_mmap() и sl_munmap().
sl_random_seed() позволяет при помощи /dev/random сгенерировать случайную затравку для использования в ГСЧ. sl_libversion() позволяет получить строку с текущей версией библиотеки.
Для работы с gettext теперь нужно определить макрос GETTEXT, в этом случае макрос _() будет вызывать gettext().
Функции работы с блочными устройствами не просто переименовались: по умолчанию я перешел на termios2. Если нужно собрать библиотеку с поддержкой "классической" termios со всеми ее убогими косяками, нужно определить макрос SL_USE_OLD_TTY (в этом случае еще и будет доступна функция sl_tty_convspd() для преобразования скорости интерфейса из числа в макрос). А остальные функции - как и были: sl_tty_new() для выделения памяти и инициализации структур; sl_tty_open() - открыть порт; sl_tty_read() и sl_tty_write() для чтения/записи; sl_tty_tmout() позволяет задать таймаут для select() в микросекундах. sl_tty_close() - закрыть устройство и очистить структуры данных.
Логгирование имеет "умолчальный" глобальный лог (sl_globlog), для которого нарисованы макросы вроде LOGERR(), LOGERRADD() и т.п. (все с суффиксом ADD добавляют текст без записи timestamp и уровня сообщения - если нужно вывести несколько строк текста; но лучше ими не пользоваться часто, особенно в многопоточных/многопроцессных приложениях). Чтобы логи писались вменяемо в многопоточных/многопроцессных, пришлось лог-файл каждый раз для записи открывать, блокировать, потом писать, закрывать и разблокировать. Чтобы избежать дедлоков или слишком долгих ожиданий, установлен "гвоздями прибитый" таймаут в 0.1с: если за это время заблокировать файл не получится, то отменяем запись и возвращаем пользователю 0; иначе возвращаем количество записанных символов. "Глобальный" лог-файл активируется при помощи макроса OPENLOG(nm, lvl, prefix) (имя, уровень, флаг добавления "префикса": строчки с расшифровкой уровня сообщения, например, [WARN] или [ERR]). Функции sl_createlog(), sl_deletelog() и sl_putlogt() позволяют активировать структуру-лог (и создать файл, если надо), удалить структуру (сам файл не удаляется) и что-нибудь туда записать/дописать.
В работе с аргументами командной строки я исправил один баг, связанный с выводом справки по "только длинным" аргументам, у которых в качестве "короткой альтернативы" проставлены одинаковые числа. Ну и была проблема с опцией "-?", которая криво обрабатывалась (т.к. я пользуюсь getopt_long). В остальном все осталось, как было: поддерживаются целочисленные аргументы (причем, arg_none означает, что к заданной переменной будет добавляться 1 каждый раз, как встречается этот флаг - удобно для задания уровня отображения как, например, -vvvv), long long, double, float, string и "аргумент-функция" (т.е. вместо присвоения значения будет выполняться заданная функция - например, чтобы парсить свои вложенные аргументы). Все также для каждого флага можно указать, что он не имеет аргументов, имеет обязательный, необязательный или множественный (в этом случае переменной-целью должен быть указатель соответствующего типа: при разборе получится NULL-terminated массив со всеми введенными пользователем данными (удобно, например, когда тебе нужно указать несколько файлов для считывания из них однообразной информации и т.п.). Поддерживаются "подопции" (когда за флагом следует строка вида "so1=par1;so2=par2..."). sl_showhelp() позволяет вызвать справку по всем опциям или только одной. Она же вызывается автоматом, если пользователь что-то не то введет. Чтобы поменять форматную строку справки, используется sl_helpstring() (там должен быть один-единственный "%s", куда вставится имя запускаемого файла). sl_parseargs() нужно запускать сразу после инициализации, и дальше уже пользоваться своей структурой с параметрами.
Для проверки, не запущен ли второй экземпляр, есть функция sl_check4running(). Если найден процесс, запускается слабая функция sl_iffound_deflt(), которую под свои нужды можно изменить. sl_getPSname() парсит /proc и выдает имя процесса с заданным PID.
FIFO/LIFO также остались без изменений: sl_list_push() и sl_list_push_tail() позволяют воткнуть запись в начало или хвост списка, а sl_list_pop() - извлечь последнюю запись.
"Новое":
sl_mem_avail() позволяет определить доступный объем ОЗУ. sl_omitspaces() и sl_omitspacesr() - удалить начальные и конечные пробелы. Помимо sl_str2d() ("безопасное" преобразование строки в double) я добавил sl_str2ll() и sl_str2i() - long long и int, соответственно.
Для проверки, можно ли в данный дескриптор писать или читать из него без блокировки, добавил функции sl_canread() и sl_canwrite().
Поддержку конфигурационных файлов тоже добавил в библиотеку. В файле все данные должны быть в формате "параметр = значение" или просто "параметр". Допускается наличие пустых строк и комментариев (все, что за символом "#" в строке). "Гвоздями" прибил максимальную длину параметра (SL_KEY_LEN, 32 байта) и значения (SL_VAL_LEN, 128 байт). Работа с флагами похожа на работу с аргументами командной строки. sl_get_keyval() чистит лишние пробелы и комментарии, выделяя непосредственно строчку "параметр [= значение]" и помещая ее в отдельные аргументы (возвращает 0 если ничего в строке не найдено, 1 - если только параметр, 2 - если и параметр, и значение); эта функция используется и в обработке запросов по сети. sl_print_opts() печатает изменяемые параметры (или все, если задан флаг) с комментариями. sl_conf_readopts() читает конфигурационный файл. Опции задаются полностью аналогично опциям командной строки, что позволяет печатать сопровождающий текст помимо флага и его значения.
Для работы с кольцевым буфером есть следующие функции: sl_RB_new() и sl_RB_delete() - инициализация и удаление данных структуры кольцевого буфера. sl_RB_hasbyte() позволяет определить, есть ли в буфере нужный символ (например, конец строки), а sl_RB_readto() - считать до него (включительно). sl_RB_putbyte(), sl_RB_write() и sl_RB_writestr() позволяют записать данные в буфер (возвращают количество записанных символов, чтобы юзер мог понять, что что-то не то, если место кончилось). sl_RB_datalen() и sl_RB_freesize() позволяют узнать, сколько места в буфере занимают данные, и сколько там свободно. Нужно помнить, что размер данных будет на 1 байт меньше длины буфера (по понятным причинам). sl_RB_clearbuf() сбрасывает содержимое буфера, как будто бы там ничего нет.
Ну и последняя, самая жирная по количеству функций и структур - сетевая часть.
Для активации соединения есть две функции: sl_sock_run_client() и sl_sock_run_server() - для клиента и сервера, соответственно. Они инициализируют структуры, открывают соединение и подключаются для клиента или вызывают bind для сервера. Клиенту заодно запускается поток, читающий приходящие данные и складывающий в кольцевой буфер. У сервера, если указать не-NULL аргумент handlers, тоже запускается отдельный поток: он обслуживает присоединенных клиентов, запускает accept для новых, закрывает отключившиеся; полученные от клиентов данные парсятся и, если среди переданной структуры handlers находится подобный ключ, то запускается соответствующий обработчик + есть три стандартных обработчика: sl_sock_inthandler(), sl_sock_dblhandler() и sl_sock_strhandler() - для int64_t, double и char* (для того, чтобы знать, когда было произведено изменение значения, я дополнительные типы данных ввел, где помимо данных есть timestamp). Предельное количество клиентов можно задать функцией sl_sock_changemaxclients(), а узнать текущее - sl_sock_getmaxclients(). sl_sock_delete() закрывает соединение и удаляет структуру.
Помимо этого, есть еще обработчики событий: подключение клиента, отключение и подключение клиента сверх предельного количества. По умолчанию там NULL (т.е. ничего не делается), но можно сменить при помощи, соответственно, функций sl_sock_connhandler(), sl_sock_dischandler() и sl_sock_maxclhandler(). В них можно, например, в логи писать, мол клиент fd=xx ip=yyy подключился/отключился. А в последнем - писать клиенту "подожди, все соединения заняты".
Отправлять данные можно непосредственно при помощи write/send (не забывая сначала заблокировать, а потом разблокировать соответствующий мьютекс), либо же при помощи оберток: sl_sock_sendbyte(), sl_sock_sendbinmessage() и sl_sock_sendstrmessage(). Читать тоже можно или при помощи соответствующих функций работы с кольцевым буфером, или же текстовые строковые сообщения можно считывать посредством sl_sock_readline().
У сервера бывают ситуации, когда одно и то же сообщение нужно отправить сразу всем клиентам. Для этого есть функция sl_sock_sendall() (она тупо перебирает все открытые и подключенные дескрипторы, да отправляет туда данные).
Примеры
clientserver - пример организации простейшего сервера и клиента. 226 строк всего: аргументы командной строки, примеры обработчиков, пример неблокирующего чтения с клавиатуры одновременно с чтением из сокета. Здесь я не парился насчет типа "локальный сетевой": можно открыть либо UNIX-сокет, либо INET. Заодно логи можно писать.
conffile - 101 строка. Дает пример ввода конфигурационных параметров как из командной строки, так и из конфигурационного файла. Также парсит строковый параметр вида "параметр=значение". Отображает состояние флагов после обработки аргументов командной строки, а затем - после считывания из конфигурационного файла. К сожалению, чтобы считать путь к конфигурационному файлу, надо сначала обработать аргументы командной строки, поэтому, если не постараться, а просто на тот же самый массив структур вызывать чтение конф-файла, то приоритет будет за ним. Хотя, логика подсказывает, что приоритет всегда должен быть за командной строкой. Поэтому в случае, когда необходимо именно иметь приоритет командной строки, нужно будет завести 2 раздельных структуры, инициализировать их сначала каким-то дефолтом (с данными, которые пользователь точно не соможет ввести), а после сравнить и все неинициализированное из командной строки инициализировать записями из конф-файла.
fifo - 61 строка. Пример работы с FIFO/LIFO. Показывает реальный размер свободной ОЗУ и затем все параметры командной строки помещает в списки: LIFO и FIFO. А затем из каждого списка по одному извлекает, демонстрируя, как это работает.
helloworld - 35 строк (из которых только 10 приходится на собственно код). Демонстрирует цветной вывод и ввод без эха.
options - 134 строки. Демонстрирует работу с разным типом опций (кроме функции: я и сам уже забыл, зачем оно мне нужно было), в т.ч. массивами. Ну и по умолчанию демонстрирует простейший терминал для работы с символьными устройствами (если пользователь введет путь).
ringbuffer - 80 строк, демонстрирующих работу кольцевого буфера. По умолчанию он имеет размер 1024 байт (т.е. вмещает 1023 символа). Дальше пользователю предлагается вводить построчно текст, который будет помещаться в буфер. Ввод заканчивается на переполнении буфера или же при нажатии ctrl+D. После чего весь буфер вычитывается и выводится на консоль.
Теперь как минимум все, что пользуется этой библиотекой, надо обновить. Ну, а сетевые демоны роботелескопов вообще радикально так переделать: чтобы "шапкой" с метеоданными не демон монтировки занимался (что вообще нелогично), а демон погоды; чтобы демоны купола, монтировки и телескопа "слушались" демона погоды. Скажем, если стоит prohibited=1, то прекращать наблюдения, когда они идут, или не давать наблюдателю открыться. Вот на БТА такую функцию АСУшник выполняет (правда, некоторые из рук вон плохо работают, позволяя наблюдателям-вредителям открывать телескоп при влажности свыше 90%, облачности или даже тумане). А на Ц-1000 почему-то до сих пор нет такого "супердемона", который бы "давал по шапке" наблюдателям-вредителям. Иначе угробят телескоп, а за это даже никакого наказания не понесут (я бы отстранял наблюдателей минимум на полгода от наблюдений, если они открывают телескоп тогда, когда этого делать ни в коем случае нельзя).