CodeCore - сервер сборки приложений. Общая логика.
mikelsv
Идея написать сервер сборки приложений зрела давно, а руки доползли только сейчас. Итак, ситуация: сборка и отладка приложения под сервером с линуксом. Значит берем ssh, консоль, mc и руками правим код, собираем, запускаем в gdb. Это все конечно здорово, но хотелось бы здоровее. То есть, в виде сайта, с графическим редактором кода и прочими возможными плюшками.

Итак. Первым делом требуется обрисовать общую логику. Пораскинув мозгами я пришел к выводу, что:
1. За основу берется HTTP сервер. В первичном варианте без скриптовых языков, чистый C++. Потом скорее всего они потребуются.
2. База данных это хорошо, даже слишком, поэтому обойдемся без нее. Файлы будут храниться на диске в исходном виде.
3. Так как сервер непростой, то к нему будет прикручен HTTPS. В минимальном варианте можно использовать самоподписанный сертификат.
4. В продолжение темы авторизации делаем пользователей и группы.
5. История. Так как накосячить и удалить часть кода можно всегда история просто необходима. Но, тут вот какая штука. В отичае от всяких svn код будет сохраняться довольно часто. Хотя, один хрен. Создаем папку vsource и храним историю изменений там, в файле с тем же названием как и исходник. История пишется блоками в том же формате как и в svn и git. Плюс там должна быть метка времени и последним идут четыре байта размера записанного блока, чтобы в обратную сторону не составило труда пробежаться. А если подумать, то нахрен тектовый формат, пусть будет бинарный. Там кажется построчное сравнивание, и всего две операции: удалено и добавлено.
6. Хотелось бы дать возможность записывать файлы напрямую в папку с кодом. Тут очень здорово подойдет fuse. С одной стороны она будет показывать всю ту же структуру с исходниками, а с другой обрабатывать перезапись файлов.
7. Консольную утилиту для добавления пользователей и управления ими.

Итого файловая структура:
/users/ - настройки пользователей.
/groups/ - настройки групп.
/users/source/ - корень для проектов и кода.
/users/vsource/ - корень для версий.

Актуальные вопросы:
1. Один корень с кодом на всех или у каждого пользователя свой?
По логике надо каждому свой с настройкой доступа.
2. А еще есть всеми любтимые группы. Что с ними?
И их надо делать.
3. А как это будет все работать?
Да ты задолбал уже! Пользователь имеет к своей папки исходников неограниченный доступ. При доступе к чужим исходникам сначала проверяется разренение для пользователя, потом для групп в которых он состоит.


Итого: чудовищные обьемы кода при том, что толком работать еще ничего не будет.

Препроцессор C++. Переписать! И дело с концом.
mikelsv
Никогда не знаешь, чем решишь заняться. В это раз это был препроцессор С++.

Собственно проблема: Захотелось написать программу профилирования памяти. Для этого потребовалось перехватить вазовы вот этих чудесных функций: malloc, free, HeapAlloc, HeapFree, LocalAlloc, LocalFree, GlobalAlloc, GlobalFree, VirtualAlloc, VirtualFree, VirtualAllocEx, VirtualFreeEx. И, чтобы не писать кучу кода, было решено написать кучу дефайнов с параметрами функций и использовать их. И вся такая чудесная идея разбилась о суровую действительность компилятора в MSVS. Оказалось, он не умеет разворачивать дефайны. То есть представить строку #define A 1, 2, 3  в #define B(a, b, c) как три параметра, а не один. Ну, не умеет, значит научим. И понеслась.

Сначала я конечно попытался взять свой старый http://mikelsv.livejournal.com/1821.html , но потыкавшись понял, что проще переписать. Ибо технологии ушли далеко и все реализуется намного проще и лучше.

Итого, препроцессор написан, и умеет не только стандартные #команды препроцессора, но и чуть большее расширение. А, самое главное, возможность разворачивать дефайны в параметры.

