7 дек. 2015 г.

Лицензии (L)GPLv2, (L)GPLv3 и MPL2

Лицензии (L)GPL и MPL2 относятся к copyleft-лицензиями (иногда их также называют "strict licenses").

В отличие от permissive-лицензий (MIT, BSD, Apache), они вводят дополнительные ограничения. В целом эти ограничения обязывают того, кто распространяет модифицированный код, публиковать те или иные части своих исходников, но детали различаются.

Текст ниже - это мое понимание, так что он может содержать ошибки.

TL;DR


Предположим, что есть библиотека или программа foo, опубликованная под одной из лицензий (L)GPL или MPL2, а вы пишете библиотеку или приложение prog.

Упрощенное резюме такое:
  • Если foo под GPLv2:
    • если prog содержит модифицированную версию foo или статически или динамически линкуется с foo, prog должна публиковаться под лицензией, совместимой и сохранающей требования GPLv2 (permissive-лицензии не являются таковыми);
    • если prog использует foo через IPC или system(), ограничений на prog не накладывается.
  • Если foo под LGPLv2:
    • если prog динамически линкуется с foo, ограничений на prog не накладывается;
    • если prog статически линкуется с foo, и предоставляются объектные файлы prog для возможности перелинковки с другой версией foo, ограничений на prog не накладывается;
    • в остальных случаях действуют правила GPLv2.
  • Если foo под GPLv2 with linking exception:
    • если prog динамически или статически линкуется с foo,  ограничений на prog не накладывается;
    • в остальных случаях действуют правила GPLv2.
  • (L)GPLv3 добавляет дополнительные ограничения связанные с патентами/DRM/тивоизацией.
  • Код под GPLv2 и GPLv3 нельзя использовать совместно в одной программе. Код под "GPLv2 or any later version" можно использовать совместно с кодом под GPLv3.
  • Если foo под MPL2:
    • prog должна предоставить модифицированные файлы foo под MPL2 или совместимой лицензией;
    • других ограничений на prog не накладывается.

GPLv2


GPLv2 вводит понятие derivative work. Является ли ваша программа prog  derivative work по отношению к foo, нужно выяснять у юриста в каждом конкретном случае, но в целом:

