Архив рубрики: Linux

Пишем монитор pulseaudio для lxpanel

С момента написания статьи об xmonad кое-что поменялось и я перешёл на использование openbox. Поменялись и панели, теперь я использую lxpanel, который разрабатывается для lxde, легковесного рабочего стола, основанного на openbox. Отображаемые lxpanel элементы являются плагинами, динамически загружаемыми библиотеками. Сегодня мы напишем свой плагин для lxpanel.
Так как в стандартной поставке lxpanel виджет для регулировки громкости не очень хорошо (скорее очень нехорошо) отображает текущий уровень громкости, то я решил написать свой плагин, который исправит ситуацию.
Эта статья будет состоять из двух частей. Первая будет посвящена работе с pulseaudio (читается [pʌlsˈɔːdɪəu] пaлсодыо), а вторая -- lxpanel.


На скриншоте выше результат можно разглядеть слева.
Писать мы будем на C. Да, именно на C, так как заголовочный файл lxpanel является ярким представителем того, как можно писать на C несовместимый с C++ код.

Внимание, весь код, приведённый в этой статье распространяется по лицензии GNU GPL версии 3 и выше!

Монитор Pulseaudio

Начиная работать с pulseaudio натыкаешься на, мягко говоря, скудную документацию, основу которой составляет сгенерированная doxygen`ом. Из документации мы узнаём, что у pulseaudio есть два типа API: simple и asynchronous. Беглый взгляд на simple API позволяет понять, что придётся использовать асинхронный API.
Основным элементом для работы с асинхронным API pulseaudio является контекст, представленный типом pa_context. Контекст представляет собой мост между приложением и демоном pulseaudio. Чтобы контекст работал, ему нужен mainloop. Pulseaudio представляет возможность использовать три варианта реализации основного цикла:
Из перечисленных вариантов нам больше всего подходит последний, так как lxpanel написана на GTK+ 2 и уже имеет запущенный GMainLoop. Остальные варианты приведут либо к зависанию lxpanel, либо к сложной и запутанной архитектуре.
Так как на машине может быть несколько звуковых устройств, то их надо как-то различать. Pulseaudio использует для этого понятие sink (не знаю, как правильно перевести). Sink`и имеют свой идентификатор или индекс, который является порядковым номером, начиная с нуля.
Итак, на основе полученной информации мы уже можем накидать интерфейс, который мы будем использовать для получения информации от pulseaudio.
#include <stdint.h>

#define PAMON_UNUSED(var) (void)var

typedef struct Pamon Pamon;

typedef enum
{
PAMON_ERROR_CONNECTION_FAILED,
PAMON_ERROR_INVALID_SINK
} PamonError;

typedef void (* PamonVolumeChangedCallback)(uint32_t current_volume_percents, void * userdata);
typedef void (* PamonErrorCallback)(PamonError error, void * userdata);

typedef struct
{
uint32_t sink_id;
PamonVolumeChangedCallback volume_callback;
PamonErrorCallback error_callback;
void * userdata;
} PamonInitData;

Pamon * pamonInit(PamonInitData * init_data);

void pamonSetSinkId(Pamon * pamon, uint32_t id);

int pamonStart(Pamon * pamon);

void pamonClose(Pamon * pamon);

const char * pamonTranslateError(PamonError error);
Немного поясню. Тип PamonError используется для сообщения об ошибке. Значение PAMON_ERROR_CONNECTION_FAILED говорит о том, что соединиться с демоном pulseaudio не удалось, а PAMON_ERROR_INVALID_SINK сообщает о неверном идентификаторе sink`а. Функция pamonTranslateError переводит идентификатор ошибки в понятный человеку вид, в строку.
Фунции pamonInit и pamonClose соответственно инициализируют и закрывают соединение с демоном pulseaudio. Для инициализации соединения в функцию pamonInit передаётся структура PamonInitData содержащая идентификатор sink`а и колбеки для уведомления о смене громкости и об ошибке. Кроме того структура  PamonInitData содержит поле userdata, которое будет передано в колбеки.
Назначение функции pamonSetSinkId должно быть ясно без пояснений.
Используя этот интерфейс можно написать консольное приложение, которое будет выводить текущий уровень громкости.
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <glib.h>
#include "pamon.h"

#define APP_NAME "pamon"
#define DEFAULT_FORMAT "%i%%"

typedef struct
{
char * format;
uint32_t sink;
int print_help;
} Options;

static void pamonVolumeChanged(uint32_t current_volume, void * userdata);
static void pamonError(PamonError error, void * userdata);
static void onSignal(int signum);
static void parseOpts(int argc, char ** argv, Options * result);
static void printUsage();

static Pamon * pamon = NULL;
static GMainLoop * main_loop = NULL;
static char * format = NULL;


int main(int argc, char ** argv)
{
struct sigaction action;
memset(&action, 0, sizeof(struct sigaction));
action.sa_handler = onSignal;
sigaction(SIGINT, &action, NULL);
PamonInitData pamon_init_data;
memset(&pamon_init_data, 0, sizeof(PamonInitData));
{
Options opts;
memset(&opts, 0, sizeof(Options));
parseOpts(argc, argv, &opts);
if(opts.print_help)
{
printUsage();
free(opts.format);
return EXIT_SUCCESS;
}
pamon_init_data.sink_id = opts.sink;
format = opts.format;
}
pamon_init_data.volume_callback = pamonVolumeChanged;
pamon_init_data.error_callback = pamonError;
main_loop = g_main_loop_new(NULL, FALSE);
pamon = pamonInit(&pamon_init_data);
g_main_loop_run(main_loop);
return EXIT_SUCCESS;
}

void onSignal(int signum)
{
pamonClose(pamon);
g_main_loop_quit(main_loop);
g_main_loop_unref(main_loop);
free(format);
printf("\nquit\n");
exit(EXIT_SUCCESS);
}

void pamonVolumeChanged(uint32_t current_volume, void * userdata)
{
PAMON_UNUSED(userdata);
printf(format ? format : DEFAULT_FORMAT, current_volume);
printf("\n");
fflush(stdout);
}

void pamonError(PamonError error, void * userdata)
{
PAMON_UNUSED(userdata);
fprintf(stderr, "Error: %s\n", pamonTranslateError(error));
}

void parseOpts(int argc, char ** argv, Options * result)
{
for(;;)
{
switch(getopt(argc, argv, "f:s:"))
{
case -1:
return;
case '?':
result->print_help = 1;
break;
case 'f':
result->format = (char *)malloc(strlen(optarg) + 1);
strcpy(result->format, optarg);
break;
case 's':
sscanf(optarg, "%i", &result->sink);
break;
default:
break;
}
}
}

void printUsage()
{
printf(
"Usage: %s [-s sink] [-f format]\n"
" sink - pulseaudio sink id\n"
" format - print format. Use %%i to print current volume value. Use %%%% to print the %% mark\n"
"\n"
"Use Ctrl+C to exit program\n",
APP_NAME);
fflush(stdout);
}
Здесь мы получаем из опций приложения идентификатор sink`а и формат сообщения. Создаём основной цикл GLib и запускам pamon, передав функции pamonVolumeChanged и pamonError в качестве колбеков. Программа завершается по нажатию Ctrl+C. Обработчик сигнала SIGINT позволит освободить занятые ресурсы перед выходом, хотя это и не обязательно.

Перейдём к самому интересному, к реализации интерфейса взаимодействия с pulseaudio.
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <pulse/pulseaudio.h>
#include <pulse/glib-mainloop.h>
#include "pamon.h"

struct Pamon
{
pa_context * pulse_context;
pa_glib_mainloop * pulse_glib_mainloop;
PamonVolumeChangedCallback volume_callback;
PamonErrorCallback error_callback;
int sink_id;
void * userdata;
};

Pamon * pamonInit(PamonInitData * init_data)
{
Pamon * pamon = (Pamon *)malloc(sizeof(Pamon));
memset(pamon, 0, sizeof(Pamon));
if(init_data)
{
pamon->userdata = init_data->userdata;
pamon->volume_callback = init_data->volume_callback;
pamon->error_callback = init_data->error_callback;
pamon->sink_id = init_data->sink_id;
}
pamon->pulse_glib_mainloop = pa_glib_mainloop_new(NULL);
pa_mainloop_api * api = pa_glib_mainloop_get_api(pamon->pulse_glib_mainloop);
pamon->pulse_context = pa_context_new(api, NULL);
pa_context_set_state_callback(pamon->pulse_context,
(pa_context_notify_cb_t)pulseContextStateCallback, pamon);
pa_context_connect(pamon->pulse_context, NULL, 0, NULL);
return pamon;
}
Функция инициализации создаёт и заполняет экземпляр структкры Pamon. Затем создаётся основной цикл из дефолтного контекста GLib и, после получения API основного цикла, инициализируется контекст. pa_mainloop_api служит абстрактным интерфейсом для работы со всеми тремя типами основных циклов. Так как мы используем асинхронные API и соединение с демоном pulseaudio происходит в отдельном потоке, функция pa_context_connect может сообщить о своём результате только в функцию обратного вызова, которую мы и устанавливаем с помощью pa_context_set_state_callback. При установке колбеков мы можем передавать пользовательские данные, которые будут отданы колбеку. Здесь и далее мы будем использовать указатель на структуру Pamon, хранящую всю служебную информацию.
void pulseContextStateCallback(pa_context * context, Pamon * pamon)
{
switch(pa_context_get_state(context))
{
case PA_CONTEXT_READY:
pa_context_set_subscribe_callback(context, (pa_context_subscribe_cb_t)pulseContextCallback, pamon);
pa_context_subscribe(context, PA_SUBSCRIPTION_MASK_SINK, NULL, NULL);
performSinkHandler(context, pamon);
break;
case PA_CONTEXT_FAILED:
performError(pamon, PAMON_ERROR_CONNECTION_FAILED);
break;
default:
break;
}
}
Здесь мы подписываемся на события контекста с помощью функции pa_context_subscribe, устнавив колбек вызовом pa_context_set_subscribe_callback. Так как нам интересны только события от sink`ов, то передаём маску PA_SUBSCRIPTION_MASK_SINK для фильтрации того, что нам будет приходить в колбек.
Если произошла ошибка, о ней сообщаем вызовом performError, который, по сути, зовёт колбек, переданный при инициализации.
void performError(Pamon * pamon, PamonError error)
{
if(pamon->error_callback)
pamon->error_callback(error, pamon->userdata);
}
Кроме подписки на события, мы форсируем получение значения громкости для изначальной инициализации подписчика.
void performSinkHandler(pa_context * context, Pamon * pamon)
{
pa_operation * operation = pa_context_get_sink_info_by_index(context, pamon->sink_id,
(pa_sink_info_cb_t)pulseSinkInfoCallback, pamon);
pa_operation_unref(operation);
}
Как видно, функция performSinkHandler выполняет только одно действие -- просит информацию о sink`е по его идентификатору вызовом функции pa_context_get_sink_info_by_index. Так как информация о ходе выполнения асинхронной операции нам не интересна, то сразу же отпускаем ссылку на экземпляр pa_operation вызвав pa_operation_unref. Итак, информация о sink`е приходит в функцию pulseSinkInfoCallback.
void pulseSinkInfoCallback(pa_context * context, const pa_sink_info * info, int eol, Pamon * pamon)
{
if(eol == -1)
{
performError(pamon, PAMON_ERROR_INVALID_SINK);
}
if(info && pamon && pamon->volume_callback)
{
uint32_t volume = info->mute || !info->volume.channels ? 0 : info->volume.values[0];
uint32_t percents = (uint32_t)round((double)(volume * 100) / PA_VOLUME_NORM);
pamon->volume_callback(percents, pamon->userdata);
}
}
Я не нашёл документации о параметре eol, но методом научного тыка выяснил, что при ошибке этот флаг принимает значение -1.
Структура pa_sink_info содержит всю необходимую нам информацию. Уровень громкости тут содержится не в процентах, а в довольно больших числах. Кроме того, уровень громкости можно получить для каждого канала отдельно. Поле channels содержит количество доступных каналов, а массив values, состоящий из 32-ух целочисленных значений имеет инициализированными ровно channels первых элементов. Здесь я схалтурил и взял первое попавшееся значение. Карту соответствия индексов массива с реальными каналами можно посмотреть в поле channel_map структуры pa_sink_info.
Высчитать текущий уровень громкости в процентах помогает константа PA_VOLUME_NORM, которая содержит значение громкости при 100%, 65536.

На этом наша работа с pulseaudio заканчивается. Осталось только корректно завершить работу.
void pamonClose(Pamon * pamon)
{
if(!pamon) return;
pa_context_disconnect(pamon->pulse_context);
pa_context_unref(pamon->pulse_context);
pa_glib_mainloop_free(pamon->pulse_glib_mainloop);
free(pamon);
}
Для этого отсоединяемся от демона pulseaudio c помощью вызова pa_context_disconnect, отпускаем ссылку на контекст с помощью pa_context_unref и высвобождаем память, занятую основным циклом используя pa_glib_mainloop_free. Естественно не забываем и о нашей структуре, хранящей служебную информацию. Следует обратить внимание, что для завершения работы с другими типами mainloop`ов нужно выполнять действия, соответствующие выбранному типу.

За бортом остались две функции: pamonSetSinkId и pamonTranslateError. Их назначение понятно, а код прост, поэтому приведу его без комментариев.
#define ERSTR_UNKNOWN "Unknown error"
#define ERSTR_INVALID_SINK "Invalid sink"
#define ERSTR_CONNECTION_FAILED "Unable to connect to the pulseaudio daemon"

void pamonSetSinkId(Pamon * pamon, uint32_t id)
{
if(!pamon || pamon->sink_id == id)
return;
pamon->sink_id = id;
performSinkHandler(pamon->pulse_context, pamon);
}

const char * pamonTranslateError(PamonError error)
{
switch(error)
{
case PAMON_ERROR_CONNECTION_FAILED:
return ERSTR_CONNECTION_FAILED;
case PAMON_ERROR_INVALID_SINK:
return ERSTR_INVALID_SINK;
default:
return ERSTR_UNKNOWN;
}
}

lxpanel

Полученное приложение уже можно использовать, например, в conky или dzen2. Так
выглядит dzen2, запущенный командой
$ ./pamon -f " Current volume: %i%%" | dzen2 -bg "#505050" -fg white -ta left
Но наша цель -- встроить монитор в lxpanel, поэтому создаём ещё один файл, lxpamon.c, который будем компилировать в динамическую библиотеку.
Первое, что нам надо сделать -- это подключить необходимые заголовочные файлы.
#include <stdlib.h>
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include <lxpanel/plugin.h>
#include "pamon.h"
Файл lxpanel/plugin.h и есть тот самый файл, который не способен переварить компилятор C++.
Документации по написанию плагинов для lxpanel ещё меньше, чем документации по pulseaudio, а точнее -- ровно одна страничка. Даже зачитав её до дыр, Вы не сможете написать полноценный плагин. Для того, чтобы написать свой, мне пришлось посмотреть в исходинки плагинов, поставляемых с lxpanel. Я скачал исходники из репозитория debian по этой ссылке. Файл src/plugins/dclock.c, содержащий исходники плагина с часами, оказался наиболее компактным и полезным.
Итак, для того, чтобы встроить плагин в панель, мы должны собрать библиотеку и положить её в  /usr/lib/lxpanel/plugins/ (или в  /usr/lib/x86_64-linux-gnu/lxpanel/plugins/ для мультиархитектурного debian`а). Есть одна особенность, имя файла не должно содержать префикс lib и иметь имя <имя_плагина>.so. В нашем случае файл будет называться lxpamon.so.
Библиотека с плагином должна экспозить экземпляр структуры PluginClass (к сожалению, ссылок мне давать не на что). Но не просто экспозить, а экспозить с определённым именем. Имя должно иметь вид <имя_плагина>_plugin_class. То есть, наш экземпляр будет иметь имя lxpamon_plugin_class.
PluginClass lxpamon_plugin_class = {
PLUGINCLASS_VERSIONING,
type: "lxpamon",
name: N_("Pulseaudio volume monitor"),
version: "1.0",
description: N_("Pulseaudio volume monitor plugin for LXPanel"),
one_per_system: FALSE,
expand_available: FALSE,
constructor: lxpamonConstructor,
destructor: lxpamonDestructor,
config: lxpamonConfigure,
save: lxpamonSaveConfig,
panel_configuration_changed: lxpamonPanelConfigurationChanged
};
Структура PluginClass описывает наш плагин. Макрос PLUGINCLASS_VERSIONING заполняет поля для определения версии структуры. Поле type должно содержать имя нашего плагина. Поля version и description вполне понятны. Флаг one_per_system позволяет ограничит количество активных экземпляров плагина одной штукой, а флаг expand_available разрешает растягивать виджет на всю свободную область панели (как tasklist или spacer). Макрос N_ объявлен в GLib и предназначен для интернационализации.
Оставшиеся поля контролируют жизненный цикл плагина, это следующие процедуры обратного вызова:
  • constructor -- инициализирует плагин;
  • destructor -- вызывается перед выгрузкой плагина;
  • config -- колбек, который будет зваться для редактирования настроек (например, при нажатии на кнопку Edit в настройках элементов панели);
  • save -- зовётся для сохранения настроек в конфиг;
  • panel_configuration_changed -- колбек, сообщающий, что конфигурация самой панели изменилась.
