Repository navigation
Treat path strings as UTF-8 on Windows - #205
Conversation
Directory and file paths reach the library as `std::string`. The helpers in `util/filesystem.cpp` handed them to `stat()` and `_mkdir()`, which on Windows interpret the bytes in the active ANSI code page, whereas the same string given to SQLite is read as UTF-8. A library in a directory whose name has characters outside the code page could therefore be created by SQLite but never found by `path_exists()`, or be found by neither. Path strings are now UTF-8 on every platform. A new `util::path_from_utf8()` converts them to `std::filesystem::path`, going via `std::u8string` on Windows so that the conversion does not depend on the code page, and `path_exists()` and `create_dir()` use `std::filesystem` on the result. The SQL script that `hydrate_database()` reads is opened through the same conversion. The public headers say so where the directory parameters and `database::directory()` are documented. A test creates and loads a database in a directory named with non-ASCII characters. On POSIX the previous code passed it too, as the bytes went through untouched, so the test only bites on Windows.
mr-smidge
left a comment
There was a problem hiding this comment.
Thanks for this - I mainly develop on Linux so it's very likely I never would have caught this bug!
Historical point: from memory, the create_directory() and path_exists() functions are there because libdjinterop used to target older OSes which didn't have std::filesystem available. The baseline OS requirements have moved on since then, and there's probably no reason for those utility functions to even exist anymore, given that we now have std::filesystem::create_directory() and std::filesystem::exists() - seeing the std library calls are probably more obvious to the casual reader.
Also, I think the change to 777 perms on POSIX systems is a regression: there are overloads of std::filesystem::create_directory() which take an existing_p path, and perhaps if we remove the utility function and move up to the call site, there will be an obvious reference directory (the parent) from which to copy attributes.
Last point: because libdjinterop is still in the 0.x phase of its life, breaking API changes are still a workable possibility. Do you think the interface would be higher quality if the public API replaced the use of std::string as a path with std::filesystem::path instead?
So in summary, my recommendation is:
- Review whether these utility functions even need to exist anymore.
- Ensure that any newly-created dir takes on appropriate permissions, not just 777.
- Maybe it's worth going crazy and replacing all use of
std::stringwithstd::filesystem::pathin the public API? Keen to hear your thoughts on this.
|
Thanks a lot for your thorough review, the background, and your thoughts on it! Let me explain mine. Dropping Permissions: you're right that the requested mode changed, from
So my proposal: I'll update this PR to call The updated version of this PR is coming up shortly. |
3c4da69 to
90ec7d6
Compare
The two helpers date from before std::filesystem was available, and were by now only thin wrappers around it. Calling std::filesystem::exists() and std::filesystem::create_directory() at the call sites makes it plain what happens, and a failure now carries the operating system's error in a std::filesystem::filesystem_error instead of a generic "Failed to create directory". New directories get the default mode of std::filesystem, which like mkdir(1) is reduced by the umask, rather than copying the attributes of the parent directory. Path strings still go through path_from_utf8(), which is the Windows fix itself.
90ec7d6 to
ac95573
Compare
|
The update is pushed as ac95573: the two helpers are gone, and the call sites use Thanks again for the thorough review, the feedback, and the time you put into this. By the way, I'm a Linux user and developer too. I'm just making my software available on Windows and Mac as well, and that's when these things surface. |
|
Here is what the |
Directory paths reach libdjinterop as
std::string. SQLite reads them as UTF-8, butpath_exists()andcreate_dir()passed them tostat()and_mkdir(), which on Windows use the ANSI code page. A library under a directory named outside the code page, such as a Japanese artist folder on a USB stick, could be created by SQLite and then not found.This adds
util::path_from_utf8(), which converts throughstd::u8stringon Windows, and usesstd::filesystemfor both helpers and for opening the schema script. The public headers now document that directory strings are UTF-8.The new test creates and loads a database in a non-ASCII directory for every schema. It passes before and after on Linux and macOS, because the bytes go through untouched there. It only fails without the fix on Windows, which this repository's CI doesn't build. I verified the fix in Seabass's MSVC build.
One side effect on POSIX: new directories get 0777 minus the umask instead of a fixed 0755. That is the same result under the usual 022 umask.