prog является derivative work от foo, если:
  • prog является модифицированной версией foo, или
  • prog включает значительную чать кода foo (копирует или импортирует, например с помощью #include), или
  • prog статически или динамически линкуется с foo.
prog не является derivative work от foo, если:
  • prog запускает foo как внешний процесс (например через system());
  • prog взаимодействует с foo через IPC (например через HTTP API);
  • prog включает только незначительную часть кода foo (например, prog использует объявления структур данных или объявления API из foo, но не использует код и реализацию API из foo).

Эти критерии не перечислены в GPL. Понятие derivative work является общеупотребимым, и, при возникновении спорной ситуации, суд будет решать, является ли работа derivative или нет.

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

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

В результате, если foo опубликована под GPLv2, а prog содержит или напрямую использует код из foo (например, линкуется с ней), тогда prog должна быть опубликована под лицензий, совместимой и сохраняющей требования GPLv2.

LGPLv2


LGPLv2 -- это модификация GPLv2. Она вводит дополнительное понятие combined work, являющееся частным случаем derivative work в треминах GPLv2, но не в терминах LGPLv2.

В целом, prog является combined work по отношению к foo,если
  • prog использует немодифицированную версию foo (например статически или динамически линкуется с ней);
  • prog позволяет пользователю подменить версию foo на стороннюю (в случае динамической линковки эта возможность очевидна; в случае статической, автор prog может предоставить объектные файлы, чтобы пользователь мог перелинковать их со своей версией foo).

LGPLv2 вводит следующее ослабление:
combined work не считается derivative work и может быть опубликована под любой лицензией.

В остальных случаях действуют обычные правила GPLv2.

GPLv2 with linking exception


Многие проекты используют текст GPLv2 с добавленным к нему исключением:
если prog статически или динамически линкуется с немодифицированной версией foo, prog не считается derivative work и может быть опубликована под любой лицензией.

Это требование слабее, чем аналогичное в LGPLv2, т.к. не обязывает предоставлять объектные файлы в случае статической линковки.

Примеры таких лицензии: eCos, OpenSSL. Некоторые из таких лицензий официально одобрены FSF (как совместимые с GPL) и OSI (как открытые), поэтому лучше использовать какую-нибудь из них, чем писать исключение самому.

GPLv3 и LGPLv3


GPLv3/LGPLv3 это следующая версия GPL, включающая уточнения и некоторые дополнительные ограничения, например:
  • патенты: разработчик, публикующий софт или derivative work под GPLv3, не может ограничить пользователя с помощью патентов в обход лицензии;
  • DCMA, DRM: разработчику, публикующему софт или derivative work под GPLv3, труднее ввести ограничения на использование софта;
  • тивоизация: разработчик, публикующий софт или derivative work под GPLv3 совместо с аппаратным обеспечением, не может лишить пользователя возможности модифицировать софтовую составляющую продукта;
  • терминология: используется терминология, не привязанная к US;
  • совместимость: внесены правки, делающие GPLv3 совместимым с некотороми другими открытыми лицензями.

Отличия между GPLv3 и LGPLv3 те же, что между GPLv2 и LGPLv2.

Несовместимость GPLv2 и GPLv3


И GPLv2, и GPLv3 разрешают публиковать derivative work только под совместимой лицензией (не накладывающих дополнительных ограничений) и сохраняющей все требования оригинальной лицензии.

Однако:
  • GPLv3 вносит дополнительные ограничения по сравнению с GPLv2, и поэтому не совместима с ней.
  • GPLv2 не сохраняет все требования GPLv3, а значит код под GPLv3 нельзя использовать в проекте под GPLv2.
В результате, программы или библиотеки под лицензиями GPLv2 и GPLv3 нельзя использовать совместно. В случае LGPL или GPL with linking exception, этого ограничения нет, когда в дело вступает исключение, связанное с линковкой.

GPLv2 "or any later version"


Многие проекты под GPLv2 публиковались с формулировкой "GPLv2 or any later version". По сути, это означает что проект публикуется сразу под несколькими лицензиями: GPLv2, GPLv3 и любые версии GPL, выпущенные FSF в будущем.

В этом случае, проект можно использовать совместно как с GPLv2 софтом, так и с GPLv3 софтом.

Однако это также означает, что авторы derivative work могут выбрать любую из лицензий или обе. Если они выберут только GPLv3, то вы не сможете использовать их derivative work совместно с GPLv2. В том числе, вы не сможете влить их изменения в основной проект одновременно под обеими лицензиями GPLv2 и GPLv3. Кроме того, их derivative work будет защищена ограниченями GPLv3, связанными с патентами, тивоизацией, etc.

Передача прав FSF


Некоторые проекты, в дополнение к лицензированию кода под GPL, также передают copyright (авторские права) на код в FSF. Это делается для того, чтобы увеличить юридическую силу лицензии: FSF является зарегистрированной огранизацией и может отстаивать права на код, переданный в FSF, при возникновении нарушений.

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

См. также gpl-violations.org.

MPL2 


MPL2, в отличие от GPL, является пофайловой лицензией. Она разрешает использовать отдельные файлы любым способом, при условии что эти файлы (и их модифицированные версии) продолжают распространяться под MPL2 или другой открытой лицензией.

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

MPL2 является совместимой с (L)GPL. При комбинировании софта под MPL2 и (L)GPL, в случае модификации файлов под MPL2, их нужно перелецензировать под (L)GPL, что явно разрешено MPL2.

GPL и более слабые лицензии


GPL не допускает перепубликацию под permissive-лицензиями, т.к. они не сохраняют требования GPL. Однако permissive-лицензии допускают любое использование, в том числе перепубликацию под GPL:
  • Если foo выпущена под более слабой лицензией, чем GPL (например, MPL2, BSD, MIT), а prog выпущена под GPL, версия foo в составе prog автоматически перелицезируется под GPL. Это требует GPL и разрешено более слабыми лицензиями (явно в MPL2 и неявно в permissive-лицезиях).
  • Если foo выпущена под GPL, использующая ее prog не может быть выпущена под более слабой лицезией, т.к. этого не разрешает GPL.

Если код выпущен под более слабой лицензией, чем GPL, авторы модификаций могут сделать их доступными под более сильной лицензией, вроде GPL.

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

Копирайты и смена лицензии


Если разработчики не передают права (copyright) на код одному лицу (FSF, компании-владельцу проекта, или одному человеку), тогда единственный легальный способ использовать их результат совместно -- использовать его под той лицензией, под которой выпущен проект и с которой разработчики автоматически согласились при его модификации.

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

Выпуск под другой лицензией может понадобится, например, для публикации на AppStore, который накладывает ограничения, несовместимые с (L)GPL, и кроме того явно запрещает софт под (L)GPL.

Некоторые проекты требуют от разработчиков передать права владельцу проекта при внесении изменений в апстрим.

1 дек. 2015 г.

SCons: проблемы с генерацией исходников, VariantDir и SConsign

Проблема


После добавления в проект, использующий VariantDir, генерации исходников и их сборки, происходит следующее:
  • после каждого запуска, scons пересобирает все или некоторые файлы заново, даже если они не изменялись;
  • если удалить файл .sconsign.dblite, scons не может перегенерировать его, как обычно, и падает в конце сборки со следующим трейсом:
OSError: [Errno 2] No such file or directory: '.sconsign.dblite':
  File "/usr/lib64/python2.7/site-packages/SCons/Script/Main.py", line 1372:
    _exec_main(parser, values)
  File "/usr/lib64/python2.7/site-packages/SCons/Script/Main.py", line 1335:
    _main(parser)
  File "/usr/lib64/python2.7/site-packages/SCons/Script/Main.py", line 1099:
    nodes = _build_targets(fs, options, targets, target_top)
  File "/usr/lib64/python2.7/site-packages/SCons/Script/Main.py", line 1297:
    jobs.run(postfunc = jobs_postfunc)
  File "/usr/lib64/python2.7/site-packages/SCons/Job.py", line 113:
    postfunc()
  File "/usr/lib64/python2.7/site-packages/SCons/Script/Main.py", line 1294:
    SCons.SConsign.write()
  File "/usr/lib64/python2.7/site-packages/SCons/SConsign.py", line 109:
    syncmethod()
  File "/usr/lib64/python2.7/site-packages/SCons/dblite.py", line 127:
    self._os_unlink(self._file_name)
Exception OSError: OSError(2, 'No such file or directory') in
  <bound method dblite.__del__ of <SCons.dblite.dblite object at 0x7fa287435c10>> ignored

У меня проблема проявляется на scons 2.3.5 и 2.4.1, но только в составе большого проекта. Воспроизвести вне этого проекта баг не удалось.

Баг


Баг заключается в том, что:
  • по-умолчанию SCons создает отдельные файлы .sconsign для каталога с исходниками (например, ту где лежит SConstruct, но я не знаю от чего это зависит) и для каталога сборки (куда указывает VariantDir);
  • при каком-то стечении обстоятельств, scons записывает информацию о сборке в .sconsign внутри VariantDir, а ищет ее в другом каталоге, и не может найти.
Т.к. SCons не смог найти информацию о предыдущей сборке файла, он считает что файл не был собран. При запуске с --debug=explain можно увидеть следующее:

scons: Cannot explain why `<FILENAME>' is being rebuilt: No previous build information found       

Решение


Чтобы все починить, можно заствить SCons использовать один глобальный файл .sconsign на весь проект. Для этого надо передать абсолютный путь в функцию SConsignFile, например:

import os.path
env.SConsignFile(os.path.join(env.Dir('#').abspath, '.sconsign.dblite'))

27 нояб. 2015 г.

Примеры декодирования и проигрывания аудио-файла в Linux

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

Код доступен на github.

Каждый пример это небольшая программа. Примеры делятся на два типа:
  • decode: программа декодирует заданный файл и выводит сырые сэмплы в stdout;
  • play: программа читает сырые сэмплы из stdin и посылает в звуковую карту.

Разделение на два шага введено специально, чтобы продемонстрировать, как можно получить сырые сэмплы, и как их можно воспроизвести. Библиотеки вроде FFmpeg и SoX позволяют сделать это также и без промежуточного шага, если построить более сложный pipeline.

Сырые сэмплы на выходе/входе всех примеров имеют одинаковый формат:
  • два канала (front Left, front Right);
  • interleaved format (L R L R ...);
  • сэмплы это 32-битные float-ы в little endian;
  • частота сэмплирования 44100 Hz.

Примеры не выполняют конвертацию порядка байт на входе/выходе, так что будут работать только на little-endian архитектурах.


Пример запуска


Программы decode и play можно объединять через пайп. Допустимы любые комбинации, например:

$ ./ffmpeg_decode     cool_song.mp3   |  ./alsa_play_tuned
$ ./sox_decode_chain  cool_song.mp3   |  ./ffmpeg_play
$ ./sndfile_decode    cool_song.flac  |  ./sox_play


FFmpeg


ffmpeg_decode

  • открывает входной файл, ищет в нем аудио-стрим и открывает подходящий декодер;
  • инициализирует конвертер из формата входного файла (определенного автоматически) в выходной формат (2 канала, 32-битные флоаты);
  • читает пакеты из входного файла;
  • передает полученные пакеты декодер и получает из него декодированные фреймы;
  • передает декодированные фреймы в конвертер и получает из него выходные сэмплы;
  • пишет сэмплы в stdout.
Конвертер может выполнять буферизацию, если входных данных больше, чем выходных. Если аккуратно настроить размеры пакетов/фреймов, можно избежать буферизации.

ffmpeg_play

  • открывает устройство вывода ALSA;
  • настраивает формат вывода;
  • читает сэмплы из stdin, формирует из них пакеты и отправляет в устройство вывода.

ffmpeg_play_encoder

  • открывает устройство вывода ALSA;
  • настраивает формат вывода;
  • создает энкодер для выходного формата;
  • читает сэмплы из stdin, формирует из них фреймы и передает их в энкодер;
  • получает их энкодера пакеты и отправляет их в устройство вывода.

SoX


sox_decode_simple

  • открывает входной файл;
  • если входной формат (количество каналов, частота сэмплирования) отличается от выходного, завершается с ошибкой;
  • читает сэмплы из входного файла и пишет их в stdout.

sox_decode_chain

  • открывает входной файл и определяет входные параметры;
  • настраивает выходные параметры;
  • создает и запускает цепочку эффектов:
    • ввод: чтение входного файла;
    • ресемплинг (эффект включается, если входная и выходная частота сэмплирования отличаются);
    • ремаппинг каналов (эффект включается, если входные и выходные каналы отличаются);
    • вывод: вызов коллбэка для записи сэмплов в stdout.

sox_play

  • открывает устройство вывода ALSA (также можно заменить "alsa" на "pulseaudio");
  • читает сэмплы из stdin и пишет их в устройство вывода.

 

ALSA (libasound)


alsa_play_simple

  • открывает pcm;
  • устанавливает параметры в значения по-умолчанию;
  • читает сэмплы из stdin и отправляет в pcm.

alsa_play_tuned

  • открывает pcm;
  • устанавливает все доступные параметры;
  • читает сэмплы из stdin и отправляет в pcm.

Интерес представляют параметры, связанные с циклическим буфером. При записи данных в pcm, они добавляются в циклический буфер, из которого ALSA читает по таймеру.

Переполнение буфера называется "buffer overrun". Попытка чтения (со стороны ALSA, по таймеру) из пустого буфера называет "buffer underrun". Вместе эти события называются "xrun".

В частности, при возникновении buffer underrun, воспроизведение прекращается до вызова snd_pcm_recover(). Если софт все время не успевает добавить данные в буфер до того, как ALSA попытается их прочитать, пользователь будет слышать "заикания" и видеть в консоли сообщения про "alsa xrun".

Настройки циклического буфера:
  • buffer_size - количество сэмплов в циклическом буфере;
  • buffer_time - продолжительность всего буфера в микросекундах;
  • period_size - количество сэмплов, которое ALSA вычитывает из циклического буфера по тику таймера;
  • period_time - интервал таймера в микросекундах.
И связанные с ним:
  • start_threshold - перед стартом воспроизведения, ALSA не должна начитать вычитывать данные, пока в циклическом буфере не накопится start_threshold сэмплов;
  • avail_min - во время воспроизведения, ALSA не должна вычитывать данные, пока в циклическом буфере не накопится avail_min сэмплов.
Как выбирать значения для избежания buffer underrun:
  • period_size надо  установить равным размеру буферов, которые софт будет отправлять в pcm за одну запись; чем больше, тем меньше вероятность underrun и больше latency;
  • buffer_size надо установить кратным period_size и в несколько раз больше;
  • start_threshold разумно установить раным buffer_size; в этом случае, чем больше buffer_size, тем меньше вероятность underrun и больше latency;
  • avail_min разумно установить равным period_size.

libsndfile


sndfile_decode

  • открывает входной файл и получет его параметры;
  • если входной формат (количество каналов, частота сэмплирования) отличается от выходного, завершается с ошибкой;
  • читает сэмплы из входного файла и пишет их в stdout.

libsndfile очень компактная библиотека с простым API, но поддерживает только простые форматы, вроде WAV и FLAC. Возможности ограничиваются чтением/записью файлов, но для ресемплинга можно использовать libresample (SRC) от того же автора.

Замечания


SoX может использовать libsndfile как бэкенд. FFmpeg может использовать SoX для ресемплинга. И SoX, и FFmpeg поддерживают ALSA и pulseaudio, но утилиты FFmpeg выводят звук не через них, а через SDL.

Другие  библиотеки, для которых нет примеров:

Все библиотеки (и для которых есть примеры, и для которых нет) кроссплатформенные.

Icons made by Anton Saputro from www.flaticon.com is licensed by CC BY 3.0

25 нояб. 2015 г.

Файловые блокировки в Linux

Advisory locking


Рекоммендательные блокировки работают только тогда, когда процессы явно устанавливают  или снимают блокировку. Они не влияют на процессы, которые ничего не знают о блокировках.

У всех блокировок есть два режима: exclusive и shared.

BSD locks (flock)

Самый простой вид блокировок это flock(2). Особенности:
  • входят в POSIX;
  • устанавливают блокировку на файл целиком;
  • привязаны к файловому дескриптору;
  • не обеспечивают атомарное изменение режима блокировки;
  • не поддерживают NFS (в Linux).
Эти блокировки ассоциируются с файловым дескриптором:
  • дубликаты файловых дескриторов созданные с помощью dup(2) ссылаются на одну и ту же блокировку;
  • независимые файловые дескпторы, полученные для одного и того же или разных файлов с помощью open(2) ссылаются на разные блокировки.
Это, в частности, позволяет избежать проблем с потоками (см. следующую секцию).

Однако, flock() не гарантирует атомарного переключения между режимами exclusive и shared. При изменении режима блокировки, flock() сначала отпускает ее, а затем ожидает возможности захватить ее с новым режимом.

POSIX record locks (fcntl)

Более сложный (и медленный) вид блокировок это POSIX record locks (см. секцию "Advisory record locking" в fcntl(2)). Особенности:
  • входят в POSIX;
  • устанавливают блокировку на заданный интервал байт в файле;
  • привязаны к паре [i-node, pid], а не файловому дескриптору;
  • обеспечивают атомарное изменение режима блокировки;
  • поддерживают NFS (в Linux).
 В POSIX также входит функция lockf(3). В Linux она является оберткой над POSIX record locks.

Эти блокировки ассоциируются с парой [i-node, pid]:
  • любые (даже несвязанные) файловые дескрипторы, отктрытые для одного и того же файла одним и тем же процессом, ссылаются на одну и ту же блокировку;
  • как следствие, все потоки одного процесса также разделяют одну и ту же блокировку для каждого файла, т.к. имеют одинаковый pid;
  • вызов close() для любого из открытых для файла дескрипторов снимает блокировку с файла, не зависимо от того, через какой дескриптор она была установленна и остаются ли у процесса другие открытые дескрипторы для этого файла.
Эта особенность затрудняет использование record locks при написании библиотек (т.е. при отсутсвии контроля над всем приложением) и в многопоточной среде.

Если необходима гарантия атомарного переключения блокировки между режимами exclusive и shared, есть только два варианта:
  • использовать open file description locks, которые доступны в современных ядрах Linux;
  • использовать record locks и избежать открытия процессом одного и того же файла блокировки через несколько файловых дескрипторов.

Если требуется атомарное переключение режима блокировки, нет возможности использовать open file description locks, и блокировки нужны для синхронизации не только процессов, но и потоков, можно сделать следующее:
  • централизованно хранить все дескрипторы файлов блокировок, отрытые процессом, и не допускать открытия нескольких дескрипторов для одного файла;
  • ассоциировать с каждым дескриптором rw-mutex, например pthread_rwlock, и счетчик количества потоков, захвативших блокировку одновременно (он будет больше единицы только при захвате в shared mode);
  • для захвата блокировки сначала захватывать мьютекс и увеличивать счетчик; и только если счетчик был нулевой, захватывать и файловую блокировку;
  • для освобождения блокировки уменьшать счетчик и только если он стал нулевым, освобождать и файловую блокировку; затем освобождать мьютекс.

Open file description locks (fcntl)

Этот вид блокировок сочетает в себе достоинства BSD locks и record locks. (см. секцию "Open file description locks (non-POSIX)" в fcntl(2)). Особенности:
  • linux-specific; доступны начиная с ядра 3.15;
  • устанавливают блокировку на заданный интервал байт в файле;
  • привязаны к файловому дескриптору;
  • обеспечивают атомарное изменение режима блокировки.
Другими словами, эти блокировки сочетают в себе достоинства BSD locks (привязка к файловому дескриптору) и POSIX record locks (блокировка интервала, атомарное переключение режима).

Mandatory locking


В Linux также есть ограниченная поддержака обязательных блокировок:
  • обязательные блокировки задействуются только если они включены при монтировании раздела и для блокировки используются POSIX record locks;
  • после захвата exclusive или shared блокировки любые системные вызовы, изменяющие файл (например write(), truncate()) будут блокированы до тех пор, пока блокировка не будет отпущена;
  • после захвата exclusive блокировки, также буду блокироваться любые системные вызовы, читающие из файла.
Однако, реализация в Linux считается ненадежной, см. сецию "Mandatory locking" в fcntl(2):
  • возможны гонки при одновременном захвате блокировки и параллельном вызове write() или read();
  • возможны гонки при одновременном использовании с mmap().
Также, обязательные блокировки не решают проблемы, описанные в секции "Удаление и переименование" ниже.

Замечания

 

Обработка падений

Приятной особенностью всех видов блокировок (и flock() и fcntl()) является то, что блокировка автоматически снимается при завершении процесса, в том числе аварийном.

Другими словами, наличие блокировки гарантирует, что существует процесс, удерживающий ее.

Удаление и переименование

Ни рекоммендательные, ни обязательные блокировки не учитываются, когда выполняются вызовы unlink() или rename().

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

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

Производительность

Если имеет значение производительность, вместо файловых блокировок можно использовать POSIX semaphores. В glibc они реализованы поверх futex-ов (однако имеют оверхед в одну страницу памяти на семафор).

Ссылки

18 июл. 2015 г.

Unix: Обработка отсутствия читателей FIFO, потоко-безопасная обработка SIGPIPE

Ссылки

 

Открытие при отсутствии читателей


open(O_RDWR) 
open() не блокируется и сразу открывает FIFO, так как вызывающий процесс становится читателем.

open(O_WRONLY | O_NONBLOCK)
open() не блокируется и сразу возвращает ошибку ENXIO.

open(O_WRONLY)
open() блокируется до тех пор, пока не появится читатель.

 

Запись при отсутствии читателей (обработка SIGPIPE)


write() в FIFO без читателей генерирует SIGPIPE независимо от флага O_NONBLOCK. Обработка SIGPIPE может вызвать определенные затруднения. Ниже перечислены решения проблемы.

1. Использовать сокеты вместо FIFO


Если использовать сокеты (например UNIX domain sockets), то генерацию SIGPIPE можно отключить, если:
  • вместо write() использовать send(), и
  • передавать в send() флаг MSG_NOSIGNAL (Linux) или установить в setsockopt() опцию SO_NOSIGPIPE (BSD).

Замечание: опция SO_NOSIGPIPE работает для send(), но не для write().

2. Использовать режим открытия O_RDWR


При использовании флага O_RDWR, SIGPIPE никогда не будет генерироваться.

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

3. Игнорировать или перехватывать SIGPIPE


Если SIGPIPE игнорируется или перехватывается, write() будет возвращать ошибку EPIPE.

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

4. Использовать select()/poll()


Если вместо блокирующего write() использовать select() или poll(), то об отсутствии читателей можно узнать, не вызывая write():
  • select() добавит дескриптор в exceptfds;
  • poll() взведет для дескриптора флаг POLLERR.

В этом случае можно не вызывать write() и избежать генерации SIGPIPE. Такой подход может работать, однако здесь присутствует гонка: читатель может завершиться за небольшой промежуток времени между вызовами select()/poll() и write(), и тогда SIGPIPE все равно будет сгенерирован.

5. Использовать синхронную обработку сигналов


Наконец, существует потоко-безопасный способ обработать SIGPIPE, используя функции для синхронной обработки сигналов. Этот способ требует больше всего кода. Он подробно описан здесь.

Схема работы следующая:
  1. Перед вызовом write() блокируем SIGPIPE для текущего потока с помощью pthread_sigmask(). Нужно заметить, что ядро всегда доставляет SIGPIPE тому потоку, который вызвал write (см. signal(7)).
  2. После блокировки сигнала с помощью sigpending() проверяем, не ожидает ли SIGPIPE обработки еще до вызова write().
  3. Вызываем write(). Если читатель отсутствует, SIGPIPE будет добавлен в маску сигналов, ожидающих обработки, для текущего потока, а write() вернет ошибку EPIPE.
  4. Если write() вернул EPIPE, удаляем сигнал с помощью неблокирующего вызов sigtimedwait(), но только если сигнал не ожидал обработки до вызова write() (в противном случае наш код "съедал" бы чужой сигнал, т.к. один и тот же сигнал, пришедший несколько раз, не ставится в очередь, а доставляется только один раз).
  5. Восстанавливаем изначальную маску сигналов с помощью pthread_sigmask().
Пример реализации

#include <unistd.h>
#include <time.h>
#include <errno.h>
#include <sys/signal.h>

ssize_t safe_write(int fd, const void* buf, size_t bufsz)
{
    sigset_t sig_block, sig_restore, sig_pending;

    sigemptyset(&sig_block);
    sigaddset(&sig_block, SIGPIPE);

    /* Block SIGPIPE for this thread.
     *
     * This works since kernel sends SIGPIPE to the thread that called write(),
     * not to the whole process.
     */
    if (pthread_sigmask(SIG_BLOCK, &sig_block, &sig_restore) != 0) {
        return -1;
    }

    /* Check if SIGPIPE is already pending.
     */
    int sigpipe_pending = -1;
    if (sigpending(&sig_pending) != -1) {
        sigpipe_pending = sigismember(&sig_pending, SIGPIPE);
    }

    if (sigpipe_pending == -1) {
        pthread_sigmask(SIG_SETMASK, &sig_restore, NULL);
        return -1;
    }

    ssize_t ret;
    while ((ret = write(fd, buf, bufsz)) == -1) {
        if (errno != EINTR)
            break;
    }

    /* Fetch generated SIGPIPE if write() failed with EPIPE.
     *
     * However, if SIGPIPE was already pending before calling write(), it was also
     * generated and blocked by caller, and caller may expect that it can fetch it
     * later. Since signals are not queued, we don't fetch it in this case.
     */
    if (ret == -1 && errno == EPIPE && sigpipe_pending == 0) {
        struct timespec ts;
        ts.tv_sec = 0;
        ts.tv_nsec = 0;

        int sig;
        while ((sig = sigtimedwait(&sig_block, 0, &ts)) == -1) {
            if (errno != EINTR)
                break;
        }
    }

    pthread_sigmask(SIG_SETMASK, &sig_restore, NULL);
    return ret;
}

8 окт. 2014 г.

Падение контейнеров Docker в Gentoo

Проблема


При использовании пакета app-emulation/docker из официального дерева portage я наблюдаю рандомные падения контейнеров при большом IO.

В логах присутствуют записи вида "mount.go: .... Segmentation Fault". Способа стабильно воспроизвести баг я не знаю.

Баг наблюдается на всех версиях с 1.0.0 по 1.2.0 включительно и на любых комбинациях USE-флагов: lxc, device-mapper, aufs (на ядре sys-kernel/aufs-sources), а так же на обоих версиях dev-lang/go: 1.2 и 1.3.


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

Проблемы не наблюдается в Debian wheezy.

Решение


Проблемы так же не наблюдается при использовании пакета app-emulation/docker-bin из оверлея docker.

Этот пакет использует deb-пакет с официального сайта.

6 июн. 2014 г.

Перечисление и мониторинг за USB-устройствами с помощью libudev

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

Документация: libudev and Sysfs Tutorial.

Пример запуска

$ ./a.out                     
usb usb_device exists 1d6b:0001 /dev/bus/usb/002/001
usb usb_device exists 046d:c05b /dev/bus/usb/002/002
usb usb_device exists 1d6b:0001 /dev/bus/usb/003/001
usb usb_device exists 1d6b:0001 /dev/bus/usb/004/001
usb usb_device exists 1d6b:0001 /dev/bus/usb/005/001
usb usb_device exists 1d6b:0002 /dev/bus/usb/001/001
usb usb_device    add 8564:1000 /dev/bus/usb/001/026
usb usb_device remove 0000:0000 /dev/bus/usb/001/026
^C

Код

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
#include <libudev.h>
#include <stdio.h>

#define SUBSYSTEM "usb"

static void print_device(struct udev_device *dev)
{
    const char *action = udev_device_get_action(dev);
    if (! action)
        action = "exists";

    const char *vendor = udev_device_get_sysattr_value(dev,"idVendor");
    if (! vendor)
        vendor = "0000";

    const char *product = udev_device_get_sysattr_value(dev,"idProduct");
    if (! product)
        product = "0000";

    printf("%s %s %6s %s:%s %s\n",
           udev_device_get_subsystem(dev),
           udev_device_get_devtype(dev),
           action,
           vendor,
           product,
           udev_device_get_devnode(dev));
}

static void process_device(struct udev_device *dev)
{
    if (dev) {
        if (udev_device_get_devnode(dev))
            print_device(dev);

        udev_device_unref(dev);
    }
}

static void enumerate_devices(struct udev *udev)
{
    struct udev_enumerate *enumerate = udev_enumerate_new(udev);

    udev_enumerate_add_match_subsystem(enumerate, SUBSYSTEM);
    udev_enumerate_scan_devices(enumerate);

    struct udev_list_entry *devices = udev_enumerate_get_list_entry(enumerate);
    struct udev_list_entry *entry;

    udev_list_entry_foreach(entry, devices) {
        const char *path = udev_list_entry_get_name(entry);
        struct udev_device *dev = udev_device_new_from_syspath(udev, path);
        process_device(dev);
    }

    udev_enumerate_unref(enumerate);
}

static void monitor_devices(struct udev *udev)
{
    struct udev_monitor *mon = udev_monitor_new_from_netlink(udev, "udev");

    udev_monitor_filter_add_match_subsystem_devtype(mon, SUBSYSTEM, NULL);
    udev_monitor_enable_receiving(mon);

    int fd = udev_monitor_get_fd(mon);

    while (1) {
        fd_set fds;
        FD_ZERO(&fds);
        FD_SET(fd, &fds);

        int ret = select(fd+1, &fds, NULL, NULL, NULL);
        if (ret <= 0)
            break;

        if (FD_ISSET(fd, &fds)) {
            struct udev_device *dev = udev_monitor_receive_device(mon);
            process_device(dev);
        }
    }
}

int main (void)
{
    struct udev *udev = udev_new();
    if (! udev) {
        fprintf(stderr, "udev_new() failed\n");
        return 1;
    }

    enumerate_devices(udev);
    monitor_devices(udev);

    udev_unref(udev);
    return 0;
}

24 мая 2014 г.

Баг с аппаратной переключалкой WiFi

У меня баг проявился на Debian Jessie с карточкой Ralink RT3090 (HP ProBook 4720s).

Симптомы

1. Индикатор wifi не светится, аппаратная кнопка включения wifi не работает.

2. wicd (или network-manager) не отображает список доступных сетей.

3. Команда:

# iwlist wlan0 scan

Выдает ошибку:

wlan0   Interface doesn't support scanning : Network is down

4. Команда:

# ifconfig wlan0 up

Выдает ошибку:

SIOCSIFFLAGS: Operation not possible due to RF-kill

5. Команда:

# rfkill list all

Выводит:

0: some-wifi: Wireless LAN
        Soft blocked: no
        Hard blocked: yes


Решение

Нужно перезагрузить компьютер и до того, как загрузится Linux, включить аппаратную кнопку wifi.

Решение описано на stackoverflow и в баг-репорте.

16 мая 2014 г.

PySide: Internal C++ object already deleted

Эта ошибка возникает в следующем случае:
  • Имеется Python(PySide)-объект с ненулевым счетчиком ссылок, который привязан к некоторому C++-объекту QObject.
  • Где-то в C++-коде C++-объект уничтожается без ведома Python.
  • Где-то в Python-коде происходит обращение к Python-объекту, который теперь ссылается на уничтоженный QObject.

Наиболее вероятный сценарий уничтожения QObject в C++ без ведома Python такой:
  • По какой-то причине из Python или из C++ было запущено уничтожение родительского объекта QObject.
  • Управление получил C++-деструктор родительского QObject, который уничтожил все дочерние C++-объекты QObject.
  • Ссылки на эти дочерние объекты из Python остались висячими.

Соотвественно, при появлении такой ошибки стоит проверить, не были ли уничтожены родительские объекты.

Если объект был уничтожен совершенно внезапно (например, при нажатии никак не связанной с ним кнопки), возможно родительские объекты были удалены сборщиком мусора, который просто не был запущен до этого.

Отлаживать такую ситуацию довольно просто:
  • Собираем Python с отладочными символами.
  • Запускаем Python под GDB.
  • Ставим breakpoint на С++-деструктор того QObject, который внезапно удаляется.
  • Смотрим C++-backtrace (команда bt) и наслаждаемся поддержкой Python в GDB, которая позволяет распечатывать все PyObject.
  • Смотрим Python-backtrace (команда py-bt).

Если дело в сборщике мусора, то по backtrace-ам можно увидеть, начиная с какого объекта началось удаление.

Если GDB не распечатывает PyObject и py-bt не работает, скорее всего Python собран без отладочных символов.
Если GDB периодически печатает ошибку:

Python Exception <type 'exceptions.TypeError'> object of type 'FakeRepr' has no len()

то можно воспользоваться патчем.

12 мая 2014 г.

Python: загрузка модуля из заданного пути


Примеры даны для следующей структуры каталогов:

.
`-- foo
    `-- bar
        `-- __init__.py

Загрузка foo.bar по пути к __init__.py

Python 2

import imp
bar = imp.load_source('bar', '/foo/bar/__init__.py')

Python 3

import importlib.machinery
loader = importlib.machinery.SourceFileLoader('bar', '/foo/bar/__init__.py')
bar = loader.load_module('bar')

Загрузка foo.bar по пути к bar

Python 3

import importlib.machinery
loader = importlib.machinery.PathFinder.find_module('bar', ['/foo'])
bar = loader.load_module('bar')


8 мая 2014 г.

QEMU: Монтирование образа qcow2 с LVM

Самый простой способ - использовать guestmount.

# guestmount -a  image.qcow2 -i --rw /mount/point

В Gentoo это пакет app-emulation/libguestfs (лучше использовать версию >= 1.26).

Если возникнет ошибка:

libguestfs: error: cannot find any suitable libguestfs supermin, fixed or old-style appliance on LIBGUESTFS_PATH (search path: /usr/lib64/guestfs)

ее можно вылечить так:

# ln -s /usr/share/guestfs/appliance /usr/lib64/guestfs

11 февр. 2014 г.

Kernel: отладка функции инициализации модуля ядра

1.1 Устанавливаем breakpoint на загрузчик модулей


(gdb) break do_one_initcall

1.2 Загружаем модуль


# insmod mymodule

1.3 Загружаем символы


(gdb) frame 1
(gdb) print mod->module_core
0xdeadbeaf
(gdb) add-symbol-file mymodule.ko 0xdeadbeaf

Gentoo: Кросскомпиляция и отладка ядра другой архитектуры

Ссылки


ARM Versatile PB


Пути


Создаем каталог, в котором будет проходить вся работа.

# mkdir /some/dir
# export VM=/some/dir

Виртуальная машина (Debian Squeeze)


Устанавливаем QEMU с включенной опцией arm в переменных QEMU_SOFTMMU_TARGETS и QEMU_USER_TARGETS.


Создаем диск.

# cd $VM
# qemu-img create -f qcow2 vm.qcow2 20G

Скачиваем образ установщика.

# cd $VM
# wget http://caesar.acc.umu.se/cdimage/archive/6.0.8/armel/iso-cd/debian-6.0.8-armel-netinst.iso

Скачиваем ядро и initrd установщика (QEMU не поддерживает загрузчик для ARM).

# cd $VM
# wget ftp://ftp.us.debian.org/debian/dists/wheezy/main/installer-armel/20130613+deb7u1+b1/images/versatile/netboot/vmlinuz-3.2.0-4-versatile
# wget ftp://ftp.us.debian.org/debian/dists/wheezy/main/installer-armel/20130613+deb7u1+b1/images/versatile/netboot/initrd.gz

Устанавливаем Debian.

# brctl show br0
bridge name        bridge id                STP enabled       interfaces
br0                8000.3085a997443e        no                eth0

# cat $VM/vm-ifup.sh
#! /bin/bash
set -x
ifconfig $1 up && brctl addif br0 $1

# qemu-system-arm \
  -m 2047 -M versatilepb -hda $VM/vm.qcow2 \
  -net nic,vlan=1,macaddr=de:ad:be:af:11:22 \
  -net tap,vlan=1,ifname=myvm,script=$VM/vm-ifup.sh \
  -kernel $VM/vmlinuz-3.2.0-4-versatile \
  -initrd $VM/initrd.gz \
  -cdrom $VM/debian-6.0.8-armel-netinst.iso \
  -boot d

Вытаскиваем initrd из установленного Debian.

# qemu-nbd --connect=/dev/nbd0 $VM/vm.qcow2
# mkdir $VM/vm-disk
# fdisk -l /dev/nbd0
# mount /dev/nbd0p1 $VM/vm-disk
# cp $VM/vm-disk/boot/initrd.img-2.6.32-5-versatile $VM/
# umount $VM/vm-disk
# rmdir $VM/vm-disk
# qemu-nbd --disconnect $VM/vm.qcow2

Загружаем дефолтный Debian.

# qemu-system-arm \
  -m 2047 -M versatilepb -hda $VM/vm.qcow2 \
  -net nic,vlan=1,macaddr=de:ad:be:af:11:22 \
  -net tap,vlan=1,ifname=myvm,script=$VM/vm-ifup.sh \
  -kernel $VM/vmlinuz-3.2.0-4-versatile \
  -initrd $VM/initrd.img-2.6.32-5-versatile \
  -append "root=/dev/sda1"

Тулчейн


Устанавливаем crossdev.

# emerge -av sys-devel/crossdev

Устанавливаем тулчейн.

# crossdev --binutils 2.20.1 --gcc 4.6.4 --kernel 3.1 --libc 2.11.3 --target arm-linux-gnueabi

Устанавливаем GDB (флаг expat нужен для парсинга XML, который раздает gdbserver QEMU).

# USE=expat emerge -av cross-arm-linux-gnueabi/gdb

Ядро


У меня работали ядра 2.6.x версий от 2.6.29 до 2.6.35.9.


Скачиваем и распаковываем ядро.

# cd $VM
# wget ftp://ftp.kernel.org/pub/linux/kernel/v2.6/linux-2.6.32.5.tar.bz2
# tar xvf linux-2.6.32.5.tar.bz2

Конфигурируем.

# cd $VM/linux-2.6.32.5
# make versatile_defconfig
# make menuconfig

Нужно включить эти опции:

General Setup --->
  [*] Kernel .config support
  [*] Enable access to .config through /proc/config.gz

Kernel Features --->
  [*] Use VM EABI to compile the kernel
  [*] Allow old ABI binaries to run with this kernel

Bus Support --->
  [*] PCI Support

Device Drivers --->
  SCSI Device Support --->
    [*] SCSI Device Support
    [*] SCSI Disk Support
    [*] SCSI CDROM support
    [*] SCSI low-lever drivers --->
      [*] SYM53C8XX  Version 2 SCSI support

  Generic Driver Options--->
    [*] Maintain a devtmpfs filesystem to mount at /dev
    [*] Automount devtmpfs at /dev, after the kernel mounted the root

  Input device support--->
    [*] Event interface

File systems --->
  <*> Ext3 journalling file system support
  <*> The Extended 4 (ext4) filesystem
  Pseudo filesystems--->
    [*] Virtual memory file system support (former shm fs)

Kernel hacking --->
  [*] Compile the kernel with debug info

Собираем ядро и модули.

# mkdir -p $VM/arm-modules
# cd $VM/linux-2.6.32.5
# make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-
# make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- modules
# make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- modules_install INSTALL_MOD_PATH=$VM/arm-modules

Копируем ядро.

# cp $VM/linux-2.6.32.5/arch/arm/boot/zImage $VM/debug-kernel

Initrd


Распаковываем оригинальный initrd.

# mkdir -p $VM/initrd
# cd $VM/initrd
# gunzip - < $VM/initrd.img-2.6.32-5-versatile | cpio -id

Заменяем модули на собранные нами.

# cd $VM/initrd/lib/modules
# rm -rf *
# cp -a $VM/arm-modules/lib/modules/* .

Запаковываем новый initrd.

# cd $VM/initrd/
# find . | cpio --create --format='newc' | gzip > $VM/debug-initrd

Запуск


Проверяем, что новое ядро успешно загружается.

# qemu-system-arm \
  -m 2047 -M versatilepb -hda $VM/vm.qcow2 \
  -net nic,vlan=1,macaddr=de:ad:be:af:11:22 \
  -net tap,vlan=1,ifname=myvm,script=$VM/vm-ifup.sh \
  -kernel $VM/debug-kernel \
  -initrd $VM/debug-initrd \
  -append "root=/dev/sda1"

Отладка ядра


Запускаем QEMU с gdbserver (опции -s и -S).

# qemu-system-arm -s -S \
  -m 2047 -M versatilepb -hda $VM/vm.qcow2 \
  -net nic,vlan=1,macaddr=de:ad:be:af:11:22 \
  -net tap,vlan=1,ifname=myvm,script=$VM/vm-ifup.sh \
  -kernel $VM/debug-kernel \
  -initrd $VM/debug-initrd \
  -append "root=/dev/sda1"

Запускаем отладчик.

# cd $VM/linux-2.6.32.5
# arm-none-linux-gnueabi-gdb
(gdb) file vmlinux
(gdb) target remote :1234
(gdb) continue
.....
^C
(gdb) backtrace
(gdb) list

Можно также поставить breakpoint на функцию icmp_reply() и попинговать виртуалку.


Отладка модуля


Пишем простой модуль.

# cat $VM/hello/Makefile
KERNEL := $VM/linux-2.6.32.5

ARCH := arm
CROSS := arm-linux-gnueabi-

CFLAGS_hello.o := -g
obj-m += hello.o

all:
    make -C $(KERNEL) M=$(PWD) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS) modules