Для примера:
#define A 1, 2, 3
#define B(a, b, ...) printf(a, __VA_ARGS__ );

Была задача реализовать B("%d %d %d\r\n", 1, 2, 3);  используя параметры из A.  Очквидно, что B("%d %d %d\r\n", A);  сработает совсем не так, так как A будет считаться вторым параметром.
Для решения проблемы был введено обозначение развертки - ::, два двоеточия. Сначала пытался взять всякие #@ или #:, но они плохо вписывались в логику препроцессора. Последний знак вписался более менее хорошо. Хотя, он используется в коде и возможно его стоит сменить.
Так вот, B("%d %d %d\r\n", ::A); говорит препроцессору развернуть A и мы получаем результат аналогичный B("%d %d %d\r\n", 1, 2, 3); Это победа. Я считаю.

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

ThreadString - потокозависимые строки - это офигенно.
mikelsv
Непонятно, почему эта идея не пришла ко мне лет десять назад. Ну хотябы пять. Ведь ThreadString это поистине офигенная штука.

Что это такое? Это строки привязанные к конкретному потоку. То есть для каждого потока создается свой буфер в котором они и хранятся. Но не долго, это исключительно временные переменные. Но, если раньше приходилось выделять память под каждую строку через malloc, то теперь есть один буфер на все строки, что резко ускоряет работу с выделением памяти. Из минусов: изменение строки в большую сторону требует выделения нового блока памяти в буфере. Это конечно тяжело, но вполне себе можно навыделять такими темпами парочку гигов.

Теперь хочется вспомнить и остальной арсенал. В котором первым номером конечно идет class VString{ public: unsigned char * data; unsigned int sz; } - указатель на строки. Давно заменивший собой char *; Класс позволяет менять свои переменные, что позволяет делать разные офигенные вещи, но к сожалению не спасает от криворукости, типа выхода за границу памяти или использования указателя на уже удаленную строку.

Используете char* для хранения строк? Убейтесь. В нем нельзя хранить строки. Ну, то есть можно, но осторожно и с параноей во все поля. Код, который можно обрушить входящими данными должен умереть.

Дальше идет class MString : public VString; Та же ерунда, только при MString str = "111"; класс выделяет память под строку.
Теперь появился TString : public VString; Хранящий строку в буфере привязанном к потоку. И все локальные MString, недоступные из других потоков дружно идут меняться на TString.

Существует множество ситуаций, где нужен временный буфер на мгновение. Для этого есть класс ITos, по умолчанию занимающий 128 байт на стеке плюс переменная MString для хранения больших обьемов. Что не всегда удобно. Теперь эту задачу можно свалить на TString.

В процессе использования напишу функции для TString, аналогичные MString. Так же, очевидно, придется решить вопросы совсестимости трех классов VString, MString и TString. И по ощущениям очень не хватает, и обязательно сделаю, возможность переключиться на новый буфер, чтобы новые TString писались в него, а при выходе из функции будет возвращаться старый буфер.

И пара технических моментов:
При создании TString создается буфер для потока, если его еще нет. А так же увеличивается счетчик  __declspec(thread) int tstring_core_count; В деструкторе счетчик уменьшается. Если счетчик равен нулю буфер очищается. Поэтому важно писать код так, чтобы все переменные уничтожались, иначе класс сожрет всю доступную память, пусть и не сразу. Так же может быть косяк при частом создании / удалении потоков. Полагаю ввести опцию удаления буфера, а не очищения, когда он больше не нужен.

А еще TString очень здорово расшифровывается как TmpString, что так же отражает его суть.
Да здраствует TString и светлое будущее!
Tags:

msl-five и виртуальные машины.
mikelsv
Первый шаг не то, что бы провален, скорее заброшен в состоянии "работать вероятно будет". Там столько мыслей и вариантов, что проще сделать чтобы оно как бы было. Отладка покажет.

