Insert generated config fields before blank section separators

This commit is contained in:
evgeny committed 2026-10-28 12:41:10 +01:00
1 parent 41930a35c4
commit f9ccca85bb
3 files changed
+50 -4

No files matched your search

+7 -3
View File
@@ -73,9 +73,10 @@ static int config_field_line(char *line, int *section, const struct config_field
return -1;
}
/* Собрать последние текстовые значения и конец последней секции каждого вида. */
/* Собрать последние значения и точку вставки до завершающих пустых строк каждой секции. */
static void config_scan(const char *text, size_t length, struct config_field fields[CFG_FIELD_COUNT], size_t ends[CFG_SECTION_COUNT]) {
int section = CFG_OTHER_SECTION;
size_t content_end = 0;
for (int i = 0; i < CFG_SECTION_COUNT; i++) ends[i] = SIZE_MAX;
for (size_t pos = 0; pos < length;) {
const char *nl = memchr(text + pos, '\n', length - pos);
@@ -84,7 +85,8 @@ static void config_scan(const char *text, size_t length, struct config_field fie
memcpy(line, text + pos, end - pos); line[end - pos] = '\0';
char *start = line;
while (isspace((unsigned char)*start)) start++;
if (*start == '[') ends[section] = pos;
int nonempty = *start != '\0';
if (*start == '[') ends[section] = content_end;
int index = config_field_line(line, &section, fields, value);
if (index >= 0) {
strcpy(fields[index].value, value); fields[index].present = 1;
@@ -100,9 +102,10 @@ static void config_scan(const char *text, size_t length, struct config_field fie
strcasecmp(value, "false") && strcasecmp(value, "true")) fields[index].invalid = 1;
} else if (config_ini_hex(value, NULL, SC_PRIVKEY_SIZE)) fields[index].invalid = 1;
}
if (nonempty) content_end = end;
pos = end;
}
ends[section] = length;
ends[section] = content_end;
}
/* Вставить отсутствующие поля в нужную секцию, при необходимости создать заголовок. */
@@ -117,6 +120,7 @@ static int config_insert_missing(struct strbuf *output, const struct config_fiel
started = 1;
}
if (strbuf_addf(output, "%s=%s%s", fields[i].key, fields[i].value, eol) < 0) return -1;
DEBUG_DEBUG(DEBUG_CATEGORY_CONFIG, "inserted [%s] %s before section separator", config_section_names[section], fields[i].key);
}
return 0;
}
+3 -1
View File
@@ -36,7 +36,9 @@ HEX проверяется посимвольно до декодировани
Корректная итоговая идентичность/REALITY не меняется и не генерируется заново только из-за раннего дубля.
Файл без таких ошибок остаётся побайтно неизменным. Если поле требует исправления,
обновляются все его вхождения в соответствующих секциях. Недостающие поля добавляются
в конец последней подходящей секции; отсутствующая секция создаётся в конце файла.
в конец последней подходящей секции, перед её завершающими пустыми строками.
Разделитель перед следующей секцией остаётся после добавленных полей; пробелы и CRLF в нём сохраняются.
Отсутствующая секция создаётся в конце файла.
Посторонние строки сохраняются побайтно. В изменяемых строках сохраняются пробелы,
комментарии и окончания строк вокруг заменяемого значения. Добавляемые строки используют
+40
View File
@@ -367,6 +367,45 @@ static void test_allocation_failures(void) {
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "PASS allocation failures: every allocation, original bytes and resource balance");
}
/* Недостающие поля остаются в блоке; исходные пустые строки продолжают отделять секции. */
static void test_insertion_before_separator(void) {
const struct { const char *input, *anchor, *first, *last, *suffix; } cases[] = {
{"[chatserver]\nstorage_autoload=1\n\n[gui]\nutun_enabled=no\n",
"storage_autoload=1", "add_key=", "add_key=", "\n[gui]\nutun_enabled=no\n"},
{"[chatserver]\nstorage_autoload=1\n\n\n",
"storage_autoload=1", "add_key=", "add_key=", "\n\n"},
{"[chatserver]\n\n[gui]\nutun_enabled=no\n",
"[chatserver]", "add_key=", "add_key=", "\n[gui]\nutun_enabled=no\n"},
{"[chatserver]\nstorage_autoload=0\n\n[chatserver]\nstorage_autoload=1\n\n[gui]\nutun_enabled=no\n",
"storage_autoload=1", "add_key=", "add_key=", "\n[gui]\nutun_enabled=no\n"},
{"[chatserver]\nstorage_autoload=1\n \t\n\n[gui]\nutun_enabled=no\n",
"storage_autoload=1", "add_key=", "add_key=", " \t\n\n[gui]\nutun_enabled=no\n"},
{"[global]\nmy_private_key=%s\n\n[gui]\nutun_enabled=no\n",
"my_private_key=", "my_public_key=", "my_node_id=", "\n[gui]\nutun_enabled=no\n"},
{"[reality]\nenabled=1\n\n[gui]\nutun_enabled=no\n",
"enabled=1", "private_key=", "short_ids=", "\n[gui]\nutun_enabled=no\n"}
};
for (int crlf = 0; crlf < 2; crlf++) for (size_t i = 0; i < sizeof(cases) / sizeof(*cases); i++) {
char plain[2048], input[4096], suffix[4096], after[8192], unchanged[8192];
snprintf(plain, sizeof(plain), cases[i].input, private_hex);
size_t pos = 0;
for (const char* p = plain; *p; p++) { if (crlf && *p == '\n') input[pos++] = '\r'; input[pos++] = *p; }
input[pos] = 0; pos = 0;
for (const char* p = cases[i].suffix; *p; p++) { if (crlf && *p == '\n') suffix[pos++] = '\r'; suffix[pos++] = *p; }
suffix[pos] = 0;
const char* eol = crlf ? "\r\n" : "\n";
write_text(input); assert(!config_ensure_keys_and_node_id(path)); read_text(after, sizeof(after));
const char* anchor = strstr(after, cases[i].anchor); assert(anchor);
const char* first = strstr(anchor, cases[i].first);
assert(first && first == strstr(anchor, eol) + strlen(eol));
const char* last = strstr(first, cases[i].last); assert(last);
const char* end = strstr(last, eol); assert(end);
assert(!strncmp(end + strlen(eol), suffix, strlen(suffix)));
assert(!config_ensure_keys_and_node_id(path)); read_text(unchanged, sizeof(unchanged)); assert(!strcmp(after, unchanged));
}
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "PASS field insertion: 14 LF/CRLF separator, EOF, empty/repeated section and stable-repeat cases");
}
static void test_server_key(void) {
char before[8192], after[8192]; uint8_t key[32];
write_text("[chatserver]\n# server credential\n");
@@ -401,6 +440,7 @@ int main(void) {
test_reality_and_validation();
test_failures();
test_allocation_failures();
test_insertion_before_separator();
test_server_key();
assert(!unlink(path) && !rmdir(directory));
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "PASS config updater regression suite");