LINUX.ORG.RU

Избранные сообщения aol

Speech to text

Форум — General

Кто имеет опыт работы с новыми средствами для распознавания речи? Что порекомендуете на десктопную машину без CUDA/OpenCL? Свободное или бесплатное. Английский, русский, французский, украинский, испанский. Скорость распознавания хотя бы 1:1.

P.S. Превращать в текст звуковые файлы.

Ответ: Vosk вполне устраивает.

 

question4
()

Ergo Framework 3.0

Новости — Open Source
Группа Open Source

Ergo Framework – это реализация идей, технологий и шаблонов проектирования из мира Erlang на языке программирования Go. Он построен на акторной модели, сетевой прозрачности и наборе готовых компонентов для разработки. Это значительно упрощает создание сложных и распределенных решений, обеспечивая при этом высокий уровень надежности и производительности.

( читать дальше... )

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

 ,

ergo
()

Ошибка Request timeout, но данные по Modbus получены

Форум — Development

Добрый день. Настраиваю modbus RTU для обмена данными приложения и PLC, но при отправке данных я получаю ошибку «Request timeout». При этом данные отправлены и получены контроллером (у меня стоит приложение на компьютере, которое может это отследить и показать). Я не могу понять, почему возникает эта ошибка. Сначала я думала увеличить время таймаута, но это ни на что не повлияло (при этом по ощущениям значение таймаута будто и не менялось вовсе). Потом я подумала, что, может, у меня посылается несколько раз пакет данных, когда первый нормально считывается, а второй выдает ошибку выхода времени, но при проверке через дебаггер часть кода, которая отвечает за полную передачу, даже не вызывается. Если у кого-нибудь будут предположения, что может быть не так, я буду рада проверить.

Код класса modbus:

modbus.h:

class QModbusClient;
class QModbusReply;

class Modbus : public QObject
{
    Q_OBJECT
public:
    Modbus(QObject *parent = nullptr);
    ~Modbus();
    QModbusDataUnit readRequest() const;
    QModbusDataUnit writeRequest() const;

private:
    void init();
    void on_writeButton_clickedRegisters(qint32 registers[], qint32 values[]);
    QModbusClient *modbusDevice;

    const int SERVER_ADDRESS = 1;
    const int START_ADDRESS = 0;
    const int AMOUNT = 24;
    const int WRITE_SIZE = 24;

    const int BAUDS = 9600;
    const int STOP_BITS = 1;
    const int DATA_BITS = 8;
    const QString PORT = "ttyUSB0";
    const int PARITY = QSerialPort::NoParity;
    const int RESPONSE_TIME = 250;
};
#endif // MODBUS_H

modbus.cpp:

    Modbus::Modbus(QObject *parent) :
    QObject(parent),
    modbusDevice(nullptr)
{
    init();
}

void Modbus::init() {
    modbusDevice = new QModbusRtuSerialMaster(this);
    connect(modbusDevice, &QModbusClient::errorOccurred, [this](QModbusDevice::Error) {
        emit sendData(modbusDevice->errorString());});
    qDebug() << "Error message: " << modbusDevice->errorString();
    if(modbusDevice) {
        connect(modbusDevice, &QModbusClient::stateChanged,this, &Modbus::onStateChanged);
    }

    if (modbusDevice->state() != QModbusDevice::ConnectedState) {
        modbusDevice->setConnectionParameter(QModbusDevice::SerialPortNameParameter,PORT);
        modbusDevice->setConnectionParameter(QModbusDevice::SerialParityParameter,PARITY);
        modbusDevice->setConnectionParameter(QModbusDevice::SerialBaudRateParameter,BAUDS);
        modbusDevice->setConnectionParameter(QModbusDevice::SerialDataBitsParameter,DATA_BITS);
        modbusDevice->setConnectionParameter(QModbusDevice::SerialStopBitsParameter,STOP_BITS);
        modbusDevice->setTimeout(RESPONSE_TIME);
        modbusDevice->setNumberOfRetries(1);
        if (!modbusDevice->connectDevice()) {
            emit sendData(modbusDevice->errorString());
        }
    } else {
        modbusDevice->disconnectDevice();
    }
}

void Modbus::on_writeButton_clickedRegisters(qint32 registers[], qint32 values[])
{

    <часть кода>
    if (auto *lastRequest = modbusDevice->sendWriteRequest(writeUnit, SERVER_ADDRESS)) {
        if (!lastRequest->isFinished()) {
            connect(lastRequest, &QModbusReply::finished, this, [ lastRequest]() {
                if (lastRequest->error() == QModbusDevice::ProtocolError) {
                    qDebug()<<"1"<<lastRequest->errorString();
                } else if (lastRequest->error() != QModbusDevice::NoError) {
                    qDebug()<<"2"<<lastRequest->errorString();
                    //ошибка проходит через эту часть кода
                }
                lastRequest->deleteLater();
            });
        } else {
            qDebug()<<"yes send";   //это не вызывается
            lastRequest->deleteLater();
        }
    } else {
        qDebug()<<lastRequest->errorString();
    }
}

П.С. Особенности кода:

  1. У меня приложение, в котором много окон, взаимодействующих друг с другом и, соответственно, много файлов. Чтобы не создавать в каждом файле свой объект modbus, я создала класс, в котором обозначила глобальный объект класса Modbus, к которому обращается каждое окно, если ему это необходимо. Выглядит это так:
#ifndef GLOBALVARIABLE_H
#define GLOBALVARIABLE_H

#include "modbus.h"
inline Modbus connect_modbus = Modbus();

#endif // GLOBALVARIABLE_H
  1. Файлы не посылают одновременно запросы Modbus’у, так как у меня работает всего один поток, то есть запросы пересекаться не будут.

 ,

alexandra
()

MiniOS 3.3.4: Обновленный дистрибутив для съемных накопителей

Новости — Linux General
Группа Linux General

Представлена новая версия MiniOS 3.3.4, легкого и модульного Linux-дистрибутива, предназначенного для использования на съемных носителях.