Далее логика разработки свелась к идее стеков. А перед этим к вопросу, почему главный класс всего один? Теперь их два, один генерит псевдокод, второй его выполняет. Пересекаться им в общем-то не надо, а структура упрощается в два раза.
Так вот о стеках. В текущих задачах использование new() для выделения памяти приравнивается к ереси с последующим сожжением пациента. Да и любые лишние выделения памяти. И тут я осознал всю прелесть стека, неразрывного блока памяти, достаточной длинны, в который запихиваются все данные, которые ложатся в логику. Например названия включаемых файлов или переменных с псевдокодом.

Итого:
- Стек для хранения данных, которые можно впоследствие выкинуть. Используется в виде: int pos = stack.GetPos(); здесь вставляем данные и работаем с ними. stack.SetPos(pos); Итого шустрая работа с памятью.
- Линейный буффер для остальных данных. Буффер для псевдокода, текстовых данных, информации о соответствии имен переменных к стеку.

По этой логике все и строится.

Теперь о текущем состоянии. msl-ce был настолько крут, что почти в том же виде потихоньку мигрирует в msl-five, постепенно меняясь под новую логику.
Что же до результатов, генерация псевдокода и исполнение написаны и, в каком-то виде работают. Но вопросов еще много. Отладка находится в районе $a = '1'; Остается писать и отлаживать. Большой вопрос, в каком виде делать переменные в стеке. msl_value весит 32 байта, что немного много. Плюс, нужны новые переменные, сейчас используется MString, которая адски тормозит в случае $a.= '1'; Так же вопросы по пространствам, global, this, local.

Сейчас я доволен лишь генерацией псевдокода, он оптимален и тормозить там нечему.
Ну, еще пять тысяч строчек кода и msl залетает.
Tags: ,

msl-five. Это пять.
mikelsv
Почему бы не вспомнить про msl и не написать очередную, пятую версию? Признаться, есть версия 4.5, которая врое как и должна быть пятой, но цифры не те. Поэтому пятая версия будет писаться с нуля и с новыми идеями.

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

Я хочу:
- Быстую работу с переменными.
- Поддержку многопоточности.

Вот на этих идеях и следует строить логику.

Поехали...
Пишу тесты, мне нужны точные измерения, на которые можно опираться... Тесты показывают, что ничего не показывают. Зазве что, память освобождается гораздо медленнее. Ну тут и так очевидно, что выделение памяти будет происходить порд строгим контролем и наблюдением. Узкое мессто как ни как.

На тему памяти у меня готова идея выделять большие блоки памяти и взять недавно созданный класс FString. Все данные размещаются в выделенной памяти. По порядку. А чтобы не было фрагментации в памяти будет указатель на переменную класса, что позволит перемещать данные. Тут правда возникает вопрос, как эти данные безопасно перемещать, однако полагаю, что перемещение будет размыто по времени, а установка идти через блокировки.

Другой вопрос, что делать с многопоточностью. Сейчас мне начинает казаться, что логика та же. Хотя, возможна ситуация, когда один поток пишет, а второй читает. С одной стороны блокировка на чтение нужна, а с другой хочется обойтись без нее, иначе придуется добавить кучу блокировок. С третьей, полагаю, стоит использовать безопасную установку, так, чтобы в любой момент времени максимальной ошибкой при чтении были кривые данные. Сваливаем проблему на пользователя. Пусть пользуется блокировками или получает проблемы. Хотя, какие проблемы, когда даже многопоточный код на php чуть ли не шутка.

