Compare commits
10 Commits
c2ad307c50
...
ed23ec5bcf
Author | SHA1 | Date | |
---|---|---|---|
ed23ec5bcf | |||
721b8c0bea | |||
99d614e55d | |||
2641210c9d | |||
61751d16cd | |||
545848e0d0 | |||
1ae2c4c40b | |||
b83744097a | |||
240151ecee | |||
b9059af19f |
@ -16,7 +16,7 @@ Digispark — неофициальное семейство Arduino-совмес
|
||||
|
||||
Платы Digispark бывают нескольких версий и выглядят во так.
|
||||
|
||||
![](https://s3.timeweb.com/60074440-9f42f663-1649-4eb6-b974-79142c108fc2/blog/2023/arduino/attiny-avrdude/01.jpg)
|
||||
![](https://cdn.a2s.su/blog/2023/arduino/attiny-avrdude/01.jpg)
|
||||
|
||||
Подробности про ATTiny85 и платы Digispark можно прочитать
|
||||
на странице посвящнной Digispark на сайте [AlexGyver](https://alexgyver.ru/lessons/digispark/).
|
||||
@ -41,18 +41,18 @@ Digispark — неофициальное семейство Arduino-совмес
|
||||
и для его прошивки нужен AVR программатор.
|
||||
Самый дешевый программатор AVR можно купить на AliExpress, но также можно использовать Arduino UNO или RaspberryPi для этой задачи.
|
||||
|
||||
![](https://s3.timeweb.com/60074440-9f42f663-1649-4eb6-b974-79142c108fc2/blog/2023/arduino/attiny-avrdude/02.jpg)
|
||||
![](https://cdn.a2s.su/blog/2023/arduino/attiny-avrdude/02.jpg)
|
||||
|
||||
Чтобы загрузить micronucleus на голый ATtiny85, я использую дешевый программатор AVR как на картинке ниже.
|
||||
|
||||
![](https://s3.timeweb.com/60074440-9f42f663-1649-4eb6-b974-79142c108fc2/blog/2023/arduino/attiny-avrdude/03.png)
|
||||
![](https://cdn.a2s.su/blog/2023/arduino/attiny-avrdude/03.png)
|
||||
|
||||
Если у вас установлена версия DIP-разъема Digispark с 8-контактным разъемом, вы можете просто подключить к нему 6-контактный разъем.
|
||||
Нижние 2 контакта должны быть не подключены, поэтому просто убедитесь, что контакт 5В совпадает.
|
||||
|
||||
У меня подобного передника не было и я себе спаял такой переходник сам 🙂
|
||||
|
||||
![](https://s3.timeweb.com/60074440-9f42f663-1649-4eb6-b974-79142c108fc2/blog/2023/arduino/attiny-avrdude/04.jpg)
|
||||
![](https://cdn.a2s.su/blog/2023/arduino/attiny-avrdude/04.jpg)
|
||||
|
||||
В ArchLinux AVR программатор определяется автоматически как **USBasp**.
|
||||
|
||||
|
57
content/posts/2024/bash/password-generator.md
Normal file
57
content/posts/2024/bash/password-generator.md
Normal file
@ -0,0 +1,57 @@
|
||||
---
|
||||
title: "🔑 Генератор паролей в виде однострочника на Bash"
|
||||
date: 2024-08-17T22:33:12+03:00
|
||||
draft: false
|
||||
tags: [linux, tips, security]
|
||||
---
|
||||
|
||||
Пост [Cyrus](https://stackoverflow.com/a/44377013) на **SoF**.
|
||||
|
||||
Команда:
|
||||
|
||||
```sh
|
||||
tr -dc 'A-Za-z0-9!?%=' < /dev/urandom | head -c 20
|
||||
```
|
||||
|
||||
Выхлоп:
|
||||
|
||||
```text
|
||||
7sixuvfqbFRAj4g=3v7Y
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Другие варианты
|
||||
|
||||
### OpenSSL
|
||||
|
||||
Команда:
|
||||
|
||||
```sh
|
||||
openssl rand -base64 20
|
||||
```
|
||||
|
||||
Выхлоп:
|
||||
|
||||
```text
|
||||
c+EtG4VK/0JLR6tWrneAlP4bHmQ=
|
||||
```
|
||||
|
||||
|
||||
### Версия Cyrus с улучшенной энтопией
|
||||
|
||||
```sh
|
||||
for i in $(seq 1 5); do echo $(tr -dc 'A-Za-z0-9!"#$%&()*+,-./:;<=>?@[\\]^_`{|}~' < /dev/urandom | head -c 20); done
|
||||
```
|
||||
|
||||
```text
|
||||
Q@2^u\U3.:%|{yGY@SMl
|
||||
T8WVte55AggJ{WxElqDi
|
||||
vG%+Gr"jp#x@f46YBj>m
|
||||
|>Wd,|S>w)jwe|:))eLU
|
||||
m`nSde+o,:vn\1JpJ1j3
|
||||
```
|
||||
|
||||
## Дополнительно
|
||||
|
||||
Пожалуй добавлю в `alias`.
|
107
content/posts/2024/linux/strings.md
Normal file
107
content/posts/2024/linux/strings.md
Normal file
@ -0,0 +1,107 @@
|
||||
---
|
||||
title: "🎹 Команда strings"
|
||||
date: 2024-08-17T22:45:47+03:00
|
||||
draft: false
|
||||
tags: [linux, tips]
|
||||
---
|
||||
|
||||
## ℹ️ strings == строки
|
||||
|
||||
## Пример
|
||||
|
||||
Накидаю простую программу на Си, которая инициализирует массив символов `ololo_str`
|
||||
со значением `It's unique text`,
|
||||
а также выводит в консоль `GrindCore \m/`.
|
||||
|
||||
Листинг `strings.c`
|
||||
|
||||
```c
|
||||
#include <stdio.h>
|
||||
|
||||
int main(int argc, char const *argv[]) {
|
||||
char * ololo_str = "It's unique text";
|
||||
|
||||
puts("GrindCore \\m/");
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
Сборка и запуск
|
||||
|
||||
```sh
|
||||
tcc -o string string.c # Компиляция
|
||||
./string # Запуск
|
||||
```
|
||||
|
||||
Выхлоп программы:
|
||||
|
||||
```text
|
||||
GrindCore \m/
|
||||
```
|
||||
|
||||
|
||||
## О `strings`
|
||||
|
||||
Двоичные файлы, такие как программные файлы, могут содержать строки читаемого человеком текста.
|
||||
Но как мы их видим? Если использовать `cat` или `less`, то, скорее всего,
|
||||
зависнет окно терминала. Программы, предназначенные для работы с текстовыми файлами,
|
||||
не могу обрабатывать исполняемые файлы, содержащие непечатаемые символы.
|
||||
|
||||
_Спизжено [отсюда](https://wiki.merionet.ru/articles/kak-ispolzovat-komandu-strings-v-linux)._
|
||||
|
||||
|
||||
## Пример работы
|
||||
|
||||
Давай натравим `strings` на исполняемый файл `string`?
|
||||
|
||||
Выполняю команду:
|
||||
|
||||
```sh
|
||||
strings ./string
|
||||
```
|
||||
|
||||
и вижу следующий результат:
|
||||
|
||||
```text
|
||||
/lib64/ld-linux-x86-64.so.2
|
||||
__libc_start_main
|
||||
puts
|
||||
main
|
||||
__gmon_start__
|
||||
libc.so.6
|
||||
GLIBC_2.34
|
||||
GLIBC_2.2.5
|
||||
libc.so.6
|
||||
PTE1
|
||||
It's unique text
|
||||
GrindCore \m/
|
||||
.text
|
||||
.data
|
||||
.bss
|
||||
.rodata.cst4
|
||||
.eh_frame
|
||||
.init
|
||||
.fini
|
||||
.preinit_array
|
||||
.init_array
|
||||
.fini_array
|
||||
.interp
|
||||
.dynsym
|
||||
.dynstr
|
||||
.hash
|
||||
.dynamic
|
||||
.got
|
||||
.rela.got
|
||||
.plt
|
||||
.gnu.version
|
||||
.gnu.version_r
|
||||
.shstrtab
|
||||
```
|
||||
|
||||
Много всего, да, но обе строки из программы присутсвуют.
|
||||
|
||||
**Вывод:** Утилита очень полезная! Применение?... много.
|
||||
Я вот сегодня вытащил из бинарника пароль администратора от устройства,
|
||||
к которому осуществлялось подключение и на котором была очень ценная информация.
|
||||
(🥲 шучу).
|
165
content/posts/2024/lmdb/getting-started.md
Normal file
165
content/posts/2024/lmdb/getting-started.md
Normal file
@ -0,0 +1,165 @@
|
||||
---
|
||||
title: "🗄️ LMDB: Введение"
|
||||
date: 2024-06-25T21:59:48+03:00
|
||||
draft: true
|
||||
tags: [db, tutorial]
|
||||
---
|
||||
|
||||
Этот пост является моим вольным (_yandex-translate_) переводом страницы из документации LMDB:
|
||||
http://www.lmdb.tech/doc/starting.html.
|
||||
|
||||
# Введение в LMDB
|
||||
|
||||
LMDB компактная, быстрая, мощная и надежная.
|
||||
LMDB представляет из себя упрощенный вариант API Berkeley DB (BDB).
|
||||
|
||||
После прочтения этой страницы основная документация по LMDB API должна стать понятной.
|
||||
Спасибо Берту Хьюберту за создание первоначальной версии этого обзора.
|
||||
|
||||
Все начинается с среды, созданной с помощью `mdb_env_create()`.
|
||||
После создания эта среда также должна быть открыта с помощью `mdb_env_open()`.
|
||||
|
||||
`mdb_env_open()` получает имя, которое интерпретируется как путь к каталогу.
|
||||
Обратите внимание, что этот каталог должен уже существовать, он не будет создан за вас.
|
||||
В этом каталоге будут созданы файл блокировки и файл хранения данных.
|
||||
Если вы не хотите использовать каталог, вы можете указать параметр `MDB_NOSUBDIR`,
|
||||
и в этом случае указанный вами путь будет использоваться непосредственно в качестве файла данных,
|
||||
а в качестве файла блокировки будет использоваться другой файл с добавленным суффиксом «`-lock`».
|
||||
|
||||
Как только среда открыта, в ней можно создать транзакцию с помощью функции `mdb_txn_begin()`.
|
||||
Транзакции могут быть доступны для чтения или только для записи,
|
||||
а транзакции чтения-записи могут быть вложенными.
|
||||
Транзакция должна использоваться только одним потоком одновременно.
|
||||
Транзакции требуются всегда, даже для доступа только для чтения.
|
||||
Транзакция обеспечивает согласованное представление данных.
|
||||
|
||||
Как только транзакция создана, база данных может быть открыта в ней с помощью функции `mdb_dbi_open()`.
|
||||
Если в среде будет использоваться только одна база данных,
|
||||
в качестве имени базы данных может быть передано значение `NULL`.
|
||||
Для именованных баз данных необходимо использовать флаг `MDB_CREATE` для создания базы данных, если она еще не существует.
|
||||
Кроме того, функция `mdb_env_set_max dps()` должна вызываться после функции `mdb_env_create()`
|
||||
и перед функцией `mdb_env_open()`, чтобы задать максимальное количество именованных баз данных, которые вы хотите поддерживать.
|
||||
|
||||
**Примечание:** одна транзакция может открыть несколько баз данных. Как правило, базы данных следует открывать только один раз,
|
||||
с помощью первой транзакции в процессе. После завершения первой транзакции дескрипторы базы данных могут свободно использоваться
|
||||
всеми последующими транзакциями.
|
||||
|
||||
В рамках транзакции `mdb_get()` и `mdb_put()` могут хранить отдельные пары ключ/значение,
|
||||
если это все, что вам нужно сделать (но смотрите раздел `Cursors` ниже, если вы хотите сделать больше).
|
||||
|
||||
|
||||
## Курсоры (Cursors)
|
||||
|
||||
Чтобы выполнять более мощные действия, мы должны использовать курсор.
|
||||
|
||||
Внутри транзакции курсор может быть создан с помощью функции `mdb_cursor_open()`.
|
||||
С помощью этого курсора мы можем сохранять/извлекать/удалять (несколько) значений,
|
||||
используя `mdb_cursor_get()`, `mdb_cursor_put()` и `mdb_cursor_del()`.
|
||||
|
||||
Функция `mdb_cursor_get()` позиционирует себя в зависимости от запрашиваемой операции с курсором,
|
||||
а для некоторых операций - от предоставленного ключа.
|
||||
Например, чтобы вывести список всех пар ключ/значение в базе данных,
|
||||
используйте операцию `MDB_FIRST` для первого вызова функции `mdb_cursor_get()` и `MDB_NEXT` для последующих вызовов,
|
||||
пока не будет достигнут конец.
|
||||
|
||||
Чтобы получить все ключи, начиная с указанного значения ключа, используйте `MDB_SET`.
|
||||
Дополнительные операции с курсором см. в документации
|
||||
[IMDB API](http://www.lmdb.tech/doc/group__mdb.html).
|
||||
|
||||
При использовании функции `mdb_cursor_put()` либо функция установит курсор для вас на основе **ключа**,
|
||||
либо вы можете использовать операцию `MDB_CURRENT` для использования текущей позиции курсора.
|
||||
Обратите внимание, что в этом случае ключ должен соответствовать ключу текущей позиции.
|
||||
|
||||
|
||||
### Подведение итогов
|
||||
|
||||
Итак, у нас есть курсор в транзакции, который открывает базу данных в среде,
|
||||
которая открывается из файловой системы после того, как она была создана отдельно.
|
||||
|
||||
Или же мы создаем среду, открываем ее из файловой системы, создаем в ней транзакцию,
|
||||
открываем базу данных в рамках этой транзакции и создаем курсор во всем вышеперечисленном.
|
||||
|
||||
Понятно?
|
||||
|
||||
|
||||
## Потоки и процессы
|
||||
|
||||
LMDB использует POSIX-блокировки для файлов, и эти блокировки могут вызвать проблемы,
|
||||
если один процесс открывает файл несколько раз.
|
||||
По этой причине не выполняйте `mdb_env_open()` многократное открытие файла одним процессом.
|
||||
Вместо этого предоставьте общий доступ к среде LMDB, в которой был открыт файл, для всех потоков.
|
||||
В противном случае, если один и тот же процесс открывает одну и ту же среду несколько раз,
|
||||
однократное ее закрытие приведет к снятию всех сохраненных на ней блокировок,
|
||||
а другие экземпляры будут уязвимы для повреждения другими процессами.
|
||||
|
||||
Также обратите внимание, что транзакция по умолчанию привязана к одному потоку, используя локальное хранилище потоков.
|
||||
Если вы хотите передавать транзакции только для чтения между потоками, вы можете использовать параметр `MDB_NOTLS` в среде.
|
||||
|
||||
|
||||
## Транзакции, откаты и т.д.
|
||||
|
||||
Чтобы действительно что-то сделать, транзакция должна быть зафиксирована с помощью функции `mdb_txn_commit()`.
|
||||
В качестве альтернативы, все операции транзакции могут быть отменены с помощью функции `mdb_txn_abort()`.
|
||||
В транзакции только для чтения никакие курсоры автоматически освобождаться не будут.
|
||||
В транзакции чтения-записи все курсоры будут освобождены и не должны использоваться повторно.
|
||||
|
||||
Очевидно, что в транзакциях только для чтения нет ничего, что можно было бы сохранить.
|
||||
Транзакция все равно должна быть в конечном итоге прервана, чтобы закрыть все открытые в ней дескрипторы базы данных,
|
||||
или зафиксирована, чтобы сохранить дескрипторы базы данных для повторного использования в новых транзакциях.
|
||||
|
||||
Кроме того, пока транзакция открыта, поддерживается согласованное представление базы данных, для чего требуется хранение.
|
||||
Транзакция только для чтения, для которой больше не требуется это согласованное представление,
|
||||
должна быть завершена (зафиксирована или прервана), когда представление больше не требуется (но смотрите ниже для оптимизации).
|
||||
|
||||
Одновременно может быть несколько активных транзакций только для чтения, но только одна из них может выполнять запись.
|
||||
Как только будет открыта единственная транзакция чтения-записи, все дальнейшие попытки запустить ее будут блокироваться до тех пор,
|
||||
пока первая не будет зафиксирована или прервана. Однако это никак не влияет на транзакции, доступные только для чтения,
|
||||
и они могут по-прежнему открываться в любое время.
|
||||
|
||||
|
||||
## Дублирование ключей
|
||||
|
||||
Функции `mdb_get()` и `mdb_put()`, соответственно, не поддерживают несколько пар ключ/значение с одинаковыми ключами
|
||||
и поддерживают их только в некоторой степени. Если у ключа несколько значений, функция `mdb_get()` вернет только первое значение.
|
||||
|
||||
Если требуется несколько значений для одного ключа, передайте флаг `MDB_SUPPORT` в функцию `mdb_dbi_open()`.
|
||||
В базе данных `MDB_SUPPORT` по умолчанию функция `mdb_put()` не заменяет значение ключа,
|
||||
если ключ уже существует. Вместо этого она добавит новое значение к ключу.
|
||||
Кроме того, функция `mdb_del()` также обратит внимание на поле `value`, позволяя удалять определенные значения ключа.
|
||||
|
||||
Наконец, становятся доступны дополнительные операции с курсором для перемещения и извлечения повторяющихся значений.
|
||||
|
||||
|
||||
## Некоторая оптимизация
|
||||
|
||||
Если вы часто запускаете и прерываете транзакции только для чтения,
|
||||
в качестве оптимизации можно только сбросить и возобновить транзакцию.
|
||||
|
||||
Функция `mdb_txn_reset()` освобождает все старые копии данных, сохраненные для транзакции только для чтения.
|
||||
Чтобы повторно использовать эту транзакцию сброса, вызовите для нее функцию `mdb_txn_renew()`.
|
||||
Все курсоры в этой транзакции также должны быть обновлены с помощью функции `mdb_cursor_renew()`.
|
||||
|
||||
Обратите внимание, что функция `mdb_txn_reset()` аналогична функции `mdb_txn_abort()`
|
||||
и закроет все базы данных, открытые вами в рамках транзакции.
|
||||
|
||||
Чтобы окончательно освободить транзакцию, независимо от того, сбрасывается она или нет, используйте функцию `mdb_txn_abort()`.
|
||||
|
||||
|
||||
## Завершение
|
||||
|
||||
Для транзакций, доступных только для чтения, все созданные в нем курсоры должны быть закрыты с помощью функции `mdb_cursor_close()`.
|
||||
|
||||
Очень редко возникает необходимость закрывать дескриптор базы данных, и, как правило, их следует просто оставлять открытыми.
|
||||
|
||||
|
||||
## В полном API
|
||||
|
||||
В полной документации по API LMDB приведены дополнительные сведения, например, как:
|
||||
|
||||
* Увеличить размер базы данных (ограничения по умолчанию намеренно невелики)
|
||||
* Удалить и очистить базу
|
||||
* Выявление и сообщения ошибок
|
||||
* Оптимизация скорости загрузки
|
||||
* (временно) Уменьшить надежность, чтобы увеличить скорость
|
||||
* Сбор статистики о базе данных
|
||||
* Определение пользовательских порядков сортировки
|
201
content/posts/2024/lmdb/lmdb-with-c.md
Normal file
201
content/posts/2024/lmdb/lmdb-with-c.md
Normal file
@ -0,0 +1,201 @@
|
||||
---
|
||||
title: "🗄️ Basics of LMDB with C"
|
||||
date: 2024-06-25T22:57:31+03:00
|
||||
draft: true
|
||||
tags: [db, lmdb, tutorial]
|
||||
---
|
||||
|
||||
https://daemon.pizza/posts/lmdb-with-c/
|
||||
|
||||
LMDB is a neat embedded key value database. It stands for Lightning Memory-Mapped Database. It is useful for small applications where you don’t have to worry about schemas or relations. Think of the usecase where you might use a hashmap but you want the state to be persistent. LMDB is written in C and is even able to be linked with pkgconfig. A lot of times people use a wrapper, but I wanted to see what it was like to use this directly with C. It isn’t difficult, but I found basic tutorials hard to come by. So in this post I’ll show you to store and retrieve string key value pairs.
|
||||
|
||||
## Installation
|
||||
|
||||
The library has been around for a while, so it is easy to install. With debian I simply run:
|
||||
|
||||
```sh
|
||||
pacman -S lmdb # ArchLinux
|
||||
sudo apt install liblmdb-dev # Debian / Ubuntu
|
||||
```
|
||||
|
||||
In my `meson.build` I set the dependency like so:
|
||||
|
||||
```sh
|
||||
lmdb = dependency('lmdb')
|
||||
```
|
||||
|
||||
Add the header to your source file like so:
|
||||
|
||||
```c
|
||||
#include "lmdb.h"
|
||||
```
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
LMDB has its own terminology, but if you are familiar with databases it shouldn’t be that foreign. [BoltDB](https://github.com/boltdb/bolt) which is written in Go was originally supposed to be a port of LMDB and I found its model to help me understand how the LMDB API works.
|
||||
|
||||
|
||||
## Environment
|
||||
|
||||
The first thing that is setup is the environment. I find it easier to think about the environment as the “database”, but LMDB has a specific use for the word database which we will see later. When we initialize the environment it will initialize the folder where the data is stored. Emphasis on folder not file. Upon initialization, in the folder there will be a lock.mdb and a data.mdb file. LMDB uses these two files to persist data. The way to initialize the environment it looks like this:
|
||||
|
||||
```c
|
||||
MDB_env *env;
|
||||
mdb_env_create(&env);
|
||||
|
||||
mdb_env_open(env, "./testdb", 0, 0664);
|
||||
```
|
||||
|
||||
The more important call was mdb_env_open here we pass the environment struct, the path to the folder, 0 to specify READ and WRITe, and the folder permissions mode. Which you will recognize from UNIX chmod calls.
|
||||
|
||||
Once we open the environment we can start carrying out transactions to read and write data.
|
||||
|
||||
|
||||
## Put
|
||||
|
||||
To put key pairs into the database we use `mdb_put` inside of a transaction.
|
||||
|
||||
Lets start by creating a transaction.
|
||||
|
||||
```c
|
||||
MDB_txn *txn;
|
||||
mdb_txn_begin(env, NULL, 0, &txn);
|
||||
```
|
||||
|
||||
Here we use the environment struct from earlier, we pass NULL for the parent transaction (it is possible to have nested transaactions). 0 for READ & WRITE, and then the transaction.
|
||||
|
||||
The next thing do is open the database. Which I think is better understood as “table” or as BoltDB calls them “buckets”. We open up access to the database inside of a transaction.
|
||||
|
||||
```c
|
||||
MDB_dbi dbi;
|
||||
mdb_open(txn, NULL, 0, &dbi);
|
||||
```
|
||||
|
||||
The parameters we used are similar to the transaction, except the NULL stands for use default database. If we specified a name it would open up a specific database, which I find easier to think about as a table. In this tutorial I won’t be showing how to do that. You can find more info on that in the [official documentation](http://www.lmdb.tech/doc/starting.html).
|
||||
|
||||
After all that ceremony we are finally ready to put the key pair in. We begin by setting up our key and value which are both MDB_val data types.
|
||||
|
||||
```c
|
||||
char *skey = "foo";
|
||||
char *sval = "bar";
|
||||
|
||||
MDB_val key, val;
|
||||
|
||||
// Use +1 to include the \0 character
|
||||
key.mv_size = strlen(skey) + 1;
|
||||
key.mv_data = skey;
|
||||
|
||||
val.mv_size = strlen(sval) + 1;
|
||||
val.mv_data = sval;
|
||||
```
|
||||
|
||||
Once the key and value are setup we put them in the database.
|
||||
|
||||
```c
|
||||
mdb_put(txn, dbi, &key, &val, 0);
|
||||
```
|
||||
|
||||
Make sure to commit the transaction which will also free the transaction and database struct memory.
|
||||
|
||||
```c
|
||||
mdb_txn_commit(txn);
|
||||
```
|
||||
|
||||
|
||||
## Get
|
||||
|
||||
Once we stored the value we want to be able to get it. This works similarly to put in that we initialize a transaction and database.
|
||||
|
||||
```c
|
||||
MDB_txn *txn;
|
||||
MDB_dbi dbi;
|
||||
|
||||
mdb_txn_beginn(env, NULL, MDB_RDONLY, &txn);
|
||||
mdb_open(txn, NULL, 0, &dbi);
|
||||
```
|
||||
|
||||
Now here we only have to setup the key:
|
||||
|
||||
```c
|
||||
char *skey = "foo";
|
||||
MDB_val key;
|
||||
key.mv_size = strlen(skey) + 1;
|
||||
key.mv_data = skey;
|
||||
```
|
||||
|
||||
To finally get the value:
|
||||
|
||||
```c
|
||||
MDB_val val;
|
||||
mdb_get(txn, dbi, &key, &val);
|
||||
```
|
||||
|
||||
With the data being in the mv_data field:
|
||||
|
||||
```c
|
||||
printf("%s\n", val.mv_data);
|
||||
```
|
||||
|
||||
Once we are done make sure to end the transaction:
|
||||
|
||||
```c
|
||||
mdb_txn_abort(txn);
|
||||
```
|
||||
|
||||
|
||||
## Cursor
|
||||
|
||||
Now that we have covered the basics the last we are going to go over is cursors. Here we will see how to list all the key pairs in the database.
|
||||
|
||||
By this point you know the drill. Setup a transaction and a database.
|
||||
|
||||
```c
|
||||
MDB_txn *txn;
|
||||
MDB_dbi dbi;
|
||||
|
||||
mdb_txn_begin(env, NULL, MDB_RDONLY, &txn);
|
||||
mdb_open(txn, NULL, 0, &dbi):
|
||||
```
|
||||
|
||||
One thing that is different is we open up our cursor:
|
||||
|
||||
```c
|
||||
MDB_cursor *cursor;
|
||||
mdb_cursor_open(txn, dbi, &cursor);
|
||||
```
|
||||
|
||||
We also need to initialize the empty key and value pair.
|
||||
|
||||
```c
|
||||
MDB_val key, data;
|
||||
```
|
||||
|
||||
Then we will iterate through the database with the cursor:
|
||||
|
||||
```c
|
||||
int rc;
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %s, value: %s\n", (char *)key.mv_data, (char *)data.mv_data);
|
||||
}
|
||||
```
|
||||
|
||||
Finally we want to close the cursor and the transaction:
|
||||
|
||||
```c
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
```
|
||||
|
||||
|
||||
## Final Remarks
|
||||
|
||||
One thing I failed to show was how each call prefixed with mdb_ returns an error code.
|
||||
|
||||
To get a meaningful error we can use this:
|
||||
|
||||
```c
|
||||
fprintf(stderr, "mdb_txn_commit: (%d) %s\n", rc, mdb_strerror(rc));
|
||||
```
|
||||
|
||||
Hopefully this helps on your journey to use LMDB in your cool projects!
|
137
content/posts/2024/lmdb/short-guide.md
Normal file
137
content/posts/2024/lmdb/short-guide.md
Normal file
@ -0,0 +1,137 @@
|
||||
---
|
||||
title: "🗄️ LMDB: Short Guide"
|
||||
date: 2024-06-25T22:22:40+03:00
|
||||
draft: true
|
||||
tags: [db, tutorial]
|
||||
---
|
||||
|
||||
Этот пост является моим вольным (_yandex-translate_) переводом статьи из блога **KOLABNOW**:
|
||||
https://blogs.kolabnow.com/2018/06/07/a-short-guide-to-lmdb.
|
||||
|
||||
# A short guide to LMDB
|
||||
|
||||
LMDB is a great embeddable key-value store that we use extensively for Kube.
|
||||
|
||||
Using it is not completely straightforward though, so here’s a short guide for future reference.
|
||||
|
||||
LMDB has a couple of unique properties:
|
||||
|
||||
* It’s embeddable. You open a file and read/write it in-process.
|
||||
* The database is a memory-mapped file, so zero-copy reading of data is possible.
|
||||
* It has good write performance and great read performance.
|
||||
* It supports single-writer/multi-reader multi-process concurrency.
|
||||
* It supports multiple named databases.
|
||||
* The database is append only, so the file will never shrink.
|
||||
* Sorted keys are supported, and you can search for partial keys (only prefix, not random substrings).
|
||||
* Works on Linux, Mac OS and Windows.
|
||||
|
||||
|
||||
# High level concepts
|
||||
|
||||
## Environment
|
||||
|
||||
To do anything with LMDB you first have to open an environment. The environment provides the necessary data structures to access a single database-file, so there is always a 1:1 mapping of database-files you access and environments you have opened. There should only ever be one environment open per database-file per process. Usually there is no good reason to close an environment once it’s open, so you can just leave it upon until the process ends.
|
||||
|
||||
```c
|
||||
MDB_env *env;
|
||||
if (const int rc = mdb_env_create(&env)) {
|
||||
//Error
|
||||
}
|
||||
//Limit large enough to accommodate all our named dbs. This only starts to matter if the number gets large, otherwise it's just a bunch of extra entries in the main table.
|
||||
mdb_env_set_maxdbs(env, 50);
|
||||
//This is the maximum size of the db (but will not be used directly), so we make it large enough that we hopefully never run into the limit.
|
||||
mdb_env_set_mapsize(env, (size_t)1048576 * (size_t)100000); // 1MB * 100000
|
||||
if (const int rc = mdb_env_open(env, "/path/to/database", MDB_NOTLS | MDB_RDONLY, 0664)) {
|
||||
//Error
|
||||
}
|
||||
```
|
||||
|
||||
* MDB_NOTLS is used to disable any LMDB internal thread related locking. As long as you manage locking yourself this allows you to have multiple read-only transactions per thread.
|
||||
* MDB_RDONLY opens the database in read-only mode.
|
||||
|
||||
|
||||
## Transaction
|
||||
|
||||
Any interaction with the database content, reading or writing, has to go through a transaction. A transaction always applies to the whole environment (representing the database file), and never to individual named databases. Transactions provide full ACID semantics.
|
||||
|
||||
```c
|
||||
MDB_txn *parentTransaction = nullptr;
|
||||
MDB_txn *transaction;
|
||||
if (const int rc = mdb_txn_begin(env, parentTransaction, readOnly ? MDB_RDONLY : 0, &transaction)) {
|
||||
//Error
|
||||
}
|
||||
|
||||
if (readOnly) {
|
||||
mdb_txn_abort(transaction);
|
||||
} else {
|
||||
mdb_txn_commit(transaction);
|
||||
}
|
||||
```
|
||||
|
||||
* Transactions can be nested using the parentTransaction argument.
|
||||
* MDB_RDONLY is useful because you can have multiple read-only transactions open, but only ever one write transaction.
|
||||
|
||||
|
||||
## Named Database
|
||||
|
||||
Named databases are similar to tables in relational databases in that they act as sub-databases of the overall database. Each named database is opened using a name and then identified by a DBI (DataBase Identifier). If named databases are not used, the database with the name “” is implicitly used.
|
||||
Named databases should only be opened once per process, and the DBI should then be reused. Special care must be taken while opening databases because there must not be two concurrent transactions opening the same database (more on that in Caveats).
|
||||
|
||||
```c
|
||||
MDB_dbi dbi;
|
||||
if (const int rc = mdb_dbi_open(transaction, "databasename", MDB_DUPSORT | MDB_CREATE, &dbi)) {
|
||||
//Error
|
||||
}
|
||||
```
|
||||
|
||||
* Various flags can be passed to mdb_dbi_open to alter various properties, such as how the database deals with duplicates or whether to use string or integer keys. Databases must always be opened with the same flags once created.
|
||||
|
||||
|
||||
## Cursors
|
||||
|
||||
Cursors allow you to lookup a key in a database with sorted keys, and then iterate over preceding or following keys.
|
||||
|
||||
```c
|
||||
MDB_dbi dbi;
|
||||
MDB_cursor *cursor;
|
||||
if (int rc = mdb_cursor_open(d->transaction, d->dbi, &cursor)) {
|
||||
//Error
|
||||
}
|
||||
|
||||
//Initialize the key with the key we're looking for
|
||||
MDB_val key = {(size_t)someString.size(), (void *)someString.data()};
|
||||
MDB_val data;
|
||||
|
||||
//Position the cursor, key and data are available in key
|
||||
if (int rc = mdb_cursor_get(cursor, &key, &data, MDB_SET_RANGE)) {
|
||||
//No value found
|
||||
mdb_cursor_close(cursor);
|
||||
return 0;
|
||||
}
|
||||
|
||||
//Position the curser at the next position
|
||||
mdb_cursor_get(cursor, &key, &data, MDB_NEXT)
|
||||
|
||||
mdb_cursor_close(cursor);
|
||||
```
|
||||
|
||||
|
||||
## Caveats
|
||||
|
||||
Using LMDB in complex scenarios has plenty of pitfalls and it’s well worth to invest some time into learning what you can and cannot do.
|
||||
Here’s a list of things worth highlighting:
|
||||
|
||||
* Retrieved values AND keys will point directly into mapped memory. That memory will only remain valid until the transaction either aborts or commits, or the next operation is executed within the transaction. This essentially means that whenever you hold on to a value you must copy the memory.
|
||||
* Opening of named databases with mdb_dbi_open must fulfill the following:
|
||||
1. There must not ever be two concurrent transactions using mdb_dbi_open.
|
||||
2. A dbi created using mdb_dbi_open will only valid in transactions after that transaction used in mdb_dbi_open has been comitted (you also need to commit read-only transactions in this case).
|
||||
* An LMDB database is a memory mapped file, and as such large chunks of it will be loaded into memory and show up as your programs memory usage. It is important to understand that this memory can be efficiently reclaimed by the OS if required. [Blogpost](https://blogs.kolabnow.com/2018/02/13/using-and-abusing-memory-with-lmdb-in-kube).
|
||||
* The database file will never shrink. Memory that is no longer used due to removed values, is internally kept track of and reused, but the file itself will never shrink. To shrink the database it would need to be copied.
|
||||
* LMDB uses 4KB pages internally (that can be changed at compile time), so in the worst-case scenario, if all values are slightly over 2KB, you will end up with twice the database file-size to what the actual payload is (plus some overhead for the keys and the B+Tree).
|
||||
* No space is reused while a read-only transaction is active, so long running transactions will result in an ever-growing database. Use short-lived read-only transactions.
|
||||
* Do not use LMDB datbases on remote filesystems.
|
||||
* LMDB does no internal bookkeeping of named databases, and you will have to ensure yourself that you open named databases with the same flags every time. This can be challenging when creating named databases dynamically (I’m maintaining the flags used for a particular named database in a separate named database).
|
||||
* In order to run LMDB under Valgrind, the maximum mapsize [must be smaller than half your available ram](https://github.com/BVLC/caffe/issues/2404).
|
||||
* On windows you will require [a couple of patches](https://github.com/cmollekopf/lmdb) from master that have not yet made it into a release to avoid the database file immediately being the size of the maximum mapsize. It’s perhaps better to just try master than my random cherry-picks 😉
|
||||
|
||||
And as a last tip; read the docs in lmdb.h closely. Ultimately everything is described in there, you just have to find the relevant sections.
|
34
content/posts/2024/python/dotenv.md
Normal file
34
content/posts/2024/python/dotenv.md
Normal file
@ -0,0 +1,34 @@
|
||||
---
|
||||
title: "💼 Python и переменные окружения | пакет dotenv"
|
||||
date: 2024-09-03T21:23:24+03:00
|
||||
draft: false💼
|
||||
tags: [python, tips]
|
||||
---
|
||||
|
||||
## О пакете
|
||||
|
||||
Пакет `python-dotenv` считывает пары ключ-значение из файла `.env`
|
||||
и может устанавливать их в качестве переменных среды.
|
||||
|
||||
Это помогает в разработке приложений, основанных на [12-факторных](https://12factor.net/) принципах.
|
||||
|
||||
Пакет [python-dotenv](https://pypi.org/project/python-dotenv/) на сайте PyPi.
|
||||
|
||||
```sh
|
||||
pip3 install python-dotenv # Установить пакет
|
||||
```
|
||||
|
||||
|
||||
## Использование
|
||||
|
||||
```python
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv() # Получает переменные окружения из файла .env.
|
||||
|
||||
# Код приложения, который использует переменные среды
|
||||
# (например, из `os.environ` или `os.getenv`)
|
||||
# ...
|
||||
```
|
||||
|
||||
С более подробной информацией и примерами применения можно ознакомиться на странице проекта.
|
9
content/posts/2024/ravdina/slot.md
Normal file
9
content/posts/2024/ravdina/slot.md
Normal file
@ -0,0 +1,9 @@
|
||||
---
|
||||
title: "🎙️ Ravdina -> SLOT"
|
||||
date: 2024-08-18T01:23:23+03:00
|
||||
draft: false
|
||||
tags: [life, music]
|
||||
---
|
||||
|
||||
Дашка теперь на вокале в [Slot](https://www.slot.ru/)...
|
||||
Бля, назуя? я плакаю...
|
119
content/posts/2024/ssh/tunnel.md
Normal file
119
content/posts/2024/ssh/tunnel.md
Normal file
@ -0,0 +1,119 @@
|
||||
---
|
||||
title: "🚰 SSH туннели и проброс портов"
|
||||
date: 2024-08-30T23:30:24+03:00
|
||||
draft: false
|
||||
tags: [linux, tutorial, ssh, proxy]
|
||||
---
|
||||
|
||||
> Оригинал: https://codex.so/ssh-tunnel
|
||||
|
||||
При администрировании серверов часто возникают следующие задачи:
|
||||
|
||||
1. Получить доступ к локальному порту на сервере с рабочей машины.
|
||||
2. Обеспечить доступ к локальному порту рабочей машины с сервера.
|
||||
|
||||
Обе эти задачи решаются с помощью туннелирования,
|
||||
но иногда требуется организовать такой туннель быстро и без создания сложной дополнительной инфраструктуры.
|
||||
Решить такую задачу можно с помощью SSH.
|
||||
|
||||
## Доступ к внутреннему порту сервера
|
||||
|
||||
Попробуем подключиться к порту, к которому нет доступа снаружи, но есть доступ с сервера
|
||||
(например, MongoDB, которая слушает порт `27017` на интерфейсе `localhost` сервера).
|
||||
Для этого используется параметр `-L`.
|
||||
|
||||
```sh
|
||||
ssh -L 9999:localhost:27017 root@server_ip
|
||||
```
|
||||
|
||||
![](https://cdn.a2s.su/blog/2024/ssh/tunnel/1.jpg)
|
||||
|
||||
После аутентификации, у нас на рабочей машине появляется порт `9999`,
|
||||
который перенаправлен на порт `27017` локального интерфейса сервера.
|
||||
|
||||
Теперь можно подключиться к **MongoDB** напрямую, как будто она запущена у вас на компьютере.
|
||||
|
||||
```sh
|
||||
mongo localhost:9999 -u user -p password
|
||||
```
|
||||
|
||||
Туннель прервётся, если вы закроете SSH соединение.
|
||||
Кстати, если оно вам не требуется, а нужен только туннель – можно добавить параметр `-N`.
|
||||
|
||||
```sh
|
||||
ssh -L 9999:localhost:27017 root@server_ip -N
|
||||
```
|
||||
|
||||
Можно также открывать доступ не к локальному порту сервера,
|
||||
а к удаленному порту другой машины, которая доступна с сервера.
|
||||
|
||||
```sh
|
||||
ssh -L 9999:another_ip:80 root@server_ip -N
|
||||
```
|
||||
|
||||
В данном примере, вы можете делать запросы на порт `9999`, связанный с `80` портом машины `another_ip`.
|
||||
|
||||
## Доступ с сервера на локальный порт
|
||||
|
||||
Теперь представим ситуацию, что нужно с сервера обратиться к какому-нибудь сервису,
|
||||
запущенному на локальном компьютере. Например, вам регулярно присылают **HTTP** запросы по `80` порту на сервер.
|
||||
Вы можете прокинуть локальный веб-сервер со своей рабочей машины
|
||||
(скажем с `3000` порта) на сервер так, что запросы будут идти напрямую к вам. За это отвечает параметр `-R`.
|
||||
|
||||
```sh
|
||||
ssh -R 80:localhost:3000 root@server_ip
|
||||
```
|
||||
|
||||
![](https://cdn.a2s.su/blog/2024/ssh/tunnel/2.jpg)
|
||||
|
||||
После этой команды на сервере откроется `80` порт, запросы на который будут прокидываться вам на `3000`.
|
||||
|
||||
По умолчанию `80` порт откроется на всех интерфейсах.
|
||||
Если вы желаете выбрать конкретный (скажем 192.168.0.1), его можно указать следующим образом.
|
||||
|
||||
```sh
|
||||
ssh -R 192.168.0.1:80:localhost:3000 root@server_ip
|
||||
```
|
||||
|
||||
## Цепочка туннелей через несколько узлов
|
||||
|
||||
Если нужно пробросить на свою машину локальный порт с **сервера №2**,
|
||||
доступ к которому по SSH есть только с **сервера №1**, можно построить цепочку из SSH туннелей.
|
||||
|
||||
```sh
|
||||
ssh -L 9999:localhost:27017 root@server2 # server1
|
||||
ssh -L 9999:localhost:9999 root@server1 # client
|
||||
```
|
||||
|
||||
![](https://cdn.a2s.su/blog/2024/ssh/tunnel/3.jpg)
|
||||
|
||||
Можно воспользоваться однострочной командой.
|
||||
|
||||
```sh
|
||||
ssh -L 9999:localhost:9999 root@server1 ssh -L 9999:localhost:27017 -N root@server2
|
||||
```
|
||||
|
||||
А в случае, если у вас есть SSH ключи для доступа к **серверу №2**,
|
||||
вы можете построить защищенный туннель так, что ваш трафик не будет виден на **сервере №1**.
|
||||
|
||||
```sh
|
||||
ssh -L 9998:server2:22 -N root@server1
|
||||
ssh -L 9999:localhost:27017 -N -p 9998 root@localhost
|
||||
```
|
||||
|
||||
![](https://cdn.a2s.su/blog/2024/ssh/tunnel/4.jpg)
|
||||
|
||||
В данном примере вы пробрасываете себе на порт `9998` порт с **сервера №2**,
|
||||
обращаясь к нему через **сервер №1**.
|
||||
А затем пробрасываете порт `27017` с **сервера №2** на локальный `9999`.
|
||||
В данной конфигурации вам уже не нужен **сервер №1**, так как порт SSH уже у вас на локальном `9998`.
|
||||
|
||||
## SOCKS прокси
|
||||
|
||||
С помощью параметра `-D` можно создать на локальной машине сокет для прослушивания динамического порта.
|
||||
|
||||
```sh
|
||||
ssh -D localhost:8123 root@server_ip
|
||||
```
|
||||
|
||||
Теперь достаточно добавить хост `localhost` и порт `8123` в браузер для использования в качестве прокси.
|
37
content/posts/2024/video/gstreamer-example-src.md
Normal file
37
content/posts/2024/video/gstreamer-example-src.md
Normal file
@ -0,0 +1,37 @@
|
||||
---
|
||||
title: "📷 Тестовый источник видео Gstreamer"
|
||||
date: 2024-06-05T19:09:57+03:00
|
||||
draft: false
|
||||
tags: [tips, linux, video, development]
|
||||
---
|
||||
|
||||
Для отладочных целей (или нет) в **GStreamer** предусмотрен источник видео `videotestsrc`.
|
||||
|
||||
![](https://cdn.a2s.su/blog/2024/videotestsrc.png)
|
||||
|
||||
Что-бы его запустить, досточно выполнить следующую команду.
|
||||
|
||||
```shell
|
||||
gst-launch-1.0 -v videotestsrc ! xvimagesink
|
||||
```
|
||||
|
||||
Можно задать разрешение изображения, например 320 на 240 пикселей,
|
||||
или 1280 на 720 пикселей.
|
||||
|
||||
```shell
|
||||
# 320 на 240 пикселей
|
||||
gst-launch-1.0 -v videotestsrc ! video/x-raw,width=320,height=240 ! xvimagesink
|
||||
|
||||
# 1280 на 720 пикселей
|
||||
gst-launch-1.0 -v videotestsrc ! video/x-raw,width=1280,height=720 ! xvimagesink
|
||||
```
|
||||
|
||||
Что-бы использовать поток в коде, нужно заменить параметр `xvimagesink` на `appsink`.
|
||||
|
||||
```c++
|
||||
std::string source = "videotestsrc ! video/x-raw,width=320,height=240 ! appsink";
|
||||
|
||||
cv::VideoCapture capture = cv::VideoCapture(source);
|
||||
// or
|
||||
cv::VideoCapture capture = cv::VideoCapture(source, cv::CAP_GSTREAMER);
|
||||
```
|
@ -3,10 +3,12 @@
|
||||
IN=./public/
|
||||
DIR=Sites/iiiypuk.me/www/
|
||||
|
||||
hugo && rsync -avz --delete \
|
||||
./hugo && rsync -avz --delete \
|
||||
--exclude 'ytcg' \
|
||||
--exclude 'chola' \
|
||||
--exclude 'y' \
|
||||
--exclude 'a.html' \
|
||||
--exclude 'DeusEx.png' \
|
||||
${IN} kvm5:~/${DIR}
|
||||
|
||||
exit 0
|
||||
|
Loading…
Reference in New Issue
Block a user