MiniOS - это Linux-дистрибутив для съемных накопителей на основе Debian, который отличается своей модульностью и гибкостью. Он позволяет пользователям легко настраивать систему под свои нужды и использовать ее на различных устройствах. MiniOS поддерживает среды рабочего стола XFCE и Fluxbox и активно развивается с конца 2020 года.

Основные изменения и улучшения в MiniOS 3.3.4:

  • Восстановлена сборка на основе ветки 3.3.x: возможность сборки дистрибутива возвращена благодаря изменениям в структуре репозитория.
  • В установщик добавлена поддержка MMC-устройств, внедрены все улучшения из разрабатываемой MiniOS 4.0.
  • В скриптах загрузки устранены проблемы с обновлением пакетов в MiniOS Puzzle.
  • Ядро Linux обновлено до версии 6.1.90.
  • Тема иконок elementary-xfce-minios заменена на elementary-minios и обновлена до версии 0.19.
  • Реализовано сжатие модулей с использованием zstd для ускорения работы системы во всех редакциях.
  • Включены все обновления Debian, доступные на момент выпуска дистрибутива.

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

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

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

 , minios,

crims0n
()

Postgres Professional обновила бесплатный курс по администрированию PostgreSQL 16

Новости — Open Source
Группа Open Source

Postgres Professional, ведущий российский СУБД-разработчик, выпустила обновленный курс по администрированию свободно распространяемой СУБД PostgreSQL. В программу добавлена информация про новейшие версии 14, 15, 16, а также:

  • Единым обзором заменены четыре темы раздела «Управление доступом», по которым в дальнейшем появится отдельный подробный курс;
  • Частично изменена структура: изложение стало более логичным и последовательным;
  • Физическая и логическая репликации теперь рассматриваются в отдельных темах.

Помимо этого, исправлены недочёты в изложении, ошибки в скриптах демонстраций и практических заданий.

DBA-1 – базовый курс для администраторов PostgreSQL, разработанный специалистами Postgres Professional. Материал доступен для самостоятельного изучения на сайте компании, а также в авторизованных учебных центрах.

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

 , ,

Postgres_Pro
()

RTranslator 2.0.0 и 2.0.1

Новости — Android
Группа Android

После почти трёхлетней паузы состоялись выпуски 2.0.0 и 2.0.1 оффлайн-переводчика для Android RTranslator, написанного на языках C++ и Java и распространяемого по лицензии Apache 2.0.
Для работы приложению необходим достаточно быстрый смартфон с, как минимум, 6GB памяти.
Поддерживаемые языки: арабский, болгарский, каталонский, китайский, чешский, датский, немецкий, греческий, английский, испанский, финский, французский, хорватский, итальянский, японский, корейский, голландский, польский, португальский, румынский, русский, словацкий, шведский, тамильский, тайский, турецкий, украинский, урду и вьетнамский.

( читать дальше... )

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

 , , ,

dataman
()

block device driver: не работает чтение

Форум — Development

Ядро: 5.15.0-70-generic.

Я раньше (параметр is_remap=0) в подобной задаче при получении входного запроса bio, формировал свой запрос bio к вышестоящему устройству, и всё работало. Но медленно. Скорость записи на флешку была ~460 kb/sec. Потом я решил пробрасывать запрос bio вышестоящему устройству напрямую (is_remap=1). Если при этом не пытаться модифицировать данные, то всё работает, и скорость возрастает до 1.8 мб/сек т.е. ~ в 4 раза. Но если начать модифицировать данные (а это нужно), то работает только запись. При чтении, dd получает нерасшифрованные данные, а bio в stackbd_end_io_read_cloned (перед этим клонированный с помощью bio_clone_fast в stackbd_io_fn_remap) вообще имеет нулевой размер. При этом размер obio ненулевой. Как такое вообще происходит, и как сделать правильно?

Интересно, что если в stackbd_end_io_read_cloned менять данные после вызова bio_endio, то в dd прилетают расшифрованные данные, но чую, что так делать не правильно. Что подтверждается тем, что fsck после mkfs вешает систему.

Вот, например, я читаю сектор:

user@astra-1:~/git/stackbd/module$ sudo dd if=/dev/stackbd0 count=1 | hexdump -C
00000000  63 d0 18 e5 e3 ee fb a6  ee e9 fc 88 8a a8 a8 88  |c...............|
00000010  8a 88 88 88 88 70 88 88  98 88 8c 88 88 88 88 88  |.....p..........|
00000020  88 48 26 8b 88 b3 88 88  88 88 88 88 8a 88 88 88  |.H&.............|
00000030  89 88 8e 88 88 88 88 88  88 88 88 88 88 88 88 88  |................|
00000040  08 88 a1 57 55 08 9b c6  c7 a8 c6 c9 c5 cd a8 a8  |...WU...........|
00000050  a8 a8 ce c9 dc bb ba a8  a8 a8 86 97 36 ff f4 24  |............6..$|
00000060  aa 48 fc 83 de 3c 86 33  8f 88 45 98 d6 63 78 ba  |.H...<.3..E..cx.|
00000070  6c 45 9e 45 91 63 76 dc  e0 e1 fb a8 e1 fb a8 e6  |lE.E.cv.........|
00000080  e7 fc a8 e9 a8 ea e7 e7  fc e9 ea e4 ed a8 ec e1  |................|
00000090  fb e3 a6 a8 a8 d8 e4 ed  e9 fb ed a8 e1 e6 fb ed  |................|
000000a0  fa fc a8 e9 a8 ea e7 e7  fc e9 ea e4 ed a8 ee e4  |................|
000000b0  e7 f8 f8 f1 a8 e9 e6 ec  85 82 f8 fa ed fb fb a8  |................|
000000c0  e9 e6 f1 a8 e3 ed f1 a8  fc e7 a8 fc fa f1 a8 e9  |................|
000000d0  ef e9 e1 e6 a8 a6 a6 a6  a8 85 82 88 88 88 88 88  |................|
000000e0  88 88 88 88 88 88 88 88  88 88 88 88 88 88 88 88  |................|
*
000001f0  88 88 88 88 88 88 88 88  88 88 88 88 88 88 dd 22  |..............."|
1+0 записей получено
1+0 записей отправлено
512 байт скопировано, 0,0063565 s, 80,5 kB/s
00000200
user@astra-1:~/git/stackbd/module$

