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