LINUX.ORG.RU

plibsys — кросс-платформенная системная библиотека на C

 , , ,


10

7

Недавно ко мне обратились с вопросом, не хочу ли я написать новость об одной из разрабатываемых библиотек (plibsys). В принципе, я не против, поэтому эксклюзивно для LOR.

Что такое plibsys?

plibsys — это кросс-платформенная системная библиотека, написанная на чистом C. Основной упор был изначально сделан на портируемость и поддержку широкого спектра компиляторов. Для достижения этих целей у библиотеки отсутствуют (небольшим исключением является SCO OpenServer 5 ввиду отсутствия на ней потоков) какие-либо зависимости — используются только те вызовы, которые доступны в целевой ОС. Также никакого ассемблера и использования прочих недокументированных возможностей. Для сборки нужен только рабочий компилятор и CMake.

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

  • Платформо-независимые типы данных
  • Потоки и средства синхронизации: мьютексы, условные переменные, блокировки чтения-записи, спинлоки, атомарные операции
  • Межпроцессное взаимодействие: семафоры, разделяемая память, кольцевой буфер
  • Сокеты (UDP, TCP) с поддержкой IPv4 и IPv6
  • Хэш-функции: MD5, SHA-1, SHA-2, SHA-3, GOST (R 34.11-94)
  • Бинарные деревья: несбалансированное, красно-черное, АВЛ
  • Загрузка разделяемых библиотек
  • Работа с памятью: mmap, установка собственного аллокатора
  • Замер времени исполнения (по возможности — в высоком разрешении)
  • Базовая работа с файлами и директориями
  • Парсер файлов INI
  • Макросы для определения архитектуры ЦПУ, ОС и компилятора
  • Различные вспомогательные структуры данных типа связанного списка, хэш-таблицы, обработка строк

На все есть документация.

Поддерживаемые платформы и компиляторы

Абсолютно все модули покрыты Unit-тестами. Есть интеграция с CI (Travis, AppVeyor), где прогоняется большое число разнообразных конфигураций. Также для улучшения качества кода и снижения числа ошибок используется сервис статического анализа кода Coverity. Для оценки покрытия тестами используется Codecov.

На данный момент поддерживаются следующие платформы:

  • GNU/Linux
  • macOS
  • Windows, Cygwin, MSYS
  • FreeBSD, NetBSD, OpenBSD, DragonFlyBSD
  • Solaris
  • AIX
  • HP-UX
  • Tru64
  • OpenVMS
  • OS/2
  • IRIX
  • QNX Neutrino, BlackBerry 10
  • UnixWare 7
  • SCO OpenServer 5
  • Haiku
  • Syllable
  • BeOS

Также работоспособность библиотеки проверена на следующих компиляторах и архитектурах:

  • MSVC (x86, x64) 2003 и выше
  • MinGW (x86, x64)
  • Open Watcom (x86)
  • Borland (x86)
  • GCC (x86, x64, PPC32be, PPC64be/le, IA-64/32, IA-64, Alpha, HPPA2.0-32, MIPS32, AArch32, SPARCv9)
  • Clang (x86, x64, PPC32be)
  • Intel (x86, x64)
  • QCC (x86, AArch32)
  • Oracle Solaris Studio (x86, x64, SPARCv9)
  • MIPSpro (MIPS32)
  • XL C (PPC64le)
  • DEC C (Alpha)
  • PGI (x86, x64)
  • Cray (x64)

Особенности работы библиотеки, сборки и тестирования на разных платформах с разными компиляторами подробно рассмотрены в Wiki.

Что дальше?

Библиотека по-тихоньку продолжает развиваться, хотя, к сожалению, свободного времени не так много. Например, недавно была добавлена поддержка для систем Cray.

Планирую сделать родной пакет для Debian. В общем-то, он готов, надо только протестировать. В связи с этим, если кто-то сможет выступить в качестве поручителя (он же sponsor в терминологии Debian) для проверки и заливки пакета — буду рад.