И вот, что вижу в логе:

Jun  9 15:24:11 astra-1 kernel: stackbd [task=00000000c60564d5] stackbd_io_fn_remap: HIT.r.1
Jun  9 15:24:11 astra-1 kernel: debugbd [task=00000000c60564d5] debugbd_submit_bio: debugbd: make request read  block 0            #pages 0    total-size 16384     
Jun  9 15:24:11 astra-1 kernel: stackbd [task=00000000c60564d5] stackbd_io_fn_remap: HIT.r.2
Jun  9 15:24:11 astra-1 kernel: stackbd [task=0000000089abc07d] stackbd_end_io_read_cloned: HIT.1
Jun  9 15:24:11 astra-1 kernel: stackbd [task=0000000089abc07d] stackbd_end_io_read_cloned: HIT.2: obio.size=16384; bio.size=0
Jun  9 15:24:11 astra-1 kernel: stackbd [task=0000000089abc07d] stackbd_end_io_read_cloned: HIT.3
Jun  9 15:24:11 astra-1 kernel: stackbd [task=0000000089abc07d] stackbd_end_io_read_cloned: HIT.4
Jun  9 15:24:11 astra-1 kernel: stackbd [task=00000000c60564d5] stackbd_io_fn_remap: HIT.r.1
Jun  9 15:24:11 astra-1 kernel: debugbd [task=00000000c60564d5] debugbd_submit_bio: debugbd: make request read  block 32           #pages 0    total-size 32768     
Jun  9 15:24:11 astra-1 kernel: stackbd [task=00000000c60564d5] stackbd_io_fn_remap: HIT.r.2
Jun  9 15:24:11 astra-1 kernel: stackbd [task=0000000089abc07d] stackbd_end_io_read_cloned: HIT.1
Jun  9 15:24:11 astra-1 kernel: stackbd [task=0000000089abc07d] stackbd_end_io_read_cloned: HIT.2: obio.size=32768; bio.size=0
Jun  9 15:24:11 astra-1 kernel: stackbd [task=0000000089abc07d] stackbd_end_io_read_cloned: HIT.3

debugbd - это тот же драйвер, только выводящий информацию о запросах, для отладки.

Исходный код драйвера stackbd:

#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>

#include <linux/version.h>
#include <linux/kernel.h> // printk()
#include <linux/fs.h>     // everything...
#include <linux/errno.h>  // error codes
#include <linux/types.h>  // size_t
#include <linux/vmalloc.h>
#include <linux/genhd.h>
#include <linux/blkdev.h>
#include <linux/hdreg.h>
#include <linux/kthread.h>

#include <trace/events/block.h>

#include "logging.h"
#include "../common/stackbd.h"

#define STACKBD_BDEV_MODE (FMODE_READ | FMODE_WRITE | FMODE_EXCL)

#define KERNEL_SECTOR_SHIFT 9
#define KERNEL_SECTOR_SIZE (1 << KERNEL_SECTOR_SHIFT)

#define DECLARE_BIO_VEC struct bio_vec
#define ACCESS_BIO_VEC(x) (x)
#define DECLARE_BVEC_ITER struct bvec_iter
#define BIO_SET_SECTOR(bio, sec) (bio)->bi_iter.bi_sector = (sec)
#define BIO_GET_SECTOR(bio) (bio)->bi_iter.bi_sector
#define BIO_GET_SIZE(bio) (bio)->bi_iter.bi_size
#define BIO_SET_BDEV(bio, bdev) bio_set_dev((bio), (bdev));

//#ifdef CONFIG_LBDAF
#define SEC_FMT "llu"
//#else
//#define SEC_FMT "lu"
//#endif

MODULE_LICENSE("Dual BSD/GPL");

static int major_num = 0;
module_param(major_num, int, 0);
static int LOGICAL_BLOCK_SIZE = 512;
module_param(LOGICAL_BLOCK_SIZE, int, 0);
static bool is_remap = false;
module_param(is_remap, bool, 0);

typedef struct
{
	char path[PATH_MAX];
    fmode_t mode; // используется в aldcc_start / aldcc_stop
	bool is_bdev_raw_ok;
	struct block_device *bdev_raw;
} stackbd_target_t;

/*
 * The internal representation of our device.
 */
static struct stackbd_t {
    sector_t capacity; /* Sectors */
    struct gendisk *gd;
    spinlock_t lock;
    struct bio_list bio_list;
    struct task_struct *thread;
    int is_active;
    stackbd_target_t tgt;
    /* Our request queue */
    struct request_queue *queue;
} stackbd;

static DECLARE_WAIT_QUEUE_HEAD(req_event);

typedef void (* t_stackbd_io_fn)(struct bio *);
static t_stackbd_io_fn p_stackbd_io_fn = NULL;
static struct bio_set bs;

int buffer_read(
	struct stackbd_t *dev,
    unsigned long sector,
    unsigned long nsect,
    char *buffer
)
{
    int result = 0;
    unsigned nsize = nsect << KERNEL_SECTOR_SHIFT;
    int npages = ((nsize - 1) >> PAGE_SHIFT) + 1;
    struct bio *bio;
    struct block_device *bdev = dev->tgt.bdev_raw;

    //PINFO("begin; sector=%ld; nsect=%ld; buffer=%p\n", sector, nsect, buffer);

    if(unlikely(!dev->tgt.is_bdev_raw_ok))
    {
        PERROR("bdev is NULL!\n");
        result = -EFAULT;
        goto out;
    }

    bio = bio_alloc(GFP_NOIO, npages);

    if(unlikely(!bio))
    {
        PERROR("bio_alloc failed!\n");
        result = -ENOMEM;
        goto out;
    }

    BIO_SET_BDEV(bio, bdev);
    BIO_SET_SECTOR(bio, sector);

    bio_set_op_attrs(bio, REQ_OP_READ, REQ_PREFLUSH);

    {
        char *ptr = buffer;
        do
        {
            struct page *page;
            page = virt_to_page(ptr);
            if(unlikely(!page))
            {
                PERROR("virt_to_page failed!\n");
                result = -ENOMEM;
                break;
            }

            {
                unsigned op = offset_in_page(ptr);
                unsigned this_step = min((unsigned)(PAGE_SIZE - op), nsize);
                bio_add_page(bio, page, this_step, op);
                nsize -= this_step;
                ptr += this_step;
            }
        } while(nsize > 0);

        if(likely(!result))
        {
            result = submit_bio_wait(bio);
        }
        bio_put(bio);
    }
out:
    //PINFO("end (%d)\n", result);
    return result;
}