Итого.
Задача создать класс FString{ unsigned char *data; unsigned int sz; }, хранящий строки в блоках памяти. Базовая структура в блоке памяти: FString* val; unsigned char data[]; В функцию FString::Set(VString line) вызывает функцию установки с использованием блокировки. При удалении или изменении размера данных доступ к осбовожденной памяти должна блокироваться, на случай, если ее читают. Тут видится два решения. Добавить unsigned int time; в блок памяти к каждой переменной, что очевидно потребует кучу памяти, а пользы приносить в принципе не будет. Поэтому мне кажется наиболее правильным второе решение, добавить маркер запрета на изменения всему блоку памяти...Хмм, что можно сказать о проблемах? Они будут.
Очевидная пробема VString a = FString b; Только представил себе бесконечную миграцию данных. Как понял, что домигрируют они до первого глюка. А в идеале хотелось бы заменить MString на FString, В первом данные всегда на месте. Решение? Флаги запрета перемещения? VFString с указателем на FString? ... А если сделать привязку переменных к блоку памяти? С запретом на изменение. Да, придется написать что-то вроде FStringLock(); И безопасно использовать переменные, зная, что они не убегут. Вопрос в правильной реализации, без заметных тормозов.

Вот как-то так, это базовый каркас для быстрых переменных и базовые размышления.

К слову, есть почти законченный msl-ce 4.5 с псевдокодом и кодогенерацией, но мы же не ищем легких путей и пишем все заново.

SkyCat - нечто похожее на скайп. Шаг 1. С чего начинается родина.
mikelsv
Порой хочется сделать что-то хрошее... но это Россия.

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

Чего я хочу? Захватить мир? Да, но это позже. А сейчас мне хочется сделать простенькую программу для общения. В перспективе безопасного, распределенного и всего остального. А пока это первый шаг и я начинаю его с разработки протокола. Как основы для всего проекта.
Из последнего опыта, мне понравился JSON, и если бы не его текстовый формат и проблемами исходящими из этого, то он вполне бы сгодился. Поэтому я решил использовать бинарный JSON. Так же я решил отказаться от типов, посчитав их излишним усложнением. В результате будет использоваться JSON в бинарном формате с единственным типом: строки. Тем самым исключаются ненужная работа с выделением памяти. Потребуется: выделить память под данные и под структуру. Память достаточно выделить всего два раза. Нагрузка на процессор минимальна. Если требуется уменьшить обьем данных, то их можно сжать в gzip. На мой взгляд это сделает размер данных не сильно больше, чем в других бинарных представлениях, а возможно и меньше.

Структура протокола: Заголовок + Данные.
Заголовок: Размер данных, размер элементов, флаг gzip.
Данные: Количество элементов, элементы.
Элемент: флаг вложенных данных, размер ключа, размер значения, данные ключа, данные значения.
Флаг вложенных данных указывает на то, что в этом элементе есть элементы, представленные в виде: Количество элементов, данные. Таким образом строится любая структура.

Заголовок:
struct CJXParseH{
    // version, flags, elements, size, gzipsize
    unsigned int ver, flags, els, size, gsize, _nouse;
};

Флаг вложенных данных, размер ключа, размер данных.
struct CJXParseL{
    unsigned short up:1;
    unsigned short keysz:5;
    unsigned short valsz:10;
//    unsigned char key[keysz];
//    unsigned char val[valsz];
};

Структура: CJXParseH + count + CJXParseL[count].

Можно заметить, что это преждевременная оптимизация, но есть задача сделать хорошо с самого начала. Код работы с форматом уже написан и тестируется. Его объем не превышает 500 строк, а вся логика очевидна и понятна.

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

PS. В следующей серии: код формата и дальнейшие планы. Бодрые шаги к ближайшей цели - jabber.
Tags: ,

RSA. Все могло быть проще.
mikelsv
Решив воспользоваться библиотекой openssl, а точнее алгоритмом RSA я был буквально удивлен той наркоманией, что творится в коде. Привыкнув к тому, что обычно код пишется легко и просто я малость охренел от этой кучи непонятных функций и количества параметров.

Первый увиденный способ добавления ключа в RSA был ужасен: PEM_read_RSA_PUBKEY(FILE*, ...); Я давно и обычного open() не касался, не то, что нелюбимого мной fopen();
Второй способ был лучше, но все же не без глюков: PEM_read_bio_RSA_PUBKEY(BIO*); Которая создавалась через BIO_new_mem_buf(char* buf, int size); Наркомания заключалась в том, что в этом случае устанавливался флаг запрета на запись в память. Ок, я снимал этот флаг вручную и сразу же натыкался на удаление моего буффера и создания нового, при записи ключа. Ребята банально не рассчитывали, что кто-то рискнет снять флаг защиты от записи и не продумали этот момент. Короче программа так и пыталась упасть там где можно и нельзя.