Если у кого-то есть доступ к каким-то машинам и компиляторам, которых нету в списке, и есть возможность организовать удаленный доступ для портирования — буду рад. В данный момент было бы интересно проверить под HP-UX с компилятором HP C/aC++. Или на машинах уровня BlueGene с компилятором IBM XL. Или на AmigaOS.

Пожелания, комментарии (конструктивные и не очень) и поток сознания (в меру) по библиотеке приветствуются :)

>>> Подробности

Ответ на: комментарий от HardCode

Смог воспроизвести. Действительно, старая версия CMake ведет себя немного по-другому на одной и той же команде. Поправил в репе.

Теперь-то собралось, но уже пошли проблемы совместимости с платформой.

----------------------------------
Run test patomic_test:
Running 1 test case...
Illegal instruction
----------------------------------
Run test pcondvariable_test:
Running 3 test cases...
** Error: PCondVariable::p_cond_variable_new: failed to allocate memory **
Illegal instruction
----------------------------------
Run test pcryptohash_test:
Running 13 test cases...
** Error: PCryptoHash::p_crypto_hash_new: failed to allocate memory **
** Error: PCryptoHash::p_crypto_hash_new: failed to allocate memory **
** Error: PCryptoHash::p_crypto_hash_new: failed to allocate memory **
Illegal instruction
----------------------------------
Run test pdir_test:
Running 2 test cases...
Illegal instruction
----------------------------------
Run test perror_test:
Running 3 test cases...
Illegal instruction
----------------------------------
Run test pfile_test:
Running 1 test case...
Illegal instruction
----------------------------------
Run test phashtable_test:
Running 4 test cases...
** Error: PHashTable::p_hash_table_new: failed(1) to allocate memory **
** Error: PHashTable::p_hash_table_insert: failed to allocate memory **
Illegal instruction
----------------------------------
Run test pinifile_test:
Running 3 test cases...
Illegal instruction
----------------------------------
Run test plibraryloader_test:
Running 2 test cases...
/root/plibsys-master/tests/plibraryloader_test.cpp(64): fatal error: in "plibraryloader_test/plibraryloader_nomem_test": critical check boost::unit_test::framework::master_test_suite().argc > 1 has failed
Illegal instruction
----------------------------------
Run test plist_test:
Running 3 test cases...
** Error: PList::p_list_append: failed to allocate memory **
** Error: PList::p_list_prepend: failed to allocate memory **
Illegal instruction
----------------------------------
Run test pmacros_test:
Running 1 test case...
** Warning: Test warning output **
** Error: Test error output **
** Debug: Test debug output **
** Debug: Likely condition triggered **
Illegal instruction
----------------------------------
Run test pmain_test:
Running 3 test cases...
Illegal instruction
----------------------------------
Run test pmem_test:
Running 2 test cases...
Illegal instruction
----------------------------------
Run test pmutex_test:
Running 3 test cases...
** Error: PMutex::p_mutex_new: failed to allocate memory **
Illegal instruction
----------------------------------
Run test pprocess_test:
Running 1 test case...
Illegal instruction
----------------------------------
Run test prwlock_test:
Running 3 test cases...
** Error: PRWLock::p_rwlock_new: failed to allocate memory **
Illegal instruction
----------------------------------
Run test psemaphore_test:
Running 3 test cases...
Illegal instruction
----------------------------------
Run test pshmbuffer_test:
Running 4 test cases...
Illegal instruction
----------------------------------
Run test pshm_test:
Running 4 test cases...
Illegal instruction
----------------------------------
Run test psocketaddress_test:
Running 3 test cases...
** Warning: PSocketAddress::p_socket_address_to_native: invalid buffer size for IPv4 **
** Error: PSocketAddress::p_socket_address_to_native: invalid buffer size for IPv6 **
** Error: PSocketAddress::p_socket_address_new: failed to allocate memory **
** Error: PSocketAddress::p_socket_address_new_any: failed to allocate memory **
** Error: PSocketAddress::p_socket_address_new_loopback: failed to allocate memory **
** Warning: PSocketAddress::p_socket_address_new_from_native: invalid IPv4 native size **
** Warning: PSocketAddress::p_socket_address_new_from_native: invalid IPv6 native size **
Illegal instruction
----------------------------------
Run test psocket_test:
Running 7 test cases...
Illegal instruction
----------------------------------
Run test pspinlock_test:
Running 3 test cases...
** Error: PSpinLock::p_spinlock_new: failed to allocate memory **
Illegal instruction
----------------------------------
Run test pstring_test:
Running 5 test cases...
Illegal instruction
----------------------------------
Run test ptimeprofiler_test:
Running 3 test cases...
** Error: PTimeProfiler: failed to allocate memory **
Illegal instruction
----------------------------------
Run test ptree_test:
Running 4 test cases...
** Error: PTree::p_tree_new_full: failed to allocate memory **
** Error: PTree::p_tree_new_full: failed to allocate memory **
** Error: PTree::p_tree_new_full: failed to allocate memory **
Illegal instruction
----------------------------------
Run test ptypes_test:
Running 6 test cases...
Illegal instruction
----------------------------------
Run test puthread_test:
Running 5 test cases...
** Error: PUThread::p_uthread_create_internal: failed to allocate memory **
** Error: PUThread::p_uthread_create_internal: failed to allocate memory **
** Error: PUThread::pp_uthread_get_tls_key: failed to allocate memory **
** Error: PUThread::p_uthread_current: failed to allocate memory **
** Error: PUThread::p_uthread_local_new: failed to allocate memory **
** Error: PUThread::pp_uthread_get_tls_key: failed to allocate memory **
** Error: PUThread::p_uthread_current: failed to allocate memory **
** Error: PUThread::pp_uthread_get_tls_key: failed to allocate memory **
** Error: PUThread::pp_uthread_get_tls_key: failed to allocate memory **
** Error: PUThread::pp_uthread_get_tls_key: failed to allocate memory **
** Error: PUThread::pp_uthread_get_tls_key: failed to allocate memory **
Illegal instruction