int buffer_write(
    struct stackbd_t *dev,
    unsigned long sector,
    unsigned long nsect,
    char *buffer
)
{
    int result = 0;
    unsigned nsize = nsect << KERNEL_SECTOR_SHIFT;
    int npages = ((nsize - 1) >> PAGE_SHIFT) + 1;
    struct bio *bio;
    struct block_device *bdev = dev->tgt.bdev_raw;

    //PINFO("begin; sector=%ld; nsect=%ld; buffer=%p\n", sector, nsect, buffer);

    if(unlikely(!dev->tgt.is_bdev_raw_ok))
    {
        PERROR("bdev is NULL!\n");
        result = -EFAULT;
        goto out;
    }

    bio = bio_alloc(GFP_NOIO, npages);
    if(unlikely(!bio))
    {
        PERROR("bio_alloc failed!\n");
        result = -ENOMEM;
        goto out;
    }
    BIO_SET_BDEV(bio, bdev);
    BIO_SET_SECTOR(bio, sector);

    bio_set_op_attrs(bio, REQ_OP_WRITE, REQ_PREFLUSH);

    {
        char *ptr = buffer;
        do
        {
            struct page *page = virt_to_page(ptr);

            if(unlikely(!page))
            {
                PERROR("alloc page failed!\n");
                result = -ENOMEM;
                break;
            }

            {
                unsigned op = offset_in_page(ptr);
                unsigned this_step = min((unsigned)(PAGE_SIZE - op), nsize);
                bio_add_page(bio, page, this_step, op);
                nsize -= this_step;
                ptr += this_step;
            }
        } while(nsize > 0);

        if(likely(!result))
        {
	#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0)
            result = submit_bio_wait(bio);
	#else
            result = submit_bio_wait(WRITE | REQ_FLUSH, bio);
	#endif
        }
        bio_put(bio);
    }
out:
    //PINFO("end (%d)\n", result);
    return result;
}

static void stackbd_end_io_read_cloned(struct bio *bio)
{
    struct bio *obio = bio->bi_private;
    PINFO("HIT.1");
    if (bio_data_dir(bio) == READ)
    {
        DECLARE_BIO_VEC bvec;
        DECLARE_BVEC_ITER iter;

        PINFO("HIT.2: obio.size=%u; bio.size=%u", BIO_GET_SIZE(obio), BIO_GET_SIZE(bio));

        bio_for_each_segment(bvec, bio, iter)
        {
            char *p = page_address(ACCESS_BIO_VEC(bvec).bv_page) + ACCESS_BIO_VEC(bvec).bv_offset;
            int len = ACCESS_BIO_VEC(bvec).bv_len;
            int i;

            print_hex_dump(KERN_INFO, "readed data (1-st 16 bytes) ", DUMP_PREFIX_OFFSET, 16, 1, p, 16, false);

            for(i = 0; i < len; i++)
            {
                //*p++ ^= 0x12345678;
                *p++ ^= 0x88;
            }

            //p += len;
        }
        PINFO("HIT.3");
        bio_put(bio);
        bio_endio(obio);
    }
    else
    {
        bio_put(bio);
        bio_endio(obio);
    }
    //bio_put(bio);
    PINFO("HIT.4");
}

static void stackbd_io_fn_remap(struct bio *bio)
{
    DECLARE_BIO_VEC bvec;
    DECLARE_BVEC_ITER iter;
    struct bio *cbio = bio_clone_fast(bio, GFP_NOIO, &bs);

    BIO_SET_BDEV(cbio, stackbd.tgt.bdev_raw);
    cbio->bi_end_io = stackbd_end_io_read_cloned;
    cbio->bi_private = bio;
    //submit_bio_noacct(cbio);

    //trace_block_bio_remap(/*bdev_get_queue(stackbd.bdev_raw), */bio,
    //    stackbd.tgt.bdev_raw->bd_dev, BIO_GET_SECTOR(bio));

    if (bio_data_dir(bio) == READ)
    {
        PINFO("HIT.r.1");
        submit_bio_noacct(cbio);
        PINFO("HIT.r.2");
    }
    else
    {
        PINFO("HIT.w.1");
        bio_for_each_segment(bvec, cbio, iter)
        {
            char *p = page_address(ACCESS_BIO_VEC(bvec).bv_page) + ACCESS_BIO_VEC(bvec).bv_offset;
            int len = ACCESS_BIO_VEC(bvec).bv_len;
            int i;

            for(i = 0; i < len; i++)
            {
                // *p++ ^= 0x12345678;
                *p++ ^= 0x88;
            }

            print_hex_dump(KERN_INFO, "writed data (1-st 16 bytes) ", DUMP_PREFIX_OFFSET, 16, 1, p, 16, false);

            //p += len;
        }
        PINFO("HIT.w.2");
        submit_bio_noacct(cbio);
        PINFO("HIT.w.3");
    }
}

static void my_bio_complete(struct bio *bio, int ret)
{
    if (ret)
        bio_io_error(bio);
    else
        bio_endio(bio);
}