Для того, чтобы хранить текущее состояние плагина, будем использовать структуру PluginData.
typedef struct
{
Pamon * pamon;
GtkWidget * label;
uint32_t current_volume;
int sink;
char * format;
int bold_font;
} PluginData;
И сразу же приведу код конструктора.
int lxpamonConstructor(Plugin * plugin, char ** config)
{
GtkWidget * label = gtk_label_new(NULL);
plugin->pwid = gtk_event_box_new();
gtk_container_add(GTK_CONTAINER(plugin->pwid), GTK_WIDGET(label));
g_signal_connect(G_OBJECT(plugin->pwid), "button_press_event",
G_CALLBACK(plugin_button_press_event), plugin);
gtk_widget_show_all(plugin->pwid);
PluginData * plug_data = g_new0(PluginData, 1);
plug_data->label = label;
lxpamonInitConfig(config, plug_data);
plugin->priv = plug_data;
PamonInitData pamon_init_data;
memset(&pamon_init_data, 0, sizeof(PamonInitData));
pamon_init_data.volume_callback = (PamonVolumeChangedCallback)pamonVolumeChanged;
pamon_init_data.error_callback = (PamonErrorCallback)pamonError;
pamon_init_data.userdata = plugin;
pamon_init_data.sink_id = plug_data->sink;
Pamon * pamon = pamonInit(&pamon_init_data);
plug_data->pamon = pamon;
return TRUE;
}
Здесь создаются GTK+ контейнер и label, который кладётся в контейнер. Вообще говоря, Вы может создавать любые виджеты. Осонвное, что Вы должны сделать -- положить указатель на основной виджет в поле pwid структуры Plugin, которая передаётся на вход конструктору. Вообще, указатель на структуру Plugin будет передаваться во все колбеки. Эта структура содержит кое-какие полезные поля, с которыми мы познакомимся по ходу дела. Поле priv предназначено для хранения пользовательских данных, поэтому положим туда указатель на объект структуры PluginData.
Вторым параметром в конструктор передаётся конфиг, который мы читаем в функции lxpamonInitConfig. Сохраняется конфиг, как уже было сказано, функцией lxpamonSaveConfig.
#define DEFAULT_FORMAT "%i%%"
#define CONFIG_FORMAT "Format"
#define CONFIG_SINK "Sink"
#define CONFIG_BOLD "BoldFont"

void lxpamonInitConfig(char ** src, PluginData * plug_data)
{
line str;
str.len = 256;
while(lxpanel_get_line(src, &str))
{
if(strcmp(CONFIG_FORMAT, str.t[0]) == 0)
plug_data->format = g_strdup(str.t[1]);
else if(strcmp(CONFIG_SINK, str.t[0]) == 0)
sscanf(str.t[1], "%i", &plug_data->sink);
else if(strcmp(CONFIG_BOLD, str.t[0]) == 0)
plug_data->bold_font = str2num(bool_pair, str.t[1], 0);
}
if(!plug_data->format)
plug_data->format = g_strdup(DEFAULT_FORMAT);
}

void lxpamonSaveConfig(Plugin * plugin, FILE * file)
{
PluginData * plug_data = (PluginData *)plugin->priv;
lxpanel_put_str(file, CONFIG_FORMAT, plug_data->format);
lxpanel_put_int(file, CONFIG_SINK, plug_data->sink);
lxpanel_put_bool(file, CONFIG_BOLD, plug_data->bold_font);
}
Для записи данных в конфиг, используются макросы lxpanel_put_str, lxpanel_put_bool и lxpanel_put_int, кторые принемают файл, имя параметра и значение. Наш конфиг будет выглядеть примерно так
Plugin {
type = lxpamon
Config {
Format=%i%%
Sink=0
BoldFont=1
}
}
Конфиги лежат в ~/.config/lxpanel/default/panels/<имя_панели>. Для последовательного чтения записей из конфига следует использовать функцию lxpanel_get_line. Она записывает в свой второй параметр пару имя-значение из конфига. Как только пары кончатся, функция вернёт ноль. Функция str2num хоть и кажется, что переводит строку в число, но на деле она работает только с предопределёнными соответствиями, которые передаются в первом параметре. Например, bool_pair представлен примерно так { {"0", 0}, {"1", 1 } }.

Для того, чтобы пользователь смог задать настройки, мы должны показать диалог при обработке соответствующего события.
void lxpamonConfigure(Plugin * plugin, GtkWindow * parent)
{
PluginData * data = (PluginData *)plugin->priv;
GtkWidget * dlg = create_generic_config_dlg(_(plugin->class->name), GTK_WIDGET(parent),
(GSourceFunc)lxpamonApplyConfiguration, plugin,
_("Pulseaudio sink"), &data->sink, CONF_TYPE_INT,
_("Volume display format"), &data->format, CONF_TYPE_STR,
_("Use %i to print current volume. Use %% to print the % mark."), NULL, CONF_TYPE_TRIM,
_("Bold font"), &data->bold_font, CONF_TYPE_BOOL,
NULL);
gtk_window_present(GTK_WINDOW(dlg));
}
Здесь используется функция create_generic_config_dlg, показывающая стандартный диалог с настройками.