В итоге пришлось решать хаком, через создание переменной BUF_MEM mp; заполнением ее, созданием пустого BIO *bp=BIO_new(BIO_s_mem()); и втыканием буффера в bp->ptr=& mp . Перед удалением BIO, буффер тем же способом тихо убирается bp->ptr=0; Править код openssl желания не было, он и так нестабилен.


В итоге у меня получилось три замечательных функции: RSACreateKeys(&pub_key, &secret_key), RSAEncode(pub_key, text), RSADecode(secret_key, text); Примерно в таком виде я надеялся получить их из библиотеки openssl, но видно не судьба.

Код функций:
void RSACreateKeys(MString &pub, MString &sec){
 // generate keys
 RSA *rsa = RSA_generate_key(2*1024, 64*1024+1, NULL, NULL);

 // bufs
 pub.Reserv(451);
 sec.Reserv(1679);

 BUF_MEM mp; mp.data=pub; mp.length=0; mp.max=pub; 
 BUF_MEM ms; ms.data=sec; ms.length=0; ms.max=sec; 

 BIO *bp=BIO_new(BIO_s_mem()); bp->ptr=∓
 BIO *bs=BIO_new(BIO_s_mem()); bs->ptr=&ms;

 PEM_write_bio_RSA_PUBKEY(bp, rsa);
 PEM_write_bio_RSAPrivateKey(bs, rsa, 0, 0, 0, 0, 0);

 bp->ptr=0;
 bs->ptr=0;

 BIO_free(bp);
 BIO_free(bs);

 RSA_free(rsa);
}

MString RSAEncode(VString key, VString text){
 MString ret;
 RSA *rsa = RSA_new();
 BIO *b=BIO_new_mem_buf(key, key);
 
 PEM_read_bio_RSA_PUBKEY(b, &rsa, 0, 0);
 ret.Reserv(RSA_size(rsa));
 int sz=RSA_public_encrypt(text, text, ret, rsa, RSA_PKCS1_PADDING);
 
 BIO_free(b);
 RSA_free(rsa);

 ret.Reserv(sz);
 return ret;
}

MString RSADecode(VString key, VString text){
 MString ret;
 RSA *rsa = RSA_new();
 BIO *b=BIO_new_mem_buf(key, key);
 
 PEM_read_bio_RSAPrivateKey(b, &rsa, 0, 0);
 ret.Reserv(RSA_size(rsa));
 int sz=RSA_private_decrypt(text, text, ret, rsa, RSA_PKCS1_PADDING);
 
 BIO_free(b);
 RSA_free(rsa);

 ret.Reserv(sz);
 return ret;
}

void test(){
 MString pub, sec, e, d;
 RSACreateKeys(pub, sec);
 e=RSAEncode(pub, "The test string for encrypt/decrypt");
 d=RSADecode(sec, e);
}




MString и VString это классы с unsigned char *data - указателем на данные и unsigned int sz - их размер. Reserv(int sz) - выделяет блок памяти.

В функции стоит добавить еще проверок, ибо, например, код может упасть если дать ему строку не соответствующую формату ключа.

Вот так, наркоманы рядом. И вы каждый день пользуетесь результатами их труда.
Tags:

Поднять svn
mikelsv
Порой я задаюсь вопросом, а за что же я так нелюблю линукс. Потом пытаюсь настроить svn и вспоминаю, вспоминаю...

Чтобы не забыл:
#Centos 6

Ставим
yum install svn cyrus-sasl cyrus-sasl-lib

Создаем
svnadmin create /usr/svn
cd /usr/svn