static void stackbd_io_fn_clone(struct bio *bio)
{
    int res;
    DECLARE_BIO_VEC bvec;
    DECLARE_BVEC_ITER iter;
    sector_t sector = BIO_GET_SECTOR(bio);
    int size = BIO_GET_SIZE(bio);
    int nsect = size >> KERNEL_SECTOR_SHIFT;
    char *src, *p;

    do
    {
		if (bio_data_dir(bio) == READ)
		{
			p = src = kmalloc(size, GFP_KERNEL);
			if (!src)
			{
				PERROR("Unable to allocate read buffer!\n");
				res = -ENOMEM;
				break;
			}

			do
			{
				res = buffer_read(&stackbd, sector, nsect, src);
				if (unlikely(res))
				{
					PERROR("i/o error while read!\n");
					break;
				}

				bio_for_each_segment(bvec, bio, iter)
				{
					char *dst = page_address(ACCESS_BIO_VEC(bvec).bv_page) + ACCESS_BIO_VEC(bvec).bv_offset;
					int len = ACCESS_BIO_VEC(bvec).bv_len;
					memcpy(dst, p, len);
					p += len;
				}
			}
			while (0);
		}
		else
		{
			p = src = kmalloc(size, GFP_KERNEL);
			if (!src)
			{
				PERROR("Unable to allocate write buffer!\n");
				res = -ENOMEM;
				break;
			}

			bio_for_each_segment(bvec, bio, iter)
			{
				char *dst = page_address(ACCESS_BIO_VEC(bvec).bv_page) + ACCESS_BIO_VEC(bvec).bv_offset;
				int len = ACCESS_BIO_VEC(bvec).bv_len;
				memcpy(p, dst, len);
				p += len;
			}
			res = buffer_write(&stackbd, sector, nsect, src);
			if (unlikely(res))
			{
				PERROR("i/o error while write!\n");
			}
		}
		kfree(src);
    }
    while (0);

    my_bio_complete(bio, res);
} // stackbd_io_fn_clone

static int stackbd_threadfn(void *data)
{
    struct bio *bio;

    set_user_nice(current, -20);

    while (!kthread_should_stop())
    {
        /* wake_up() is after adding bio to list. No need for condition */ 
        wait_event_interruptible(req_event, kthread_should_stop() ||
                !bio_list_empty(&stackbd.bio_list));

        spin_lock_irq(&stackbd.lock);
        if (bio_list_empty(&stackbd.bio_list))
        {
            spin_unlock_irq(&stackbd.lock);
            continue;
        }

        bio = bio_list_pop(&stackbd.bio_list);
        spin_unlock_irq(&stackbd.lock);

        p_stackbd_io_fn(bio);
    }

    return 0;
}

// Handle an I/O request.
static blk_qc_t stackbd_submit_bio(struct bio *bio)
{
    /*PINFO("stackbd: make request %-5s block %-12" SEC_FMT " #pages %-4hu total-size %-10u\n",
        bio_data_dir(bio) == WRITE ? "write" : "read",
        BIO_GET_SECTOR(bio),
        bio->bi_vcnt,
        BIO_GET_SIZE(bio)
    );*/

    spin_lock_irq(&stackbd.lock);
    if (!stackbd.tgt.bdev_raw)
    {
        PERROR("Request before bdev_raw is ready, aborting\n");
        goto abort;
    }
    if (!stackbd.is_active)
    {
        PERROR("Device not active yet, aborting\n");
        goto abort;
    }
    bio_list_add(&stackbd.bio_list, bio);
    wake_up(&req_event);
    spin_unlock_irq(&stackbd.lock);

    goto exit;

abort:
    spin_unlock_irq(&stackbd.lock);
    PERROR("<%p> Abort request\n", bio);
    bio_io_error(bio);
exit:
    return BLK_QC_T_NONE;
}

static int stackbd_target_open(stackbd_target_t *p_tdev)
{
    int res = 0;
    char *path = p_tdev->path;

    PINFO("Open %s\n", path);
    {
        struct block_device *bdev_raw = blkdev_get_by_path(path, p_tdev->mode, p_tdev);
        p_tdev->bdev_raw = bdev_raw;

        if (unlikely(IS_ERR(bdev_raw)))
        {
            res = PTR_ERR(bdev_raw);
            PINFO("error opening raw device %s <%d>\n", path, res);
        }

        p_tdev->is_bdev_raw_ok = !res;
        return res;
    }
}

static void stackbd_target_close(stackbd_target_t *p_tdev)
{
    if (p_tdev->is_bdev_raw_ok)
    {
        blkdev_put(p_tdev->bdev_raw, p_tdev->mode);
        p_tdev->bdev_raw = NULL;
        p_tdev->is_bdev_raw_ok = false;
    }
}

static int stackbd_start(char dev_path[])
{
    unsigned max_sectors;
    sector_t lba;

    stackbd_target_t *p_tgt = &stackbd.tgt;
    strcpy(p_tgt->path, dev_path);
    p_tgt->mode = STACKBD_BDEV_MODE;

    if(stackbd_target_open(p_tgt) < 0)
    {
        PERROR("Error while stackbd_target_open(..)!");
        return -EFAULT;
    }

    /* Set up our internal device */
    lba = i_size_read(p_tgt->bdev_raw->bd_inode) >> KERNEL_SECTOR_SHIFT;

    stackbd.capacity = lba;//get_capacity(stackbd.bdev_raw->bd_disk);
    PINFO("Device real capacity: %" SEC_FMT "\n", stackbd.capacity);

    set_capacity(stackbd.gd, stackbd.capacity);

    max_sectors = queue_max_hw_sectors(bdev_get_queue(p_tgt->bdev_raw));
    blk_queue_max_hw_sectors(stackbd.queue, max_sectors);
    PINFO("Max sectors: %u\n", max_sectors);

    stackbd.thread = kthread_create(stackbd_threadfn, NULL,
           stackbd.gd->disk_name);
    if (IS_ERR(stackbd.thread))
    {
        PERROR("error kthread_create <%lu>\n", PTR_ERR(stackbd.thread));
        goto error_after_bdev;
    }

    PINFO("done initializing successfully\n");
    stackbd.is_active = 1;
    wake_up_process(stackbd.thread);

    return 0;

error_after_bdev:
    stackbd_target_close(p_tgt);

    return -EFAULT;
}

static int stackbd_ioctl(struct block_device *bdev, fmode_t mode,
		     unsigned int cmd, unsigned long arg)
{
    char dev_path[80];
    void __user *argp = (void __user *)arg;

    switch (cmd)
    {
    case STACKBD_DO_IT:
        PINFO("\n*** DO IT!!!!!!! ***\n\n");

        if (copy_from_user(dev_path, argp, sizeof(dev_path)))
            return -EFAULT;

        return stackbd_start(dev_path);
    default:
        return -ENOTTY;
    }
}