krotozer ()
Ответ на: комментарий от lberserq

Да, только в boost это появляется для всех и сразу.

Стандарт рафинирован. Если что-то появилось в нём, то оно есть на всех платформах. У буста это далеко не факт.

Есть ещё и бытовое преимущество стандарта: по нему пишут справочники и книги на русском языке, а по бусту — одна родная документация. И та — убогая.

Но да. В целом нынче буст именно тащит фичи нового стандарта на старый как г мамонта компилятор)

krotozer ()
Ответ на: комментарий от krotozer

Спасибо за проверку. Странно, что падают абсолютно все тесты, даже самые безобидные. Возникает несколько вопросов:

1. Версия boost достаточно новая по сравнению с компиляторами и CMake, причем boost уже собранный. Это официальная поставка или собственная сборка?

2. Падает даже тест на 3 строки (pprocess_test). Можете вынести содержимое этого теста в отдельную программу, собрать и запустить? Таким образом можно будет определить, виноват ли boost или нет. Будет примерно следующее:

#include <plibsys.h>

int main (void)
{
    puint32	pid;

    p_libsys_init ();

    pid = p_process_get_current_pid ();

    if (pid > 0)
        printf ("PID OK\n");
	
    if (p_process_is_running (pid) == TRUE)
        printf ("RUNNING\n");

    p_libsys_shutdown ();

    return 0;
}

3. Если отдельная программа не падает, можете попробовать собрать с boost статически? Для этого команда конфигурации CMake (лучше делать в чистой директории для сборки) будет выглядеть следующим образом:

