Leave rtss-profile.txt alone when it is there but does not read, so the lines of other executables in the folder stay.

The review found that rtss_limit.c state_put took a state file plat_file_read could not read for a missing one: a file
another program held open (a scan) was rewritten with this profile's line alone, or deleted when the run removed its
line, and the lines of other executables that share the folder (dev or probe builds) were lost. state_put now returns
0 when the read gave nothing but plat_file_size finds the file with bytes in it; the run then goes on as with a file
that cannot be written (one log line, and a crash puts the profile back as an orderly exit does). An empty file is
still rewritten. tests/rtss_worker_check.c state_locked checks a file holding another executable's line that does not
read: the run limits as before, logs it, and the file stays as it was through the exit.

Not reproduced before the change. Checks that passed: tests/rtss_limit_check.py, pass_cc_check.py --wall on
rtss_limit.c, tools/check_content.py --staged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
chrg6
2026-11-13 19:53:26 -05:00
parent da822abec3
commit 9f45d7448f
2 changed files with 37 additions and 2 deletions

View File

@@ -173,6 +173,9 @@ static int state_put(const RtssSaved *r) {
if (state_off || !*state_path) return 0;
uint64_t n = 0;
char *text = (char *)plat_file_read(state_path, &n);
// a file that is there but did not read (another program holds it, a scan) keeps the lines of the other executables
// in the folder: rewritten from nothing, they would go
if (!text && plat_file_size(state_path) > 0) return 0;
size_t cap = (size_t)(text ? n : 0) + sizeof RTSS_SAVED_HEADER + 2 * (RTSS_NAME_MAX + 96);
char *out = malloc(cap);
int records = 0, ok = 0;

View File

@@ -262,12 +262,13 @@ int rt_knob(const char *name, const char *def) { (void)name, (void)def; return 0
// The state file next to the executable: here the scenario's folder.
static char exe_dir[MAX_PATH];
static int state_readonly; // the state file cannot be written
static int state_unreadable; // the state file is there but does not read (another program holds it)
static int no_exe_path; // the executable's path is unknown: no state file at all
int plat_exe_path(char *out, size_t cap) {
int k = snprintf(out, cap, "%s\\ocarina.exe", exe_dir);
return !no_exe_path && k > 0 && (size_t)k < cap;
}
uint8_t *plat_file_read(const char *path, uint64_t *size) {
static uint8_t *read_all(const char *path, uint64_t *size) {
FILE *f = fopen(path, "rb");
if (!f) return NULL;
fseek(f, 0, SEEK_END);
@@ -279,6 +280,15 @@ uint8_t *plat_file_read(const char *path, uint64_t *size) {
if (b) b[n] = 0, *size = (uint64_t)n;
return b;
}
uint8_t *plat_file_read(const char *path, uint64_t *size) { return state_unreadable ? NULL : read_all(path, size); }
int64_t plat_file_size(const char *path) {
FILE *f = fopen(path, "rb");
if (!f) return -1;
fseek(f, 0, SEEK_END);
long n = ftell(f);
fclose(f);
return n;
}
int plat_file_write_atomic(const char *path, const void *data, size_t n) {
if (state_readonly) return 0;
FILE *f = fopen(path, "wb");
@@ -298,7 +308,7 @@ static const char *state(void) { // the state file as it is now; "" when there
char path[MAX_PATH + 32];
snprintf(path, sizeof path, "%s\\rtss-profile.txt", exe_dir);
uint64_t n = 0;
uint8_t *b = plat_file_read(path, &n);
uint8_t *b = read_all(path, &n);
snprintf(state_text, sizeof state_text, "%s", b ? (char *)b : "");
free(b);
return state_text;
@@ -998,6 +1008,27 @@ static void put_back_undeleted(void) {
CHECK(!own.exists && removed == 1 && !*state(), "the exit removes it:\n%s", state());
}
// The state file is there with another executable's line but does not read: it is left as it is, with that line, and
// the run goes on as with a file that cannot be written.
static void state_locked(void) {
char path[MAX_PATH + 32];
snprintf(path, sizeof path, "%s\\rtss-profile.txt", exe_dir);
FILE *f = fopen(path, "wb");
if (f) fputs("rtss1 0 0 1 60 1 60 1 ocarina_dev.exe\n", f), fclose(f);
state_unreadable = 1;
app_set(1, 0xA, 1000, 2000, 60);
ask(60000);
poll();
poll();
show("a state file that does not read");
CHECK(own.exists && own.limit == 60 && rtss_limit_active(), "RTSS limits as before");
CHECK(logged("rtss-profile.txt could not be written"), "logged");
CHECK(!strcmp(state(), "rtss1 0 0 1 60 1 60 1 ocarina_dev.exe\n"), "the file as it was:\n%s", state());
state_unreadable = 0;
rtss_limit_restore();
CHECK(!own.exists && !strcmp(state(), "rtss1 0 0 1 60 1 60 1 ocarina_dev.exe\n"), "still as it was:\n%s", state());
}
// A crash with the line in the state file: no call into RTSS's library, the profile and the line stay for the next
// start, and nothing after it reaches the profile.
static void crash_recorded(void) {
@@ -1209,6 +1240,7 @@ static const struct { const char *name; void (*run)(void); } scenarios[] = {
{"state_record", state_record},
{"state_player", state_player},
{"state_unwritable", state_unwritable},
{"state_locked", state_locked},
{"crash_recorded", crash_recorded},
{"kill_restart", kill_restart},
{"kill_restart_player", kill_restart_player},