/*
 * The HDIO_GETGEO ioctl is handled in blkdev_ioctl(), which
 * calls this. We need to implement getgeo, since we can't
 * use tools such as fdisk to partition the drive otherwise.
 */
int stackbd_getgeo(struct block_device * block_device, struct hd_geometry * geo)
{
	long size;

	/* We have no real geometry, of course, so make something up. */
	size = stackbd.capacity * (LOGICAL_BLOCK_SIZE / KERNEL_SECTOR_SIZE);
	geo->cylinders = (size & ~0x3f) >> 6;
	geo->heads = 4;
	geo->sectors = 16;
	geo->start = 0;
	return 0;
}

/*
 * The device operations structure.
 */
static struct block_device_operations stackbd_ops = {
    .owner  = THIS_MODULE,
    .submit_bio = stackbd_submit_bio,
    .getgeo = stackbd_getgeo,
    .ioctl  = stackbd_ioctl,
};

static int __init stackbd_init(void)
{
    PINFO("is_remap=%d\n", is_remap);

    if (is_remap)
    {
        p_stackbd_io_fn = stackbd_io_fn_remap;
    }
    else
    {
        p_stackbd_io_fn = stackbd_io_fn_clone;
    }

    /* Set up our internal device */
    spin_lock_init(&stackbd.lock);

    /* Get registered */
    if ((major_num = register_blkdev(major_num, STACKBD_NAME)) < 0)
    {
        PERROR("unable to get major number\n");
        goto error_after_alloc_queue;
    }

    /* Gendisk structure */
    if (!(stackbd.gd = blk_alloc_disk(NUMA_NO_NODE)))
    {
        PERROR("unable to alloc disk\n");
        goto error_after_register_blkdev;
    }

    stackbd.gd->major = major_num;
    stackbd.gd->first_minor = 0;
    stackbd.gd->minors = 1 << 4;
    stackbd.gd->fops = &stackbd_ops;
    stackbd.gd->private_data = &stackbd;
    strcpy(stackbd.gd->disk_name, STACKBD_NAME_0);
    stackbd.queue = stackbd.gd->queue;

    if(bioset_init(&bs, 64, 0, BIOSET_NEED_BVECS) < 0)
    //if(bioset_init(&bs, BIO_POOL_SIZE, 0, 0) < 0)
    {
        PERROR( "Cannot allocate bioset");
        goto error_after_register_blkdev;
    }

    if(add_disk(stackbd.gd) < 0)
    {
        PERROR("unable to add disk\n");
        goto error_after_register_blkdev;
    }

    PINFO("init done\n");

    return 0;

error_after_register_blkdev:
    unregister_blkdev(major_num, STACKBD_NAME);
error_after_alloc_queue:
    blk_cleanup_queue(stackbd.queue);

    return -EFAULT;
}

static void __exit stackbd_exit(void)
{
    PINFO("exit\n");

    if (stackbd.is_active)
    {
        kthread_stop(stackbd.thread);
        stackbd_target_close(&stackbd.tgt);
    }

    del_gendisk(stackbd.gd);
    put_disk(stackbd.gd);
    bioset_exit(&bs);
    unregister_blkdev(major_num, STACKBD_NAME);
    blk_cleanup_queue(stackbd.queue);
}

module_init(stackbd_init);
module_exit(stackbd_exit);

https://github.com/zenbooster/stackbd/blob/5.15.0-70-generic/module/main.c

 , ,

zenbooster
()

Можно ли собрать Линукс совсем с нуля?

Форум — Linux-install

Лфс требует gcc и вообще toolchain, а если нырнуть глубже, и собрать gcc старым gcc, его ещё более старым, и так до 1.27 (первая версия на x86). А дальше? Дальше мы возьмём какой-нибудь K&R компилятор, да? А его как собрать? На самом дне, наверное, будет какой-то минимальный ассемблер, набранный вручную в кодах? Но его надо будет запустить на какой-то примитивной ос. Dos 1.0? Minix?

Хардмод - сделать всё это, не пользуясь несвободными инструментами.

Не знаю, какие теги ставить, и вообще не лучше ли перенести в development.

 

true_old
()

Organic Maps в Google Summer of Code 2024

Новости — Open Source
Группа Open Source

Organic Maps призывает всех желающих принять участие в ежегодной программе Google Summer of Code 2024 (GSoC), в рамках которой контрибьюторы (не только студенты!) со всего мира участвуют в разработке программного обеспечения с открытым исходным кодом под руководством опытных наставников из различных организаций. Мы участвуем в программе третий год подряд.

Мы делаем открытые мобильные офлайн-карты для путешественников и любителей активного отдыха на основе данных OpenStreetMap. Приложение уже установили более миллиона пользователей из 186 стран мира. Приложение доступно в F-Droid, Google Play, AppStore и даже FlatHub (desktop версия для разработки). Про нас пишут в блогах и упоминают в ведущих СМИ. На GitHub проекте более 8k ⭐ и около 700 forks.

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

( читать дальше... )

 , , , ,

rtsisyk
()

Rust is not safe!

Форум — Development

https://youtu.be/vfMpIsJwpjU

Для Ъ: в этом вашем Rust с 2015 года существует уязвимость, позволяющая неограниченно расширить время жизни любой переменной и воспроизвести любые уязвимости, связанные с памятью.

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

А Linux тут при том, что и на нём эти уязвимости прекрасно воспроизведутся.

 ,

shkolnick-kun
()

Как разобраться в формате файла? (было: Как заинструментировать код на Си)

Форум — Development

Пост обновлен, предыдущее название: «Как заинструментировать код на Си и понять что он делает? Реверсинг формата файла»


Вступление

Есть какой-то файл и я хочу понять его внутреннее устройство. В качестве примера, возмем файл формата PNG, так как он достаточно простой и хорошо документирован. Но далеко не все форматы хорошо документированы, поэтому полагаться только на документацию - нельзя.