cmake -DPLIBSYS_TESTS_STATIC=ON -DBOOST_ROOT=<корневая_директория_исходников_boost> ../plibsys

4. Если п.3 не помогает, можете попробовать собрать с boost статически, но используя версию 1.34, которая в составе библиотеки? Иногда на новый boost навешивают слишком много.

К сожалению, не знаю как еще диагностировать проблему в данном случае кроме как использования отладчика. Возможно, на ОС Эльбрус какие-то свои особенности. Спасибо.

HardCode ()
Ответ на: комментарий от kirk_johnson

Оно и сделано в рамках независимых библиотек, ваш К.О.

anonymous ()

На самом то деле список список можно было бы ограничить GNU/Linux, Windows, MacOS, FreeBSD. А так на самом то деле либо может очень даже сильно помочь в разработке кроссплатформенной софтины, так что обязательно затестю.

Gargamel ()
Ответ на: комментарий от krotozer

Стандарт рафинирован

В том то и дело что нет. Комитетчики постоянно фиксят Defect reports и вносят изменения в старый стандарт. Во вторых компиляторов поддерживающих полный стандарт мало. Та же msvc140(aka msvs 2015u3) в полной мере C++11 не поддерживает и __cplusplus в 201103l не определяет.

lberserq ()
Ответ на: комментарий от Dron

Ну общий контекст сообщения твоего мне показался злым )

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

waker ★★★★★ ()
Ответ на: комментарий от kirk_johnson

Во первых совместимость библиотек друг с другом?(вот эта библиотека притащит мне потоки вот эта работу с файловой системой вот эта со строками, ой строки от третьей библиотеки нельзя юзать во второй, а операции во второй вызывают дедлоки в первой), во вторых множества поддерживаемых платформ могут пересекаться но не совпадать. В третьих буст и есть набор разрозненных проектов под одним именем, но с одним coding-style, совместимостью между частями, в основном со «стандартным api»(повторяющим стандарт c++)и общим багтрекером. И реализацией доступной для большинства платформ. Даже C++98 only компиляторы поддерживаются.

lberserq ()
Ответ на: комментарий от lberserq

Как раз об этом я и говорю. Из-за отсутствия нормальной пакетной инфраструктуры с зависимостями и переиспользованием мелких модулей, имеем зоопарк жирных проектов где все свое.

kirk_johnson ★☆ ()
Ответ на: комментарий от kirk_johnson

Ну под виндой пакетная инфраструктура есть и для плюсов nuget и vcpkg называется(пакеты там есть), это что-то меняет? Несовместимость между библиотеками никуда не исчезнет, даже если их вдруг запакуют в пакеты

lberserq ()
Ответ на: комментарий от lberserq

Почему в других языках все ок, а здесь внезапно немовместимость?

kirk_johnson ★☆ ()
Ответ на: комментарий от kirk_johnson

Почему в других языках все ок, а здесь внезапно немовместимость?

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

waker ★★★★★ ()
Ответ на: комментарий от HardCode

1. Версия boost достаточно новая по сравнению с компиляторами и CMake, причем boost уже собранный. Это официальная поставка или собственная сборка?

Сборка это или нет, я не в курсе, но оно в системе уже было.

2. Падает даже тест на 3 строки (pprocess_test). Можете вынести содержимое этого теста в отдельную программу, собрать и запустить? Таким образом можно будет определить, виноват ли boost или нет.

./pprocess_test_exec
PID OK
RUNNING

3. Если отдельная программа не падает, можете попробовать собрать с boost статически?

Ошибки идут в тестах:

  • pcondvariable_test
  • pcryptohash_test
  • phashtable_test
  • plibraryloader_test
  • plist_test
  • pmacros_test
  • pmain_test
  • pmutex_test
  • prwlock_test
  • psocketaddress_test
  • psocket_test
  • pspinlock_test
  • ptimeprofiler_test

Дальше впадает в бесконечный цикл на ptree_test с выводом: C++ runtime abort: terminate() called itself recursively