Вся проблема в том, что этой функции нет в файле lxpanel/plugin.h. Она объявлена в самой панели, но стандартные плагины используют именно её. Поэтому мы добавим себе её объявление.
extern GtkWidget * create_generic_config_dlg(const char * title, GtkWidget * parent,
GSourceFunc apply_func, Plugin * plugin, const char * name, ...);
Эта функция принимает родителя, заголовок и колбек, который будет вызван для применения настроек. В списке неопределённых параметров функция принимает тройки "имя параметра", "указатель на значение" и "тип значения". Завершается список параметров NULL`ом. Макрос _ объявлен в GLib и нужен для интернационализации. При принятии параметров мы устанавливаем новый sink для монитора и перерисовываем виджет
void lxpamonApplyConfiguration(Plugin * plugin)
{
PluginData * plug_data = (PluginData *)plugin->priv;
pamonSetSinkId(plug_data->pamon, plug_data->sink);
lxpamonDrawVolume(plugin);
}

static void lxpamonDrawVolume(Plugin * plugin)
{
PluginData * plug_data = (PluginData *)plugin->priv;
char * format = plug_data->format ? plug_data->format : "%%i";
char * buf = (char *)malloc(strlen(format) + 4);
if(INVALID_VOLUME == plug_data->current_volume)
strcpy(buf, "ERROR");
else
sprintf(buf, plug_data->format, plug_data->current_volume);
panel_draw_label_text(plugin->panel, plug_data->label, buf, plug_data->bold_font, TRUE);
free(buf);
}
При отрисовке мы снова используем функцию, которая не объявлена в lxpanel/plugin.h.
extern void panel_draw_label_text(Panel * p, GtkWidget * label, char * text,
gboolean bold, gboolean custom_color);
Функция panel_draw_label_text рисует текст на лейбле цветом, заданным в конфигурации панели. Структура Plugin содержит поле panel, у которого в свою очередь есть поля, хранящие цвета панели, в том числе цвет шрифта. Но есть и флаг usefontcolor, который установлен в 0, а при попытке использовать значение цвета получается что-то красное. Функция panel_draw_label_text также позволяет рисовать полужирным шрифтом, чем мы тоже пользуемся.
Значение INVALID_VOLUME устанавливается при обработке ошибки от pamon. В этом случае наш плагин напишет слово "ERROR".

Функция panel_draw_label_text не последняя из тех, которые забыли положить в файл lxpanel/plugin.h, ещё нам нужен стандартный обработчик клика мыши.
extern gboolean plugin_button_press_event(GtkWidget * widget, GdkEventButton * event, Plugin * plugin);
Эту функцию мы повесели на "button_press_event" в конструкторе. Эта функция добавляет в контекстное меню пункты для редактирования настроек и удаления виджета с панели.

После того, как настройки панели изменены нам нужно перерисовать свой виджет. Именно для этого мы и подписывались на это событие.
void lxpamonPanelConfigurationChanged(Plugin * plugin)
{
lxpamonDrawVolume(plugin);
}

Обработчики событий от pulseaudio тривиальны и не нуждаются в комментариях за одним исключением, ошибка может произойти до того, как поле plugin->priv будет присвоено, поэтому его нужно проверить.
void lxpamonPanelConfigurationChanged(Plugin * plugin)
{
lxpamonDrawVolume(plugin);
}

void pamonVolumeChanged(uint32_t current_volume, Plugin * plugin)
{
PluginData * plug_data = (PluginData *)plugin->priv;
plug_data->current_volume = current_volume;
lxpamonDrawVolume(plugin);
}

void pamonError(PamonError error, Plugin * plugin)
{
fprintf(stderr, "Pulseaudio monitor error: %s\n", pamonTranslateError(error));
PluginData * plug_data = (PluginData *)plugin->priv;
if(plug_data)
{
plug_data->current_volume = INVALID_VOLUME;
lxpamonDrawVolume(plugin);
}
}
Последнее, что осталось сделать -- освободить ресурсы при выгрузке плагина. Здесь это гораздо важнее, чем в консольном приложении, так как панель может продолжать работать ещё долгое время и утекшая память не вернётся системе.
void lxpamonDestructor(Plugin * plugin)
{
PluginData * plug_data = (PluginData *)plugin->priv;
pamonClose(plug_data->pamon);
g_free(plug_data->format);
g_free(plug_data);
}
Поле plugin->pwid осовобождать не нужно, lxpanel это сделает за Вас.

P.S.

Исходники проекта лежат на github.
git clone https://github.com/brainstream/lxpamon.git
Возможно, проект я буду дорабатывать, если кому интересна изначальная версия, она лежит здесь.

Получившийся результат можно посмотреть на видео ниже

Virtual inline методы C++

C++ даёт возможность определять встраиваемые (inline) функции и методы. Реализация таких функций будет встраиваться в другие функции по месту вызова (за исключением некоторых случаев, когда это невозможно). Как правило, в конечном исполняемом файле или динамической библиотеке отдельной реализации таких функций не генерируется.
Другой замечательный механизм, предоставляемый языком C++ — это виртуальные методы. Эти методы участвуют в полиморфизме. Вы может вызывать такие функции из производного класс по указателю или ссылке на базовый. Достигается это путём помещения указателя на конкретную реализацию в таблицу виртуальных функций, которая помещается компилятором в известное ему место относительно полиморфного указателя или ссылке (конкретная реализация не стандартизирована и зависит от компилятора).

Стандарт C++ позволяет применять модификатор virtual к inline функциям. Тут мы наталкиваемся на неоднозначность. Как в таблицу виртуальных функций попадёт указатель, если тело функции не генерируется? Ответ на этот вопрос также не стандартизирован и зависит от компилятора. Давайте попробуем разобраться.
Итак, допустим у нас есть такой код
extern "C" void exFunction(int);

class Base
{
public:
virtual int doSomething(int x, int y)
{
return x + y;
}
};

class Child : public Base
{
public:
virtual int doSomething(int x, int y)
{
return x * y;
}
};

extern "C" void run()
{
Base base;
int result = base.doSomething(1, 2);
exFunction(result);
Child child;
result = child.doSomething(2, 2);
exFunction(result);
}
Функция exFunction нужна лишь для того, чтобы компилятор не соптимизировал наши вычисления.
Я буду использовать компилятор g++ 4.7.2 и операционную систему Debian GNU/Linux 7.0 (wheezy) 64 bit.
Сохраним исходный текст в файл test.cpp соберём динамическую библиотеку с помощью команды
g++ -g -O2 -shared -fPIC  test.cpp
Я собираю динамическую библиотеку, чтобы было меньше мусора в дампе. Кроме того, я включил оптимизацию и отладочные символы. Посмотрим результат компиляции с помощью программы objdump.
objdump -d a.out
00000000000006e0 <run>:
6e0: 48 83 ec 08 sub $0x8,%rsp
6e4: bf 03 00 00 00 mov $0x3,%edi
6e9: e8 e2 fe ff ff callq 5d0 <exFunction@plt>
6ee: bf 04 00 00 00 mov $0x4,%edi
6f3: 48 83 c4 08 add $0x8,%rsp
6f7: e9 d4 fe ff ff jmpq 5d0 <exFunction@plt>
Как видим, компилятор не обратил внимание на модификатор virtual и просто встроил inline методы в код, соптимизировав сложение и умножение.
Теперь добавим в код полиморфизма.
extern "C" void exFunction(int);

class Base
{
public:
virtual int doSomething(int x, int y)
{
return x + y;
}
};

class Child : public Base
{
public:
virtual int doSomething(int x, int y)
{
return x * y;
}
};

extern "C" void run()
{
Base base;
int result = base.doSomething(1, 2);
exFunction(result);
Child child;
result = child.doSomething(2, 2);
exFunction(result);
Base * polymorphic = &child;
result = polymorphic->doSomething(3, 3);
exFunction(result);
}
Скомпилируем и посмотрим на дизассемблированный код.
0000000000000a50 <run>:
a50: 48 83 ec 18 sub $0x18,%rsp
a54: bf 03 00 00 00 mov $0x3,%edi
a59: e8 e2 fe ff ff callq 940 <exFunction@plt>
a5e: 48 8b 05 e3 03 20 00 mov 0x2003e3(%rip),%rax # 200e48 <_DYNAMIC+0x228>
a65: bf 04 00 00 00 mov $0x4,%edi
a6a: 48 83 c0 10 add $0x10,%rax
a6e: 48 89 04 24 mov %rax,(%rsp)
a72: e8 c9 fe ff ff callq 940 <exFunction@plt>
a77: 48 8b 04 24 mov (%rsp),%rax
a7b: 48 89 e7 mov %rsp,%rdi
a7e: ba 03 00 00 00 mov $0x3,%edx
a83: be 03 00 00 00 mov $0x3,%esi
a88: ff 10 callq *(%rax)
a8a: 89 c7 mov %eax,%edi
a8c: e8 af fe ff ff callq 940 <exFunction@plt>
a91: 48 83 c4 18 add $0x18,%rsp
a95: c3 retq
a96: 90 nop
a97: 90 nop
a98: 90 nop
a99: 90 nop
a9a: 90 nop
a9b: 90 nop
a9c: 90 nop
a9d: 90 nop
a9e: 90 nop
a9f: 90 nop

0000000000000aa0 <_ZN4Base11doSomethingEii>:
aa0: 8d 04 16 lea (%rsi,%rdx,1),%eax
aa3: c3 retq
aa4: 90 nop
aa5: 90 nop
aa6: 90 nop
aa7: 90 nop
aa8: 90 nop
aa9: 90 nop
aaa: 90 nop
aab: 90 nop
aac: 90 nop
aad: 90 nop
aae: 90 nop
aaf: 90 nop

0000000000000ab0 <_ZN5Child11doSomethingEii>:
ab0: 89 f0 mov %esi,%eax
ab2: 0f af c2 imul %edx,%eax
ab5: c3 retq
ab6: 90 nop
ab7: 90 nop
Теперь мы видим, что компилятор сгенерировал реализации для виртуальных методов и использовал виртуальную таблицу для третьего вызова. При этом первые два вызова остались встроенными.

Вообще говоря, компилятор всегда будет генерировать тело inline функции, если где-либо в программе требуется её адрес (явно или неявно). При этом, Вы можете расчитывать на встраивание virtual inline функции, если не используете для её вызова указатель или ссылку.

Я провёл подобное исследование с Visual C++, и оказалось, что этот компилятор ведёт себя аналогичным образом.

Настройка графического окружения Linux на основе xmonad и xmobar

Так совпало, что меня заинтересовали две вещи: язык программирования Haskell и фреймовые (или тайловые) оконные менеджеры под Linux. И тут я обнаружил замечательный тайловый менеджер, написанный на Haskell. Я не смог пройти мимо и установил себе xmonad.
Эта статья будет не совсем обычная для этого блога. Здесь практически не будет программирования. Я просто покажу Вам как настроить окружение на основе оконного менеджера xmonad и панелей xmobar.

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

Сразу после установки xmonad, Вы, скорее всего, не сможете даже запустить какое-либо приложение. Вам придётся сходить в терминал и прочесть man xmonad, где описаны все дефолтные сочетания клавиш. Дабы облегчить Вам задачу, сразу же приведу основные, без которых работать в среде невозможно:
  • Mod+p: открыть dmenu для запуска приложений;
  • Mod+Shift+Enter: открыть терминал;
  • Mod+Shift+c: закрыть текущее окно;
  • Mod+j: перевести фокус на следующее окно;
  • Mod+k: перевести фокус на предыдущее окно;
  • Mod+[1-9]: перейти на рабочее пространство [1-9];
  • Mod+Shift+[1-9]: перекинуть текущее окно на рабочее пространство [1-9];
  • Mod+q: перезагрузить xmonad;
  • Mod+Shift+q: выйти из xmonad.
Mod по умолчанию -- клавиша Alt.
Никаких баров с xmonad не поставляется, но этот оконный менеджер хорошо интегрируется с xmobar и dzen2.

Базовая настройка xmonad

Для начала настроим оконный менеджер, а затем перейдём к барам, трею и дополнительным скриптам.
Xmonad настраивается путём описания настроек на языке Haskell. Если Вы не знакомы с этим замечательным языком, я советую Вам исправить эту ситуацию как можно скорее. Без базового понимания Haskell, Вы сможете только скопировать готовый конфиг и кастомизировать его по аналогии с уже написанным кодом. В то время как зная Haskel, Вам откроются безграничные просторы для фантазии.
Настройки xmonad должны находиться в файле ~/.xmonad/xmonad.hs.
Минимальный файл конфигурации должен содержать функцию main, в которой происходит вызов функции xmonad с передачей структуры XConfig, содержащей конфигурацию.
import XMonad

main = do xmonad $ defaultConfig
XConfig проще всего получить из функции defaultConfig и на основе настроек по умолчанию создать свои собственные.
Первое, что мне захотелось сделать -- это изменить клавишу-модификатор  с Alt на Super, так как клавиша Alt используется многими другими приложениями. Сделать это очень легко, достаточно назначить полю modMask значение типа KeyMask, в нашем случае это mod4Mask.
import XMonad

main = do
xmonad $ defaultConfig {
modMask = mod4Mask }

Настройка xmobar и trayer

Теперь, когда мы знаем, как настраивать xmonad, было бы не плохо обзавестись тулбаром, который покажет время, загрузку CPU, а самое главное, информацию о том, где (в контексте xmonad) мы сейчас находимся. Кроме того, было бы здорово убрать почтовый клиент, jabber и прочие фоновые задачи в трей.
Xmobar -- это бар, который написан на haskell специально для xmonad, но может работать и без него. Настройки xmobar можно передавать через параметры напрямую или через конфигурационный файл. Я предпочитаю второй способ.
Для отображения трея будем использовать программу trayer.
Основная идея заключается в том, что я хочу, чтобы у меня справа отображались часы и раскладка клавиатуры, слева индикаторы загрузки процессора и памяти и состояние xmonad. Между правой и левой панелью я хочу поместить трей. Для реализации этой идеи мне понадобится запустить два экземпляра xmobar.
Мой монитор имеете ширину 1920 пикселов, поэтому я буду исходить из неё.
Конфигурационный файл xmobar имеет синтаксис конструктора с именованными полями языка haskell. Все его поля очень подробно описаны в man xmobar, поэтому я лишь кратко опишу свои файлы.
Итак, я создал каталог ~/local/conf и положил туда два файла xmobar_left и xmobar_right. Вот содержимое файла xmobar_right.
Config { font = "xft:DejaVu Sans Mono:size=10:bold:antialias=true",
bgColor = "#000000",
fgColor = "#BBBBBB",
position = Static { xpos = 1690 , ypos = 0, width = 230, height = 20 },
commands = [
Run Date "%a %b %_d %Y %H:%M:%S" "date" 10,
Run Kbd [("us", "US"), ("ru", "RU")]
],
template = "<fc=green>%kbd%</fc> %date%"
}
Назначение полей font, bgColor и fgColor не требует объяснений. Содержимое поля position было установлено опытным путём. Эксперименты показали, что для отображения даты и раскладки клавиатуры потребуется ровно 230 пикселов. Так как трей я хочу сделать шириной в 250 пикселов, то начальная позиция правой панели будет в точке 1690 по оси X. Высота бара будет состовлять 20 пикселов.
Самые важные поля в конфиге -- это commands и template. Поле commands представляет собой список команд, которые xmobar должен запустить. Список поддерживаемых команд можно найти в man xmobar. Команды имеют параметры. Команда Date принимает три аргумента: формат даты, переменная, которая будет использоваться в шаблоне для доступа к значению команды и частоту обновления в десятых долях секунды. Команда Kdb принемает список двуместных кортежей, описывающий доступные раскладки клавиатуры. Первый элемент кортежа -- это наименование раскладки, а второй -- отображаемое имя. В шаблоне к значению этой команды можно обратиться по предопределённому имени kdb.
В параметре template записывается шаблон выводимого текста. Переменные из параметра commands нужно использовать, обернув их в символы %. Кроме переменных можно использовать простой текст и разметку, указывающую цвет символов.
Далее приведу конфиг для левой панели.
Config { font = "xft:DejaVu Sans Mono:size=10:bold:antialias=true",
bgColor = "#000000",
fgColor = "#BBBBBB",
position = Static { xpos = 0 , ypos = 0, width = 1530, height = 20 },
commands = [ Run Cpu ["-S", "True", "-t", "CPU: <total>", "-L","5","-H","40","--normal","#FFFF00","--high","#FF0000"] 10,
Run Memory ["-S", "True", "-t","RAM: <usedratio> (<used>MiB of <total>MiB)"] 10,
Run XMonadLog
],
template = "%cpu% %memory% %XMonadLog%"
}
Здесь используются команды Cpu, Memory и XMonadLog. Команде Cpu передаётся два параметра. Первый параметр -- это набор опций для отображения. Шаблон передаётся через опцию -t (опция и её значение указываются в подряд следующих элементах списка). Доступны следующие переменные: total, bar, user, nice, system, idle, iowait, значение которых известно любому линуксойду. Опция -S в True просит xmobar показывать знак единицы измерения величины (в данном случае %). Опции -L и -H задают нижний и верхний предел для раскрашивания выводимого значения соответственно цветами в оциях --normal и --high. До значения -L цвет текста будет по умолчанию, между -L и -H цвет будет браться из опции --normal, для значений выше и равных -H будет использоваться цвет --high. Вторым параметром команде Cpu передаётся частота обновления в десятых долях секунды.
Команда Memory аналогична команде Cpu, только отображает загрузку памяти. Для этой команды доступны переменные total, free, buffer, cache, rest, used, usedratio, usedbar и freebar.
Самая интересная команда, которая тут используется -- это команда XMonadLog. Она не принимает никаких аргументов, а для её использования нам придётся кое-что написать в конфигурационном файле xmonad.
Запуск trayer`а достаточно тривиален.
trayer --edge top --align right --margin 230  --widthtype pixel --width 200 --heighttype pixel --height 20 --tint 0x0 --alpha 0 --transparent true
Думаю, что тут всё понятно и я не буду останавливаться на этой команде.
Теперь помещаем в файл ~/.xsession необходимые команды для запуска xmonad, xmobar`ов и trayer`а.
xmobar ~/local/conf/xmobar_left &
xmobar ~/local/conf/xmobar_right &
trayer --edge top --align right --margin 230 --widthtype pixel --width 200 --heighttype pixel --height 20 --tint 0x0 --alpha 0 --transparent true &
exec xmonad

Полная настройка xmonad

Если Вы перезапустите xmonad, то увидите, что окна перекрывают Ваш бар. Структура данных XConfig содержит несколько полей, которым можно передать функцию обработки некоторых событий. Для того, чтобы бары не перекрывались, следует определить функцию layoutHook и обернуть лейауты в конструктор AvoidStruts при помощи функции avoidStruts. Мы можем использовать лейауты по умолчанию, но лучше определим свой собственный список. Это позволит добавить собственные лейауты и настроить дефолтные.
import XMonad
import XMonad.Hooks.ManageDocks
import XMonad.Layout.NoBorders
import XMonad.Layout.Tabbed
import XMonad.Layout.StackTile

myLayouts = ( avoidStruts $ smartBorders $
Tall 1 (3/100) (1/2) |||
StackTile 1 (3/100) (1/2) |||
simpleTabbed ) |||
noBorders Full

main = do
xmonad $ defaultConfig {
modMask = mod4Mask,
layoutHook = myLayouts }

Новый код выделен полужирным шрифтом. Итак, мы создаём 4 лейаута:
  1. Tall имеет 2 столбца. В левом отображаются основные окна, а в правом вспомогательные. Конструктор принимает три аргумента. Первый -- количество окон в основном (левом) столбце. Второй -- процент, на который изменяется размер столбцов при увеличении и уменьшении. Последний -- отношение ширины столбцов по умолчанию;
  2. StackTile. Все окна располагаются в вертикальном стеке. Параметры означают то же самое, что и в конструкторе Tall;
  3. simpleTabbed -- функция, которая создаёт лейаут, в котором все окна максимально развёрнуты, а сверху экрана появляются вкладки с их заголовками;
  4. Full. Все окна максимально развёрнуты.
Лейауты объединяются оператором |||. Объединённые лейауты передаём функции smartBorders, которая убирает рамку окна, если оно одно. Далее вызывается функция avoidStruts для предотвращения перекрытия баров. Полноэкранный лейаут должен перекрывать бары, поэтому он не должен попадать под действие avoidStruts. А так как окно там всегда одно, то вместо smartBorders, мы применяем к этому режиму функцию noBorders для предотвращения отрисовки рамки. Документацию и список доступных лейаутов можно найти здесь.