clean:
    make -C $(KERNEL) M=$(PWD) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS) clean
# cat $VM/hello/hello.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>

void goodbye(void); // без static!

void goodbye(void)
{
    printk(KERN_INFO "goodbye!\n");
}

static int __init hello_init(void)
{
    printk(KERN_INFO "hello_init\n");
    return 0;
}

static void __exit hello_exit(void)
{
    goodbye();
    printk(KERN_INFO "hello_exit\n");
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("hello module");

Собираем модуль.

# cd $VM/hello
# make

Копируем hello.ko на виртуалку и загружаем.

# insmod hello.ko
# cat /sys/module/hello/sections/.text
0x0000000000000001
# cat /sys/module/hello/sections/.data
0x0000000000000002
# cat /sys/module/hello/sections/.bss
0x0000000000000003

Загружаем символы модуля в GDB.

(gdb) add-symbol-file ../hello/hello.ko \
      0x0000000000000001 -s .data 0x0000000000000002 -s .bss  0x0000000000000003
(gdb) b goodbye
(gdb) c

Выгружаем модуль, чтобы сработал breakpoint.

# rmmod hello

Отладка в Emacs


Советую настроить Emacs так:

(setq gdb-non-stop-setting nil)
(setq gdb-many-windows t)

И запускать отладчик так:

M-x gdb
arm-linux-gnueabi-gdb -i=mi

И затем в консоли GDB:

(gdb) file vmlinux
(gdb) target remote :1234
.....

PowerPC


Для PowerPC последовательность действий та же, за исключением перечисленных ниже моментов.


QEMU


QEMU был собран с флагом ppc.


Debian


Использовался Debian Wheezy с ядром 3.x.


Т.к. QEMU под PowerPC поддерживает загрузчик, вручную передавать ядро и initrd из установщика не требуется (опции -kernel и -initrd).


Crossdev


Использовался тулчейн powerpc-unknown-linux-gnu и соответственно отладчик cross-powerpc-unknown-linux-gnu/gdb.


Ядро


Проверялись ядра 2.6.39 и 3.6.11 с флагами сборки:

ARCH="powerpc" CROSS="powerpc-unknown-linux-gnu-"

Вместо versatile_defconfig и включения перечисленных опций ядра был использован конфиг ядра из Debian Wheezy.


В QEMU опцией -kernel передавался файл $VM/linux-3.6.11/vmlinux.

12 янв. 2014 г.

Debian Wheezy 64 bit, Fglrx, Steam

Список пакетов fglrx (закрытый драйвер ATI):

fglrx-atieventsd
fglrx-control
fglrx-driver
fglrx-glx
fglrx-modules-dkms
glx-alternative-fglrx
libfglrx
libfglrx-amdxvba1
libgl1-fglrx-glx


libfglrx:i386
libgl1-fglrx-glx:i386


Установка:

$ sudo apt-get install fglrx-atieventsd fglrx-control fglrx-driver fglrx-glx fglrx-modules-dkms glx-alternative-fglrx libfglrx libfglrx-amdxvba1 libgl1-fglrx-glx libfglrx:i386 libgl1-fglrx-glx:i386

$ sudo aticonfig --initial

Установщик Steam: Steam Installer for Wheezy

$ wget https://dl.dropboxusercontent.com/u/82856418/Steam-Installer-For-Wheezy/steam-debian_1.0.0.45-4_all.deb
$ sudo dpkg -i steam-debian_1.0.0.45-4_all.deb

9 янв. 2014 г.

Gentoo: /usr/lib64/libc.so.6: invalid ELF header

Ошибка при сборке или запуске программ:

/usr/lib64/libc.so.6: invalid ELF header

У меня эта ошибка проявилсь в следующей конфигурации:

$ ls -l /usr/lib64/libc.so.6
lrwxrwxrwx 1 root root 18 Jul  9  2013 libc.so.6 -> /usr/lib64/libc.so


$ file /usr/lib64/libc.so
/usr/lib64/libc.so: ASCII text


cat /usr/lib64/libc.so
/* GNU ld script
   Use the shared library, but some functions are only in
   the static library, so try that secondarily.  */
OUTPUT_FORMAT(elf64-x86-64)
GROUP ( /lib64/libc.so.6 /usr/lib64/libc_nonshared.a  AS_NEEDED ( /lib64/ld-linux-x86-64.so.2 ) )


$ ls -l /lib/libc.so.6
lrwxrwxrwx 1 root root 12 Jan  9 18:30 /lib/libc.so.6 -> libc-2.17.so*
 

$ file /lib/libc-2.17.so
/lib/libc-2.17.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.16, stripped


То есть, /usr/lib64/libc.so.6 -- это linker script (используется при линковке программы), а /lib/libc.so.6 -- это настоящая библиотека (используется при запуске программы).

Проблема проявляется, если при запуске вместо настоящей библиотеки загрузчик находит linker script.

Решение 1

$ export LD_LIBRARY_PATH=/lib64

Решение 2

# rm /usr/lib64/libc.so.6
# ln -s /lib/libc.so.6 /usr/lib64/
# ldconfig

3 нояб. 2013 г.

Debian: потеря темы GTK2 в KDE

Симпотмы проблемы:
  • в некоторых приложениях на pygtk2, при запуске в KDE, вместо выбранной темы GTK2 (например, QtCurve) используется страшная дефолтная тема;
  • gtk-chtheme отображает выбранную тему, а gtk-theme-switch2 всегда отображает дефолтную.

Возможное решение:

$ ln -s ~/.gtkrc-2.0 ~/.gtkrc-2.0-kde

7 июл. 2013 г.

Unix: job control в подсистеме терминалов

Ссылки


Что такое терминал?


Ядро предоставляет программам абстракцию терминала, которая включает в себя:
  • Ввод/вывод
    Терминал является обычным файлом. Простым программам достаточно уметь читать и записывать поток байтов.

  • Line discipline
    Терминал имеет несколько режимов обработки вводимых символов, которые называются line discipline.

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

    Как работает этот режим можно увидеть, набрав команду cat.

  • Job control
    Терминал хранит информацию о группах процессов, которые к нему привязаны, а процессы хранят информацию о своем терминале.

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

    Job control вступает в силу, когда вы запускаете фоновую задачу, вроде gedit &, нажимаете ^C или ^Z, либо используете команды на подобие fg, bg и kill %1.

Механизм vs политика


Терминал – это один из редких примеров, когда в ядре UNIX реализована политика, а не механизм. Это обусловлено тем, что терминал позволяет незаметно для программы заменить взаимодействие с другой программой на взаимодействие с пользователем {1}.

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

Вот что получается в результате:
  1. Ядро реализует простой режим редактирования для взаимодействия с простыми программами; сложные программы, вроде vim, readline и curses, отключают его и реализуют обработку и отображение символов сами.
  2. Ядро предоставляет системные вызовы для управления группами процессов и реализует взаимодействие с этими группами драйвера терминала и line descipline с помощью сигналов; оболочки, вроде bash и zsh, создают группы процессов и предоставляют команды для переключения между этими группами.
Благодаря этому пользователю одинаково просто работать с любыми текстово-ориентированными программами, а для большинства программ взаимодействие с пользователем прозрачно.

В этом посте рассматривается второй пункт.

Параметры процессов и терминалов


Параметры процесса


Каждый процесс имеет набор параметров, которые используются для реализации job control.
  • Controlling tty
    Терминал, к которому привязан процесс.

  • pid (process id)
    Идентификатор процесса.

  • pgid (process group id)
    Идентификатор группы процессов. Равен pid лидера группы процессов (первый процесс в группе).

    Для каждого нового pipeline шелл создает новую группу и все процессы и их потомки, запущенные в этом pipeline, принадлежат этой группе. Вместе эти процессы называются "задачей" ("job").

    Группы процессов нужны для того, чтобы можно было выделить "активную" группу процессов ("foreground process group") для данного терминала.

  • sid (session id)
    Идентификатор сессии. Равен pid лидера сессии (первый процесс в сессии).

    Для каждого нового входа в систему програма login(1), которую обычно вызывает getty, создает новую сессию; шелл и его потомки принадлежат этой сессии.

    Сессии нужны для того, чтобы можно было найти все процесы, использующие данный терминал. У каждого терминала есть не более одной ассоциированной с ним сессии, она называется "controlling process group".

Параметры терминала


Каждый терминал ссылается на сессию, связанную с ним, и активную группу процессов.
  • Foreground process group
    Идентификатор pgid группы процессов, которая в данный момент активна.

    Процессам из активной группы разрешено читать и писать в терминал. Если процесс пытается читать или писать из своего управляющего терминала, но при этом не находится в активной группе процессов, драйвер посылает ему сигналы SIGTTIN (при чтении) и SIGTTOU (при записи, если включен флаг TOSTOP у терминала {2}. По-умолчанию они приостанавливают процесс до тех пор, пока кто-нибудь, например команда fg, не пошлет ему сигнал SIGCONT.

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

    NB: При вводе/выводе в терминал, который не является управляющим терминалом процесса, никакие сигналы не генерируются, но для этого обычно требуются права root.

    При нажатии специальных клавиш драйвер line discipline посылает сигналы процессам из активной группы:
    • SIGINT (^C)
    • SIGQUIT (^\)
    • SIGTSTP (^Z)

    Сигнал SIGWINCH также обычно посылается процессам активной группы.

  • Controlling process group
    Идентификатор sid сессии, ассоциированной с данным терминалом.

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

Примеры


Вот примеры команд, в которых все процессы будут помещены в одну группу:
$ ls
$ find / | wc -l   # При нажатии ^C или ^Z сигнал будет послан обоим процессам.
А в этом примере будет создано две группы процессов:
$ vim & cat -n
В первой группе – vim, который будет приостановлен сигналом SIGTTIN до тех пор, пока пользватель не наберет fg. Во второй группе – cat -n, эта группа станет активной группой терминала. Оба процесса будут находится в одной и той же сессии.

Основные функции


Создание группы процессов


Текущий процесс можно переместить в новую группу вызвовом setpgrp() или setpgid(0, 0). После успешного завершения вызова, pgid процесса становится равным его pid; процесс становится лидером новой группы.

Создание сессии


Текущий процесс можно переместить в новую сессию вызовом setsid(). После успешного завершения вызова, sid и pgid процесса становятся раным его pid; процесс становится лидером новой сессии и новой группы и не имеет управляющего терминала.

Изначально процесс не должен быть лидером группы, в противном случае сгенерируется ошибка EPERM. NB: первая команда в pipeline-е обычно является лидером группы.

Вот пример запуска программы в отдельной сессии:
if (fork() == 0) {
  // Здесть гарантированно pgid != pid.
  if (setsid() == -1) {
    perror("setsid");
    exit(1);
  }

  execlp("bash", "bash", NULL);
}
Запустив этот код, вы можете увидеть следующее сообщение:
bash: cannot set terminal process group (-1): Inappropriate ioctl for device
bash: no job control in this shell
Сообщение означает, что сессия, в которой был запущен bash, не является controling process group для текущего терминала, поэтому полноценное управление процессами будет недоступно.

Если теперь набрать команду cat, а затем нажать ^C, убиты будут и cat, и bash, так как bash не создавал для cat отедльную группу.

Скорее всего вы увидели эту ошибку, так как программа была запущена из шелла, а значит controlling process group терминала уже ссылается на другую сессию, в которую входит этот шелл.

Чтобы этого не произошло, нужно чтобы controlling process group терминала был пуст, что справедливо, когда нет процессов, ассоциированных с данным терминалом. Например, это так в тот момент, когда login(1) запускает bash.

Управление группами, связанными с терминалом


С помощью функций tcgetsid() и tcgetpgrp() можно узнать, соотвественно, controlling process group и foreground process group управляющего терминала текущего процесса.

С помощью функции tcsetpgrp() можно установить foreground process group управляющего терминала текущего процесса.

Функции для установки controlling process group нет. Вместо этого этот параметр автоматически обновляется при возникновении одного из событий во время установки управляющего терминала какого-нибудь процесса (см. следующий раздел):
  • Нет ни одного процесса, управляющий терминал которого равен данному (то есть controlling process group терминала пуст) и процесс устанавливает свой управляющий терминал равным данному.
  • Процесс захватывает терминал, отбирая его у текущей controlling process group.

В обоих случаях controlling process group становится раным sid процесса.

Управляющий терминал процесса

  • Процесс может установить свой управляющий терминал следующими способами:
    • С помощью open()
      Если процесс открывает файл устройства терминала вызовом open() без флага O_NOCTTY и у процесса нет управляющего терминала, этот терминал становится его управляющим терминалом.

    • С помощью ioctl(TIOCSCTTY)
      Если процесс является лидером сессии и не имеет управляющего терминала, он может установить управляющий терминал вызовом ioctl(fd, TIOCSCTTY, arg).

      Если терминал уже имеет непустой controlling process group:
      • вызов ioctl(fd, TIOCSCTTY, 0) (arg != 1) вернет ошибку EPERM;
      • вызов ioctl(fd, TIOCSCTTY, 1) (arg = 1) захватит терминал, пошлет всем процессам из текущей controlling process group сигналы SIGHUP и SIGCONT и заменит ее на sid вызывающего процесса (для этого нужны права root).

      В последнем случае процессы, которые не умрут от SIGHUP, смогут по-прежнему читать и писать в терминал, но не будут участвовать в job control, то есть:
      • им больше не будут доставляться сигналы, связанные с терминалом;
      • они смогут хаотично захватывать введенные символы и замусоривать вывод;
      • им не будут доступны функции вроде tcgetsid() и tcgetpgrp().

  • Процесс может отказаться от управляющего терминала следующими способами:
    • С помощью setsid()
    • С помощью ioctl(TIOCNOTTY)

Примечания


{1} К статье The TTY demystified есть интересный комментарий о причинах реализации line discipline в ядре:
It's not to keep application simple that the line discipline is in the kernel. The unix philosophy is quite the contrary, to keep the KERNEL simple, and deport to user space the handing of complexities that they don't want inside the kernel. (See for example interrupted system calls and signal handling, but that's another topic).

The reason why the line discipline is inside the kernel, is to avoid context switches at the reception of each character (which in the early times of small core memories, would imply swap-outs and swap-ins!). So the line discipline keeps in a kernel buffer a line of input, and since it's simple enough to test for a specific byte and decrement a counter to implement the backspace "editing" (and a few other simple editing functions), it's done there.

The alternative, is to use the raw mode, where the characters are forwarded to the application as soon as they're received, which is needed for more sophisticated editors, like the (at the time) famously known Eight Megabytes And Constantly Swapping editor (emacs). And indeed, since emacs had to use this raw mode, which implies a context switch at the reception of each character typed, it was constantly swapping when the computers hadn't enough memory to keep emacs and all the other programs in core.
{2} Флаг TOSTOP можно включить командой stty tostop.