mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-26 20:57:32 +00:00
fix(tci): рекордер линейного выхода — память под потолком, файл только в своём каталоге
Два дефекта уровня P1 в LINE_OUT_RECORDER_*. Авторизации в протоколе нет
(§3.1), bind наружу разрешён — значит «сколько стоит одна строка из сети»
это вопрос живучести процесса, а не аккуратности.
1. Неограниченное выделение памяти. CmdRecorder проверял только ValidRx
(номер в потолке), а конструктор выделял буфер целиком: 300 с × 48 кГц
× 2 канала × int16 = 57.6 МБ на команду, приёмников 1+MAX_SLICES=7, то
есть 403 МБ семью строками. У мёртвого приёмника Feed не зовут — значит
срок записи никто не проверял; уход клиента рекордеры не трогал вовсе;
DropDeadRxStreams бежит только на rfDevice/rfConnected и смене карты
слайсов, в покое не срабатывает. Память жила до остановки сервера.
Лечение тремя замками:
- память набирается кусками по секунде, START не стоит ни байта;
куски не перевыделяются (никакого realloc в DSP-потоке) и склеиваются
один раз в Take — уже после того, как рекордер вынут из таблицы;
- общий бюджет TCI_RECORD_MAX_BYTES (128 МБ) на все рекордеры сразу,
спрашивается на каждый кусок; отказ не рушит запись, набранное
остаётся сохраняемым;
- освобождение по трём событиям: START требует живого приёмника
(RxActive, ответ receiver is not running), тик сервера подметает
истёкшие окна по часам (SweepRecorders — окно закрывается от START
и без единого блока звука), уход клиента забирает его записи
(DropClientRecorders; рекордер живёт на приёмнике, но платит за него
тот, кто нажал START).
2. Перезапись произвольного файла. Путь из сети уходил в fmCreate почти
как пришёл — вместе с '..' и абсолютными путями. Теперь TCIRecordPath
берёт из строки ТОЛЬКО имя файла, каталог — настроенный tci.record_dir
(пусто = <каталог конфигурации>/records). Каталог из просьбы
отбрасывается молча: полный путь на сервере клиенту всё равно
бесполезен, файл ложится не на его машину. Имя валидируется (пусто,
'.', '..', управляющие, ':', длиннее 120, расширение не .wav →
bad file name); после ExtractFileName выйти за каталог нечем. Файл
создаётся эксклюзивно (TCICreateNewFile: O_EXCL|O_NOFOLLOW на Unix,
CREATE_NEW на Windows) — ни перезаписи, ни симлинка, без окна между
FileExists и созданием; клиенту заранее file exists.
Попутно: MainForm.ApplyTCISettings собирал TTCISettings по полям с
чистого листа — новое поле RecordDir обнулялось бы при каждом применении
вкладки CAT. В uses TCIStreams Windows стоит первым намеренно: иначе его
TCriticalSection перекрыл бы SyncObjs (та же грабля, что в DX-кластере).
Стенд 189/189 (было 169): 11 проверок имени файла (/etc/passwd.wav,
../../.., D|\rec\a.wav), бюджет (START не выделяет, растёт кусками,
потолок, отказ не рушит запись, срок истекает без Feed), существующий WAV
не перезаписывается, START на мёртвом приёмнике и с чужим номером, и
сквозная проверка в части E — клиент стартует запись, набирает память
живым звуком через WDSP, рвёт TCP, бюджет возвращается к нулю. Без фиксов
новые проверки краснеют. GUI (--ws=qt6) и демон зелёные.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -466,6 +466,12 @@ type
|
||||
Enabled: Boolean;
|
||||
Port: Integer; // 1..65535, умолчание 40001 (как у ExpertSDR3)
|
||||
BindAddr: string; // '127.0.0.1' или '0.0.0.0'
|
||||
// ★Единственный каталог, куда TCI пишет записи линейного выхода
|
||||
// (LINE_OUT_RECORDER_SAVE). Пусто — <каталог конфигурации>/records.
|
||||
// Имя файла клиент выбирает сам, но КАТАЛОГ из его строки не берётся
|
||||
// никогда: авторизации в протоколе нет, и полный путь из сети означал бы
|
||||
// запись в любой доступный процессу файл (см. TCIRecordPath).
|
||||
RecordDir: string;
|
||||
end;
|
||||
|
||||
// Transverter (XVTR) entry — один трансвертер.
|
||||
@@ -2999,6 +3005,7 @@ begin
|
||||
T.Enabled := False; // включается осознанно, как CAT-транспорты
|
||||
T.Port := 40001;
|
||||
T.BindAddr := '127.0.0.1';
|
||||
T.RecordDir := ''; // пусто = <каталог конфигурации>/records
|
||||
end;
|
||||
|
||||
procedure TSettingsManager.LoadTCISettings(out T: TTCISettings);
|
||||
@@ -3010,6 +3017,7 @@ begin
|
||||
T.Enabled := JB(O, 'enabled', False);
|
||||
T.Port := EnsureRange(JI(O, 'port', 40001), 1, 65535);
|
||||
T.BindAddr := JS(O, 'bind_addr', '127.0.0.1');
|
||||
T.RecordDir := JS(O, 'record_dir', '');
|
||||
end;
|
||||
|
||||
procedure TSettingsManager.SaveTCISettings(const T: TTCISettings);
|
||||
@@ -3019,6 +3027,7 @@ begin
|
||||
JW(O, 'enabled', T.Enabled);
|
||||
JW(O, 'port', T.Port);
|
||||
JWS(O, 'bind_addr', T.BindAddr);
|
||||
JWS(O, 'record_dir', T.RecordDir);
|
||||
Save;
|
||||
end;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user