Ещё одна беда заключается в том, что панель trayer принимает фокус и присутствует лишь на одном workspace`е. Чтобы решить эту проблему, нужно добавить в обработчик manageHook функцию manageDocks.
import XMonad
import XMonad.Hooks.ManageDocks
import XMonad.Layout.NoBorders
import XMonad.Layout.Tabbed
import XMonad.Layout.StackTile

myLayouts = ( avoidStruts $ smartBorders $
Tall 1 (3/100) (1/2) |||
StackTile 1 (3/100) (1/2) |||
simpleTabbed ) |||
noBorders Full

main = do
xmonad $ defaultConfig {
modMask = mod4Mask,
layoutHook = myLayouts,
manageHook = manageHook defaultConfig <+> manageDocks }
Хуки, как видно, объединяются оператором <+>. Кроме управления барами, в обработчике manageHook можно управлять поведением различных окон. Все доступные операции приведены в документации здесь. Я покажу, на примере своего конфига, как делать определённые окна плавающими.
myManageHook = composeAll [
className =? "Gtk-recordmydesktop" --> doFloat,
className =? "Xmessage" --> doFloat,
className =? "Gxmessage" --> doFloat,
className =? "Galculator" --> doFloat,
className =? "Gksu" --> doFloat ]
manageHook = manageHook defaultConfig <+> manageDocks <+> myManageHook
Здесь я получаю класс окна при помощи функции className и сравниваю его с образцом оператором =?. Если образец и класс совпадают, то окно передаётся функции doFload оператором -->, которая делает окно плавающим. Класс окна можно получить командой
$ xprop | grep CLASS
Кроме класса окна можно использовать имя приложения или заголовок окна. Все доступные операции описаны в документации.

В обработчике manageHook можно ловить окна и перенаправлять их на другой workspace по его ID функцией doShift. Идентификаторы (они же имена) передаются в XConfig в параметре workspaces, который является списком строк.
myWorkspaces = [ "1", "2", "3", "4", "5", "6", "7", "8", "9" ]
main = do
xmonad $ defaultConfig {
modMask = mod4Mask,
workspaces = myWorkspaces,
layoutHook = myLayouts,
manageHook = manageHook defaultConfig <+> manageDocks <+> myManageHook }
Конечно же Вы можете назвать ваши рабочие пространства как хотите. Эти имена будут отображаться в нашем xmobar.

Вот и пришло время настроить тот самый лог xmonad, который мы использовали в левом xmobar. Как сказано в man xmobar, мы должны указать в параметре logHook такое выражение
logHook = dynamicLogString defaultPP >>= xmonadPropLog
И это будет работать. Но вывод, предоставленный функцией defaultPP отображается одним цветом и немного неудобно (для меня) отформатирован. Функция dynamicLogString принимает экземпляр типа PP (pretty-printing) и отдаёт строку, отформатированную согласно этому параметру. Создать экземпляр PP лучше всего функцией xmobarPP.
myLog = dynamicLogString xmobarPP {
ppCurrent = xmobarColor "green" "" . wrap "[" "]",
ppTitle = xmobarColor "lightblue" "" . wrap "[" "]",
ppHidden = xmobarColor "yellow" "",
ppHiddenNoWindows = xmobarColor "darkgray" "",
ppLayout = xmobarColor "orange" "" }
logHook = myLog >>= xmonadPropLog,
Для работы этого кода необходимо подключить модуль XMonad.Hooks.DynamicLog.
Итак, лог выглядит следующим образом:
<workspace`ы> : <имя лейаута> : <заголовок текущего окна>
Разделителем по умолчанию между воркспейсами служит пробел, а между воркспейсами, именем лейаута и именем окна -- двоеточие, окружённое пробелами. Разделители меня устраивают. В структуре PP мы меняем следующие параметры:
  • ppCurrent: имя текущего workspace`а. Его я отображаю зелёным цветом при помощи функции xmobarColor и оборачиваю в символы [ и ] стандартной функцией haskell wrap;
  • ppTile: заголовок текущего окна;
  • ppHidden: имена воркспейсов, на которых есть окна, но которые не являются текущими;
  • ppHiddenNoWindows: не текущие воркспейсы, на которых нет окон (по умолчанию не отображаются вообще);
  • ppLayout: имя лейаута.

Раз уж мы начали заниматься красивосятми, то, мне кажется, нужно поменять и рамку окон xmonad. Сделать это можно передачей параметров borderWidth, normalBorderColor и focusedBorderColor в XConfig.
main = do
xmonad $ defaultConfig {
modMask = mod4Mask,
borderWidth = 3,
normalBorderColor = "gray",
focusedBorderColor = "red",

workspaces = myWorkspaces,
layoutHook = myLayouts,
logHook = myLog >>= xmonadPropLog,
manageHook = manageHook defaultConfig <+> manageDocks <+> myManageHook }

Я использую клавиатуру с кучей мультимедийных клавиш и хотел бы использовать некоторые из них в повседневной работе. Xmonad позволяет очень легко привязать обработчики к любым клавишам. Привязки клавиш  передаются в XConfig параметром keys.
myKeys x = M.union (keys defaultConfig x) (keysToAdd x) 
where
keysToAdd = c -> mkKeymap c $ [
("<XF86HomePage>", spawn "x-www-browser"),
("<XF86AudioRaiseVolume>", spawn "~/local/bin/pactrl.sh inc"),
("<XF86AudioLowerVolume>", spawn "~/local/bin/pactrl.sh dec"),
("<XF86AudioMute>", spawn "~/local/bin/pactrl.sh mute"),
("<XF86AudioPlay>", spawn "xterm -e cmus"),
("<XF86Mail>", spawn "icedove"),
("<XF86Search>", spawn "pcmanfm"),
("<XF86Calculator>", spawn "galculator"),
("<XF86Launch5>", spawn "emacs"),
("<XF86Launch6>", spawn "gthumb"),
("<XF86Favorites>", spawn "gksu halt"),
("M-<XF86Favorites>", spawn "gksu reboot"),
("<Print>", spawn "~/local/bin/printscreen.sh"),
("M1-<Tab>", windows W.focusDown),
("M1-<F4>", kill) ]
main = do
xmonad $ defaultConfig {
modMask = mod4Mask,
borderWidth = 3,
normalBorderColor = "gray",
focusedBorderColor = "red",
workspaces = myWorkspaces,
layoutHook = myLayouts,
logHook = myLog >>= xmonadPropLog,
manageHook = manageHook defaultConfig <+> manageDocks <+> myManageHook,
keys = myKeys }
Для работы кода требуется импортировать следующие модули:
import XMonad.Util.EZConfig
import XMonad.Operations
import qualified XMonad.StackSet as W
import qualified Data.Map as M
Привязки клавиш представляют собой карту кортежей из модификатора, клавиши и обработчика. Существует несколько способов создать такую карту, но я предпочитаю использовать функцию mkKeymap. Функция mkKeymap принимает список кортежей состоящий из строкового обозначения клавиатурного сочетания в стиле emacs и обработчика. Получить имена клавиш можно с помощью программы xev или подглядеть их в минибуфере emacs.
Функция spawn запускает внешнее приложение.
Для управления окнами используются два модуля: XMonad.Operations и XMonad.StackSet. Первый предоставляет API высокого уровня для работы с окнами, а второй позволяет работать непосредственно со стеком окон и экранами.
Функция focusDown принимает стек окон, перемещает фокус на следующее окно и возвращает новый стек. Но обработчики клавиатурных сочетаний должны возвращать тип X (). Здесь нам поможет функция windows, которая принимает функцию, принимающую и возвращающую стек окон, и возвращает нужный нам X ().
Чтобы закрыть текущее окно, достаточно применить функцию kill. Она сразу же возвращает необходимое значение, поэтому оборачивать её ни во что не нужно.
Получив карту клавиатурных сочетаний, её нужно объединить с картой по умолчанию. Для этого используется функция union из модуля Data.Map.

Xmobar, по умолчанию, предоставляет одно сочетание клавиш для вызова терминала -- Mod+Shift+Enter. Изменить терминал можно через параметр terminal.

Полный конфигурационный файл ~/.xmonad/xmonad.hs

import XMonad
import XMonad.Hooks.ManageDocks
import XMonad.Layout.NoBorders
import XMonad.Layout.Tabbed
import XMonad.Layout.StackTile
import XMonad.Hooks.DynamicLog
import XMonad.Util.EZConfig
import XMonad.Operations
import qualified XMonad.StackSet as W
import qualified Data.Map as M

myWorkspaces = [ "1", "2", "3", "4", "5", "6", "7", "8", "9" ]

myLayouts = ( avoidStruts $ smartBorders $
Tall 1 (3/100) (1/2) |||
StackTile 1 (3/100) (1/2) |||
simpleTabbed ) |||
noBorders Full

myManageHook = composeAll [
className =? "Gtk-recordmydesktop" --> doFloat,
className =? "Xmessage" --> doFloat,
className =? "Gxmessage" --> doFloat,
className =? "Galculator" --> doFloat,
className =? "Gksu" --> doFloat ]

myLog = dynamicLogString xmobarPP {
ppCurrent = xmobarColor "green" "" . wrap "[" "]",
ppTitle = xmobarColor "lightblue" "" . wrap "[" "]",
ppHidden = xmobarColor "yellow" "",
ppHiddenNoWindows = xmobarColor "darkgray" "",
ppLayout = xmobarColor "orange" "" }

myKeys x = M.union (keys defaultConfig x) (keysToAdd x)
where
keysToAdd = c -> mkKeymap c $ [
("<XF86HomePage>", spawn "x-www-browser"),
("<XF86AudioRaiseVolume>", spawn "~/local/bin/pactrl.sh inc"),
("<XF86AudioLowerVolume>", spawn "~/local/bin/pactrl.sh dec"),
("<XF86AudioMute>", spawn "~/local/bin/pactrl.sh mute"),
("<XF86AudioPlay>", spawn "xterm -e cmus"),
("<XF86Mail>", spawn "icedove"),
("<XF86Search>", spawn "pcmanfm"),
("<XF86Calculator>", spawn "galculator"),
("<XF86Launch5>", spawn "emacs"),
("<XF86Launch6>", spawn "gthumb"),
("<XF86Favorites>", spawn "gksu halt"),
("M-<XF86Favorites>", spawn "gksu reboot"),
("<Print>", spawn "~/local/bin/printscreen.sh"),
("M1-<Tab>", windows W.focusDown),
("M1-<F4>", kill) ]

main = do
xmonad $ defaultConfig {
modMask = mod4Mask,
terminal = "xterm",
borderWidth = 3,
normalBorderColor = "gray",
focusedBorderColor = "red",
workspaces = myWorkspaces,
layoutHook = myLayouts,
logHook = myLog >>= xmonadPropLog,
manageHook = manageHook defaultConfig <+> manageDocks <+> myManageHook,
keys = myKeys }

Немного скриптов

Как Вы могли заметить, я привязал к клавишам два скрипта: один регулирует громкость PulseAudio, другой снимает скриншоты. Приведу их тут с кратким описанием для полноты картины.
#!/bin/bash

VOLUME_FILE=/tmp/pa_volume
MUTE_FILE=/tmp/pa_mute

function doMute() {
if [ -f $MUTE_FILE ];
then
pactl set-sink-mute 0 0
rm -rf $MUTE_FILE
else
pactl set-sink-mute 0 1
touch $MUTE_FILE
fi
}

function doChangeVolume() {
if [ ! -f $VOLUME_FILE ];
then
echo 100 > $VOLUME_FILE
fi
VOLUME=`cat $VOLUME_FILE`
NEW_VOLUME=$((VOLUME+$1))
if [ $NEW_VOLUME -lt 0 ];
then
exit 0
fi
pactl set-sink-volume 0 $NEW_VOLUME%
echo $NEW_VOLUME > $VOLUME_FILE
}

case $1 in
inc)
doChangeVolume 2
;;
dec)
doChangeVolume -2
;;
mute)
doMute
;;
esac
exit 0
Скрипт использует утилиту pactl для изменения громкости. Можно было бы использовать её напрямую в xmonad.hs, но существуют три проблемы:
  1. Из-за ошибки в программе, невозможно передать ей отрицательное значение для изменения громкости; она воспринимает его как неизвестный параметр;
  2. После того, как громкость достигла нуля, pactl продолжает её уменьшать, после чего придётся увеличивать её несколько раз, чтобы достичь нуля;
  3. Программа не предоставляет возможности узнать, в каком состоянии находится mute.
Примечание: по прошествии времени после публикации статьи, все проблемы с pactl решены.
  • Первая решается отменой восприятия "-2%" как параметра с помощью команды pactl set-sink-volume 0 -- -2%;
  • Вторая проблема решена в последней версии pulseaudio;
  • Последняя проблема не актуальна, так как появилась возможность попросить переключение состояния mute командой pactl set-sink-mute 0 toggle.
Скрипт записывает текущую громкость (в процентах) в файл и при поступлении команды либо увеличивает её, либо уменьшает. Для индикации состояния mute используется создание и удаление файла с предопределённым именем.

Для снятия скриншота используется конвеер
xwd -root | convert - <filename>
Скрипт нужен лишь для того, чтобы определить имя файла и показать его имя в диалоге с предложением открыть в просмоторщике.
#!/bin/bash

FILEPATH=~/
FILENAME=snapshot
FILEEXT=.png
FULLFILENAME=$FILEPATH$FILENAME$FILEEXT

if [ -f /usr/bin/gxmessage ];
then
XMESSAGE=gxmessage
else
XMESSAGE=xmessage
fi

for((I=1; ;I++)); do
if [ ! -f $FULLFILENAME ]; then
break
fi
FULLFILENAME=$FILEPATH$FILENAME$I$FILEEXT
done

xwd -root | convert - $FULLFILENAME
$XMESSAGE "Snapshot has been saved to $FULLFILENAME" -center -default Ok -buttons Ok:0,Open:1

if [ $? -eq 1 ];
then
gthumb $FULLFILENAME
fi

На скриншоте Вы могли заметить, что у меня на фоне корневого окна установлены обои. Делается это при помощи утилиты feh в два этапа. Первый -- это непосредственно установка изображения
$ feh --bg-scale <имя файла>
А второй -- восстановление изображения после перезапуска X`ов. Для этого нужно поместить такую команду в файл ~/.xsession
eval $(cat ~/.fehbg)

Нюхаем сеть через Linux

В этой статье речь пойдёт не столько о написании сниффера для Linux, сколько о структуре пакетов стека протоколов TCP/IP. Идея статьи взята из статьи на сайте xakep.ru.
Мы будем рассматривать пакеты, которые попадают к нам на сетевую карту. Эти пакеты относятся к протоколу Ethernet. Примерная структура этого пакета приведена в таблице ниже.
Ethernet заголовок
Ethernet данные (фрейм сетевого протокола)
IP заголовок
IP данные (фрейм транспортного протокола)
TCP или UDP заголовок
Данные приклодного протокола (HTTP, FTP, SMB итд).

Содержание статьи

Sniffer

Было бы не справедливо, если бы я приводил примеры анализа пакетов, не показав, как их получить. Для этой цели я написал сниффер, аналогичный тому, что описан в упомянутой статье.
#ifndef __LINUX_SNIFFER_SHIFFER_H__
#define __LINUX_SNIFFER_SHIFFER_H__


#include <set>
#include <string.h>
#include <stdexcept>
#include "Analyzer.h"


namespace LinuxSniffer {

class SnifferError :
public std::runtime_error
{
public:
SnifferError(const std::string & message) throw() :
std::runtime_error(message)
{
}

virtual ~SnifferError() throw()
{
}
}; // class SnifferError


class Sniffer
{
public:
explicit Sniffer(const std::string & device_name, bool sniff_all = false)
throw(SnifferError);
virtual ~Sniffer();
bool addAnalyzer(Analyzer * analyzer);
bool removeAnalyzer(Analyzer * analyzer);
void start() throw(SnifferError);
void stop();

private:
void deinit();
void makeSocket() throw(SnifferError);
void bindSocketToDevice() throw(SnifferError);
void setPromiscuousMode() throw(SnifferError);
void unsetPromiscuousMode();
void runAnalyzers(const unsigned char * frame, size_t frame_size);

private:
Sniffer(const Sniffer &);
Sniffer & operator = (const Sniffer &);

private:
const std::string m_device;
const bool m_sniff_all;
std::set<Analyzer *> m_analyzers;
int m_socket;
bool m_is_promiscuouse_mode_set;
bool m_is_stopping;
unsigned char * mp_frame_buffer;
static const size_t m_frame_buffer_size = 65536;
}; // class Sniffer


} // namespace LinuxSniffer


#endif // __LINUX_SNIFFER_SHIFFER_H__
Когда сниффер получает пакет, он запускает всех зарегистрированных анализаторов. Для их регистрации и служат методы addAnalyzer и removeAnalyzer.
Каждый анализатор должен реализовывать интерфейс Analyzer
class Analyzer
{
public:
virtual ~Analyzer() { }
virtual void analyze(const uint8_t * frame, size_t frame_size) = 0;
}; // class Analyzer
Именно этим механизмом мы и будем пользоваться при анализе структуры пакетов стека TCP/IP.
Приведу полную реализацию класса Sniffer
#include <errno.h>
#include <netpacket/packet.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
#include <linux/if.h>
#include <linux/if_ether.h>
#include <linux/sockios.h>
#include "Sniffer.h"


using namespace LinuxSniffer;


Sniffer::Sniffer(const std::string & device_name, bool sniff_all)
throw(SnifferError) :
m_device(device_name),
m_sniff_all(sniff_all),
m_is_promiscuouse_mode_set(false),
m_is_stopping(false),
mp_frame_buffer(0)
{
makeSocket();
mp_frame_buffer = new unsigned char[m_frame_buffer_size];
}

Sniffer::~Sniffer()
{
deinit();
}

void Sniffer::deinit()
{
::close(m_socket);
unsetPromiscuousMode();
delete [] mp_frame_buffer;
}

bool Sniffer::addAnalyzer(Analyzer * analyzer)
{
return m_analyzers.insert(analyzer).second;
}

bool Sniffer::removeAnalyzer(Analyzer * analyzer)
{
return m_analyzers.erase(analyzer) > 0;
}

void Sniffer::makeSocket()
throw(SnifferError)
{
m_socket = ::socket(AF_PACKET, SOCK_RAW, ::htons(ETH_P_ALL));
if(-1 == m_socket)
throw SnifferError(::strerror(errno));
try
{
bindSocketToDevice();
if(m_sniff_all)
setPromiscuousMode();
}
catch(...)
{
deinit();
throw;
}
}

void Sniffer::bindSocketToDevice()
throw(SnifferError)
{
const size_t device_name_len = m_device.length() + 1;
char * device = new char[device_name_len];
::strcpy(device, m_device.c_str());
device[m_device.length()] = '';
int setopt_result = ::setsockopt(m_socket, SOL_SOCKET,
SO_BINDTODEVICE, device, device_name_len);
delete [] device;
if(-1 == setopt_result)
throw SnifferError(::strerror(errno));
}

void Sniffer::setPromiscuousMode() throw(SnifferError)
{
ifreq iface;
::strcpy(iface.ifr_name, m_device.c_str());
if(::ioctl(m_socket, SIOCGIFFLAGS, &iface) < 0)
throw SnifferError(::strerror(errno));
iface.ifr_flags |= IFF_PROMISC;
if(::ioctl(m_socket, SIOCSIFFLAGS, &iface) < 0)
throw SnifferError(::strerror(errno));
m_is_promiscuouse_mode_set = true;
}

void Sniffer::unsetPromiscuousMode()
{
if(!m_is_promiscuouse_mode_set)
return;
ifreq iface;
::strcpy(iface.ifr_name, m_device.c_str());
if(::ioctl(m_socket, SIOCGIFFLAGS, &iface) >= 0)
{
iface.ifr_flags &= ~IFF_PROMISC;
if(::ioctl(m_socket, SIOCSIFFLAGS, &iface) >= 0)
m_is_promiscuouse_mode_set = false;
}
}

