Close the file and parent on every failed open - #195
Merged
Conversation
chd_open_core_file_callbacks() closes the caller's file and parent when an open fails: the cleanup path runs chd_close() on the half-built handle. The two exits before that handle exists - an invalid parent, and a failed malloc of the chd_file - returned without closing anything, so the same call either consumed its arguments or leaked them depending on where it failed. Close them on those two exits as well, and document the contract in chd.h. Taking the other side - leaving the file to the caller on failure - would break existing callers: one wraps the file in an object its fclose callback deletes and relies on libchdr to do so on failure, and none of them close the parent after a failed open. chd_open_file() is unaffected, its fclose callback does nothing. The only callers this can hurt are ones that close the file again after a failure, and those already close it twice on every other failure. The ESP-IDF bridge in contrib/ was one: it fclose()d the FILE after a failed open, a double fclose() on any image that failed past the allocation. It and the FatFs bridge now leave the close to libchdr. The new test builds its images in memory, so it runs without the corpus. Without this change it fails on the invalid-parent exit, both through a counting fclose callback and, for chd_open(), through the file descriptor the failed open kept. Fixes #193 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01846EhHuAFk5qvxwEA5Gq6y
rtissera
added a commit
that referenced
this pull request
Sep 10, 2026
Without one, every backward f_lseek walks the FAT chain again from the file's first cluster - and every COMPRESSION_SELF hunk is a backward seek, to an earlier hunk the image references instead of storing twice. The cost grows with how far into the file the reader is, so a short test near the start never shows it. On an ESP32-P4 a 29%-self-referenced image did not finish in 47 minutes until the benchmark built a map (070341a). The BL616 bridge an integration copies never had that fix, and its FatFs is built with FF_USE_FASTSEEK on. chd_fatfs_open() now builds the map right after f_open, before chd_open, so the header and map reads benefit too. The table is two words per fragment plus one; it starts at 16 words, grows to exactly what FatFs reports it needs, and past CHD_FATFS_CLMT_MAX runs without a map rather than failing - slower backward seeks, still correct. Compiled out entirely when FF_USE_FASTSEEK is 0. The close callback frees the table. libchdr calls it on a failed open too (#195), so the bridge's error path has nothing left to close. The RP2350 benchmark builds the same map, behind BENCH_LINKMAP so it can be measured on and off. Not measured yet on either board. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01846EhHuAFk5qvxwEA5Gq6y
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #193.
chd_open_core_file_callbacks()closes the caller's file and parent on a failed open, throughchd_close()on the half-built handle. Two exits before that handle exists skipped it: an invalid parent, and a failedmallocof thechd_file. Depending on where the open failed, the same call either consumed its arguments or leaked them.This PR closes them on those two exits too, and documents the contract in
chd.h: once passed, the file and the parent belong to libchdr, whether the open succeeds or not.Why not the other way round
#193 proposed the opposite: leave the file to the caller on failure. That would break existing callers:
fclosecallback deletes. It relies on libchdr calling that on failure, and it retries a child that returnedCHDERR_REQUIRES_PARENTwith a new wrapper.chd_open_file()is unaffected either way, since itsfclosecallback does nothing.What changes for callers
contrib/esp32p4/chd/chd_esp_vfs.cwas such a caller: it didfclose(f)after a failed open, which is a doublefclose()and undefined behaviour. It and the FatFs bridge now leave the close to libchdr.Test
tests/open_ownership.cbuilds one-hunk CHDv5 images in memory, so it runs without the generated corpus. It countsfclosecalls for these cases:chd_close())chd_fileCHDERR_REQUIRES_PARENTfollowed by a retry with the parentFor
chd_open(), which opens its ownFILE, the test checks on POSIX that the file descriptor was released. LeakSanitizer cannot see this, because glibc keeps every openFILEreachable.LOWRAM_TARGET, system zlib, no subcode/raw sectors, no block CRC, micro-flac, andLOWRAM_TARGETwith micro-flac.mallocexit is not covered by a test.Follow-ups
chd_fatfs_open(). It will be rebased onto this and drop its close-on-failure.🤖 Generated with Claude Code