fix(platform): resolve_case_insensitive must not abort on an unconvertible neighbour - #167
Open
russimicro wants to merge 1 commit into
Conversation
…tible neighbour resolve_case_insensitive() scans every entry of the parent directory with fs::directory_iterator and converts each name from wide (UTF-16) to narrow via fs::path::string(). On Windows that conversion THROWS std::system_error when a name has no representation in the narrow codepage -- notably the NTFS PUA remap (U+F000-U+F0FF) applied to a filename that picked up an illegal character (e.g. ":" from a botched robocopy/redirect). Nothing caught it, so opening ANY table crashed the whole process as soon as one such neighbour sat in the same directory -- unrelated to the table being opened, and easy to misread as index corruption (reported downstream as ADSADT/7017 "indice corrupto"). Reproduced end-to-end through Harbour rddads against a real .dbf/.cdx: with the stray file present the process aborts inside AdsOpenTable -> Connection::open_table -> Connection::resolve_table_file -> resolve_case_insensitive, confirmed via a Debug build + cdb.exe stack; with it absent (or with this fix), the same table opens clean. Fix: guard every path::string() call in the two directory scans behind a try/catch, skip the unconvertible entry instead of propagating the exception. New test creates a neighbour with a PUA-remapped name and checks that resolving an unrelated, well-formed file in the same directory still succeeds.
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.
OpenADS —
resolve_case_insensitivemust not abort on an unconvertible neighbour filerddads+ OpenADS x64)src/platform/path.cpp—resolve_case_insensitive()ADS_CDXandADS_ADTalike1. Symptom
Opening a perfectly healthy
.dbf/.cdx(or.adt/.adi) crashes the process with anunhandled C++ exception, reported downstream as
ADSADT/7017 "indice corrupto"— the tableitself is not corrupt.
Reproduced with a real table:
prueba3.dbf/.cdx(6000 records, 2 tags), created and grownwith the genuine Advantage Data Architect (SAP), opened through Harbour
rddads:Crashes with
Unrecoverable error 6005: Exception error / Exception Code:E06D7363beforecontrol returns to the application.
2. Root cause
resolve_case_insensitive()scans every entry of the parent directory withfs::directory_iteratorand converts each name to a narrowstd::stringviafs::path::string()— for both the exact-case pass and the case-folded pass:On Windows,
path::string()converts the native wide (UTF-16) name to the narrow/ACPencoding, and throws
std::system_errorwhen some character has no representation inthat codepage. That is exactly what happens with the NTFS PUA remap (
U+F000-U+F0FF),which the filesystem uses for a filename that picked up an illegal character — in our repro,
a stray zero-byte file left behind by a botched
robocopy/redirect that dropped a literal:into a filename (F+U+F03A+temp_robocopy.log, NTFS's remap of the illegal:).That file has nothing to do with the table being opened — it just happens to live in the
same directory. Because nothing catches the exception, it unwinds straight out of
AdsOpenTable, throughConnection::open_table->Connection::resolve_table_file->resolve_case_insensitive, and kills the process. Confirmed with a Debug build +cdb.exe(stack below), and confirmed in both directions by moving the stray file in and out of the
directory: present -> crash every time; absent -> the same table opens clean, 6000 records,
OrdCount()=2, 0 duplicate tag names.This is why the symptom looked like table corruption: any directory that happens to contain
one file with a bad name (a stray temp/log file, a half-written backup, anything an external
tool dropped) makes ADS refuse to open every table in it, regardless of which one the
application asked for.
3. Fix
Guard every
path::string()call inside the two directory scans and skip the entry insteadof letting the exception escape:
applied to both the exact-case pass and the case-folded pass; an entry whose name cannot be
converted is skipped (
continue), it simply never matches. Net: +28 / -4 in one file.4. Verification
4.1 Real data, through Harbour
rddads, Debug buildUnrecoverable error 6005,E06D7363)4.2 Regression test
tests/unit/platform_path_test.cpp— new case, Windows-only (#if defined(_WIN32)):creates a well-formed neighbour and one with a PUA-remapped name (
0xF03A) in the sametemp directory, then checks that resolving the well-formed one still succeeds.
4.3 Full suite
Built and run against a clean checkout of
main(no unrelated changes in the tree):5. Scope
This only touches
resolve_case_insensitive()'s two directory scans.Connection's ownpath-building calls (
cand.string(),rel.string(),full.string()insession/connection.cpp) operate on names the caller/data-root already supplied, not onarbitrary directory entries, so they are outside this fix's scope — no report of a crash
from that side.