void Sniffer::start() throw(SnifferError)
{
while(!m_is_stopping)
{
ssize_t length = ::recvfrom(m_socket, mp_frame_buffer,
m_frame_buffer_size, 0, 0, 0);
if(-1 == length)
throw SnifferError(::strerror(errno));
runAnalyzers(mp_frame_buffer, length);
}
}

void Sniffer::runAnalyzers(const unsigned char * frame, size_t frame_size)
{
for(std::set<Analyzer *>::iterator it = m_analyzers.begin();
m_analyzers.end() != it; ++it)
{
Analyzer * analyzer = *it;
if(0 != analyzer)
analyzer->analyze(frame, frame_size);
}
}

void Sniffer::stop()
{
m_is_stopping = true;
}
Конструктор принимает два параметра: имя устройства для прослушивания, например eth0 и флаг, включающий неразборчивое прослушивание. Для тех сетей, которые работают через хабы, этот режим будет прослушивать все пакеты, даже те, которые не адресованы Вашей машине.
Конструктор вызывает метод makeSocket, который создаёт сокет для прослушивания устройства. Делается это вызовом
m_socket = ::socket(AF_PACKET, SOCK_RAW, ::htons(ETH_P_ALL));
Я отошёл от того вызова, что был показан в статье на xakep.ru по причине того, что в man 2 socket написано
SOCK_PACKET
Устарело и не должно использоваться в новых программах; см. packet(7).
А страница man 7 packet говорит, что нужно использовать SOCK_RAW.
Далее метод bindSocketToDevice привязывает созданный сокет к устройству вызовом
::setsockopt(m_socket, SOL_SOCKET, SO_BINDTODEVICE, device, device_name_len); 
Последним шагом в подготовке сокета является необязательная установка опции неразборчивого прослушивания методом setPromiscuousMode. Для её установки мы должны получить структуру ifreq из сокета вызовом
::ioctl(m_socket, SIOCGIFFLAGS, &iface)
добавить флаг IFF_PROMISC в поле ifr_flags
iface.ifr_flags |= IFF_PROMISC;
и записать сруктуру обратно
::ioctl(m_socket, SIOCSIFFLAGS, &iface)
Всё готово к запуску сниффера. Метод start вызывает в цикле функцию recvfrom и передаёт полученный буфер анализаторам.
ssize_t length = ::recvfrom(m_socket, mp_frame_buffer, m_frame_buffer_size, 0, 0, 0);

Стек протоколов TCP/IP

Прежде чем перейти к описанию анализатора, давайте рассмотрим фреймы протоколов стека TCP/IP.

Ethernet

Пакеты этого протокола являются низшими из тех, что мы можем получить. Существует несколько версий пакетов ethernet, мы рассмотрим самую популярную - вторую версию. В таблице ниже показана структура фрейма Ethernet 2
Смещение Размер Описание
0 байт 6 байт MAC адрес назначения
6 байт 6 байт MAC адрес источника
12 байт 2 байта Тип Etherner
14 байт 46 - 1500 байт Данные
Последние 4 байта 4 байта CRC контрольная сумма
Для разбора фреймов всех протоколов я написал базовый класс
#ifndef __LINUX_SNIFFER_PROTOCOL_FRAME_H__
#define __LINUX_SNIFFER_PROTOCOL_FRAME_H__

#include <string>
#include <sys/types.h>
#include <stdint.h>

namespace LinuxSniffer {


class ProtocolFrame
{
public:
ProtocolFrame(const std::string & protocol_name) :
m_protocol_name(protocol_name),
m_data_offset(0)
{
}

virtual ~ProtocolFrame()
{
}

const std::string & getName() const
{
return m_protocol_name;
}

virtual bool init(const uint8_t * buffer, size_t buffer_size) = 0;

size_t getDataOffset() const
{
return m_data_offset;
}

protected:
void setDataOffset(size_t offset)
{
m_data_offset = offset;
}

private:
const std::string m_protocol_name;
size_t m_data_offset;
}; // class ProtocolFrame


} // namespace LinuxSniffer


#endif // __LINUX_SNIFFER_PROTOCOL_FRAME_H__
Этот класс хранит и возвращает имя протокола описываемого фрейма и смещение данных от начала фрейма. Также класс передоставляет абстрактный метод инициализации из буфера фрейма и его размера.
Класс EthernetFrameV2 наследуется от ProtocolFrame и добавляет специфическую информацию.
#ifndef __LINUX_SNIFFER_ETHERNET_FRAME_V2_H__
#define __LINUX_SNIFFER_ETHERNET_FRAME_V2_H__

#include <cstring>
#include "ProtocolFrame.h"
#include "MacAddress.h"

namespace LinuxSniffer {

class EthernetFrameV2 :
public ProtocolFrame
{
public:
EthernetFrameV2() :
ProtocolFrame("Ethernet Version 2")
{
}

virtual ~EthernetFrameV2()
{
}

virtual bool init(const uint8_t * buffer, size_t buffer_size);

const MacAddress & getSourceMacAddress() const
{
return m_src_mac_addr;
}

const MacAddress & getDestinationMacAddress() const
{
return m_dest_mac_addr;
}

static const uint8_t (& getEthernetType())[2]
{
static bool is_init = false;
static uint8_t type[2];
if(!is_init)
{
::memcpy(type, "x08x00", 2);
is_init = true;
}
return type;
}

private:
MacAddress m_src_mac_addr;
MacAddress m_dest_mac_addr;
}; // class EthernetFrameV2

} // namespace LinuxSniffer


#endif // __LINUX_SNIFFER_ETHERNET_FRAME_V2_H__
Дополнительные поля являются MAC адресами отправителя и получателя. Эти поля имеют тип MacAddress, который описан простым классом
#ifndef __LINUX_SNIFFER_MAC_ADDRESS_H__
#define __LINUX_SNIFFER_MAC_ADDRESS_H__

#include <stdint.h>
#include <string>
#include <cstdio>

namespace LinuxSniffer {


class MacAddress
{
public:
MacAddress() :
b0(0),
b1(0),
b2(0),
b3(0),
b4(0),
b5(0)
{
}

explicit MacAddress(const uint8_t address[6]) :
b0(address[0]),
b1(address[1]),
b2(address[2]),
b3(address[3]),
b4(address[4]),
b5(address[5])
{
}

std::string toString() const
{
char str[16];
::sprintf(str, "%02x:%02x:%02x:%02x:%02x:%02x", b0, b1, b2, b3, b4, b5);
return str;
}

public:
uint8_t b0;
uint8_t b1;
uint8_t b2;
uint8_t b3;
uint8_t b4;
uint8_t b5;
}; // class MacAddress


} // namespace LinuxSniffer



#endif // __LINUX_SNIFFER_MAC_ADDRESS_H__
Реализацтя метода init класса EthernetFrameV2 сводится к проверке версии протокола Ethernet и получению MAC адресов отправителя и получателя. Для простоты, получение контрольной суммы опустим. Для того, чтобы получить все необходимые поля, достаточно привести буфер фрейма к типу ether_header, объявленному в netinet/ether.h.
#include <cstring>
#include <netinet/ether.h>
#include "EthernetFrameV2.h"

using namespace LinuxSniffer;


bool EthernetFrameV2::init(const uint8_t * buffer, size_t buffer_size)
{
if(buffer_size < sizeof(ether_header))
return false;
const ether_header * hdr = reinterpret_cast<const ether_header *>(buffer);
if(::memcmp(&hdr->ether_type, getEthernetType(), sizeof(getEthernetType())))
return false;
setDataOffset(sizeof(ether_header));
m_src_mac_addr = MacAddress(hdr->ether_shost);
m_dest_mac_addr = MacAddress(hdr->ether_shost);
return true;
}

Internet Protocol

В секции данных фрейма протокола ethernet хранится фрейм протокола IP. Этот протокол добавляет к пакету информацию об IP адресах отправителя и получателя и множество служебных данных. В следующей таблице показана структура фрейма IP версии 4.
Байты Смещение в битах Размер в битах Описание
0 0 4 Версия
4 4 Размер заголовка
1 8 6 Точка кода дифференцированных услуг (Differentiated services code point)
14 2 Явное уведомление о перегруженности (Explicit congestion notification)
2-3 16 16 Размер пакета
4-5 32 16 Идентификатор
6-7 48 3 Флаги
51 13 Смещение фрагмента
8 64 8 Время жизни
9 72 8 Протокол
10 80 16 Контрольная сумма заголовка
11-15 96 32 IP адрес источника
16-20 128 32 IP адрес назначения
20-24 160 32 Опции (если размер заголовка > 5)
20+ или 24+ 160 или 192 - Данные
В ранних версиях спецификации протокола "Точка кода дифференцированных услуг" и "Явное уведомление о перегруженности" были объединены в одно поле "Тип сервиса". Для того, чтобы использовать новое деление участники обмена должны договориться об этом. Я не буду разделять эти поля и буду использовать тип сервиса.
В поле "Флаги" биты от старшего к младшему означают:
0: Зарезервирован, должен быть равен 0;
1: Не фрагментировать;
2: У пакета еще есть фрагменты.
"Идентификатор" используется для предоставления информации о фрагментации пакета. Поле "Протокол" сообщает идентификатор протокола, фрейм которого находится в данных.
Думаю, остальные поля не нуждаются в комментариях.
Класс, описывающий IP фрейм приведён ниже
#ifndef __LINUX_SNIFFER_IP_FRAME_V4_H__
#define __LINUX_SNIFFER_IP_FRAME_V4_H__

#include <string>
#include "ProtocolFrame.h"

namespace LinuxSniffer {


class IpFrameV4 :
public ProtocolFrame
{
public:
IpFrameV4() :
ProtocolFrame("Internet Protocol Version 4"),
m_header_length(0),
m_tos(0),
m_package_size(0),
m_id(0),
m_flags(0),
m_frag_offset(0),
m_time_to_life(0),
m_protocol(0),
m_checksum(0)
{
}

virtual ~IpFrameV4()
{
}

virtual bool init(const uint8_t * buffer, size_t buffer_size);

const std::string & getSourceAddress() const
{
return m_src_ip_addr;
}

const std::string & getDestinationAddress() const
{
return m_dest_ip_addr;
}

uint8_t getHeaderLength() const
{
return m_header_length;
}

uint8_t getTypeOfService() const
{
return m_tos;
}

uint16_t getPackageSize() const
{
return m_package_size;
}

uint16_t getId() const
{
return m_id;
}

uint8_t getFlags() const
{
return m_flags;
}

uint16_t getFragmintationOffset() const
{
return m_frag_offset;
}

uint8_t getTimeToLife() const
{
return m_time_to_life;
}

uint8_t getProtocolId() const
{
return m_protocol;
}

uint16_t getHeaderCheckSum() const
{
return m_checksum;
}

public:
static const uint8_t m_ipv4_version = 4;

private:
void splitFragmentOffsetAndFlags();

private:
std::string m_src_ip_addr;
std::string m_dest_ip_addr;
uint8_t m_header_length;
uint8_t m_tos;
uint16_t m_package_size;
uint16_t m_id;
uint8_t m_flags;
uint16_t m_frag_offset;
uint8_t m_time_to_life;
uint8_t m_protocol;
uint16_t m_checksum;
}; // class IpFrameV4


} // namespace LinuxSniffer



#endif // __LINUX_SNIFFER_IP_FRAME_V4_H__
Для того, чтобы получить все поля из буфера фрейма достаточно привести указатель на него к указателю на структуру iphdr, объявленной в файле netinet/ip.h. В реализации метода init класса IpFrameV4 именно так и сделано
#include <netinet/ip.h>
#include <arpa/inet.h>
#include "IpFrameV4.h"



using namespace LinuxSniffer;

bool IpFrameV4::init(const uint8_t * buffer, size_t buffer_size)
{
if(buffer_size < sizeof(iphdr) || 0 == buffer)
return false;
const iphdr * ip_header = reinterpret_cast<const iphdr *>(buffer);
if(m_ipv4_version != ip_header->version)
return false;
in_addr addr;
addr.s_addr = ip_header->saddr;
m_src_ip_addr = ::inet_ntoa(addr);
addr.s_addr = ip_header->daddr;
m_dest_ip_addr = ::inet_ntoa(addr);
m_header_length = ip_header->ihl;
m_tos = ip_header->tos;
m_package_size = ip_header->tot_len;
m_id = ip_header->id;
m_frag_offset = ip_header->frag_off;
m_time_to_life = ip_header->ttl;
m_protocol = ip_header->protocol;
m_checksum = ip_header->check;
setDataOffset(m_header_length);
splitFragmentOffsetAndFlags();
return true;
}

void IpFrameV4::splitFragmentOffsetAndFlags()
{
union
{
struct
{
#if __BYTE_ORDER == __LITTLE_ENDIAN
uint16_t flags : 3;
uint16_t frag_offset : 13;
#elif __BYTE_ORDER == __BIG_ENDIAN
uint16_t frag_offset : 13;
uint16_t flags : 3;
#endif
} spl;
uint16_t num;
} splitter;

splitter.num = m_frag_offset;
m_flags = splitter.spl.flags;
m_frag_offset = splitter.spl.frag_offset;
}
Смещение фрагмента и флаги в структуре iphdr объединены. Для их разделения я написал метод splitFragmentOffsetAndFlags.

Transmission Control Protocol

Протоколы траспортного уровня добавляют сведения о портах источника и назначения. Для описания общих свойств всех фреймов транспортных протоколов я ввёл класс TransportProtocolFrame, унаследованный от ProtocolFrame.
#ifndef __LINUX_SNIFFER_TRANSPORT_PROTOCOL_FRAME_H__
#define __LINUX_SNIFFER_TRANSPORT_PROTOCOL_FRAME_H__

#include "ProtocolFrame.h"


namespace LinuxSniffer {


class TransportProtocolFrame :
public ProtocolFrame
{
public:
TransportProtocolFrame(const std::string & protocol_name) :
ProtocolFrame(protocol_name),
m_src_port(0),
m_dest_port(0)
{
}

virtual ~TransportProtocolFrame()
{
}

uint16_t getSourcePort() const
{
return m_src_port;
}

uint16_t getDestinationPort() const
{
return m_dest_port;
}

protected:
void setSourcePort(uint16_t port)
{
m_src_port = port;
}

void setDestinationPort(uint16_t port)
{
m_dest_port = port;
}

private:
uint16_t m_src_port;
uint16_t m_dest_port;
}; // class TransportProtocolFrame


} // namespace LinuxSniffer


#endif // __LINUX_SNIFFER_TRANSPORT_PROTOCOL_FRAME_H__
Структура фрейма протокола TCP, являющегося одним из протоколов транспортного уровня, показана в следующей таблице.
Байты Смещение в битах Размер в битах Описание
0-1 0 16 Порт источника
2-3 16 16 Порт назначения
4-8 32 32 Номер последовательности
8-12 64 32 Номер подтверждения последовательности
13-14 96 4 Смещение данных
100 6 Зарезервировано
106 6 Флаги
15-16 112 16 Размер окна
17-18 128 16 Контрольная сумма
19-20 144 16 Указатель важности
21+ 160 - Необязательные опции
За опциями или 21+ За опциями или 160 - Данные
Протокол TCP контролирует последовательность пакетов. Именно для этого нужны поля "Номер последовательности" и "Номер подтверждения последовательности".
"Флаги":
URG - Поле "Указатель важности" задействовано;
ACK - Поле "Номер подтверждения" задействовано;
PSH - Инструктирует получателя протолкнуть данные, накопившиеся в приемном буфере, в приложение пользователя;
RST - Оборвать соединения;
SYN - Синхронизация номеров последовательности;
FIN - флаг указывает на завершение соединения.
"Размер окна" - это число байтов, которые получатель готов принять.
"Указатель важности" указывает на номер октета, которым заканчиваются важные данные. Это поле игнорируется, если флаг URG не установлен.
Для чтения фрейма протокола TCP я расширил класс TransportProtocolFrame классом TcpFrame.
#ifndef __LINUX_SNIFFER_TCP_FRAME_H__
#define __LINUX_SNIFFER_TCP_FRAME_H__

