Preserve UTF-8 boundaries in DM peer names
This commit is contained in:
1 parent
fa76447f53
commit
d09ed0316f
4 files changed
+235
-8
No files matched your search
+43
-6
@@ -217,7 +217,7 @@ struct dm_conv {
|
||||
uint64_t peer_node_id;
|
||||
uint8_t peer_x25519[32];
|
||||
uint8_t peer_ed25519[32];
|
||||
char peer_name[64];
|
||||
char peer_name[DM_PEER_NAME_MAX + 1];
|
||||
uint64_t group_id;
|
||||
uint8_t content_key[DM_CONTENT_KEY_SIZE];
|
||||
uint64_t last_out_seq;
|
||||
@@ -227,6 +227,37 @@ struct dm_conv {
|
||||
|
||||
enum dm_conv_result { DM_CONV_ERROR = -1, DM_CONV_FOUND = 0, DM_CONV_NOT_FOUND = 1 };
|
||||
|
||||
/* Копировать целый UTF-8 префикс; NUL внутри SQL-строки и битый суффикс не попадают в имя/JSON. */
|
||||
static void dm_copy_peer_name(char out[DM_PEER_NAME_MAX + 1], const char* name, size_t len, const char* conv) {
|
||||
size_t size = 0;
|
||||
while (size < len && size < DM_PEER_NAME_MAX) {
|
||||
unsigned c = (unsigned char)name[size];
|
||||
if (c && c < 0x80) { size++; continue; }
|
||||
unsigned count, scalar, minimum;
|
||||
if (c >= 0xc2 && c <= 0xdf) { count = 1; scalar = c & 31; minimum = 0x80; }
|
||||
else if (c >= 0xe0 && c <= 0xef) { count = 2; scalar = c & 15; minimum = 0x800; }
|
||||
else if (c >= 0xf0 && c <= 0xf4) { count = 3; scalar = c & 7; minimum = 0x10000; }
|
||||
else goto invalid;
|
||||
if (count > len - size - 1) goto invalid;
|
||||
for (unsigned i = 1; i <= count; i++) {
|
||||
c = (unsigned char)name[size + i];
|
||||
if ((c & 0xc0) != 0x80) goto invalid;
|
||||
scalar = (scalar << 6) | (c & 63);
|
||||
}
|
||||
if (scalar < minimum || scalar > 0x10ffff || (scalar >= 0xd800 && scalar <= 0xdfff)) goto invalid;
|
||||
if (count + 1 > DM_PEER_NAME_MAX - size) break;
|
||||
size += count + 1;
|
||||
}
|
||||
if (size < len) DEBUG_DEBUG(DEBUG_CATEGORY_DM, "%s: peer name shortened conv=%s bytes=%zu->%zu", DM_ID, conv, len, size);
|
||||
goto done;
|
||||
invalid:
|
||||
DEBUG_WARN(DEBUG_CATEGORY_DM, "%s: invalid peer name encoding conv=%s offset=%zu bytes=%zu; suffix omitted",
|
||||
DM_ID, conv, size, len);
|
||||
done:
|
||||
if (size) memcpy(out, name, size);
|
||||
out[size] = '\0';
|
||||
}
|
||||
|
||||
/* FOUND выдаёт полную беседу; NOT_FOUND — только SQLITE_DONE; ERROR не меняет *c и БД. */
|
||||
static enum dm_conv_result dm_conv_load(struct dm_state* dm, const char* conv_id, struct dm_conv* c) {
|
||||
sqlite3_stmt* st = NULL;
|
||||
@@ -259,7 +290,7 @@ static enum dm_conv_result dm_conv_load(struct dm_state* dm, const char* conv_id
|
||||
memcpy(loaded.peer_x25519, x, 32);
|
||||
memcpy(loaded.peer_ed25519, e, 32);
|
||||
const char* name = (const char*)sqlite3_column_text(st, 3);
|
||||
if (name) snprintf(loaded.peer_name, sizeof(loaded.peer_name), "%s", name);
|
||||
dm_copy_peer_name(loaded.peer_name, name, (size_t)sqlite3_column_bytes(st, 3), conv_id);
|
||||
loaded.group_id = (uint64_t)sqlite3_column_int64(st, 4);
|
||||
loaded.last_out_seq = (uint64_t)sqlite3_column_int64(st, 5);
|
||||
loaded.last_in_seq = (uint64_t)sqlite3_column_int64(st, 6);
|
||||
@@ -493,7 +524,9 @@ int dm_accept_message(struct UTUN_INSTANCE* inst, const uint8_t* msg, size_t len
|
||||
}
|
||||
if (dm_node_pubkeys(dm, author, c.peer_x25519, c.peer_ed25519) != 0 ||
|
||||
dm_derive_content_key(inst->my_keys.private_key, c.peer_x25519, c.content_key) != 0) return -1;
|
||||
chat_core_get_node_name(inst, author, c.peer_name, sizeof(c.peer_name));
|
||||
char name[128] = {0};
|
||||
chat_core_get_node_name(inst, author, name, sizeof(name));
|
||||
dm_copy_peer_name(c.peer_name, name, strlen(name), conv_id);
|
||||
}
|
||||
if (c.peer_node_id != author) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_DM, "%s: conversation author mismatch conv=%s", DM_ID, conv_id);
|
||||
@@ -760,7 +793,7 @@ int dm_start(struct UTUN_INSTANCE* inst, uint64_t peer_node_id,
|
||||
c.peer_node_id = peer_node_id;
|
||||
memcpy(c.peer_x25519, peer_x25519, 32);
|
||||
memcpy(c.peer_ed25519, peer_ed25519, 32);
|
||||
if (peer_name) snprintf(c.peer_name, sizeof(c.peer_name), "%s", peer_name);
|
||||
dm_copy_peer_name(c.peer_name, peer_name, peer_name ? strlen(peer_name) : 0, conv_id);
|
||||
c.group_id = source_ch_id ? strtoull(source_ch_id, NULL, 10) : 0;
|
||||
if (dm_derive_content_key(inst->my_keys.private_key, c.peer_x25519, c.content_key) != 0) return -1;
|
||||
|
||||
@@ -927,17 +960,21 @@ int dm_list_conversations_json(struct UTUN_INSTANCE* inst, char* buf, size_t buf
|
||||
const char* conv_id = (const char*)sqlite3_column_text(st, 0);
|
||||
uint64_t peer = (uint64_t)sqlite3_column_int64(st, 1);
|
||||
const char* name = (const char*)sqlite3_column_text(st, 2);
|
||||
size_t name_len = (size_t)sqlite3_column_bytes(st, 2);
|
||||
uint64_t last_ts = (uint64_t)sqlite3_column_int64(st, 3);
|
||||
uint64_t out_seq = (uint64_t)sqlite3_column_int64(st, 4);
|
||||
uint64_t in_seq = (uint64_t)sqlite3_column_int64(st, 5);
|
||||
char resolved[128];
|
||||
if (!name || !name[0]) {
|
||||
if (!name || !name_len) {
|
||||
resolved[0] = '\0';
|
||||
chat_core_get_node_name(inst, peer, resolved, sizeof(resolved));
|
||||
name = resolved;
|
||||
name_len = strlen(resolved);
|
||||
}
|
||||
char safe_name[DM_PEER_NAME_MAX + 1];
|
||||
dm_copy_peer_name(safe_name, name, name_len, conv_id);
|
||||
char esc_name[512];
|
||||
json_escape(name ? name : "", esc_name, sizeof(esc_name));
|
||||
json_escape(safe_name, esc_name, sizeof(esc_name));
|
||||
if (strbuf_addf(&json,
|
||||
"%s{\"conv_id\":\"%s\",\"peer_node_id\":%llu,\"peer_name\":\"%s\","
|
||||
"\"last_ts\":%llu,\"out_seq\":%llu,\"in_seq\":%llu,\"generation\":%llu}",
|
||||
|
||||
+3
-2
@@ -34,6 +34,7 @@ extern "C" {
|
||||
#define DM_MSG_SIG_SIZE 64
|
||||
#define DM_DATA_MAX 1024
|
||||
#define DM_RECEIPT_SIZE 136
|
||||
#define DM_PEER_NAME_MAX 63 /* UTF-8 байты без NUL; сохраняются только целые code points. */
|
||||
|
||||
/* Вычислить полную длину канонического тела из его заголовка.
|
||||
* Возвращает 1 и *out_len, либо 0 если буфер мал/повреждён (выход не меняется).
|
||||
@@ -78,6 +79,7 @@ void dm_core_destroy(struct UTUN_INSTANCE* inst);
|
||||
* ещё нет, выводит conv_id/ключи. Подключение для передачи медиа принадлежит media.
|
||||
* Снимает hidden без смены поколения; публикует CONV_UPDATED с текущим поколением.
|
||||
* Ключи обязательны, peer_name/source_ch_id могут быть NULL; переданные ключи пира проверяет вызывающий.
|
||||
* Имя ограничено DM_PEER_NAME_MAX байтами по границе UTF-8; некорректный суффикс отбрасывается с WARN.
|
||||
* Возвращает 0 при успехе, <0 при ошибке.
|
||||
* Отказ чтения/проверки ключей не создаёт беседу, не меняет её поля/hidden и не выдаёт CONV_UPDATED.
|
||||
* Создание разрешено только после успешного SELECT без строки; существующие seq сохраняются. */
|
||||
@@ -104,8 +106,7 @@ void dm_cancel_pending_history(struct UTUN_INSTANCE* inst, const char* conv);
|
||||
|
||||
/* До 200 видимых бесед по last_ts DESC: [{conv_id,peer_node_id,peer_name,last_ts,out_seq,in_seq,generation}].
|
||||
* buf вмещает JSON и NUL; 0 и *out_len без NUL при успехе, -1 при ошибке. Все вызовы в uasync.
|
||||
* Известный дефект: имя может быть обрезано внутри UTF-8 в dm_start/load, и результат с кодом0 будет
|
||||
* некорректным UTF-8 JSON. См. doc/dm_audit_findings.txt, раздел8. */
|
||||
* Имя из БД либо nodes нормализуется по правилам dm_start; снимок не меняет сохранённые строки. */
|
||||
int dm_list_conversations_json(struct UTUN_INSTANCE* inst, char* buf, size_t buf_size, size_t* out_len);
|
||||
|
||||
/* Сообщения беседы в JSON: расшифрованное содержимое, статусы доставки и медиа.
|
||||
|
||||
@@ -171,6 +171,7 @@ check_PROGRAMS += \
|
||||
test_etcp_router \
|
||||
test_dm_worker \
|
||||
test_dm_history \
|
||||
test_dm_names \
|
||||
test_chat_join \
|
||||
test_radio \
|
||||
test_bgp_route_exchange \
|
||||
@@ -904,6 +905,10 @@ test_dm_history_SOURCES = test_dm_history.c
|
||||
test_dm_history_CFLAGS = $(test_dm_e2e_CFLAGS)
|
||||
test_dm_history_LDADD = $(LIBUTUN) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
|
||||
test_dm_names_SOURCES = test_dm_names.c
|
||||
test_dm_names_CFLAGS = $(test_dm_e2e_CFLAGS)
|
||||
test_dm_names_LDADD = $(LIBUTUN) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
|
||||
if OS_LINUX
|
||||
test_dm_load_SOURCES = test_dm_load.c
|
||||
test_dm_load_CFLAGS = $(test_dm_e2e_CFLAGS)
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
/* Настоящие DM/SQLite: целые UTF-8 имена при создании, загрузке, fallback и restart. */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "../src/utun_instance.h"
|
||||
#include "../src/dm/dm_core.h"
|
||||
#include "../src/dm/dm_crypto.h"
|
||||
#include "../src/chat/chat_core.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include "../lib/json_flat.h"
|
||||
#include "../lib/mem.h"
|
||||
#include "test_utils.h"
|
||||
|
||||
#define EXPECT(condition) do { if (!(condition)) { \
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_DM, "DM name test failed line=%d", __LINE__); return -1; \
|
||||
} } while (0)
|
||||
static unsigned cases, snapshots;
|
||||
|
||||
static struct UTUN_INSTANCE* start(struct UASYNC* ua, const char* directory, const struct SC_MYKEYS* identity) {
|
||||
char config[1536], pub[65], priv[65];
|
||||
for (unsigned i = 0; i < 32; i++) {
|
||||
snprintf(pub + 2 * i, 3, "%02x", identity->public_key[i]);
|
||||
snprintf(priv + 2 * i, 3, "%02x", identity->private_key[i]);
|
||||
}
|
||||
snprintf(config, sizeof(config), "[global]\nmy_public_key=%s\nmy_private_key=%s\ndb_path=%s\n"
|
||||
"[chatserver]\ngroup_autoconnect=0\nstorage_autoload=0\n", pub, priv, directory);
|
||||
struct UTUN_INSTANCE* inst = utun_instance_create_from_str(ua, config);
|
||||
if (inst && (utun_core_start(inst) || chat_service_start(inst))) { utun_instance_destroy(inst); return NULL; }
|
||||
return inst;
|
||||
}
|
||||
|
||||
static int stored(sqlite3* db, const char* conv, const char* expected, size_t len) {
|
||||
sqlite3_stmt* st = NULL;
|
||||
EXPECT(sqlite3_prepare_v2(db, "SELECT CAST(peer_name AS BLOB) FROM dm_conversations WHERE conv_id=?", -1, &st, NULL) == SQLITE_OK);
|
||||
sqlite3_bind_text(st, 1, conv, -1, SQLITE_STATIC);
|
||||
int rc = sqlite3_step(st);
|
||||
int exact = rc == SQLITE_ROW && (size_t)sqlite3_column_bytes(st, 0) == len &&
|
||||
(!len || !memcmp(sqlite3_column_blob(st, 0), expected, len));
|
||||
sqlite3_finalize(st);
|
||||
EXPECT(exact);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int replace(sqlite3* db, const char* conv, const char* name, size_t len) {
|
||||
sqlite3_stmt* st = NULL;
|
||||
EXPECT(sqlite3_prepare_v2(db, "UPDATE dm_conversations SET peer_name=? WHERE conv_id=?", -1, &st, NULL) == SQLITE_OK);
|
||||
sqlite3_bind_text(st, 1, name, (int)len, SQLITE_STATIC);
|
||||
sqlite3_bind_text(st, 2, conv, -1, SQLITE_STATIC);
|
||||
int rc = sqlite3_step(st);
|
||||
sqlite3_finalize(st);
|
||||
EXPECT(rc == SQLITE_DONE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Точные ожидаемые строки проверяются здесь; полный JSON отдельно сохраняется для строгого внешнего парсера. */
|
||||
static int snapshot(struct UTUN_INSTANCE* inst, const char* conv, const char* expected, FILE* file) {
|
||||
char json[65536], token[96], member[1024], actual[256]; size_t len = 0;
|
||||
int changes = sqlite3_total_changes(inst->topo_sqlite_db);
|
||||
EXPECT(!dm_list_conversations_json(inst, json, sizeof(json), &len));
|
||||
EXPECT(len == strlen(json) && sqlite3_total_changes(inst->topo_sqlite_db) == changes);
|
||||
snprintf(token, sizeof(token), "\"conv_id\":\"%s\"", conv);
|
||||
const char* row = strstr(json, token);
|
||||
const char* name = row ? strstr(row, "\"peer_name\":") : NULL;
|
||||
EXPECT(name);
|
||||
/* json_flat_get останавливается после первого ключа, до числовых полей снимка. */
|
||||
snprintf(member, sizeof(member), "{%s", name);
|
||||
EXPECT(!json_flat_get(member, "peer_name", actual, sizeof(actual)) && !strcmp(actual, expected));
|
||||
EXPECT(fwrite(json, 1, len, file) == len && fputc('\n', file) != EOF);
|
||||
snapshots++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int check_case(struct UTUN_INSTANCE* inst, const char* input, size_t len, const char* expected,
|
||||
FILE* file, char last[64]) {
|
||||
struct SC_MYKEYS peer; uint8_t ed[32];
|
||||
EXPECT(!sc_generate_keypair(&peer) && !sc_derive_ed25519_pubkey(peer.private_key, ed));
|
||||
uint64_t id = sc_derive_node_id_from_pubkey(peer.public_key);
|
||||
snprintf(last, 64, "%llu", (unsigned long long)dm_derive_conv_id(inst->node_id, id));
|
||||
sqlite3* db = inst->topo_sqlite_db;
|
||||
sqlite3_stmt* st = NULL;
|
||||
EXPECT(sqlite3_prepare_v2(db, "INSERT INTO nodes(node_id,x25519_pubkey,ed25519_pubkey,name) VALUES(?,?,?,?)",
|
||||
-1, &st, NULL) == SQLITE_OK);
|
||||
sqlite3_bind_int64(st, 1, (sqlite3_int64)id);
|
||||
sqlite3_bind_blob(st, 2, peer.public_key, 32, SQLITE_STATIC); sqlite3_bind_blob(st, 3, ed, 32, SQLITE_STATIC);
|
||||
sqlite3_bind_text(st, 4, input, (int)len, SQLITE_STATIC);
|
||||
int rc = sqlite3_step(st); sqlite3_finalize(st); EXPECT(rc == SQLITE_DONE);
|
||||
EXPECT(!dm_start(inst, id, peer.public_key, ed, input, NULL));
|
||||
EXPECT(!stored(db, last, expected, strlen(expected)) && !snapshot(inst, last, expected, file));
|
||||
|
||||
/* Старый SQL может содержать полное длинное имя, оборванный UTF-8 или NUL внутри TEXT. */
|
||||
EXPECT(!replace(db, last, input, len));
|
||||
EXPECT(!snapshot(inst, last, expected, file) && !stored(db, last, input, len));
|
||||
char messages[8192]; size_t bytes = 0;
|
||||
int changes = sqlite3_total_changes(db);
|
||||
EXPECT(!dm_list_messages_json(inst, last, 10, 0, messages, sizeof(messages), &bytes) && !strcmp(messages, "[]"));
|
||||
EXPECT(sqlite3_total_changes(db) == changes && !stored(db, last, input, len));
|
||||
EXPECT(!dm_send(inst, last, "text", (const uint8_t*)"one", 3));
|
||||
EXPECT(!stored(db, last, expected, strlen(expected)) && !snapshot(inst, last, expected, file));
|
||||
|
||||
/* Пустое сохранённое имя разрешается через nodes: этот путь тоже обязан выдавать целый UTF-8. */
|
||||
EXPECT(!replace(db, last, "", 0) && !snapshot(inst, last, expected, file) && !stored(db, last, "", 0));
|
||||
/* Убрать повреждение фикстуры: следующие снимки не должны повторять предупреждения прошлых случаев. */
|
||||
EXPECT(!replace(db, last, expected, strlen(expected)));
|
||||
EXPECT(sqlite3_prepare_v2(db, "UPDATE nodes SET name=? WHERE node_id=?", -1, &st, NULL) == SQLITE_OK);
|
||||
sqlite3_bind_text(st, 1, expected, -1, SQLITE_STATIC); sqlite3_bind_int64(st, 2, (sqlite3_int64)id);
|
||||
rc = sqlite3_step(st); sqlite3_finalize(st); EXPECT(rc == SQLITE_DONE);
|
||||
cases++;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "DM name test: case=%u input=%zu kept=%zu create/load/save/fallback PASS",
|
||||
cases, len, strlen(expected));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int run(struct UTUN_INSTANCE* inst, FILE* file, char last[64], char expected[256]) {
|
||||
char input[512];
|
||||
const size_t lengths[] = {0, 1, 62, 63, 64, 127};
|
||||
for (size_t i = 0; i < sizeof(lengths) / sizeof(lengths[0]); i++) {
|
||||
size_t len = lengths[i], keep = len < 63 ? len : 63;
|
||||
memset(input, 'A', len); input[len] = 0; memset(expected, 'A', keep); expected[keep] = 0;
|
||||
EXPECT(!check_case(inst, input, len, expected, file, last));
|
||||
}
|
||||
const char* points[] = {"\xd0\xaf", "\xe2\x82\xac", "\xf0\x9f\x98\x80", "\xf4\x8f\xbf\xbf"};
|
||||
for (size_t p = 0; p < sizeof(points) / sizeof(points[0]); p++) {
|
||||
size_t width = strlen(points[p]);
|
||||
for (size_t prefix = 58; prefix <= 64; prefix++) {
|
||||
memset(input, 'a', prefix); memcpy(input + prefix, points[p], width); input[prefix + width] = 'z';
|
||||
input[prefix + width + 1] = 0;
|
||||
size_t keep = prefix < 63 ? prefix : 63;
|
||||
memcpy(expected, input, keep);
|
||||
if (prefix + width <= 63) {
|
||||
memcpy(expected + keep, points[p], width); keep += width;
|
||||
if (keep < 63) expected[keep++] = 'z';
|
||||
}
|
||||
expected[keep] = 0;
|
||||
EXPECT(!check_case(inst, input, prefix + width + 1, expected, file, last));
|
||||
}
|
||||
for (size_t count = 63 / width; count <= 63 / width + 1; count++) {
|
||||
for (size_t n = 0; n < count; n++) memcpy(input + n * width, points[p], width);
|
||||
input[count * width] = 0;
|
||||
size_t keep = (count * width <= 63 ? count : 63 / width) * width;
|
||||
memcpy(expected, input, keep); expected[keep] = 0;
|
||||
EXPECT(!check_case(inst, input, count * width, expected, file, last));
|
||||
}
|
||||
}
|
||||
const char* invalid[] = {"\x80", "\xc0\xaf", "\xe0\x80\x80", "\xf0\x80\x80\x80", "\xed\xa0\x80",
|
||||
"\xf4\x90\x80\x80", "\xf5\x80\x80\x80", "\xff", "\xc2", "\xe2\x82", "\xf0\x9f\x98", "\xd0q"};
|
||||
for (size_t i = 0; i < sizeof(invalid) / sizeof(invalid[0]); i++) {
|
||||
for (size_t prefix = 0; prefix <= 2; prefix += 2) {
|
||||
memset(input, 'a', prefix); memcpy(input + prefix, invalid[i], strlen(invalid[i]) + 1);
|
||||
memset(expected, 'a', prefix); expected[prefix] = 0;
|
||||
EXPECT(!check_case(inst, input, prefix + strlen(invalid[i]), expected, file, last));
|
||||
}
|
||||
}
|
||||
memcpy(input, "OK\0hidden", 9); input[9] = 0; snprintf(expected, 256, "OK");
|
||||
EXPECT(!check_case(inst, input, 9, expected, file, last));
|
||||
snprintf(input, sizeof(input), "quote\" slash\\ line\n%s", points[0]); memcpy(expected, input, strlen(input) + 1);
|
||||
EXPECT(!check_case(inst, input, strlen(input), expected, file, last));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
debug_config_init(); debug_set_level(DEBUG_LEVEL_ERROR);
|
||||
debug_set_category_level(DEBUG_CATEGORY_DM, getenv("UTUN_TEST_DEBUG") ? DEBUG_LEVEL_DEBUG : DEBUG_LEVEL_WARN);
|
||||
debug_set_category_level(DEBUG_CATEGORY_DEBUG, DEBUG_LEVEL_INFO);
|
||||
size_t allocations = u_get_allocated_count();
|
||||
char directory[512] = "/tmp/utun_dm_names_XXXXXX", path[768], last[64], expected[256];
|
||||
struct UASYNC* ua = NULL; struct UTUN_INSTANCE* inst = NULL; FILE* file = NULL;
|
||||
struct SC_MYKEYS identity; int failed = 1;
|
||||
if (test_mkdtemp(directory) || sc_generate_keypair(&identity) || !(ua = uasync_create())) goto done;
|
||||
if (!(inst = start(ua, directory, &identity))) goto done;
|
||||
snprintf(path, sizeof(path), "%s/names.jsonl", directory); file = fopen(path, "wb");
|
||||
if (!file || run(inst, file, last, expected)) goto done;
|
||||
utun_instance_destroy(inst); inst = start(ua, directory, &identity);
|
||||
if (!inst || snapshot(inst, last, expected, file)) goto done;
|
||||
failed = 0;
|
||||
done:
|
||||
if (failed) DEBUG_ERROR(DEBUG_CATEGORY_DM, "DM name test failed after cases=%u snapshots=%u", cases, snapshots);
|
||||
if (file && fclose(file)) failed = 1;
|
||||
if (inst) utun_instance_destroy(inst);
|
||||
if (ua) uasync_destroy(ua, 0);
|
||||
if (allocations != u_get_allocated_count()) failed = 1;
|
||||
printf("test_dm_names: cases=%u snapshots=%u allocations=%zu->%zu %s artifacts=%s\n",
|
||||
cases, snapshots, allocations, u_get_allocated_count(), failed ? "FAIL" : "PASS", directory);
|
||||
return failed;
|
||||
}
|
||||
Reference in new issue
Block a user