Настраиваем.
#/usr/svn/conf/svnserve.conf
[general]
anon-access = none
auth-access = write
realm = my_svn_server
password-db = passwd
authz-db = authz


#/usr/svn/conf/authz
[/]
test = rw

#/usr/svn/conf/passwd
[users]
test = 123

#/etc/sysconfig/svnserve
OPTIONS="--config-file /usr/svn/conf/svnserve.conf --root /usr/svn/ --log-file=/var/log/svn.log"

Запускаем
service svnserve start

Настройку sasl  я так и не осилил.
Tags: ,

GZip, затянись с нами.
mikelsv
Хотите разобраться, как работают архиваторы? Правильно, да ну нафик. Эти алгоритмы, какие-то структуры и все такое прочее. А мне внезапно оказалось надо, надо так, что без вариантов. Разобраться как создать gzip. В начале все казалось просто, взять стандартную функцию inflate() из zlib и прикрутить к ней шапку. То есть даже проще чем казалось. И тут начались чудеса. Беру шапку gzip + inflate(text) + crc(text) + text.sz. Нифига не работает, архиватор не хочет это понимать. После чего путем хитрых манипуляций со сжатием "1" и сравнением результатов выяснилось, что реальная формула то: gzip header + inflate(text).str(2, text.sz-6) + crc(text) + text.sz. Не решаюсь сказать, что курили разработчики, но затягивались явно здорово.
Распаковка идет по тому же принципу. К данным из gzip файла спереди присобачиваются те же оторванные 2 байта и текст разжимается в deflate(). Разжимается с криками и визгом. Видно не хватает тех четырех байт, но как их генерировать я не понял, да и rfc вроде как молчит и не колется. Если узнаете, скажите. Ага?

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

Вызов неопределенных функций
mikelsv
Эта тема волнует меня достаточно давно. Как вызвать функцию не напрямую, имея имя и параметры, а в обход, получая информацию перед самым вызовом. С вызовом через прототип я разобрался довольно быстро и тема имени отпала. Остались параметры и начались поиски решений.
В прошлый раз исследования привели к решению вызывать функции через ассемблер. Передавая параметры в стеи и делая вызов. Тогда это решение устроило. Но вот проблема встала снова и захотелось чего-то нового.
Помощь пришла с совершенно неожиданной стороны. Те, кто пользовался функциями типа int func(...) и va_list знают, насколько оно чувствительно к параметрам и как любит падать. В общем адская штука и пользоваться ей категорически не советую. Но, как оказалось, при вызове такой функции компилятор кладет параметры с тек так же как при вызове любой другой. И это внезапно открыло новые перспективы.

#include <stdio.h>
#include <string.h>

// Берем функцию:
int test(int a, char *b, double c){
    printf("%d %s %f", a, b, c);
    return 0;
}

// Делаем прототип:
typedef int (*callfunc)(...);

// И пишем код:
int main(int args, char* arg[]){
    // параметры функции, которые хотим передать.
    double d=11; int str[4]={15, (unsigned int)"text"}; memcpy(str+2, &d, 8);

    // структура, которая будет полностью скопирована в стек. Если передавать str, то будет передан указатель, а нам нужно передать именно данные.
    struct efunc_s{ unsigned int a[4]; };

    // получаем адрес функции.
    callfunc cf=(callfunc)test;

    // Приводим в вид, который будет скопирован в стек.
    efunc_s &s=*(efunc_s*)&str;

   // вызываем функцию
    (*cf)(s);

    return 0;
}

Код прекрасно работает в MSVS 2010. К сожалению под g++ этот код работать не захотел, но думаю это не надолго. Естественно, при сборке под 64 бита. В g++ -m32 отлично работает, под 64 бита надо принять во внимание размер char*.
struct efunc_s{ unsigned int a[4]; }; может быть больше без ущерба для работы программы. Лишние данные никак не повлияют.
Наконец-то достигнут именно тот результат, который я искал.
Tags: ,

?

Log in

No account? Create an account