Files
utun2/doc/member_files.txt
T

295 lines
28 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
Файлы участников CHAT-группы
===========================
Модуль: src/member_files/. Desktop-браузер: tools/chatgui/src/member_files/.
Отдельный сервис router ETCP_RT_ID_MEMBER_FILES (0x39), отдельно от вложений
сообщений media_delivery и E2E-файлов DM. Серверы выбираются явно; между
серверными деревьями автоматической репликации нет.
Хранение и конфигурация
----------------------
Корень по умолчанию: <db_path>/member_files/ (без db_path — ./member_files/).
Публичное дерево: <корень>/<group_id>/<node_id>/<относительный путь>.
Временные файлы: <корень>/.uploads/<upload UUID в hex>.
При старте сервис создаёт/открывает корень в дисковом worker, удаляет старые
незавершённые temp и сканирует опубликованные файлы. До готовности отвечает BUSY.
Ошибка сканирования оставляет локальное хранилище недоступным до restart;
частичные счётчики клиенту не выдаются. Клиентские передачи к другим серверам продолжают работать.
Очистка кэша вложений media/ это дерево не обходит.
Настройки [chatserver] / [chat]:
member_files_enabled=1
member_files_user_quota_mb=1024
member_files_upload_ttl_sec=3600
member_files_path=/путь/к/хранилищу (опционально, до 255 байт UTF-8)
Путь, квота и TTL читаются при старте чат-сервиса; изменение требует его restart.
enabled проверяется на каждом запросе. Лимит одинаков для каждого UID отдельно,
размер UID суммируется по всем группам на этом сервере. Значение в МиБ (1024*1024 байт).
Счётчики used/reserved/files/directories хранятся в памяти дискового worker и восстанавливаются с диска только при старте.
Каталоги не занимают байтовую квоту. files/directories считают опубликованные файлы и каталоги внутри деревьев UID
по всем группам; корни групп, корни UID и .uploads исключены. Пустой опубликованный файл считается файлом.
Сохранённые файлы удалённых участников также занимают квоту.
При начале загрузки резервируется размер нового файла или положительный прирост при замене.
WRITE не изменяет used; COMMIT обновляет used на разницу размеров и освобождает резерв.
ABORT/Cancel/TTL освобождают резерв после удаления temp; REMOVE вычитает размер файла.
Переименование внутри UID не меняет размер и числа объектов. Новый COMMIT увеличивает files на 1;
замена существующего файла, включая нулевой размер, сохраняет files. MKDIR увеличивает directories на 1,
REMOVE уменьшает соответствующий счётчик. Для mkdir учитывается факт создания даже при последующей ошибке sync.
BEGIN/WRITE/ABORT/Cancel/TTL не увеличивают files: temp не опубликован.
Все изменения корректируют счётчики инкрементально, без обхода каталога пользователя;
QUOTA только читает их из памяти. LIST выполняет свой обычный обход для отображения записей, не пересчитывая статистику.
Повторы BEGIN/COMMIT/MKDIR/REMOVE не меняют учёт повторно.
Уже превышенная квота не удаляет данные: чтение, удаление и уменьшение файла доступны,
новый положительный прирост запрещён. Физический temp может занимать больше места,
чем резерв прироста; нехватка места файловой системы остаётся отдельной I/O-ошибкой.
Нехватка квоты/места возвращает NO_SPACE; опубликованные файлы не вытесняются.
Общая member_files_quota_mb удалена. Корень обслуживается только файловым сервисом;
ручные изменения требуют повторного сканирования через restart.
Сервер — действующий полный мембер группы с подписанным adm_tags.storage="yes".
Колонка peers.storage — производный сетевой приоритет, для полномочий не используется.
Каждый запрос и ответ подписаны Ed25519 и зашифрованы end-to-end средствами
route_crypto. group/src берутся только из проверенного заголовка router;
ETCP peer промежуточного хопа не считается автором запроса.
Читать могут действующие участники той же группы. Изменять дерево может только
src == owner. Удалённые записи и source=1 (плейсхолдеры) доступа не дают.
Открытый fd/UUID загрузки не отменяют повторную проверку членства.
Файлы удалённого участника сохраняются на диске, но через сервис не доступны.
Wire, версия 4
--------------
Все числа LE, без сырых структур и padding. Фиксированный заголовок 125 байт:
0 protocol_version:u8 = 4
1 kind:u8 (1=request, 2=response)
2 command:u8
3 status:u16 (request=0)
5 request_id:u64, ненулевой случайный ID
13 owner:u64 (UID каталога; 0 только для LIST виртуального корня группы)
21 offset:u64
29 size:u64
37 cursor:u64
45 version:16
61 upload:16
77 hash:32
109 mtime:u64, Unix seconds
117 type:u8 (1=file, 2=directory)
118 more:u8 (0/1)
119 path_len:u16
121 target_len:u16
123 data_len:u16
После заголовка: path[path_len], target[target_len], data[data_len], без NUL.
Длина пакета обязана точно совпадать с указанными размерами.
Путь максимум 1024 байта, компонент максимум 255 байт, максимум 60 компонентов.
Данные максимум 8192 байт; вместе с полным router-путём и crypto укладываются
в лимит normalizer 16384 байта. Размер файла/смещение ограничены INT64_MAX.
Пути — строгий UTF-8, относительные, разделитель '/'. Запрещены пустые компоненты,
компоненты с ведущей '.', завершающей точкой/пробелом, управляющие символы,
Windows device names и символы \ : * ? " < > |. Корень внутри UID — пустой путь.
POSIX разрешает путь через *at/O_NOFOLLOW; regular-файлы с hardlink отвергаются.
Windows удерживает родительские каталоги без SHARE_DELETE и не принимает reparse points.
Команды
---------------
1 LIST: owner/path/cursor/version. Первая страница cursor=0, version=0.
Ответ data — записи: name_len:u16,type:u8,size:u64,mtime:u64,version:16,name.
cursor — сколько записей уже выдано. more=1 требует следующую страницу с
возвращёнными cursor/version. При изменении снимка — STALE, начать сначала.
Размер каталога не ограничивает память страницы. Виртуальный корень группы
выдаёт UID действующих полных участников, даже если каталог ещё не создан.
Изменение namespace сервера консервативно инвалидирует дисковые LIST-курсоры.
2 STAT: owner/path. Ответ type/size/mtime/version. Не хеширует весь файл.
version — непрозрачный снимок identity/size/mtime/ctime и эпохи сервера.
Закрытие fd по таймауту токен не меняет; restart меняет дисковые токены.
3 READ: owner/path/version/offset/size (1..8192).
Ответ size — полный размер файла, data_len — фактически прочитано,
more — есть ли байты после ответа. offset==EOF возвращает 0 байт/more=0;
offset>EOF — INVALID_RANGE. Изменение файла между блоками — STALE.
Клиент не смешивает версии и публикует локальный файл только по завершении.
4 BEGIN_WRITE: owner/path/upload/size/hash/version.
upload — 16 случайных байт, size — конечный размер, hash — SHA256 содержимого.
version=0 означает создать новый файл, существующий возвращает EXISTS.
Для замены требуется версия из STAT/LIST. В один путь одна активная загрузка.
UUID привязан к group/src/owner/path; повтор BEGIN с теми же параметрами безопасен.
5 WRITE: owner/path/upload/offset/size/data. size==data_len, диапазон внутри
конечного размера. Можно писать в произвольном порядке и повторять блок.
ACK подтверждает запись в temp, а не durable публикацию всего файла.
6 COMMIT: owner/path/upload. Проверяет SHA256, fsync/_commit и ожидаемую
версию назначения, затем атомарно заменяет файл и синхронизирует namespace.
Прежний файл виден до публикации. Ответ содержит размер/версию/SHA256.
Незаписанные sparse-области читаются как нули; неправильное содержимое не
проходит SHA256. Пустые файлы поддерживаются. При I/O-ошибке во время
публикации результат может быть неопределённым; нужно повторно проверить STAT.
7 ABORT: owner/path/upload. Удаляет temp, освобождает резервацию.
Неизвестный UUID — OK; опубликованные файлы никогда не удаляет.
8 MKDIR: owner/path. Создаёт один каталог; родители должны существовать.
9 RENAME: owner/path/target/version. Только внутри UID/группы, без замены
существующего назначения. Активная загрузка в затрагиваемом пути — BUSY.
10 REMOVE: owner/path/version. Файл или пустой каталог, без рекурсивного удаления.
11 STREAM_WRITE: открытие потоковой загрузки; поля как BEGIN_WRITE,
cursor — ненулевой ID потока. Ответ подтверждает готовность temp и конечный размер.
12 STREAM_READ: открытие потокового скачивания; owner/path/version/size/cursor,
upload — случайный токен клиента. Сервер сверяет снимок и размер перед началом.
13 STREAM_DATA: request_id — ID потока, owner/path/upload, offset/size/data.
kind=request для upload, response для download. Ответа на блок нет.
offset обязан равняться следующему принятому смещению; size==data_len, не более 8192.
14 STREAM_END: ID потока, owner/path/upload, offset==size==размер файла, SHA256.
Upload: клиент отправляет END после всех DATA, сервер отвечает END после COMMIT.
Download: сервер отправляет END после DATA и повторной проверки версии; клиент
проверяет SHA256 записанных данных, синхронизирует и публикует локальный файл.
Ошибка сервера приходит END.status!=OK; ошибка открытия — ответом на STREAM_READ/WRITE.
15 STREAM_CANCEL: клиент отменяет конкретный поток. Поздние DATA не публикуются.
16 QUOTA: owner=UID, path/target пустые, cursor=0, data_len=0.
Права чтения те же, что у STAT/LIST выбранного участника. Ответ data_len=40:
used:u64, limit:u64, reserved:u64, files:u64, directories:u64, LE.
Первые три значения в байтах, последние два — числа объектов. Снимок общий для UID во всех группах.
Не резервирует место и не занимает квитанцию мутации. used может превышать limit
после уменьшения лимита; сервер не выдаёт фиктивный нулевой размер при ошибке учёта.
Открытие потока — обычный повторяемый запрос с отдельным request_id. cursor связывает
его с DATA/END/CANCEL. Поток определяется group/peer/kind/ID; один ID не может
одновременно обозначать разные открытия. Каждый пакет по-прежнему подписан и зашифрован.
При закрытии/перезапуске router-канала его handles завершаются с IO_ERROR;
байты разных эпох не смешиваются. Возобновление через новую эпоху пока не реализовано.
Статусы (0..13): OK, ACCESS_DENIED, NOT_FOUND, INVALID_PATH, INVALID_RANGE,
EXISTS, BUSY, STALE, NO_SPACE, IO_ERROR, BAD_PACKET, HASH_MISMATCH, CANCELLED, TIMEOUT.
Мутации COMMIT/MKDIR/RENAME/REMOVE сохраняют успешный ответ на 120 секунд
(64 квитанции); тот же request_id с другим содержимым — BAD_PACKET.
COMMIT дополнительно узнаётся по upload UUID. При заполнении квитанций новая
мутация возвращает BUSY до истечения старых. Клиент повторяет запрос с прежним
request_id раз в 5 секунд, общий срок 120 секунд. Повторы после этого срока
не дают гарантии того же результата; перед новой мутацией требуется STAT.
Потоки, таймауты и остановка
---------------------------
На экземпляр один дисковый pthread с очередью до 64 обычных задач. Сеть,
ACL, таймеры и state machines выполняются в uasync; open/stat/readdir/read/write,
resize/hash/sync/rename/remove/close — в дисковом потоке. GUI только передаёт
команды через bridge и получает события. Нет вызова дискового I/O из callback
таймера. Completion резервируется до постановки задачи; без polling workers.
Локальные файлы transfer и серверное дерево используют тот же дисковый поток.
Для обязательной очистки резервируется отдельная задача до открытия файла.
Обращение к своему серверу доставляется через отдельную ограниченную очередь
call_soon в тот же обработчик, без сетевого канала. ACL и дисковый поток общие;
сетевой recv не принимает unsigned даже при src == self.
Сервер держит до 64 открытых файлов. Каждый READ/WRITE/BEGIN продлевает idle.
После 10 секунд простоя fd закрывается на очередном дисковом sweep (таймер 1 с);
занятый дисковый поток откладывает sweep до освобождения. Закрытие не публикует
и не удаляет файл. Temp можно открыть заново до истечения upload TTL.
После restart незавершённые temp удаляются; опубликованные файлы остаются.
Лимиты: 16 клиентских запросов, 32 серверных задания/ответа, 16 временных
загрузок, 16 клиентских и 16 серверных потоков на экземпляр. Каждый поток имеет
кольцо 32 x 8192 байт (256 КиБ); worker обрабатывает до 16 блоков за задачу.
Чтение следующей порции перекрывается с отправкой уже подготовленной.
Все серверные и локальные I/O используют один FIFO worker, без блокировки uasync.
Отправитель добавляет пакеты, пока send_q router находится под нижним порогом
(сейчас count<=0), затем ждёт etcp_router_on_send_ready. Файловый слой не устанавливает
своё окно inflight и не повторяет DATA; транспортные ACK, retransmit и порядок — router.
Callback отправляет не более 8 блоков за запуск, уступая очередь другим потокам.
Параллельные файлы одного пира разделяют канал и FIFO/round-robin waiters.
Готовность приёмника проверяется router до доставки следующего seq. Заполненное
кольцо или очередь заданий оставляют пакет в recv_q: rx_seq/ACK не продвигаются.
Освобождение места вызывает отложенный resume; pump отменяется при reset/close.
ACK RECEIVE_BUSY (flags bit0) раз в 100 мс отличает ожидание диска от потери транспорта.
Отправитель приостанавливает новые DATA, а живые busy ACK продлевают таймаут router.
Буфер router ограничен его receive window. Локальная доставка использует такую же
проверку готовности и ограниченный FIFO, а не сеть или отдельные подтверждения.
При остановке сначала отменяются producers/requests и дожидаются все stream cleanup
continuations; только затем worker освобождает storage. Отмена после уже выполненной
публикации не удаляет опубликованный файл.
Transfer владеет CM handle до окончания/отмены; DOWN/TIMEOUT handle не освобождает.
quiesce запрещает новые команды, отменяет таймеры, waiter и запросы; destroy
вне callbacks дожидается только своих работ/completions и закрывает дисковый
поток до уничтожения CHAT-групп. Контексты не освобождаются под работающим I/O.
Отмена не прерывает текущий системный I/O или хеширование, а ждёт его completion.
Desktop
-------
Контекстное меню канала: Group files. Меню участника: Member files.
Окно: выбор сервера/UID, путь, Up/Refresh, таблица каталогов/файлов,
Upload/Download/New folder/Rename/Delete, прогресс и Cancel transfer.
Чужой UID доступен только для чтения. Автор может быть offline.
Open скачивает в выбранный пользователем локальный файл и открывает его
приложением ОС после завершения. Смена контекста отбрасывает поздние ответы.
Для выбранного UID строка хранилища показывает занято/всего, число файлов/каталогов и резерв текущих загрузок.
Точные байты доступны в tooltip. QUOTA запрашивается отдельно от LIST при выборе
сервера/UID, обновлении каталога, завершении передачи и каждые две секунды видимого окна.
Поздние ответы старого UID/сервера игнорируются. BUSY при стартовом сканировании
сохраняет Loading directory и повторяет LIST через секунду; новый переход отменяет повтор.
API параллельных передач
-----------------------
member_files_transfer_start(request, callback, arg) копирует request и возвращает
отдельный mf_transfer* на каждый файл. upload задаёт направление, group/server/owner
выбираются для каждой операции. id=0 выбирает случайный ID; id в callback — фактический.
NULL означает отказ запуска без callback; причина пишется в лог.
callback(arg, mf_transfer_progress*) получает id/done/total/status/finished.
Прогресс заимствован до возврата callback. done для отправителя означает байты,
принятые router в очередь; успешный finished подтверждает завершение операции,
для upload — публикацию на сервере, для download — публикацию локального файла.
finished приходит ровно один раз, handle к этому моменту уже освобождён;
вызывающий обнуляет свой указатель в callback. member_files_transfer_cancel(handle)
отменяет только этот файл, ждёт его I/O и ставит CANCEL в очередь с backpressure.
Stop сервиса/группы завершает все принадлежащие ему handles.
Все API/callbacks выполняются в uasync-потоке экземпляра. GUI trampolines используют
тот же API и превращают callback в существующие события прогресса; Qt не делает I/O.
Один локальный файл назначения не может одновременно принадлежать двум download.
Проверки и диагностика
---------------------
tests/test_member_files: wire/UTF-8, произвольные блоки, повторы, EOF, версии,
короткая замена, SHA256, пустой файл, независимые квоты UID, общий учёт по группам,
резервирование/повторы/TTL, инкрементальные files/directories, замены пустых файлов,
создание/удаление/rename каталогов, отсутствие runtime-rescan, стартовый scan и его ошибки, ссылки, expiry/restart, 10-секундный
fd idle и принадлежность work/done потокам.
tests/test_member_files_network: настоящие A/B/M, подписанная storage-роль,
параллельные upload/download одного пира, local loopback, пустые файлы, побайтная
проверка, отмена одного handle, restart router и destroy с активным server I/O.
Шестисекундная задержка worker проверяет receive backpressure и busy ACK.
Также проверяются чужая запись, виртуальный корень и подписанное удаление читателя.
QUOTA проверяется на настоящем подписанном/зашифрованном канале; удалённый читатель не получает счётчики.
test_services — stop/start общего чат-сервиса.
Текущая диагностика выделена в категорию debug. Настройка для полного трасса:
[debug]
debug=trace
debug=debug показывает запрос/ответ, ACL, очереди, маршрут, ACK, crypto-решения,
I/O, fd idle и состояние браузера. trace дополнительно показывает каждый
read/write и передачу задач/завершений между дисковым потоком и uasync.
stream progress раз в секунду показывает ID/направление, done/total, число блоков
в кольце, активность I/O, причину ожидания и скорость B/s. Router логирует переходы
receive paused/resumed и peer receive busy без повторного сообщения на каждый блок.
quota scan started/ready показывают путь, число пользователей и время обхода;
quota updated показывает UID/операцию/used/reserved/files/directories; quota exceeded — used/reserved/growth/limit.
По request_id видны GUI post → core request → router seq → server rx → ACL →
disk queued/start/done → response tx → GUI result/bridge → directory ready.
PID создания worker, PID отправки задачи и startup PID позволяют однозначно
обнаружить fork после создания потока; несовпадение PID логируется как ошибка.
Ключи шифрования, plaintext файлов и сырые медиа в debug не выводятся.
Старые VAD/рация/GUI/вложения сообщения перенесены в vad/radio/chat/media/config;
ошибки и пользовательские info остаются в тематических категориях.