open
https://gitlab.synchro.net/main/sbbs/-/issues/1168
## Summary
When an ini **section header exists but the section is empty** (its header is immediately followed by another `[section]` header, or by EOF), `iniSetString()`
/ `iniSetValue()` writes new keys for that section at the **end of the file** —
under whatever the last section happens to be — instead of into the intended (empty) section. The misfiled keys then cannot be read back via `iniGetString(section, key)`, and repeated rewrites accumulate duplicates.
## Root cause
`section_start()` in `src/xpdev/ini_file.c` (~line 196) returns `strListCount(list)` (the end of the list) when the section is empty:
```c
static size_t section_start(str_list_t list, size_t index)
{
char* p = list[index];
if (p != NULL) {
SKIP_WHITESPACE(p);
if (*p == INI_OPEN_SECTION_CHAR) // A new section starts immediately?
return strListCount(list);
}
return index;
}
```
`find_section()` -> `get_value()` use this as the search/insert start point. For
an empty section it points past the end of the list, so `ini_set_string()` inserts the new key at the very end of the file (under the last section), and a later `iniGetString()` for the intended section never finds it.
## Minimal repro (links libxpdev)
```c
str_list_t b = strListInit();
iniSetString(&b, "input", "kpturn", "50", NULL);
iniSetString(&b, "video", "frames_in_flight", "4", NULL);
iniRemoveKey(&b, "input", "kpturn"); // empties [input]; its header remains
iniSetString(&b, "input", "mouse", "off", NULL); // intended for [input]
// iniGetString(b, "input", "mouse", ...) -> NOT FOUND
```
Resulting file:
```
[input]
[video]
frames_in_flight=4
mouse=off <- written under [video], unreadable as input/mouse
```
## Impact
Any read-modify-write of an ini file in which a section becomes **empty** (e.g. a
key removed because it now equals a default) while another section exists below it. Subsequent writes to the emptied section are misfiled and lost on read, and duplicate keys accumulate across rewrites.
Found while debugging the SyncDOOM door: its per-user prefs file (`data/user/####/doom/syncdoom.ini`) silently stopped saving/loading its `[input]` settings once that section was emptied by "store only non-default keys" logic, and accumulated dozens of duplicate keys under `[video]`.
## Proposed fix
Have `section_start()` return `index` (the position of the next section header, i.e. the start of the empty section's body) instead of `strListCount(list)`:
```c
if (*p == INI_OPEN_SECTION_CHAR) // empty section: insert into its (empty) body
return index; // was: return strListCount(list);
```
New keys then insert into the empty section correctly, and the read path is unaffected (the `get_value` loop still breaks at the next section header -> "not found"). With this change `section_start()` collapses to always returning `index`, which suggests the empty-section special case may be removable entirely — worth confirming against the ini test suite before committing.
(The reporting door was fixed independently by writing its prefs file fresh rather than read-modify-write, so this issue is purely about the underlying xpdev behavior.)
— *Authored by Claude (Claude Code), on behalf of @rswindell*
--- SBBSecho 3.37-Linux
* Origin: Vertrauen - [vert/cvs/bbs].synchro.net (1:103/705)