#include "TransportProtocolFrame.h"

namespace LinuxSniffer {


class TcpFrame :
public TransportProtocolFrame
{
public:
TcpFrame() :
TransportProtocolFrame("Transmission Control Protocol"),
m_sequence_number(0),
m_ascknowledgment_number(0),
m_flag_fin(false),
m_flag_syn(false),
m_flag_rst(false),
m_flag_psh(false),
m_flag_ack(false),
m_flag_urg(false),
m_window_size(0),
m_checksum(0),
m_urgent_ptr(0)
{
}

virtual ~TcpFrame()
{
}

virtual bool init(const uint8_t * buffer, size_t buffer_size);

uint32_t getSequenceNumber() const
{
return m_sequence_number;
}

uint32_t getAscknowledgmentNumber() const
{
return m_ascknowledgment_number;
}

bool isFinFlagSet() const
{
return m_flag_fin;
}

bool isSynFlagSet() const
{
return m_flag_syn;
}

bool isRstFlagSet() const
{
return m_flag_rst;
}

bool isPshFlagSet() const
{
return m_flag_psh;
}

bool isAckFlagSet() const
{
return m_flag_ack;
}

bool isUrgFlagSet() const
{
return m_flag_urg;
}

uint16_t getWindowSize() const
{
return m_window_size;
}

uint16_t getCheckSum() const
{
return m_checksum;
}

uint16_t getUrgentPtr() const
{
return m_urgent_ptr;
}

public:
static const uint8_t m_protocol_id = 6;

private:
uint32_t m_sequence_number;
uint32_t m_ascknowledgment_number;
bool m_flag_fin;
bool m_flag_syn;
bool m_flag_rst;
bool m_flag_psh;
bool m_flag_ack;
bool m_flag_urg;
uint16_t m_window_size;
uint16_t m_checksum;
uint16_t m_urgent_ptr;
}; // class TcpFrame


} // namespace LinuxSniffer


#endif // __LINUX_SNIFFER_TCP_FRAME_H__
Как и в случае с IP, для фрейма TCP в заголовочных файлах linux припасена структура tcphdr. Находится она в файле netinet/tcp.h. Реализуем метод init с использованием этой структуры.
#include <netinet/tcp.h>
#include "TcpFrame.h"

using namespace LinuxSniffer;


bool TcpFrame::init(const uint8_t * buffer, size_t buffer_size)
{
if(buffer_size < sizeof(tcphdr))
return false;
const tcphdr * tcp_header = reinterpret_cast<const tcphdr *>(buffer);
setSourcePort(tcp_header->source);
setDestinationPort(tcp_header->dest);
setDataOffset(tcp_header->doff);
m_sequence_number = tcp_header->seq;
m_ascknowledgment_number = tcp_header->ack_seq;
m_flag_fin = tcp_header->fin != 0;
m_flag_syn = tcp_header->syn != 0;
m_flag_rst = tcp_header->rst != 0;
m_flag_psh = tcp_header->psh != 0;
m_flag_ack = tcp_header->ack != 0;
m_flag_urg = tcp_header->urg != 0;
m_window_size = tcp_header->window;
m_checksum = tcp_header->check;
m_urgent_ptr = tcp_header->urg_ptr;
return true;
}

User Datagram Protocol

Последний протокол, который мы рассмотрим в этой статье - UDP. Это простой протокол транспортного уровня, который ни как не контролирует доставку пакетов и не устанавливает соединения. По этим причинам, фрейм этого протокола весьма прост.
Байты Смещение в битах Размер в битах Описание
0-1 0 16 Порт источника
2-3 16 16 Порт назначения
4-5 32 16 Длина дейтаграммы
6-7 48 16 Контрольная сумма
8+ 64 - Данные
В этой таблице всё понятно без коментариев.
Второй класс, унаследованный от TransportProtocolFrame - UdpFrame.
#ifndef __LINUX_SNIFFER_UDP_FRAME_H__
#define __LINUX_SNIFFER_UDP_FRAME_H__

#include "TransportProtocolFrame.h"

namespace LinuxSniffer {


class UdpFrame :
public TransportProtocolFrame
{
public:
UdpFrame() :
TransportProtocolFrame("User Datagram Protocol")
{
}

virtual ~UdpFrame()
{
}

virtual bool init(const uint8_t * buffer, size_t buffer_size);

uint16_t getDatagramLength() const
{
return m_datagram_length;
}

uint16_t getCheckSum() const
{
return m_checksum;
}

public:
static const uint8_t m_protocol_id = 17;

private:
uint16_t m_datagram_length;
uint16_t m_checksum;
}; // class UdpFrame


} // namespace LinuxSniffer


#endif // __LINUX_SNIFFER_UDP_FRAME_H__
Аналогично фреймам протоколов IP и TCP, реализуем метод init с использованием структуры udphdr, объявленной в файле netinet/udp.h.
#include <netinet/udp.h>
#include "UdpFrame.h"

using namespace LinuxSniffer;


bool UdpFrame::init(const uint8_t * buffer, size_t buffer_size)
{
if(buffer_size < sizeof(udphdr))
return false;
const udphdr * udp_header = reinterpret_cast<const udphdr *>(buffer);
setSourcePort(udp_header->source);
setDestinationPort(udp_header->dest);
setDataOffset(sizeof(udphdr));
m_datagram_length = udp_header->len;
m_checksum = udp_header->check;
return true;
}

Анализатор

Теперь, когда мы знаем структуру основных протоколов, ничего не стоит проанализировать данные, которые мы получаем от сниффера. Для этого реализуем интерфейс Analyzer.
#ifndef __LINUX_SNIFFER_IP_ANALYZER_H__
#define __LINUX_SNIFFER_IP_ANALYZER_H__

#include <utility>
#include "Analyzer.h"


namespace LinuxSniffer {

class TransportProtocolFrame;

class IpAnalyzer :
public Analyzer
{
public:
virtual ~IpAnalyzer() { }
virtual void analyze(const uint8_t * frame, size_t frame_size);

private:
size_t tryEthV2Analyze(const uint8_t * frame, size_t frame_size);
std::pair<size_t, uint8_t> tryIpV4Analyze(const uint8_t * frame, ize_t frame_size);
size_t tryTcpAnalyze(const uint8_t * frame, size_t frame_size);
size_t tryUdpAnalyze(const uint8_t * frame, size_t frame_size);
void printTransportProtocolFrame(const TransportProtocolFrame & frame) const;
}; // class IpAnalyzer


} // namespace LinuxSniffer


#endif // __LINUX_SNIFFER_IP_ANALYZER_H__
#include <iostream>
#include "IpAnalyzer.h"
#include "EthernetFrameV2.h"
#include "IpFrameV4.h"
#include "TcpFrame.h"
#include "UdpFrame.h"

using namespace LinuxSniffer;

void IpAnalyzer::analyze(const uint8_t * frame, size_t frame_size)
{
const size_t ip_offset = tryEthV2Analyze(frame, frame_size);
if(0 == ip_offset)
return;

const uint8_t * ip_frame = &frame[ip_offset];
const size_t ip_frame_size = frame_size - ip_offset;
std::pair<size_t, uint8_t> ip_analyze_result =
tryIpV4Analyze(ip_frame, ip_frame_size);
if(0 == ip_analyze_result.first)
return;


const uint8_t * transport_frame = ip_frame + ip_analyze_result.first;
const size_t transport_frame_size = ip_frame_size - ip_analyze_result.first;
size_t application_protocol_data_offset;
switch(ip_analyze_result.second)
{
case TcpFrame::m_protocol_id:
application_protocol_data_offset = tryTcpAnalyze(
transport_frame, transport_frame_size);
break;
case UdpFrame::m_protocol_id:
application_protocol_data_offset = tryUdpAnalyze(
transport_frame, transport_frame_size);
break;
default:
std::cout << "======= Unsupported transport protocol ======n";
}

std::cout << std::endl;
}

size_t IpAnalyzer::tryEthV2Analyze(const uint8_t * frame, size_t frame_size)
{
EthernetFrameV2 eth_frame;
if(!eth_frame.init(frame, frame_size))
return 0;

std::cout << "====== " << eth_frame.getName() << " ======n" <<
"Source MAC Address: " << eth_frame.getSourceMacAddress().toString() <<
std::endl << "Destination MAC Address: " <<
eth_frame.getDestinationMacAddress().toString() << std::endl;

return eth_frame.getDataOffset();
}

std::pair<size_t, uint8_t> IpAnalyzer::tryIpV4Analyze(const uint8_t * frame,
size_t frame_size)
{
IpFrameV4 ip_frame;
if(!ip_frame.init(frame, frame_size))
return std::make_pair(0, 0);

std::cout << "====== " << ip_frame.getName() << " ======n" <<
"Source IP Address: " << ip_frame.getSourceAddress() << std::endl <<
"Destination IP Address: " << ip_frame.getDestinationAddress() <<
std::endl <<
"Header Length: " << std::dec <<
static_cast<uint32_t>(ip_frame.getHeaderLength()) << std::endl <<
"Type Of Service: " <<
static_cast<uint32_t>(ip_frame.getTypeOfService())<< std::endl <<
"Package Size: "<< ip_frame.getPackageSize() << std::endl <<
"Identification: " << ip_frame.getId() << std::endl <<
"Flags: " << static_cast<uint32_t>(ip_frame.getFlags())<< std::endl <<
"Fragmentation Offset: " << ip_frame.getFragmintationOffset() <<
std::endl <<
"Time To Live: " << static_cast<uint32_t>(ip_frame.getTimeToLife()) <<
std::endl <<
"Transport Protocol ID: " <<
static_cast<uint32_t>(ip_frame.getProtocolId()) << std::endl <<
"CRC-16 Header CheckSum:" << std::hex << ip_frame.getHeaderCheckSum() <<
std::endl;
return std::make_pair(ip_frame.getDataOffset(), ip_frame.getProtocolId());
}

size_t IpAnalyzer::tryTcpAnalyze(const uint8_t * frame, size_t frame_size)
{
TcpFrame tcp_frame;
if(!tcp_frame.init(frame, frame_size))
return 0;

printTransportProtocolFrame(tcp_frame);

std::cout << std::dec <<
"Sequence Number: " << tcp_frame.getSequenceNumber() << std::endl <<
"Ascknowledgment Number: " << tcp_frame.getAscknowledgmentNumber() <<
std::endl <<
"Window Size: " << tcp_frame.getWindowSize() << std::endl <<
"Urgent Pointer: " << tcp_frame.getUrgentPtr() << std::endl <<
"Flags:n" <<
" FIN: " << std::boolalpha << tcp_frame.isFinFlagSet() << std::endl <<
" SYN: " << tcp_frame.isSynFlagSet() << std::endl <<
" RST: " << tcp_frame.isRstFlagSet() << std::endl <<
" PSH: " << tcp_frame.isPshFlagSet() << std::endl <<
" ACK: " << tcp_frame.isAckFlagSet() << std::endl <<
" URG: " << tcp_frame.isUrgFlagSet() << std::endl <<
"CheckSum: " << std::hex << tcp_frame.getCheckSum() << std::endl;

return tcp_frame.getDataOffset();
}

size_t IpAnalyzer::tryUdpAnalyze(const uint8_t * frame, size_t frame_size)
{
UdpFrame udp_frame;
if(!udp_frame.init(frame, frame_size))
return 0;

printTransportProtocolFrame(udp_frame);
std::cout << std::dec <<
"Datagram Length: " << std::dec << udp_frame.getDatagramLength() <<
std::endl <<
"Datagram CheckSum: " << std::hex << udp_frame.getCheckSum() <<
std::endl;
return udp_frame.getDataOffset();
}

void IpAnalyzer::printTransportProtocolFrame(
const TransportProtocolFrame & frame) const
{
std::cout << "====== " << frame.getName() << " ======n" <<
std::dec << "Source Port: " << frame.getSourcePort() << std::endl <<
"Destination Port: " << frame.getDestinationPort() <<
std::endl;
}
Всё очень просто. Пытаемся преобразовать полученный фрейм в фрейм протокола Ethernet версии 2. Затем, используя полученное смещение данных, получаем фрейм IP. Проанализировав номер протокола, пытаемся получить данные либо о TCP, либо о UDP фрейме. Попутно выводим всю полученную информацию на консоль. В конце анализа у нас остаётся переменная, хранящая смещение данных прикладного протокола, которую Вы можете применить для дальнейшего разворачивания стека.

Заключение

Полученную программу нельзя запустить от пользователя, чей UID не равен нулю. Это и естественно, ведь мы слишком много себе позволили.
Исходные тексты программы можно скачать здесь.

Автомонтирование флешек в Linux

Случилось так, что я отказался от полноценных рабочих столов в пользу оконного менеджера fluxbox. Всё меня устраивает, кроме одного - автоматического монтирования флешек в оконном менеджере, естественно, не предусмотрено. Один мой приятель, на своём сайте разместил статью об автомонтировании флешек через правила демона udev. В этой заметке я напишу о том, как я немного расширил правила, приведённые в указной статье.
Всем Вам известно, что накопители в Linux монтируются в существующий каталог. И каждый, кто монтировал флешки вручную, знает, что перед монтированием нужно создать каталог, затем смонтировать, а после размонтирования - удалить.  Альтернативой может служить заранее созданная куча каталогов, но это засоряет файловую систему и, к тому же, не красиво, с эстетической точки зрения.
Для автоматизации указанных действий, мною был написан следующий скрипт на bash.
#!/bin/bash

# $1 - action: mount or umount
# $2 - device name
# $3 - device file

ACTION=$1
MOUNT_ACTION=mount
UMOUNT_ACTION=umount
DEVICE_NAME=$2
DEVICE_FILE=$3
MOUNT_DIR=/mnt/
MOUNT_CMD=mount
UMOUNT_CMD=umount
MOUNT_OPTS="-o rw,umask=0"
SCRIPT_NAME=devmount


function printUsage() {
echo Usage:
echo "$SCRIPT_NAME $MOUNT_ACTION <device name> <device file>"
echo or
echo "$SCRIPT_NAME $UMOUNT_ACTION <device name>"
}

# $1 - error message
function errorMessage() {
echo -e 'E[31m'<$SCRIPT_NAME> Error: $1
tput sgr0
}

# $1 - warning message
function warningMessage() {
echo -e 'E[33m'<$SCRIPT_NAME> Warning: $1
tput sgr0
}

# $1 - message
function logMessage() {
echo -e 'E[32m'<$SCRIPT_NAME>: $1
tput sgr0
}

# $1 - error message
function errorExit() {
errorMessage "$1"
echo
printUsage
echo
exit 1
}

# $1 - error message
function crashExit() {
errorMessage "$1"
echo
exit 2
}

# $1 - directory path
# returns 1 - true, 0 - false
function isDirectoryEmpty() {
DIR_CONTENT=`ls -A $1 3> /dev/null`
NOT_EMPTY=X$DIR_CONTENT
if [ "$NOT_EMPTY" == "X" ]; then
return 1
else
return 0
fi
}

# $1 - directory name
function deleteDirectory() {
rmdir $1
if [ ! 0 == $? ]; then
warningMessage "couldn't delete $1"
return 1
fi
return 0
}