4. Если п.3 не помогает, можете попробовать собрать с boost статически, но используя версию 1.34, которая в составе библиотеки? Иногда на новый boost навешивают слишком много.

Ситуация аналогична пункту 3.

Кстати, а разве прилагаемые сборки boost не рассчитаны на статичную сборку изначально, чтобы не зависеть от boost-а в системе?

Эта попытка проводится на Эльбрус 401-РС. Там стоит Эльбрус 4С+.

Если нужен дополнительный выход на подобную машину, то можно решить вопрос через Шигорина (мейнтейнера Alt-Linux) - mike@altlinux.ru. У них такая же машина, но своя.

krotozer ()
Ответ на: комментарий от lberserq

Та же msvc140(aka msvs 2015u3) в полной мере C++11 не поддерживает и __cplusplus в 201103l не определяет.

Взял на заметку. Мне сейчас эта особенность могла бы подкинуть проблему.

В том то и дело что нет. Комитетчики постоянно фиксят Defect reports и вносят изменения в старый стандарт. Во вторых компиляторов поддерживающих полный стандарт мало.

Это категорически печально, что я могу сказать... Неужели всегда придётся бустом затыкать дыры в поддержке стандарта?

krotozer ()
Ответ на: комментарий от matumba

Вот, почитай умных людей: http://www.opennet.ru/opennews/art.shtml?num=47093

Вы всегда повторяете то, что читаете? С таким подходом тогда обратитесь, например, к разработчикам VLC и разъясните им, что они нуждаются в главвраче, потому что до сих пор поддерживают OS/2.

HardCode ()
Ответ на: комментарий от krotozer

Дальше впадает в бесконечный цикл на ptree_test с выводом: C++ runtime abort: terminate() called itself recursively

Похоже, что проблема все-таки в Boost, раз без него код работает. Я пару раз встречался с подобным (правда в другой форме), видимо его надо допиливать под Эльбрус.

Кстати, а разве прилагаемые сборки boost не рассчитаны на статичную сборку изначально, чтобы не зависеть от boost-а в системе?

Большинство библиотек Boost можно использовать и так, и так. Единственный недостаток при использовании статической (header-only) версии - время компиляции может быть долгим, особенно на старых машинах.

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

Если нужен дополнительный выход на подобную машину, то можно решить вопрос через Шигорина (мейнтейнера Alt-Linux) - mike@altlinux.ru. У них такая же машина, но своя.

HardCode ()
Ответ на: Добавил в pkgsrc-wip от zhtw

Круто. Сделал пакет из для pkgsrc.

Спасибо! Как раз использую pkgsrc под QNX и некоторыми *BSD.

HardCode ()
Ответ на: комментарий от waker

Ну так если был бы репозиторий в духе Hackage, проблемы бы не было.

kirk_johnson ★☆ ()
Ответ на: комментарий от krotozer

CMake

Вот и я тоже не любитель этого языка программирования без запятых между параметрами. Узаю scons не смотря на все его недостатки, чтоб язык описания сборки был привычным питоном.

q0tw4 ★★★★ ()
Ответ на: комментарий от kirk_johnson

Ты так говоришь, как будто таких репозиториев нет. О том и речь, что они не помогают :-)

waker ★★★★★ ()
Ответ на: комментарий от kirk_johnson

Насчет удобства сомневаюсь, но назначение примерно то же самое

waker ★★★★★ ()
Ответ на: комментарий от waker

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

kirk_johnson ★☆ ()
Ответ на: комментарий от kirk_johnson

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

dzidzitop ★★ ()
Ответ на: комментарий от dzidzitop

Больше. Что не мешает использовать для общих случаев более-менее среднюю реализацию. И рядом опакетить специализированные.

kirk_johnson ★☆ ()
Последнее исправление: kirk_johnson (всего исправлений: 1)
Ответ на: комментарий от kirk_johnson