Итак, у нас есть какой-то файл формата PNG - что с ним дальше можно сделать? Можно было бы взять готовую библиотеку для чтения данного формата (libpng), но она содержит более 100 000 строк кода, что крайне сложно для понимания.

Я предполагаю, что более наглядным для изучения будет разглядывать такие картинки:

Здесь сразу видны байтики и их значение. По крайней мере, лично мне, такое изучение было бы наиболее близким и понятным. В этом месте можно назвать меня гуманитарием и посоветовать биореактор^Wчтение исходников, но это не так наглядно.

К сожалению, не все форматы файлов так хорошо описаны, как описан формат PNG. Далеко не для всех форматов файлов есть такие картинки и тем более шаблоны для hex-редакторов. И я бы хотел делать такие картинки самостоятельно.


Идея

Я предполагаю, что если для этого формата есть открытая библиотека (libpng), то для него можно написать свою читалку (my_png_reader). В свою очередь, у нашей библиотеки (libpng) могут быть свои зависимости (zlib) и я предполагаю, что нужно будет разобраться и с устройством этой библиотеки тоже.

Само собой, для тестирования, нужно будет собрать некий тестовый датасет (набор png-файлов с разными разрешениями, режимами компрессии, битые файлы). Для этого было бы неплохо использовать утилиты из набора AFL для уменьшения датасета и уменьшения самих исходных данных: https://youtu.be/0dqL6vfPCek (фаззинг файла вместе с ffmpeg)

Вопрос: как бы заинструментировать имеющийся софт (программу, библиотеки), чтобы понять, что такое оно делает с файлом, что внутри и как его вообще читать? Да и вообще, что можно вытащить из подобной затеи?

Например, очень бы хотелось узнать:

  • Сигнатуру файла (\x89PNG в начале файла)
  • Структуру «чанков», что она представляет собой 4 поля: длинна, тип, данные, CRC32
  • Алгоритм расчета CRC32: какая часть данных считается, с заголовком или без, какой полином
  • Какая часть файла пожата zlib, какие части файла просто являются несжатыми массивами
  • Нарисовать какую-то диаграмму, где какой байт чему соответстует, вроде https://i.imgur.com/eLd44xQ.png или https://i.imgur.com/AwpmSxV.png

Я себе это представляю как-то так:

  1. Читаем кусочек файла и помечаем, что это наш файл (перехватываем read/fread, «отравляем» эти участки памяти)
  2. Ставим брейтпоинт на чтение памяти, логгируем, смотрим по map-файлу где мы это читали, желательно размотать стек
  3. По коду уже можно представить, читаем мы float или int16, находимся ли в структурке или еще где. Если мы внутри функции check_file_signature(), то это вообще очевидно. Можно пометить как ручками, так и чем-то вроде IDA.

Зачем? Это поможет в написании файлов-шаблонов для таких программ как https://ide.kaitai.io/ или 010 Editor. Конечно, в идеале запустить на входе PNG-файл, а на выходе получить KSY (формат описания файлов kaitai) или BT (формат для 010 Editor), но понятно, что идеальной документации не будет. Но я стремлюсь именно к этому.

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

Буду благодарен на статьи просто по инструментации сишного кода. Или не только сишного.


Референсы и идеи

  • https://github.com/AFLplusplus/AFLplusplus - отличное средство для фаззинга (пример фаззинга libpng: https://youtu.be/LsdDRat4S0U), но я не очень понимаю как это применить именно к описанию файла. А вот тулзы вроде afl-cmin - чистое золото
  • https://www.intel.com/content/www/us/en/developer/articles/tool/pin-a-dynamic-binary-instrumentation-tool.html - Intel PIN
  • Address Sanitizer or ASAN - можно попробовать поиграться с ним и залоггировать доступ к памяти
  • valgrind + lackey - очень многообещающе, но боюсь не осилить
  • Попробовать сделать доступ к страничке PROT_NONE, получить сегфолт, но непонятно как возвращать управление (сделал, но споткнулся на последнем пункте)

 , , , ,

ruzisufaka
()

Помогите настроить OPENVPN c обфускацией

Форум — Admin

Ребят, подскажите по братски. Есть сервер с OPENVPN для частной ЛОКАЛЬНОЙ сети между компами (10 шт) Доступ идет чисто как локалка чтобы работало пару прог типа базы данных. Вот такой конфиг сервера и клиента

port 1194
proto udp
dev tap
#crl-verify /etc/openvpn/crl.pem
ca /etc/openvpn/ca.crt
cert /etc/openvpn/server.crt
key /etc/openvpn/server.key
dh /etc/openvpn/dh.pem
server 172.28.55.0 255.255.255.0
route 172.28.55.1 255.255.255.0
push «route 172.28.55.1 255.255.255.0»
client-config-dir /etc/openvpn/ccd
keepalive 10 120
tls-auth /etc/openvpn/ta.key 0
cipher AES-256-CBC
persist-key
persist-tun
client-to-client
push «dhcp-option DNS 172.28.0.1»
sndbuf 524288
rcvbuf 524288
push «sndbuf 524288»
push «rcvbuf 524288»
duplicate-cn
verb 4

. . .

Клиенты

. . .

client
remote ip.мое.го.серва 1194 
dev tap
proto udp
auth-nocache
nobind
persist-key
persist-tun
cipher AES-256-CBC
sndbuf 524288
rcvbuf 524288
verb 3
key-direction 1
<ca>
-----BEGIN CERTIFICATE-----
123
-----END CERTIFICATE-----
</ca>
<cert>
-----BEGIN CERTIFICATE-----
123
-----END CERTIFICATE-----
</cert>
<key>
-----BEGIN PRIVATE KEY-----
123
-----END PRIVATE KEY-----
</key>
<tls-auth>
-----BEGIN OpenVPN Static key V1-----
123
-----END OpenVPN Static key V1-----
</tls-auth>

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

 ,

Wosh
()

Embox v0.6.1

Новости — Open Source
Группа Open Source

8 января 2024 года вышла очередная версия открытой операционной системы реального времени Embox.

