You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

522 lines
29 KiB

#include "private.h"
#include "../utun_instance.h"
#include "../chat/chat_event.h"
#include "../chat/chat_setting.h"
#include "../routing_layer/etcp_router.h"
#include "../routing_layer/conn_mgr.h"
#include "../routing_layer/topo_group.h"
#include "../transport_layer/etcp.h"
#include "../../lib/mem.h"
#include "../../lib/debug_config.h"
#include "../../lib/json_flat.h"
#include <openssl/rand.h>
#include <openssl/evp.h>
#include <string.h>
#include <stdio.h>
#define MF_MAX_PENDING 16
#define MF_MAX_JOBS 32
#define MF_REQUEST_TB 1200000ULL
struct mf_pending {
struct mf_pending* next;
struct member_files* files;
uint64_t group, server, started, sent;
struct mf_message request;
mf_response_fn callback;
void* arg;
void* pump;
struct CONN_MGR_HANDLE* cm;
struct queue_waiter_handle waiter;
};
struct mf_job {
struct mf_job* next;
struct member_files* files;
uint64_t group, peer, created;
struct mf_message request, response;
struct queue_waiter_handle waiter;
int state; /* 0 queued, 1 worker, 2 reply; command=0 — sweep */
};
struct mf_local {
struct mf_local* next;
struct member_files* files;
uint64_t group;
struct mf_message message;
void* task;
};
static void mf_server_pump(struct member_files* f);
static void mf_client_ready(struct ll_queue* q, void* arg);
static void mf_dispatch(struct member_files* f, uint64_t group, uint64_t peer, const struct mf_message* message);
static void mf_local_free(struct mf_local* local) {
struct member_files* f = local->files;
if (local->task) uasync_call_soon_cancel(f->inst->ua, local->task);
struct mf_local** link = &f->locals;
while (*link && *link != local) link = &(*link)->next;
if (*link) *link = local->next;
f->local_count--; u_free(local);
}
/* Локальная доставка всегда отложена: ответ не освобождает pending внутри send. */
static void mf_local_deliver(void* arg) {
struct mf_local* local = arg; local->task = NULL;
struct member_files* f = local->files;
struct mf_local** link = &f->locals;
while (*link && *link != local) link = &(*link)->next;
if (*link) *link = local->next;
f->local_count--;
if (!f->closing) mf_dispatch(f, local->group, f->inst->node_id, &local->message);
u_free(local);
}
int member_files_member(struct UTUN_INSTANCE* inst, uint64_t group, uint64_t node, int storage) {
struct TOPO_GROUP* g = inst && inst->topo_groups ? topo_groups_find(inst->topo_groups, group) : NULL;
if (!g || g->group_type != TOPO_GROUP_TYPE_CHAT || !node || !inst->topo_sqlite_db) {
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "member_files: ACL missing CHAT group=%llu node=%llu role=%d",
(unsigned long long)group, (unsigned long long)node, storage); return 0;
}
char sql[256];
snprintf(sql, sizeof(sql), "SELECT adm_tags,source,adm_tags_sig,deleted FROM \"peers_%llu\" WHERE node_id=?",
(unsigned long long)group);
sqlite3_stmt* st = NULL;
if (sqlite3_prepare_v2(inst->topo_sqlite_db, sql, -1, &st, NULL) != SQLITE_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "member_files: membership query failed group=%llu: %s",
(unsigned long long)group, sqlite3_errmsg(inst->topo_sqlite_db)); return 0;
}
sqlite3_bind_int64(st, 1, (sqlite3_int64)node);
int rc = sqlite3_step(st);
char role[16]; const char* tags = (const char*)sqlite3_column_text(st, 0);
int storage_role = tags && json_flat_get(tags, "storage", role, sizeof(role)) == 0 && !strcmp(role, "yes");
int allowed = rc == SQLITE_ROW && !sqlite3_column_int(st, 1) && !sqlite3_column_int(st, 3) &&
(!storage || (storage_role && sqlite3_column_bytes(st, 2) == 64));
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG,
"member_files: ACL allowed=%d group=%llu node=%llu require_storage=%d row=%d storage=%d source=%d signature=%d deleted=%d",
allowed, (unsigned long long)group, (unsigned long long)node, storage, rc == SQLITE_ROW,
storage_role, sqlite3_column_int(st, 1), sqlite3_column_bytes(st, 2), sqlite3_column_int(st, 3));
if (rc != SQLITE_ROW && rc != SQLITE_DONE) DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "member_files: membership step failed rc=%d", rc);
sqlite3_finalize(st); return allowed;
}
static int mf_server_allowed(struct member_files* f, uint64_t group, uint64_t peer, const struct mf_message* q) {
int enabled = chat_setting_get_int(f->inst, "member_files_enabled", 1);
int allowed = enabled && member_files_member(f->inst, group, f->inst->node_id, 1) &&
member_files_member(f->inst, group, peer, 0) && (!q->owner || member_files_member(f->inst, group, q->owner, 0)) &&
(q->command < MF_BEGIN_WRITE || (q->owner && q->owner == peer));
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "member_files: server access id=%llu cmd=%s enabled=%d allowed=%d owner_is_peer=%d",
(unsigned long long)q->request_id, mf_command_text(q->command), enabled, allowed, q->owner == peer);
return allowed;
}
/* Router подтверждает доставку отдельно; здесь отправка всегда соблюдает порог очереди. */
static int mf_send(struct member_files* f, uint64_t group, uint64_t peer, const struct mf_message* m) {
if (peer == f->inst->node_id) {
if (f->local_count >= MF_MAX_JOBS) return 1;
struct mf_local* local = u_calloc(1, sizeof(*local));
if (!local) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "member_files: local delivery allocation failed"); return -1; }
local->files = f; local->group = group; local->message = *m;
local->task = uasync_call_soon(f->inst->ua, local, mf_local_deliver);
if (!local->task) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "member_files: local delivery scheduling failed"); u_free(local); return -1; }
local->next = f->locals; f->locals = local; f->local_count++;
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "member_files: local queued id=%llu cmd=%s kind=%u count=%u",
(unsigned long long)m->request_id, mf_command_text(m->command), m->kind, f->local_count);
return 0;
}
struct ETCP_ROUTER_CONN* rc = etcp_router_conn_get(f->inst, group, peer, ETCP_RT_ID_MEMBER_FILES);
if (!rc) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "member_files: router allocation failed"); return -1; }
queue_set_threshold(rc->send_q, 0, 0); etcp_router_set_max_inflight(rc, 16);
if (!etcp_router_send_q_has_room(f->inst, group, peer, ETCP_RT_ID_MEMBER_FILES)) {
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "member_files: tx waiting id=%llu cmd=%s send_q=%u inflight=%u sync=%u no_route=%u",
(unsigned long long)m->request_id, mf_command_text(m->command), rc->send_q->count, rc->inflight_q->count,
rc->peer_sync_done, rc->no_route);
return 1;
}
uint8_t wire[MF_WIRE_MAX];
int n = mf_encode(m, wire, sizeof(wire));
if (n < 0) return -1;
uint32_t seq = rc->tx_seq;
int result = etcp_router_conn_send(rc, wire, (size_t)n, ROUTER_FLAG_SIGNED | ROUTER_FLAG_ENCRYPTED);
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG,
"member_files: tx id=%llu kind=%u cmd=%s group=%llx peer=%llx owner=%llx status=%s bytes=%d seq=%u accepted=%d",
(unsigned long long)m->request_id, m->kind, mf_command_text(m->command), (unsigned long long)group,
(unsigned long long)peer, (unsigned long long)m->owner, mf_status_text(m->status), n, seq, result == 0);
if (result) DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "member_files: send failed group=%llu peer=%llu cmd=%u rc=%d",
(unsigned long long)group, (unsigned long long)peer, m->command, result);
return result ? -1 : 0;
}
static void mf_pending_finish(struct mf_pending* p, const struct mf_message* response, int status) {
struct member_files* f = p->files;
if (p->pump) uasync_call_soon_cancel(f->inst->ua, p->pump);
etcp_router_cancel_send_ready(f->inst, p->group, p->server, ETCP_RT_ID_MEMBER_FILES, &p->waiter);
if (p->cm) conn_mgr_close(p->cm);
struct mf_pending** link = &f->pending;
while (*link && *link != p) link = &(*link)->next;
if (*link) *link = p->next;
f->pending_count--;
struct mf_message result;
if (!response) { result = p->request; result.kind = MF_RESPONSE; result.status = (uint16_t)status; result.data_len = 0; response = &result; }
mf_response_fn cb = p->callback; void* arg = p->arg;
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "member_files: request done id=%llu cmd=%s status=%s pending=%u elapsed_ms=%llu",
(unsigned long long)p->request.request_id, mf_command_text(p->request.command), mf_status_text(response->status),
f->pending_count, (unsigned long long)((get_time_tb() - p->started) / 10));
u_free(p); cb(arg, response);
}
static void mf_pending_pump(void* arg) {
struct mf_pending* p = arg; p->pump = NULL;
if (p->files->closing) return;
int rc = mf_send(p->files, p->group, p->server, &p->request);
if (!rc) p->sent = get_time_tb();
else if (rc > 0) etcp_router_on_send_ready(p->files->inst, p->group, p->server, ETCP_RT_ID_MEMBER_FILES,
&p->waiter, mf_client_ready, p);
}
static void mf_pending_wake(struct mf_pending* p) {
if (p->pump || p->files->closing) return;
p->pump = uasync_call_soon(p->files->inst->ua, p, mf_pending_pump);
if (!p->pump) DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "member_files: client pump allocation failed");
}
static void mf_client_ready(struct ll_queue* q, void* arg) { (void)q; mf_pending_wake(arg); }
static void mf_connected(struct CONN_MGR_HANDLE* h, uint64_t peer, uint64_t group, enum conn_mgr_event event, void* arg) {
(void)h; (void)peer; (void)group;
struct mf_pending* p = arg;
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "member_files: CM event=%d request=%llu", event, (unsigned long long)p->request.request_id);
if (event == CONN_EVENT_UP) mf_pending_wake(p);
}
struct mf_pending* member_files_request(struct UTUN_INSTANCE* inst, uint64_t group, uint64_t server,
const struct mf_message* request, mf_response_fn cb, void* arg) {
struct member_files* f = inst ? inst->member_files : NULL;
if (request && (request->command < MF_LIST || request->command > MF_REMOVE || request->status ||
request->data_len > MF_BLOCK_SIZE || !mf_path_valid(request->path, request->command == MF_LIST || request->command == MF_STAT) ||
(request->command == MF_RENAME && !mf_path_valid(request->target, 0)))) {
DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "member_files: invalid client command=%u status=%u data=%u",
request->command, request->status, request->data_len); return NULL;
}
if (!f || f->closing || !request || !cb || f->pending_count >= MF_MAX_PENDING ||
!member_files_member(inst, group, inst->node_id, 0) || !member_files_member(inst, group, server, 1)) {
DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "member_files: client request denied group=%llu server=%llu",
(unsigned long long)group, (unsigned long long)server); return NULL;
}
struct mf_pending* p = u_calloc(1, sizeof(*p));
if (!p) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "member_files: client allocation failed"); return NULL; }
p->files = f; p->group = group; p->server = server; p->request = *request; p->request.kind = MF_REQUEST;
p->callback = cb; p->arg = arg; p->started = get_time_tb();
if (!p->request.request_id && (RAND_bytes((uint8_t*)&p->request.request_id, 8) != 1 || !p->request.request_id)) {
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "member_files: request random failed"); u_free(p); return NULL;
}
for (struct mf_pending* other = f->pending; other; other = other->next) if (other->request.request_id == p->request.request_id) {
DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "member_files: duplicate local request id"); u_free(p); return NULL;
}
p->next = f->pending; f->pending = p; f->pending_count++;
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG,
"member_files: request id=%llu cmd=%s group=%llx server=%llx owner=%llx path=%s offset=%llu size=%llu pending=%u",
(unsigned long long)p->request.request_id, mf_command_text(p->request.command), (unsigned long long)group,
(unsigned long long)server, (unsigned long long)p->request.owner, p->request.path,
(unsigned long long)p->request.offset, (unsigned long long)p->request.size, f->pending_count);
if (server != inst->node_id && conn_mgr_open(inst, group, server, mf_connected, p, &p->cm) != 0) {
DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "member_files: CM launch failed server=%llu", (unsigned long long)server);
if (p->pump) uasync_call_soon_cancel(inst->ua, p->pump);
f->pending = p->next; f->pending_count--; u_free(p); return NULL;
}
mf_pending_wake(p);
return p;
}
void member_files_cancel(struct mf_pending* p) { if (p) mf_pending_finish(p, NULL, MF_CANCELLED); }
void member_files_cancel_request_id(struct UTUN_INSTANCE* inst, uint64_t id) {
struct member_files* f = inst ? inst->member_files : NULL;
if (f) for (struct mf_pending* p = f->pending; p; p = p->next) if (p->request.request_id == id) {
member_files_cancel(p); break;
}
}
static void mf_job_free(struct mf_job* j) {
struct member_files* f = j->files;
etcp_router_cancel_send_ready(f->inst, j->group, j->peer, ETCP_RT_ID_MEMBER_FILES, &j->waiter);
struct mf_job** link = &f->jobs;
while (*link && *link != j) link = &(*link)->next;
if (*link) *link = j->next;
f->job_count--; u_free(j);
}
static void mf_server_deferred(void* arg) {
struct member_files* f = arg; f->pump = NULL; mf_server_pump(f);
}
static void mf_server_ready(struct ll_queue* q, void* arg) {
(void)q;
struct member_files* f = arg;
if (!f->pump && !f->closing) {
f->pump = uasync_call_soon(f->inst->ua, f, mf_server_deferred);
if (!f->pump) DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "member_files: server pump allocation failed");
}
}
static void mf_server_work(void* arg) {
struct mf_job* j = arg;
if (j->request.command) DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "member_files: disk start id=%llu cmd=%s group=%llx peer=%llx path=%s",
(unsigned long long)j->request.request_id, mf_command_text(j->request.command), (unsigned long long)j->group,
(unsigned long long)j->peer, j->request.path);
if (j->request.command) mf_storage_execute(j->files->storage, j->group, j->peer, &j->request, &j->response);
else mf_storage_sweep(j->files->storage, get_time_tb());
}
static void mf_server_done(void* arg, int error) {
struct mf_job* j = arg;
struct member_files* f = j->files; f->worker = NULL;
if (f->closing || !j->request.command) { mf_job_free(j); if (!f->closing) mf_server_pump(f); return; }
if (error) { j->response = j->request; j->response.kind = MF_RESPONSE; j->response.status = MF_BUSY; j->response.data_len = 0; }
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "member_files: disk done id=%llu cmd=%s status=%s error=%d jobs=%u",
(unsigned long long)j->request.request_id, mf_command_text(j->request.command), mf_status_text(j->response.status), error, f->job_count);
j->state = 2; mf_server_pump(f);
}
/* Виртуальный корень — действующие участники, в том числе ещё без файлов на диске. */
static void mf_roots(struct mf_job* j) {
struct mf_message* r = &j->response;
*r = j->request; r->kind = MF_RESPONSE; r->type = MF_DIRECTORY; r->data_len = 0; r->more = 0;
if (*r->path || r->command != MF_LIST) { r->status = MF_INVALID_PATH; return; }
char sql[256];
snprintf(sql, sizeof(sql), "SELECT node_id,join_ts,update_ts,adm_tags FROM \"peers_%llu\" WHERE deleted=0 AND source=0 ORDER BY node_id",
(unsigned long long)j->group);
sqlite3_stmt* st = NULL; EVP_MD_CTX* hash = EVP_MD_CTX_new();
if (!hash || EVP_DigestInit_ex(hash, EVP_sha256(), NULL) != 1 ||
sqlite3_prepare_v2(j->files->inst->topo_sqlite_db, sql, -1, &st, NULL) != SQLITE_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "member_files: virtual root query/hash failed"); r->status = MF_IO_ERROR; goto done;
}
uint64_t seen = 0; int rc;
while ((rc = sqlite3_step(st)) == SQLITE_ROW) {
uint8_t metadata[24];
uint64_t id = (uint64_t)sqlite3_column_int64(st, 0);
mf_put64(metadata, id); mf_put64(metadata + 8, (uint64_t)sqlite3_column_int64(st, 1));
mf_put64(metadata + 16, (uint64_t)sqlite3_column_int64(st, 2));
const char* tags = (const char*)sqlite3_column_text(st, 3);
if (EVP_DigestUpdate(hash, metadata, sizeof(metadata)) != 1 ||
(tags && EVP_DigestUpdate(hash, tags, strlen(tags) + 1) != 1)) { r->status = MF_IO_ERROR; goto done; }
if (seen++ < j->request.cursor || r->more) continue;
char name[32]; int n = snprintf(name, sizeof(name), "%llu", (unsigned long long)id);
if (r->data_len + MF_ENTRY_HEADER + (unsigned)n > MF_BLOCK_SIZE) { r->more = 1; continue; }
uint8_t* p = r->data + r->data_len;
memset(p, 0, MF_ENTRY_HEADER); p[0] = (uint8_t)n; p[2] = MF_DIRECTORY;
memcpy(p + MF_ENTRY_HEADER, name, (size_t)n); r->data_len += MF_ENTRY_HEADER + (uint32_t)n; r->cursor++;
}
uint8_t digest[32]; unsigned n;
if (rc != SQLITE_DONE || EVP_DigestFinal_ex(hash, digest, &n) != 1 || n != 32) { r->status = MF_IO_ERROR; goto done; }
memcpy(r->version, digest, 16);
if ((j->request.cursor && mf_zero(j->request.version, 16)) ||
(!mf_zero(j->request.version, 16) && memcmp(j->request.version, r->version, 16))) r->status = MF_STALE;
done:
sqlite3_finalize(st); EVP_MD_CTX_free(hash);
if (r->status) r->data_len = 0;
}
/* Один disk worker: I/O, sweep и namespace-мутации никогда не пересекаются. */
static void mf_server_pump(struct member_files* f) {
if (f->closing) return;
struct mf_job* j = f->jobs;
while (j) {
struct mf_job* next = j->next;
if (j->state == 2 && !j->waiter.internal) {
if (j->response.status == MF_OK && !mf_server_allowed(f, j->group, j->peer, &j->request)) {
j->response.status = MF_ACCESS_DENIED; j->response.data_len = 0;
}
int result = mf_send(f, j->group, j->peer, &j->response);
if (result <= 0) mf_job_free(j);
else etcp_router_on_send_ready(f->inst, j->group, j->peer, ETCP_RT_ID_MEMBER_FILES, &j->waiter, mf_server_ready, f);
}
j = next;
}
if (f->worker) {
DEBUG_TRACE(DEBUG_CATEGORY_DEBUG, "member_files: server waiting for disk id=%llu cmd=%s jobs=%u",
(unsigned long long)f->worker->request.request_id, mf_command_text(f->worker->request.command), f->job_count);
return;
}
for (j = f->jobs; j; j = j->next) if (!j->state) break;
if (!j && f->sweep_due && f->job_count < MF_MAX_JOBS) {
j = u_calloc(1, sizeof(*j));
if (!j) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "member_files: sweep allocation failed"); return; }
j->files = f; j->next = f->jobs; f->jobs = j; f->job_count++; f->sweep_due = 0;
}
if (!j) return;
if (j->request.command && !mf_server_allowed(f, j->group, j->peer, &j->request)) {
j->response = j->request; j->response.kind = MF_RESPONSE; j->response.status = MF_ACCESS_DENIED;
j->response.data_len = 0; j->state = 2; mf_server_pump(f); return;
}
j->state = 1; f->worker = j;
if (j->request.command) DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "member_files: disk queued id=%llu cmd=%s jobs=%u",
(unsigned long long)j->request.request_id, mf_command_text(j->request.command), f->job_count);
mf_worker_submit(f->disk, mf_server_work, j, mf_server_done, j);
}
static void mf_tick(void* arg) {
struct member_files* f = arg; f->tick = NULL;
uint64_t now = get_time_tb();
for (;;) {
struct mf_pending* expired = NULL;
for (struct mf_pending* p = f->pending; p; p = p->next) if (now - p->started >= MF_REQUEST_TB) { expired = p; break; }
if (!expired) break;
DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "member_files: timeout id=%llu cmd=%u",
(unsigned long long)expired->request.request_id, expired->request.command);
mf_pending_finish(expired, NULL, MF_TIMEOUT);
}
for (struct mf_pending* p = f->pending; p; p = p->next)
if (!p->sent || now - p->sent >= 50000) mf_pending_wake(p);
struct mf_job* j = f->jobs;
while (j) {
struct mf_job* next = j->next;
if (j != f->worker && j->created && now - j->created >= MF_REQUEST_TB) {
DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "member_files: server reply/queue timeout peer=%llu", (unsigned long long)j->peer); mf_job_free(j);
}
j = next;
}
f->sweep_due = f->used_storage; mf_server_pump(f);
f->tick = uasync_set_timeout(f->inst->ua, 10000, f, mf_tick, "member_files");
if (!f->tick) DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "member_files: timer allocation failed");
}
/* Общая обработка проверенного сетевого сообщения или команды внутри экземпляра. */
static void mf_dispatch(struct member_files* f, uint64_t group, uint64_t peer, const struct mf_message* message) {
struct mf_message m = *message;
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "member_files: rx id=%llu kind=%u cmd=%s group=%llx peer=%llx owner=%llx jobs=%u worker=%p",
(unsigned long long)m.request_id, m.kind, mf_command_text(m.command), (unsigned long long)group,
(unsigned long long)peer, (unsigned long long)m.owner, f->job_count, (void*)f->worker);
if (m.kind == MF_RESPONSE) {
for (struct mf_pending* p = f->pending; p; p = p->next) {
if (p->group != group || p->server != peer || p->request.request_id != m.request_id) continue;
if (p->request.command != m.command || p->request.owner != m.owner || strcmp(p->request.path, m.path)) {
DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "member_files: mismatched response id=%llu", (unsigned long long)m.request_id); return;
}
if (!member_files_member(f->inst, group, peer, 1)) { m.status = MF_ACCESS_DENIED; m.data_len = 0; }
mf_pending_finish(p, &m, 0); break;
}
return;
}
for (struct mf_job* other = f->jobs; other; other = other->next) if (other->group == group && other->peer == peer &&
other->request.request_id == m.request_id) return;
if (f->job_count >= MF_MAX_JOBS) {
DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "member_files: server queue full peer=%llu", (unsigned long long)peer); return;
}
struct mf_job* j = u_calloc(1, sizeof(*j));
if (!j) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "member_files: server job allocation failed"); return; }
j->files = f; j->group = group; j->peer = peer; j->created = get_time_tb(); j->request = m;
struct mf_job** tail = &f->jobs; while (*tail) tail = &(*tail)->next;
*tail = j; f->job_count++;
if (!mf_server_allowed(f, group, peer, &m)) {
j->response = m; j->response.kind = MF_RESPONSE; j->response.status = MF_ACCESS_DENIED; j->response.data_len = 0; j->state = 2;
DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "member_files: ACL denied group=%llu peer=%llu owner=%llu cmd=%u",
(unsigned long long)group, (unsigned long long)peer, (unsigned long long)m.owner, m.command);
} else if (!m.owner) { mf_roots(j); j->state = 2; }
else f->used_storage = 1;
mf_server_pump(f);
}
static void mf_recv(struct ETCP_CONN* conn, struct ll_entry* e) {
struct member_files* f = conn ? conn->instance->member_files : NULL;
if (!f || f->closing || !e || !e->dgram || e->len <= ROUTER_SVC_PAYLOAD_OFF) {
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "member_files: recv discarded context=%p closing=%d bytes=%u",
(void*)f, f ? f->closing : 0, e ? e->len : 0);
goto out;
}
uint64_t group, peer;
memcpy(&group, e->dgram + ROUTER_SVC_GROUP_OFF, 8); memcpy(&peer, e->dgram + ROUTER_SVC_SRC_OFF, 8);
if ((e->dgram[ROUTER_SVC_FLAGS_OFF] & (ROUTER_FLAG_SIGNED | ROUTER_FLAG_ENCRYPTED)) !=
(ROUTER_FLAG_SIGNED | ROUTER_FLAG_ENCRYPTED)) {
DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "member_files: unsigned/unencrypted packet peer=%llu", (unsigned long long)peer); goto out;
}
struct mf_message m;
if (!mf_decode(e->dgram + ROUTER_SVC_PAYLOAD_OFF, e->len - ROUTER_SVC_PAYLOAD_OFF, &m)) mf_dispatch(f, group, peer, &m);
out:
if (e) { queue_dgram_free(e); queue_entry_free(e); }
}
int member_files_init(struct UTUN_INSTANCE* inst) {
if (!inst || inst->member_files) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "member_files: invalid init"); return -1; }
struct member_files* f = u_calloc(1, sizeof(*f));
if (!f) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "member_files: owner allocation failed"); return -1; }
f->inst = inst;
f->disk = mf_worker_create(inst->ua); f->cleanup = mf_worker_reserve();
if (!f->disk || !f->cleanup) {
mf_worker_destroy(f->disk); mf_worker_unreserve(f->cleanup); u_free(f); return -1;
}
char path[2048]; const char* custom = chat_setting_get(inst, "member_files_path");
if (custom && !*custom) custom = NULL;
snprintf(path, sizeof(path), "%s%s%s", custom ? custom : inst->config->global.db_path, custom ? "" : "/", custom ? "" : "member_files");
if (!custom && !*inst->config->global.db_path) snprintf(path, sizeof(path), "member_files");
f->storage = mf_storage_create(path, (uint64_t)chat_setting_get_int(inst, "member_files_quota_mb", 1024) * 1024 * 1024,
(uint64_t)chat_setting_get_int(inst, "member_files_upload_ttl_sec", 3600) * 10000);
if (!f->storage || etcp_router_bind(inst, ETCP_RT_ID_MEMBER_FILES, mf_recv)) {
mf_storage_destroy(f->storage); mf_worker_destroy(f->disk); mf_worker_unreserve(f->cleanup); u_free(f); return -1;
}
inst->member_files = f;
f->tick = uasync_set_timeout(inst->ua, 10000, f, mf_tick, "member_files");
if (!f->tick) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "member_files: init timer failed"); member_files_destroy(inst); return -1; }
DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "member_files: initialized root=%s idle=10s max_requests=%d max_jobs=%d", path, MF_MAX_PENDING, MF_MAX_JOBS);
return 0;
}
void member_files_quiesce(struct UTUN_INSTANCE* inst) {
struct member_files* f = inst ? inst->member_files : NULL;
if (!f || f->closing) return;
f->closing = 1;
if (f->pump) uasync_call_soon_cancel(inst->ua, f->pump);
f->pump = NULL;
if (f->tick) uasync_cancel_timeout(inst->ua, f->tick);
f->tick = NULL;
mf_transfers_quiesce(f, 0);
while (f->pending) member_files_cancel(f->pending);
while (f->locals) mf_local_free(f->locals);
struct mf_job* j = f->jobs;
while (j) { struct mf_job* next = j->next; if (j != f->worker) mf_job_free(j); j = next; }
etcp_router_unbind(inst, ETCP_RT_ID_MEMBER_FILES);
DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "member_files: quiesce workers=%d transfers=%u", f->worker != NULL, f->transfer_count);
}
static void mf_storage_cleanup(void* arg) { mf_storage_destroy(arg); }
static void mf_cleanup_done(void* arg, int error) { (void)arg; (void)error; }
void member_files_destroy(struct UTUN_INSTANCE* inst) {
struct member_files* f = inst ? inst->member_files : NULL;
if (!f) return;
member_files_quiesce(inst);
mf_worker_submit_reserved(f->disk, f->cleanup, mf_storage_cleanup, f->storage, mf_cleanup_done, NULL);
f->cleanup = NULL;
mf_worker_destroy(f->disk);
u_free(f); inst->member_files = NULL;
}
void member_files_cancel_group(struct UTUN_INSTANCE* inst, uint64_t group) {
struct member_files* f = inst ? inst->member_files : NULL;
if (!f) return;
mf_transfers_quiesce(f, group);
struct mf_local* local = f->locals;
while (local) { struct mf_local* next = local->next; if (local->group == group) mf_local_free(local); local = next; }
for (;;) {
struct mf_pending* found = NULL;
for (struct mf_pending* p = f->pending; p; p = p->next) if (p->group == group) { found = p; break; }
if (!found) break;
member_files_cancel(found);
}
struct mf_job* j = f->jobs;
while (j) { struct mf_job* next = j->next; if (j->group == group && j != f->worker) mf_job_free(j); j = next; }
}
static void mf_gui_done(void* arg, const struct mf_message* response) {
struct mf_gui_request* q = arg;
uint8_t event[16 + MF_WIRE_MAX]; mf_put64(event, q->group); mf_put64(event + 8, q->server);
int n = mf_encode(response, event + 16, MF_WIRE_MAX);
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "member_files: GUI result id=%llu cmd=%s status=%s bytes=%d",
(unsigned long long)response->request_id, mf_command_text(response->command), mf_status_text(response->status), n);
if (n > 0) chat_event_post(q->inst, CHAT_EVT_MEMBER_FILES_RESPONSE, event, n + 16);
u_free(q);
}
void member_files_gui_request(void* arg) {
struct mf_gui_request* q = arg;
if (member_files_request(q->inst, q->group, q->server, &q->message, mf_gui_done, q)) return;
q->message.kind = MF_RESPONSE; q->message.status = MF_ACCESS_DENIED; q->message.data_len = 0;
mf_gui_done(q, &q->message);
}