Не мешает использовать. Но вполне себе оправдывает наличие собственных велосипедов под конкретные задачи.

dzidzitop ★★ ()
Ответ на: комментарий от krotozer

Ну лично я с APR столкнулся в своё время, когда пришлось собирать из исходников Subversion. Так что не только Web-сервера.

hobbit ★★★★★ ()
Ответ на: комментарий от kirk_johnson

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

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

waker ★★★★★ ()
Ответ на: комментарий от Deleted

было живо вроде.

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

я посмотрю, что там с восстановлением паролей. обычно оно работает, но редко кто проверяет.

Iron_Bug ★★★★ ()
Ответ на: комментарий от kirk_johnson

был бы репозиторий в духе Hackage, проблемы бы не было.

ну лежали бы там несколько десятков реализаций строк, сотня хештаблиц и стопицот mmap-ов. И?

arkhnchul ★★ ()
Ответ на: комментарий от arkhnchul

Зачем тебе несколько десятков реализаций строк? Одной generic (а-ля std::string) и пары—тройки специализированных было бы достаточно.

kirk_johnson ★☆ ()
Ответ на: комментарий от HardCode

Назначение этой shared memory - по имени получить доступ в одну и ту же область памяти для обмена данными. Если хотите - можете навернуть что-то более изощренное

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


но в любом случае - желаю всяческих успехов.

dinama ()

думаю, что следует распилить библиотеку на более мелкие. как вы пишете «запуск потоков» «работа с сокетами».
отдельно библиотеку контейнеров.
и тд.

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

dinama ()

Эталонные говнокодеры в треде.

anonymous ()
Ответ на: комментарий от q0tw4

Узаю scons не смотря на все его недостатки

Спасибо за наводку! Не знал о его существовании.

krotozer ()

krotozer, можете проверить на Эльбрусе поледнюю версию из репозитория, ветка no-boost? Я таки выпилил там boost, теперь тесты прогоняются на своих макросах. На Debian проверил, должно на Эльбрусе завестись.

HardCode ()
Ответ на: комментарий от dinama

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

Основная концепция - предоставление первичной абстракции от ОС для базовых функций, которые потребуются при написании приложений. Если вам нужны какие-то более высокоуровневые или специфичные функции - то придется искать дополнительно что-то еще.

Изначально нет и не было цели написать очередной комбайн. Нету планов добавлять что-то вроде конвертации utf8 строк, парсер регулярки, xml-парсер и т.д. Это уже сделано много раз, есть комбайны вроде GLib и Qt. Сама библиотека достаточно компактна, там просто нечего делить. Неиспользование части функций в библиотеке (любой) - это очень частый случай.

Типичный use-case: при запуске приложения читается конфиг, загружаются плагины, заводятся потоки для обработки данных/запросов, открывается сокет или память для обмена с клиентской частью (или железкой), дальше пошло-поехало.

То, что есть некоторые дополнительные вещи типа деревьев или хэшей - ну так в другом случае это понадобится. Это довольно-таки частая задача. Например, хранить большие объемы key-value или проверить, не менялся ли файл. Причем это сделано по модульному принципу - если надо, можно добавить другую реализацию или переключиться на другую. По-моему, лучше иметь в комплекте уже готовую и отлаженную реализацию таких вещей, чем при необходимости выдергивать rb-tree из ядра и переделывать на свой лад.

Кстати, пример тех же деревьев. Какую реализацию использовать из имеющихся? Оно либо не портабельно, либо нет тестов, либо тесты простые и нет оценки их покрытия, либо лицензия GPL only.

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

но в любом случае - желаю всяческих успехов.

Спасибо :)

HardCode ()
Ответ на: комментарий от lberserq

Проблем вида притащил мне фичи нового стандарта и другой сахар. На мой старый как г мамонта компилятор и платформу

boost - это rc стандарта.

samy_volosaty ★★★★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.