Среди изменений:

  • Улучшена поддержка архитектуры AARCH64.
  • Улучшена поддержка архитектуры RISC-V.
  • Добавлена поддержка платы STM32F103 Blue Pill.
  • Добавлена поддержка платы Vostok Uno-VN035.
  • В язык Mybuild добавлена аннотация @NoCode.
  • Улучшена подсистема устройств (devices subsystem).
  • Переработана поддержка flash-устройств.
  • Переработана подсистема журналирования (Logger).
  • Улучшена поддержка STM32.
  • Переработан контроллер прерываний GIC.
  • В систему портирован проект libIEC61850 (реализация IEC 61850, стандарта сетей и систем связи на энергетических подстанциях).
  • Портирован Mbed TLS.
  • Множество других исправлений и улучшений.

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

 , ,

abondarev
()

Оставим любителям яблок их 8Gb как 16...

Галерея — Скриншоты

Пришел мой новый рабочий аппарат из Поднебесной.

На HP ожидаемо завелся Linux, и всё оборудование под этой ОС работает корректно.

Аппарат ожидаемо оказался тихим и легким. Странно, что в одном сервисе по по доставке товаров из-за рубежа он выставлен как HP Star Book 14 Pro, именно там я его и заказал. Подобных надписей на устройстве нет, по сути это HP Pavilion 14 Plus, о чем гласят надписи на корпусе.

В комплект поставки входит NVMe Solidigm на 2Tb, что так же отмечено только на сайте этой малоизвестной конторки, на маркетплейсах аппарат отмечен как оснащенный только 1Tb. Сначала меня насторожило название незнакомого бренда, но вот википедия сообщает нам, что Solidigm принадлежит Hynix и является бывшим подразделением Intel по производсву NAND-чипов.

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

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

Теперь мой HP Elitbook 840 G8 отправляется в резерв.

Отличия от «старого» аппарата:

  • естественно актуальный CPU AMD Ryzen 7840H;
  • OLED экран 120Hz, 2880х1800;
  • встроенная камера дает приличное изображение, чего в «элитбуке» не наблюдалось;
  • рабочие температуры колеблются от 39 до 49 градусов, вместо 50-60 (на моих задачах естественно), если брать чисто практический аспект шума, «Рязань» тут работает потише;
  • естественно, более производительная встройка. Intel с какого то времени начала маркировть все видеокарты как Iris Xe, что явное лукавство, во всех процессорах это совершенно разные по возможностям устройства, AMD же честно пишет модель видеочипа.

Что напрягало, но удалось устранить:

  • Windows в поставке;
  • традиционный китайский язык в BIOS;
  • английская клавиатура, в итоге решил что поскольку владею слепым методом печати, просто научусь работать без русских букв.

Сейчас аппарат стоит ~87К рублей, считаю это отличной ценой за подобное устройство.

>>> Просмотр (2560x1440, 3580 Kb)

 , ,

user13
()

Immutable-дистрибутивы, что это такое, и с чем это едят?

Статьи — Desktop
Immutable-дистрибутивы, что это такое, и с чем это едят?

Начнем с простого

Как часто вы сталкиваетесь с ситуацией когда ваш любимый линукс вдруг перестает работать? А как часто после обновления выясняется что что-то отвалилось или вообще отказывается грузится?

Если вы один из таких «счастливчиков», то добро пожаловать в мир immutable-дистрибутивов! В этой статье мы наглядно разберем все плюсы и минусы, а также выясним, действительно ли immutable лучше традиционных дистрибутивов?

( читать дальше... )

 , ,

Unixson
()

Вышел srsRAN 4G 23.11 — OpenSource-реализация стека 4G/LTE

Новости — Open Source
Группа Open Source

Проект software radio systems Radio Access Network или srsRAN нацелен на реализацию стеков 4G/LTE и 5G. Ранее проект известен был как srsLTE.

( читать дальше... )

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

 ,

Root-msk
()

Какая СУБД для OLAP?

Форум — Development

Добрый вечер. Скажите, пожалуйста, какие сейчас есть СУБД для OLAP? Нужно быстро крутить ~5ТБ, делать группировки и отдавать в BI. Единственное, что приходит в голову - затюненный под DWH Оракл. Ещё есть Кликхаус, но кажется, это немного локальный продукт. Т.к. важна скорость отклика, экосистема хадупа не подходит.

 , ,

Paka_RD
()

Нужны ваши мысли по code review

Форум — Development

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

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

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

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

Например тот же Gerrit мне всегда казался слишком сложным и его использование на практике в коммерческой разработке я не видел.

 ,

alex0x08
()

В чем фишка Qt Quick

Форум — Development

Изучаю Qt. Вижу, что есть два подхода к разработке приложения: Qt Widgets и Qt Quick. Судя по всему за Qt Quick активно топят. На официальной странице нашел сравнение этих технологий. И вроде как пишут, что Qt Quick для стильных модных молодежных, а Qt Widgets если не Deprecated, то для старперов. На всяких Reddit-ах тоже активно нахваливают QML.

Интуитивно кажется, что Quick потянет за собой либо какой-то встроенный интерпретатор JavaScript, либо какой-то хитрый компилятор, но в любом случае добавит накладные расходы на взаимодействие между JavaScript и C++ кодом. Кажется, что это будет работать медленнее, чем если всё написано сразу на C++.

С другой стороны, если хочется быстрой разработки, чтобы раз-два и в продакшен - это же точно не про C++. Для этого есть Electron или Web в браузере. C++ для GUI, на мой взгляд, имеет смысл выбирать только если хочется максимальной производительности и минимального расхода ресурсов, а сроки разработки не особо важны.

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

Поэтому возникает вопрос: какой практический смысл в Qt Quick?

 , ,

Goganchic
()

Claim to support the GTK_FRAME_EXTENTS (enables full GTK CSD)

Галерея — Скриншоты

Поддержка клиентского декорирования GTK добавлена в openbox. Pull-request с соответсвующим заголовком уже месяц остается без внимания. Результат на снимке. Исходники взяты из ветки work по адресу https://github.com/Mikachu/openbox.

>>> Просмотр (1680x1050, 170 Kb)

 ,

zombi_pony
()