function mountDevice() {
if [ -d $MOUNT_DIR$DEVICE_NAME ]; then
warningMessage "$MOUNT_DIR$DEVICE_NAME already exist"
isDirectoryEmpty $MOUNT_DIR$DEVICE_NAME
if [ $? == 0 ]; then
crashExit "$MOUNT_DIR$DEVICE_NAME is not empty"
fi
else
mkdir $MOUNT_DIR$DEVICE_NAME
if [ ! 0 == $? ]; then
crashExit "couldn't create $MOUNT_DIR$DEVICE_NAME"
fi
fi
$MOUNT_CMD $DEVICE_FILE $MOUNT_DIR$DEVICE_NAME $MOUNT_OPTS
if [ ! 0 == $? ]; then
deleteDirectory $MOUNT_DIR$DEVICE_NAME
crashExit "couldn't mount device"
else
logMessage "mount success"
fi
}

function umountDevice() {
$UMOUNT_CMD $MOUNT_DIR$DEVICE_NAME
if [ $? == 0 ]; then
logMessage "unmount success"
deleteDirectory $MOUNT_DIR$DEVICE_NAME
else
crashExit "unmount failure"
fi
}

case $ACTION in
$MOUNT_ACTION)
mountDevice
;;

$UMOUNT_ACTION)
umountDevice
;;

*)
errorExit "unknown parametr "$ACTION
;;
esac
Этот скрипт создаёт в каталоге /mnt каталог с переданным именем и монтирует в него указанное устройство. Если каталог существует, скрипт выдаст предупреждение, а если существующий каталог не пуст - ошибку.
Этот скрипт я и скормил udev`у, как сказано в статье.
SUBSYSTEM=="block", KERNEL=="sd[c-z][0-9]", ACTION=="add", RUN+="/usr/local/bin/devmount mount flash-%k /dev/%k"
SUBSYSTEM=="block", KERNEL=="sd[c-z][0-9]", ACTION=="remove", RUN+="/usr/local/bin/devmount umount flash-%k"
Теперь при вставке флешки, она у меня автоматически монтируется, а при вытаскивании - размонтируется. Кроме того, скрипт позволяет проводить эти операции вручную, что необходимо, если Вы не уверены в безопасности извлечения флешки в данный момент.

Автоматизация монтирования NFS каталогов в Linux

Есть у меня сетевое хранилище Thecus N299. Из путей доступа к нему есть протоколы SMB, NFS, FTP и web-интерфейс через HTTP. Так как я пользуюсь, чаще всего, ОС Linux, то протокол SMB является чужеродным и дико тормозящим, но зато он поддерживается любым файловым менеджером. Монтировать каталоги по NFS было очень лениво, поэтому я пользовался, в основном, SMB в ущерб скорости. Надо ли говорить, что работать с файлами через SMB напрямую не умеют почти все программы UNIX. Конечно же, решением (через одно место) могло бы быть монтирование через smbfs, но тут я вспомнил о NFS.
Писать постоянно что-то вроде
sudo mkdir /mnt/video
sudo mount -t nfs 192.168.1.100:/web/raid/video /mnt/video
лениво. И я решил автоматизировать себе этот процесс. После написания скрипта на bash, я подумал, что, может быть, кому-то он тоже будет полезен и решил выложить его сюда. Итак, встерчайте
#!/bin/bash

# Корневой каталог для монтирования.
DIRECTORY="/mnt/thecus/"
# Адрес сервера.
SERVER="192.168.1.100"
# Корневой каталог сервера.
SERVER_ROOT="/web/raid/"
# Каталоги на сервере, теже имена имеют каталоги, в которые они монтируются.
SERVER_DIRECTORIES=("video" "documents" "development" "projects" "soft")
# Параметр для монтирования всех каталогов сервера.
ALL="all"
# Имя команды скрипта.
COMMAND="thecus"
# Параметр скрипта для монтирования.
MOUNT="mount"
# Параметр скрипта для размонтирования.
UMOUNT="umount"

#####################################################################
#####################################################################


# $1 - имя каталога на nfs сервере.
# Возвращает 0 в случае успеха.
function doMount() {
if [ ! -d $DIRECTORY ]; then
mkdir $DIRECTORY
if [ ! 0 == $? ]; then
echo $COMMAND: Не удалось создать каталог $DIRECTORY
return 1
fi
fi

if [ ! -d $DIRECTORY$1 ]; then
mkdir $DIRECTORY$1
if [ ! 0 == $? ]; then
echo $COMMAND: Не удалось создать каталог $DIRECTORY$1
return 1
fi
fi

mount -t nfs $SERVER:$SERVER_ROOT$1 $DIRECTORY$1
return $?
}

# $1 - имя каталога в $DIRECTORY для размонтирования.
# Возвращает 0 в случае успеха.
function doUmount() {
umount $DIRECTORY$1
return $?
}

# Монтирует все каталоги, игнорируя ошибки.
function mountAll() {
for DIR in ${SERVER_DIRECTORIES[@]}; do
doMount $DIR
done
}

# Размонтирует все каталоги, игнорируя ошибки.
function umountAll() {
for DIR in ${SERVER_DIRECTORIES[@]}; do
doUmount $DIR
done
}

# Проверяет $1 на валидность, в случае успеха возвращает 0.
function checkTargetName() {
for DIR in ${SERVER_DIRECTORIES[@]}; do
if [ $1 == $DIR ]; then
return 0
fi
done
return 1
}

# Печатает справку.
function printHelp() {
echo Использование:
echo -e " $COMMAND $MOUNT имя_каталога"
echo -e " $COMMAND $UMOUNT имя_каталога"
echo -e " $COMMAND $MOUNT $ALL"
echo -e " $COMMAND $UMOUNT $ALL"
echo
echo -e "$MOUNT - смонтировать каталог"
echo -e "$UMOUNT - размонтировать каталог"
echo
echo Допустимые имена каталогов:
for DIR in ${SERVER_DIRECTORIES[@]}; do
echo -e " $DIR"
done
echo
echo -e "$ALL - смонтировать/размонтировать все каталоги"
echo
echo Все каталоги монтируются в $DIRECTORY
}

# Выход с ошибкой
function errorExit() {
echo
printHelp
echo
exit 1
}

#####################################################################
#####################################################################


# Если параметров меньше 2-х, то напечатать справку и выйти.
if [ $# -lt 2 ]; then
errorExit
fi

# Если вторым параметром выбрали $ALL, то действие относится ко всем каталогам.
if [ $2 == $ALL ]; then
case $1 in
$MOUNT)
mountAll
;;
$UMOUNT)
umountAll
;;
*)
errorExit
;;
esac
else
# Проверяем второй параметр на валидность.
checkTargetName $2
if [ ! $? == 0 ]; then
echo $COMMAND: Неверный каталог: $2
errorExit
fi

case $1 in
$MOUNT)
doMount $2
;;
$UMOUNT)
doUmount $2
;;
*)
errorExit
;;
esac
fi
Я сохранил этот скрипт под именем thecus в каталог /usr/local/bin и назначил права на выполнение. Теперь, для монтирования всех моих каталогов на NFS сервере, мне достаточно дать команду
sudo thecus mount all
Из комментов в тексте скрипта ясно,что он делает.

Анализ и оптимизация кода на C++ для Linux

Какие проблемы могут нас подстерегать при разработке программ? Неэффективные алгоритмы, утечки памяти, работа с невалидными указателями и не инициализированными переменными. Даже несмотря на, казалось бы, тщательное написание кода, мы порой делаем ошибки. Человеку свойственно ошибаться, поэтому эта статья посвящена контролю машин над человеком -- машинной проверки кода и исполняемых файлов. Я поделю весь процесс на три части:
  1. Статический анализ исходных текстов;
  2. Проверка утечек памяти;
  3. Нахождение участков кода, требующих неприлично много машинного времени.
В этой статье, говоря о компиляторе, я буду подразумевать g++, входящий в состав GCC (GNU Compiler Collection).

Статический анализ исходных текстов

Этот раздел опирается, во многом, на этот пост на хабре.

g++

Наряду со статическими анализаторами, компилятор g++ может выдать очень много полезной информации. Для того, чтобы добиться от компилятора максимум возмущений, следует добавит несколько опций.
-Wall - включает почти все стандартные предупреждения. Эту опцию нужно ставить всегда и везде, это должно стать Вашим правилом.
-Wextra - сообщит об ошибках в условных операторах, пустые if'ы и сравнение signed с unsigned.
-pedantic - по приказу этой опции компилятор начнёт следовать стандарту ISO C++. Например, запретит тип long long.
-Weffc++ -  эта опция напомнит Вам о некоторых правилах Скотта Майерса, которые Вы все, надеюсь, читали. В частности, это следующие правила из книги "Эффективное использование C++. 50 рекомендаций по улучшению ваших программ и проектов":
  • Правило 11. "Для классов с динамическим выделением памяти объявляйте копирующий конструктор и оператор присваивания".
  • Правило 12. "Предпочитайте инициализацию присваиванию в конструкторах".
  • Правило 14. "Убедитесь, что базовые классы имеют виртуальные деструкторы".
  • Правило 15. "operator= должен возвращать ссылку на *this".
  • Правило 23. "Никогда не пытайтесь вернуть ссылку, когда вы должны вернуть объект".
И несколько правил из книги "Наиболее эффективное использование C++. 35 новых рекомендаций по улучшению ваших программ и проектов":
  • Правило 6. "Различайте префиксную и постфиксную формы операторов инкремента и декремента".
  • Правило 7. "Никогда не перегружайте операторы &&, || и ,".
-Woverloaded-virtual - сообщит о перегрузке виртуальных функций.
-Wctor-dtor-privacy - возмутится, если найдёт у Вас в коде класс с закрытыми конструкторами и деструктором, который нигде не используется.
-Wnon-virtual-dtor - этой опции не нравятся не виртуальные деструкторы.
-Wold-style-cast - поможет избавится от приведений типов в стиле языка C.
-Wconversion -Wsign-conversion - заставят компилятор сказать пару ласковых о неверных приведениях типов, при которых Вы можете лишиться  части своих значений.
-Wunreachable-code - укажет на участки кода, которые никогда не будут выполнены. Эта опция может выдать очень много ворнингов даже в стандартной библиотеке, полезно её включать только при проверке.

Давайте проверим всё вышесказанное на практике. Для этой цели я написал следующий код полный ошибок
#include <iostream>

class CBicycle
{
public:
CBicycle(unsigned short max_speed)
{
m_max_speed = max_speed;
}

unsigned short GetMaxSpeed() const
{
return m_max_speed;
}

private:
short m_max_speed;
};

class CBicyclist
{
public:
CBicyclist(const CBicycle & bicycle)
{
mp_bicycle = new CBicycle(bicycle);
m_speed = 0;
}

unsigned short GetSpeed() const
{
return m_speed;
}

bool Move(short speed)
{
bool result = true;
if(speed <= mp_bicycle->GetMaxSpeed())
{
m_speed = speed;
std::cout << "I'm move with speed of " << m_speed << " km/hn";
}
else
{
result = false;
std::cout << "Sorry, this bicycle can't move with speed of " <<
speed << " km/hn";
}
return result;
}

private:
CBicycle * mp_bicycle;
unsigned short m_speed;
};

int TestMaxSpeed(CBicyclist & bicyclist)
{
int speed = bicyclist.GetSpeed();
int max_speed = speed;
bool loop = true;
do
{
if(bicyclist.Move(max_speed))
{
++max_speed;
}
else
{
loop = false;
--max_speed;
bicyclist.Move(speed);
}
} while(loop);
return max_speed;
}

int main()
{
CBicycle * bicycle = new CBicycle(75);
CBicyclist * bicyclist = new CBicyclist(*bicycle);
bicyclist->Move(600);
std::cout << "Begin max speed testn";
int max_speed = TestMaxSpeed(*bicyclist);
std::cout << "End max speed test. Max speed = " << max_speed << std::endl;
return 0;
}
У нас есть некое подобие классов, класс велосипеда и класс велосипедиста. Велосипед принимает в конструкторе максимальную скорость, а велосипедист - велосипед. Велосипедист может разогнаться на столько быстро, на сколько позволит велосипед или тип unsigned short, с которым у меня в коде связано много ошибок. Класс велосипедиста "забывает" удалить свой велосипед, а функция main "забывает" не только про велосипед, но и про велосипедиста. Задача функции TestMaxSpeed проверить, какая же скорость у велосипеда максимальная, используя для этой цели только велосипедиста. Но и у этой функции не всё в порядке с типами.
Итак, скомпилируем код с вышеуказанными опциями.
$ g++ -Wall -Wextra -pedantic -Weffc++ -Woverloaded-virtual -Wctor-dtor-privacy -Wnon-virtual-dtor -Wold-style-cast -Wconversion -Wsign-conversion -Wunreachable-code -g -O0  -c main.cpp
main.cpp: In constructor ‘CBicycle::CBicycle(short unsigned int)’:
main.cpp:6: warning: ‘CBicycle::m_max_speed’ should be initialized in the member initialization list
main.cpp:8: warning: conversion to ‘short int’ from ‘short unsigned int’ may change the sign of the result
main.cpp: In member function ‘short unsigned int CBicycle::GetMaxSpeed() const’:
main.cpp:13: warning: conversion to ‘short unsigned int’ from ‘short int’ may change the sign of the result
main.cpp: In constructor ‘CBicyclist::CBicyclist(const CBicycle&)’:
main.cpp:23: warning: ‘CBicyclist::mp_bicycle’ should be initialized in the member initialization list
main.cpp:23: warning: ‘CBicyclist::m_speed’ should be initialized in the member initialization list
main.cpp: In member function ‘bool CBicyclist::Move(short int)’:
main.cpp:39: warning: conversion to ‘short unsigned int’ from ‘short int’ may change the sign of the result
main.cpp: In function ‘int TestMaxSpeed(CBicyclist&)’:
main.cpp:63: warning: conversion to ‘short int’ from ‘int’ may alter its value
main.cpp:71: warning: conversion to ‘short int’ from ‘int’ may alter its value
main.cpp: In constructor ‘CBicyclist::CBicyclist(const CBicycle&)’:
main.cpp:25: warning: will never be executed
g++ -Wall -Wextra -pedantic -Weffc++ -Woverloaded-virtual -Wctor-dtor-privacy -Wnon-virtual-dtor -Wold-style-cast -Wconversion -Wsign-conversion -Wunreachable-code -g -O0 *.o -o test
Теперь давайте проанализируем вывод компилятора.
main.cpp: In constructor ‘CBicycle::CBicycle(short unsigned int)’:
main.cpp:6: warning: ‘CBicycle::m_max_speed’ should be initialized in the member initialization list
Нам сообщают, что в конструкторе класса CBicycle мы должны инициализировать член m_speed в списке инициализации, а не в теле конструктора.
main.cpp:8: warning: conversion to ‘short int’ from ‘short unsigned int’ may change the sign of the result
Конструктор CBicycle принимает значение unsigned short и присваивает его, почему-то, переменной типа short. (Это мы забыли написать unsigned в типе переменной-члена m_max_speed).
main.cpp: In member function ‘short unsigned int CBicycle::GetMaxSpeed() const’:
main.cpp:13: warning: conversion to ‘short unsigned int’ from ‘short int’ may change the sign of the result
Теперь мы ещё и возвращаем short от туда, от куда должны вернуть unsigned short.
main.cpp:23: warning: ‘CBicyclist::mp_bicycle’ should be initialized in the member initialization list
main.cpp:23: warning: ‘CBicyclist::m_speed’ should be initialized in the member initialization list
Класс CBicyclist повторил "подвиг" класса CBicycle и инициализировал все свои переменные в теле, а не в списке инициализации.
main.cpp: In member function ‘bool CBicyclist::Move(short int)’:
main.cpp:39: warning: conversion to ‘short unsigned int’ from ‘short int’ may change the sign of the result
Опять забыли про unsigned, какие же мы невнимательные.
main.cpp: In function ‘int TestMaxSpeed(CBicyclist&)’:
main.cpp:63: warning: conversion to ‘short int’ from ‘int’ may alter its value
main.cpp:71: warning: conversion to ‘short int’ from ‘int’ may alter its value
А это уже претенденты на потерю данных, конверсия из int в short.
main.cpp: In constructor ‘CBicyclist::CBicyclist(const CBicycle&)’:
main.cpp:25: warning: will never be executed
Этот ворнинг выдаёт опция -Wunreachable-code, его нужно проанализировать, и если Вы уверены, что всё в порядке -- можно проигнорировать.

cppcheck

Существуют специальные программы для анализа исходных текстов на C++, которые позволяют выявить потенциальные ошибки ещё до сборки (или использования) программ. Одна из таких программ -- cppcheck. В man руководстве к этой программе говорится, что она предназначена для выявления тех ошибок, которые не находит компилятор. Это такие ошибки как некоторые утечки памяти, выход за границу массива, исключения в деструкторах, разыменование нулевых и освобождённых указателей, виртуальность деструктора базовых классов и другое.
Работать с этой программой очень просто. Для демонстрации возьмём следующий небольшой пример.
int main()
{
int * x = new int[10];
x = 0;
return 0;
}
После запуска cppcheck в этом малюсеньком коде обнаруживается сразу две ошибки.
$ cppcheck --enable=all -v .
Checking ./main.cpp...
[./main.cpp:6]: (style) Variable 'x' is assigned a value that is never used
[./main.cpp:7]: (error) Memory leak: x
Checking usage of global functions..
Во-первых, мы объявили переменную x и никогда её не используем, а во-вторых, у нас зафиксирована утечка памяти.

Проверка утечек памяти

Практика показывает, что не всегда можно найти утечки памяти статическими анализаторами. Первый приведённый в этой статье листинг -- тому подтверждение. Если запустить cppcheck для этой программы, то мы ничего не найдём. Но отчаиваться ещё рано, нам может помочь утилита valgrind, предназначенная специально для этого.  Эта утилита находит утечки памяти не статически, а при работе программы. Давайте познокомимся с выводом этой программы
$ valgrind --leak-check=full -v ./test > /dev/null
==31174== Memcheck, a memory error detector
==31174== Copyright (C) 2002-2010, and GNU GPL'd, by Julian Seward et al.
==31174== Using Valgrind-3.6.0.SVN-Debian and LibVEX; rerun with -h for copyright info
==31174== Command: ./test
==31174==
--31174-- Valgrind options:
--31174-- --suppressions=/usr/lib/valgrind/debian-libc6-dbg.supp
--31174-- --leak-check=full
--31174-- -v
--31174-- Contents of /proc/version:
--31174-- Linux version 2.6.32-5-amd64 (Debian 2.6.32-29) (ben@decadent.org.uk) (gcc version 4.3.5 (Debian 4.3.5-4) ) #1 SMP Fri Dec 10 15:35:08 UTC 2010
--31174-- Arch and hwcaps: AMD64, amd64-sse3-cx16
--31174-- Page sizes: currently 4096, max supported 4096
--31174-- Valgrind library directory: /usr/lib/valgrind
--31174-- Reading syms from /mnt/data/projects/blog/CodeOptimize/code/test (0x400000)
--31174-- Reading syms from /lib/ld-2.11.2.so (0x4000000)
--31174-- Considering /lib/ld-2.11.2.so ..
--31174-- .. CRC mismatch (computed 91367345 wanted f148be98)
--31174-- Considering /usr/lib/debug/lib/ld-2.11.2.so ..
--31174-- .. CRC is valid
--31174-- Reading syms from /usr/lib/valgrind/memcheck-amd64-linux (0x38000000)
--31174-- object doesn't have a dynamic symbol table
--31174-- Reading suppressions file: /usr/lib/valgrind/debian-libc6-dbg.supp
--31174-- Reading suppressions file: /usr/lib/valgrind/default.supp
--31174-- REDIR: 0x40162d0 (strlen) redirected to 0x380408a7 (vgPlain_amd64_linux_REDIR_FOR_strlen)
--31174-- Reading syms from /usr/lib/valgrind/vgpreload_core-amd64-linux.so (0x4a20000)
--31174-- Reading syms from /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so (0x4c21000)
==31174== WARNING: new redirection conflicts with existing -- ignoring it
--31174-- new: 0x040162d0 (strlen ) R-> 0x04c25850 strlen
--31174-- REDIR: 0x4016140 (index) redirected to 0x4c25460 (index)
--31174-- REDIR: 0x40161c0 (strcmp) redirected to 0x4c25e30 (strcmp)
--31174-- Reading syms from /usr/lib/libstdc++.so.6.0.13 (0x4e29000)
--31174-- object doesn't have a symbol table
--31174-- Reading syms from /lib/libm-2.11.2.so (0x513d000)
--31174-- Considering /lib/libm-2.11.2.so ..
--31174-- .. CRC mismatch (computed b0a7ab6b wanted 907fac55)
--31174-- Considering /usr/lib/debug/lib/libm-2.11.2.so ..
--31174-- .. CRC is valid
--31174-- Reading syms from /lib/libgcc_s.so.1 (0x53bf000)
--31174-- Considering /lib/libgcc_s.so.1 ..
--31174-- .. CRC mismatch (computed 07151771 wanted 3f9779e8)
--31174-- object doesn't have a symbol table
--31174-- Reading syms from /lib/libc-2.11.2.so (0x55d5000)
--31174-- Considering /lib/libc-2.11.2.so ..
--31174-- .. CRC mismatch (computed 21e032ea wanted d5c67601)
--31174-- Considering /usr/lib/debug/lib/libc-2.11.2.so ..
--31174-- .. CRC is valid
--31174-- REDIR: 0x5652600 (__GI_strrchr) redirected to 0x4c25280 (__GI_strrchr)
--31174-- REDIR: 0x5650b40 (__GI_strlen) redirected to 0x4c25810 (__GI_strlen)
--31174-- REDIR: 0x4ef46a0 (operator new(unsigned long)) redirected to 0x4c24d78 (operator new(unsigned long))
--31174-- REDIR: 0x5650b10 (strlen) redirected to 0x4a205ac (_vgnU_ifunc_wrapper)
==31174== WARNING: new redirection conflicts with existing -- ignoring it
--31174-- new: 0x05650b40 (__GI_strlen ) R-> 0x04c257f0 strlen
--31174-- REDIR: 0x5653e70 (mempcpy) redirected to 0x4c26bc0 (mempcpy)
--31174-- REDIR: 0x5654750 (memcpy) redirected to 0x4c25f00 (memcpy)
--31174-- REDIR: 0x564b7e0 (free) redirected to 0x4c24076 (free)
==31174==
==31174== HEAP SUMMARY:
==31174== in use at exit: 20 bytes in 3 blocks
==31174== total heap usage: 3 allocs, 0 frees, 20 bytes allocated
==31174==
==31174== Searching for pointers to 3 not-freed blocks
==31174== Checked 180,104 bytes
==31174==
==31174== 2 bytes in 1 blocks are definitely lost in loss record 2 of 3
==31174== at 0x4C24DFA: operator new(unsigned long) (vg_replace_malloc.c:261)
==31174== by 0x400B17: main (main.cpp:79)
==31174==
==31174== 18 (16 direct, 2 indirect) bytes in 1 blocks are definitely lost in loss record 3 of 3
==31174== at 0x4C24DFA: operator new(unsigned long) (vg_replace_malloc.c:261)
==31174== by 0x400B38: main (main.cpp:80)
==31174==
==31174== LEAK SUMMARY:
==31174== definitely lost: 18 bytes in 2 blocks
==31174== indirectly lost: 2 bytes in 1 blocks
==31174== possibly lost: 0 bytes in 0 blocks
==31174== still reachable: 0 bytes in 0 blocks
==31174== suppressed: 0 bytes in 0 blocks
==31174==
==31174== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 4 from 4)
--31174--
--31174-- used_suppression: 2 dl-hack3-cond-1
--31174-- used_suppression: 2 glibc-2.5.x-on-SUSE-10.2-(PPC)-2a
==31174==
==31174== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 4 from 4)

Здесь мы можем увидеть статистику выделения и высвобождения памяти
==31174== HEAP SUMMARY:
==31174== in use at exit: 20 bytes in 3 blocks
==31174== total heap usage: 3 allocs, 0 frees, 20 bytes allocated
Три раза память была выделена, но ни разу не освобождалась. Чуть далее в выводе программы valgrind мы можем найти информацию немного подробнее.
==31174== 2 bytes in 1 blocks are definitely lost in loss record 2 of 3
==31174== at 0x4C24DFA: operator new(unsigned long) (vg_replace_malloc.c:261)
==31174== by 0x400B17: main (main.cpp:79)
==31174==
==31174== 18 (16 direct, 2 indirect) bytes in 1 blocks are definitely lost in loss record 3 of 3
==31174== at 0x4C24DFA: operator new(unsigned long) (vg_replace_malloc.c:261)
==31174== by 0x400B38: main (main.cpp:80)
Вот тут-то valgrind и сдаёт нас с потрохами, выдавая все наши грехи.

Оптимизация кода

Теперь пришло время задуматься о производительности. Пусть у нас есть такой код
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <algorithm>
#include <cstdlib>
#include <sys/time.h>

template<typename Type>
std::vector<Type> & Sort(std::vector<Type> & array)
{
size_t arr_size = array.size();
for(size_t i = 0; i < arr_size; ++i)
{
bool swaped = false;
for(size_t j = 1; j < arr_size; ++j)
{
if(array[j - 1] > array[j])
{
std::swap(array[j - 1], array[j]);
swaped = true;
}
}
if(!swaped)
break;
}
return array;
}

template<typename Type>
class CWriter
{
public:
CWriter(std::ostream & stream) :
mr_stream(stream)
{
}

void operator () (const Type & value)
{
mr_stream << value << std::endl;
}

private:
std::ostream & mr_stream;
};

template<typename Type>
bool WriteArrayToFile(const std::string & filename,
const std::vector<Type> & array)
{
bool result = false;
std::ofstream out(filename.c_str());
if(out.is_open())
{
std::for_each(array.begin(), array.end(), CWriter<Type>(out));
result = true;
out.close();
}
return result;
}

int main(int argc, char ** argv)
{
if(argc < 3)
{
std::cerr << "To few argumentsn";
return 1;
}
size_t array_size = static_cast<size_t>(::atoi(argv[1]));
std::string filename = argv[2];
::srand(static_cast<unsigned int>(::time(0)));
std::vector<int> array;
array.reserve(array_size);

for(size_t i = 0; i < array_size; ++i)
{
array.push_back(::rand());
}

if(! ::WriteArrayToFile(filename, ::Sort(array)))
{
std::cerr << "Error writing the file with name "" <<
filename << ""n";
return 2;
}

return 0;
}
Здесь генерируется массив из n элементов, сортируется и записывается в файл. Если мы его запустим, то увидим картину, не очень-то удовлетваряющую нас -- большие затраты времени. В этом можно убедиться, запустив такую команду
$ time ./test 100000 test.txt

real 0m24.913s
user 0m24.566s
sys 0m0.344s
25 секунд -- это, явно, слишком много. Естественным желанием будет оптимизировать работу программы так, чтобы предельно сократить время её работы. Из данного примера очевидно, что пузырьковая сортировка и является причиной нашего недовольства, но в больших проектах причина может быть не столь очевидной. Давайте сделаем вид, что мы не знаем причину столь затяжного выполнения и попробуем найти её методом профилирования программы. А поможет нам в этом утилита с именем gprof. Подробно о работе с этой утилитой можно прочитать на man-странице проекта opennet.
Для того, чтобы можно было работать с профилировщиком gprof, нужно добавить опцию -pg к команде сборки проекта, а затем собрать проект с отладочными символами. После сборки нужно запустить программу с тем же параметрами, с которыми мы получили неудовлетворительный результат, что приведёт к созданию файла gmon.conf в последнем текущем каталоге программы, а у нас этот каталог не менялся. Запустить программу gprof можно следующим образом
$ gprof ./test > gprof.log
На выходе программа gprof выдаёт очень много информации, поэтому её лучше перенаправить в файл. Теперь нужно открыть этот файл и проанализировать его. Я советую открывать этот файл в текстовом редакторе, поддерживающем подсветку синтаксиса и выбрать режим подсветки C++, так как профиль и граф вызовов содержат много текста на C++. Кроме того, лучше отключить динамический перенос строк. Я приведу только часть простого профиля файла, который у меня получился.
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls s/call s/call name
55.50 98.49 98.49 1 98.49 154.05 std::vector<int, std::allocator<int> >& Sort<int>(std::vector<int, std::allocator<int> >&)
24.22 141.47 42.99 7769644628 0.00 0.00 std::vector<int, std::allocator<int> >::operator[](unsigned long)
10.77 160.58 19.11 73 0.26 0.26 std::vector<int, std::allocator<int> >::size() const
6.94 172.89 12.31 2507056584 0.00 0.00 void std::swap<int>(int&, int&)
Из приведённых выше строк простого профиля видно, что 55,5% времени программа тратит на сортировку и ещё 24,22% на обращение к элементам вектора. Скорее всего, нам удастся решить обе проблемы, подставив более эффективный алгоритм сортировки. Давайте проверим, заменим сортировку пузырьком сортировкой слияниями.
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <algorithm>
#include <cstdlib>
#include <sys/time.h>


template<typename Type>
std::vector<Type> & MergeSort(std::vector<Type> & array)
{
struct CMergeSort
{
void Sort(std::vector<Type> & array)
{
size_t array_size = array.size();
if(array_size <= 1)
return;

typename std::vector<Type>::iterator middle =
array.begin() + static_cast<ptrdiff_t>(array_size) / 2;
std::vector<Type> left(array.begin(), middle);
std::vector<Type> right(middle, array.end());
Sort(left);
Sort(right);
array = Merge(left, right);
}

std::vector<Type> Merge(const std::vector<Type> & left,
const std::vector<Type> & right)
{
std::vector<Type> result;
result.reserve(left.size() + right.size());
for(typename std::vector<Type>::const_iterator l =
left.begin(), r = right.begin();;)
{
if(left.end() == l)
{
typename std::vector<Type>::iterator it =
result.end();
result.insert(it, r, right.end());
break;
}
if(right.end() == r)
{
typename std::vector<Type>::iterator it =
result.end();
result.insert(it, l, left.end());
break;
}
if(*l < *r)
{
result.push_back(*l);
++l;
}
else
{
result.push_back(*r);
++r;
}
}
return result;
}
}; // struct CMergeSort

CMergeSort().Sort(array);
return array;
}

template<typename Type>
class CWriter
{
public:
CWriter(std::ostream & stream) :
mr_stream(stream)
{
}

void operator () (const Type & value)
{
mr_stream << value << std::endl;
}

private:
std::ostream & mr_stream;
};

template<typename Type>
bool WriteArrayToFile(const std::string & filename,
const std::vector<Type> & array)
{
bool result = false;
std::ofstream out(filename.c_str());
if(out.is_open())
{
std::for_each(array.begin(), array.end(), CWriter<Type>(out));
result = true;
out.close();
}
return result;
}

int main(int argc, char ** argv)
{
if(argc < 3)
{
std::cerr << "To few argumentsn";
return 1;
}
size_t array_size = static_cast<size_t>(::atoi(argv[1]));
std::string filename = argv[2];
::srand(static_cast<unsigned int>(::time(0)));
std::vector<int> array;
array.reserve(array_size);

for(size_t i = 0; i < array_size; ++i)
{
array.push_back(::rand());
}

if(! ::WriteArrayToFile(filename, ::MergeSort(array)))
{
std::cerr << "Error writing the file with name "" <<
filename << ""n";
return 2;
}

return 0;
}
Если мы откомпилируем этот код и запустим программу, то получим следующее
$ time ./test 100000 test.txt

real 0m0.437s
user 0m0.076s
sys 0m0.360s
Очевидно, что такие результаты нас устраивают.

---------------
Источники:
Статический анализ кода C++
Профилятор gprof