Skip to content

Commit c5214c0

Browse files
authored
Patch leveldb for Windows UTF16 path support (#1315)
* Use patched Firestore SDK * Also patch leveldb if building RTDB without Firestore. * Add release note for the fix. * Update leveldb version number patch. * Updated release note.
1 parent 9a4af62 commit c5214c0

File tree

4 files changed

+320
-12
lines changed

4 files changed

+320
-12
lines changed

cmake/external/firestore.patch.txt

-9
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,3 @@ index 920bf2928..c5c9cc7ee 100644
1414

1515
ExternalProject_Get_property(snappy SOURCE_DIR)
1616
set(snappy_source_dir "${SOURCE_DIR}")
17-
@@ -39,7 +42,7 @@ ExternalProject_Add(
18-
DOWNLOAD_DIR ${FIREBASE_DOWNLOAD_DIR}
19-
DOWNLOAD_NAME leveldb-${version}.tar.gz
20-
URL https://github.com/google/leveldb/archive/${version}.tar.gz
21-
- URL_HASH SHA256=55423cac9e3306f4a9502c738a001e4a339d1a38ffbee7572d4a07d5d63949b2
22-
+ URL_HASH SHA256=9a37f8a6174f09bd622bc723b55881dc541cd50747cbd08831c2a82d620f6d76
23-
24-
PREFIX ${PROJECT_BINARY_DIR}
25-

cmake/external/leveldb.cmake

+6-3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ if(TARGET leveldb)
1818
return()
1919
endif()
2020

21+
set(patch_file
22+
${CMAKE_CURRENT_LIST_DIR}/../../scripts/git/patches/leveldb/0001-leveldb-1.23-windows-paths.patch)
23+
2124
# This version must be kept in sync with the version in firestore.patch.txt.
2225
# If this version ever changes then make sure to update the version in
2326
# firestore.patch.txt accordingly.
@@ -27,9 +30,8 @@ ExternalProject_Add(
2730
leveldb
2831

2932
DOWNLOAD_DIR ${FIREBASE_DOWNLOAD_DIR}
30-
DOWNLOAD_NAME leveldb-${version}.tar.gz
31-
URL https://github.com/google/leveldb/archive/${version}.tar.gz
32-
URL_HASH SHA256=9a37f8a6174f09bd622bc723b55881dc541cd50747cbd08831c2a82d620f6d76
33+
GIT_REPOSITORY https://github.com/google/leveldb.git
34+
GIT_TAG "${version}"
3335

3436
PREFIX ${PROJECT_BINARY_DIR}
3537

@@ -38,4 +40,5 @@ ExternalProject_Add(
3840
INSTALL_COMMAND ""
3941
TEST_COMMAND ""
4042
HTTP_HEADER "${EXTERNAL_PROJECT_HTTP_HEADER}"
43+
PATCH_COMMAND git apply ${patch_file} && git gc --aggressive
4144
)

release_build_files/readme.md

+7
Original file line numberDiff line numberDiff line change
@@ -627,6 +627,13 @@ workflow use only during the development of your app, not for publicly shipping
627627
code.
628628

629629
## Release Notes
630+
### Upcoming Release
631+
- Changes
632+
- Database/Firestore (Desktop): Fixed a crash on Windows when the user's
633+
home directory contains non-ANSI characters (Unicode above U+00FF).
634+
- Storage (Desktop): Fixed a crash on Windows when uploading files from a
635+
path containing non-ANSI characters (Unicode above U+00FF).
636+
630637
### 11.0.1
631638
- Changes
632639
- Auth (iOS): Fixed a crash in `Credential::is_valid()` when an `AuthResult`
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,307 @@
1+
diff --git a/util/env_windows.cc b/util/env_windows.cc
2+
index 449f564..812c728 100644
3+
--- a/util/env_windows.cc
4+
+++ b/util/env_windows.cc
5+
@@ -375,8 +375,9 @@ class WindowsEnv : public Env {
6+
*result = nullptr;
7+
DWORD desired_access = GENERIC_READ;
8+
DWORD share_mode = FILE_SHARE_READ;
9+
- ScopedHandle handle = ::CreateFileA(
10+
- filename.c_str(), desired_access, share_mode,
11+
+ auto wFilename = toUtf16(filename);
12+
+ ScopedHandle handle = ::CreateFileW(
13+
+ wFilename.c_str(), desired_access, share_mode,
14+
/*lpSecurityAttributes=*/nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
15+
/*hTemplateFile=*/nullptr);
16+
if (!handle.is_valid()) {
17+
@@ -392,8 +393,9 @@ class WindowsEnv : public Env {
18+
*result = nullptr;
19+
DWORD desired_access = GENERIC_READ;
20+
DWORD share_mode = FILE_SHARE_READ;
21+
+ auto wFilename = toUtf16(filename);
22+
ScopedHandle handle =
23+
- ::CreateFileA(filename.c_str(), desired_access, share_mode,
24+
+ ::CreateFileW(wFilename.c_str(), desired_access, share_mode,
25+
/*lpSecurityAttributes=*/nullptr, OPEN_EXISTING,
26+
FILE_ATTRIBUTE_READONLY,
27+
/*hTemplateFile=*/nullptr);
28+
@@ -413,11 +415,12 @@ class WindowsEnv : public Env {
29+
}
30+
31+
ScopedHandle mapping =
32+
- ::CreateFileMappingA(handle.get(),
33+
- /*security attributes=*/nullptr, PAGE_READONLY,
34+
- /*dwMaximumSizeHigh=*/0,
35+
- /*dwMaximumSizeLow=*/0,
36+
- /*lpName=*/nullptr);
37+
+ ::CreateFileMappingW(handle.get(),
38+
+ /*security attributes=*/nullptr,
39+
+ PAGE_READONLY,
40+
+ /*dwMaximumSizeHigh=*/0,
41+
+ /*dwMaximumSizeLow=*/0,
42+
+ /*lpName=*/nullptr);
43+
if (mapping.is_valid()) {
44+
void* mmap_base = ::MapViewOfFile(mapping.get(), FILE_MAP_READ,
45+
/*dwFileOffsetHigh=*/0,
46+
@@ -438,8 +441,9 @@ class WindowsEnv : public Env {
47+
WritableFile** result) override {
48+
DWORD desired_access = GENERIC_WRITE;
49+
DWORD share_mode = 0; // Exclusive access.
50+
- ScopedHandle handle = ::CreateFileA(
51+
- filename.c_str(), desired_access, share_mode,
52+
+ auto wFilename = toUtf16(filename);
53+
+ ScopedHandle handle = ::CreateFileW(
54+
+ wFilename.c_str(), desired_access, share_mode,
55+
/*lpSecurityAttributes=*/nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL,
56+
/*hTemplateFile=*/nullptr);
57+
if (!handle.is_valid()) {
58+
@@ -455,8 +459,9 @@ class WindowsEnv : public Env {
59+
WritableFile** result) override {
60+
DWORD desired_access = FILE_APPEND_DATA;
61+
DWORD share_mode = 0; // Exclusive access.
62+
- ScopedHandle handle = ::CreateFileA(
63+
- filename.c_str(), desired_access, share_mode,
64+
+ auto wFilename = toUtf16(filename);
65+
+ ScopedHandle handle = ::CreateFileW(
66+
+ wFilename.c_str(), desired_access, share_mode,
67+
/*lpSecurityAttributes=*/nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL,
68+
/*hTemplateFile=*/nullptr);
69+
if (!handle.is_valid()) {
70+
@@ -469,14 +474,16 @@ class WindowsEnv : public Env {
71+
}
72+
73+
bool FileExists(const std::string& filename) override {
74+
- return GetFileAttributesA(filename.c_str()) != INVALID_FILE_ATTRIBUTES;
75+
+ auto wFilename = toUtf16(filename);
76+
+ return GetFileAttributesW(wFilename.c_str()) != INVALID_FILE_ATTRIBUTES;
77+
}
78+
79+
Status GetChildren(const std::string& directory_path,
80+
std::vector<std::string>* result) override {
81+
const std::string find_pattern = directory_path + "\\*";
82+
- WIN32_FIND_DATAA find_data;
83+
- HANDLE dir_handle = ::FindFirstFileA(find_pattern.c_str(), &find_data);
84+
+ WIN32_FIND_DATAW find_data;
85+
+ auto wFind_pattern = toUtf16(find_pattern);
86+
+ HANDLE dir_handle = ::FindFirstFileW(wFind_pattern.c_str(), &find_data);
87+
if (dir_handle == INVALID_HANDLE_VALUE) {
88+
DWORD last_error = ::GetLastError();
89+
if (last_error == ERROR_FILE_NOT_FOUND) {
90+
@@ -488,11 +495,12 @@ class WindowsEnv : public Env {
91+
char base_name[_MAX_FNAME];
92+
char ext[_MAX_EXT];
93+
94+
- if (!_splitpath_s(find_data.cFileName, nullptr, 0, nullptr, 0, base_name,
95+
+ auto find_data_filename = toUtf8(find_data.cFileName);
96+
+ if (!_splitpath_s(find_data_filename.c_str(), nullptr, 0, nullptr, 0, base_name,
97+
ARRAYSIZE(base_name), ext, ARRAYSIZE(ext))) {
98+
result->emplace_back(std::string(base_name) + ext);
99+
}
100+
- } while (::FindNextFileA(dir_handle, &find_data));
101+
+ } while (::FindNextFileW(dir_handle, &find_data));
102+
DWORD last_error = ::GetLastError();
103+
::FindClose(dir_handle);
104+
if (last_error != ERROR_NO_MORE_FILES) {
105+
@@ -501,22 +509,25 @@ class WindowsEnv : public Env {
106+
return Status::OK();
107+
}
108+
109+
- Status RemoveFile(const std::string& filename) override {
110+
- if (!::DeleteFileA(filename.c_str())) {
111+
+ Status DeleteFile(const std::string& filename) override {
112+
+ auto wFilename = toUtf16(filename);
113+
+ if (!::DeleteFileW(wFilename.c_str())) {
114+
return WindowsError(filename, ::GetLastError());
115+
}
116+
return Status::OK();
117+
}
118+
119+
Status CreateDir(const std::string& dirname) override {
120+
- if (!::CreateDirectoryA(dirname.c_str(), nullptr)) {
121+
+ auto wDirname = toUtf16(dirname);
122+
+ if (!::CreateDirectoryW(wDirname.c_str(), nullptr)) {
123+
return WindowsError(dirname, ::GetLastError());
124+
}
125+
return Status::OK();
126+
}
127+
128+
Status RemoveDir(const std::string& dirname) override {
129+
- if (!::RemoveDirectoryA(dirname.c_str())) {
130+
+ auto wDirname = toUtf16(dirname);
131+
+ if (!::RemoveDirectoryW(wDirname.c_str())) {
132+
return WindowsError(dirname, ::GetLastError());
133+
}
134+
return Status::OK();
135+
@@ -524,7 +535,8 @@ class WindowsEnv : public Env {
136+
137+
Status GetFileSize(const std::string& filename, uint64_t* size) override {
138+
WIN32_FILE_ATTRIBUTE_DATA file_attributes;
139+
- if (!::GetFileAttributesExA(filename.c_str(), GetFileExInfoStandard,
140+
+ auto wFilename = toUtf16(filename);
141+
+ if (!::GetFileAttributesExW(wFilename.c_str(), GetFileExInfoStandard,
142+
&file_attributes)) {
143+
return WindowsError(filename, ::GetLastError());
144+
}
145+
@@ -538,7 +550,9 @@ class WindowsEnv : public Env {
146+
Status RenameFile(const std::string& from, const std::string& to) override {
147+
// Try a simple move first. It will only succeed when |to| doesn't already
148+
// exist.
149+
- if (::MoveFileA(from.c_str(), to.c_str())) {
150+
+ auto wFrom = toUtf16(from);
151+
+ auto wTo = toUtf16(to);
152+
+ if (::MoveFileW(wFrom.c_str(), wTo.c_str())) {
153+
return Status::OK();
154+
}
155+
DWORD move_error = ::GetLastError();
156+
@@ -547,7 +561,7 @@ class WindowsEnv : public Env {
157+
// succeed when |to| does exist. When writing to a network share, we may not
158+
// be able to change the ACLs. Ignore ACL errors then
159+
// (REPLACEFILE_IGNORE_MERGE_ERRORS).
160+
- if (::ReplaceFileA(to.c_str(), from.c_str(), /*lpBackupFileName=*/nullptr,
161+
+ if (::ReplaceFileW(wTo.c_str(), wFrom.c_str(), /*lpBackupFileName=*/nullptr,
162+
REPLACEFILE_IGNORE_MERGE_ERRORS,
163+
/*lpExclude=*/nullptr, /*lpReserved=*/nullptr)) {
164+
return Status::OK();
165+
@@ -567,8 +581,9 @@ class WindowsEnv : public Env {
166+
Status LockFile(const std::string& filename, FileLock** lock) override {
167+
*lock = nullptr;
168+
Status result;
169+
- ScopedHandle handle = ::CreateFileA(
170+
- filename.c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ,
171+
+ auto wFilename = toUtf16(filename);
172+
+ ScopedHandle handle = ::CreateFileW(
173+
+ wFilename.c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ,
174+
/*lpSecurityAttributes=*/nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL,
175+
nullptr);
176+
if (!handle.is_valid()) {
177+
@@ -608,10 +623,11 @@ class WindowsEnv : public Env {
178+
return Status::OK();
179+
}
180+
181+
- char tmp_path[MAX_PATH];
182+
- if (!GetTempPathA(ARRAYSIZE(tmp_path), tmp_path)) {
183+
+ wchar_t wtmp_path[MAX_PATH];
184+
+ if (!GetTempPathW(ARRAYSIZE(wtmp_path), wtmp_path)) {
185+
return WindowsError("GetTempPath", ::GetLastError());
186+
}
187+
+ std::string tmp_path = toUtf8(std::wstring(wtmp_path));
188+
std::stringstream ss;
189+
ss << tmp_path << "leveldbtest-" << std::this_thread::get_id();
190+
*result = ss.str();
191+
@@ -622,7 +638,8 @@ class WindowsEnv : public Env {
192+
}
193+
194+
Status NewLogger(const std::string& filename, Logger** result) override {
195+
- std::FILE* fp = std::fopen(filename.c_str(), "w");
196+
+ auto wFilename = toUtf16(filename);
197+
+ std::FILE* fp = _wfopen(wFilename.c_str(), L"w");
198+
if (fp == nullptr) {
199+
*result = nullptr;
200+
return WindowsError(filename, ::GetLastError());
201+
@@ -678,6 +695,31 @@ class WindowsEnv : public Env {
202+
GUARDED_BY(background_work_mutex_);
203+
204+
Limiter mmap_limiter_; // Thread-safe.
205+
+
206+
+ // Converts a Windows wide multi-byte UTF-16 string to a UTF-8 string.
207+
+ // See http://utf8everywhere.org/#windows
208+
+ std::string toUtf8(const std::wstring& wstr) {
209+
+ if (wstr.empty()) return std::string();
210+
+ int size_needed = WideCharToMultiByte(
211+
+ CP_UTF8, 0, &wstr[0], (int)wstr.size(), NULL, 0, NULL, NULL);
212+
+ std::string strTo(size_needed, 0);
213+
+ WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), &strTo[0],
214+
+ size_needed, NULL, NULL);
215+
+ return strTo;
216+
+ }
217+
+
218+
+ // Converts a UTF-8 string to a Windows UTF-16 multi-byte wide character
219+
+ // string.
220+
+ // See http://utf8everywhere.org/#windows
221+
+ std::wstring toUtf16(const std::string& str) {
222+
+ if (str.empty()) return std::wstring();
223+
+ int size_needed = MultiByteToWideChar(
224+
+ CP_UTF8, 0, &str[0], (int)str.size(), NULL, 0);
225+
+ std::wstring strTo(size_needed, 0);
226+
+ MultiByteToWideChar(CP_UTF8, 0, &str[0], (int)str.size(), &strTo[0],
227+
+ size_needed);
228+
+ return strTo;
229+
+ }
230+
};
231+
232+
// Return the maximum number of concurrent mmaps.
233+
diff --git a/util/env_windows_test.cc b/util/env_windows_test.cc
234+
index d6822d2..d108ad9 100644
235+
--- a/util/env_windows_test.cc
236+
+++ b/util/env_windows_test.cc
237+
@@ -55,6 +55,70 @@ TEST_F(EnvWindowsTest, TestOpenOnRead) {
238+
ASSERT_LEVELDB_OK(env_->RemoveFile(test_file));
239+
}
240+
241+
+TEST_F(EnvWindowsTest, TestOpenOnRead_Unicode) {
242+
+ // Write some test data to a single file that will be opened |n| times.
243+
+ std::string test_dir;
244+
+ ASSERT_LEVELDB_OK(env_->GetTestDirectory(&test_dir));
245+
+ std::string test_file = test_dir + u8"/open_on_run🏃_read.txt";
246+
+
247+
+ std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
248+
+ std::wstring wideUtf8Path = converter.from_bytes(test_file);
249+
+ FILE* f = _wfopen(wideUtf8Path.c_str(), L"w");
250+
+ ASSERT_TRUE(f != nullptr);
251+
+ const char kFileData[] = "abcdefghijklmnopqrstuvwxyz";
252+
+ fputs(kFileData, f);
253+
+ fclose(f);
254+
+
255+
+ // Open test file some number above the sum of the two limits to force
256+
+ // leveldb::WindowsEnv to switch from mapping the file into memory
257+
+ // to basic file reading.
258+
+ const int kNumFiles = kMMapLimit + 5;
259+
+ leveldb::RandomAccessFile* files[kNumFiles] = {0};
260+
+ for (int i = 0; i < kNumFiles; i++) {
261+
+ ASSERT_LEVELDB_OK(env_->NewRandomAccessFile(test_file, &files[i]));
262+
+ }
263+
+ char scratch;
264+
+ Slice read_result;
265+
+ for (int i = 0; i < kNumFiles; i++) {
266+
+ ASSERT_LEVELDB_OK(files[i]->Read(i, 1, &read_result, &scratch));
267+
+ ASSERT_EQ(kFileData[i], read_result[0]);
268+
+ }
269+
+ for (int i = 0; i < kNumFiles; i++) {
270+
+ delete files[i];
271+
+ }
272+
+ ASSERT_LEVELDB_OK(env_->DeleteFile(test_file));
273+
+}
274+
+
275+
+TEST_F(EnvWindowsTest, TestGetChildrenEmpty) {
276+
+ // Create some dummy files.
277+
+ std::string test_dir;
278+
+ ASSERT_LEVELDB_OK(env_->GetTestDirectory(&test_dir));
279+
+
280+
+ std::vector<std::string> result;
281+
+ ASSERT_LEVELDB_OK(env_->GetChildren(test_dir, &result));
282+
+ ASSERT_EQ(2, result.size()); // "." and ".." are always returned.
283+
+}
284+
+
285+
+TEST_F(EnvWindowsTest, TestGetChildren_ChildFiles) {
286+
+ // Create some dummy files.
287+
+ std::string test_dir;
288+
+ ASSERT_LEVELDB_OK(env_->GetTestDirectory(&test_dir));
289+
+
290+
+ int childFilesCount = 10;
291+
+ for (int i = 0; i < childFilesCount; i++) {
292+
+ std::string test_file = test_dir + u8"/run🏃_and_jump🦘_" + std::to_string(i) + ".txt";
293+
+ std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
294+
+ std::wstring wTest_file = converter.from_bytes(test_file);
295+
+ FILE* f = _wfopen(wTest_file.c_str(), L"w");
296+
+ ASSERT_TRUE(f != nullptr);
297+
+ fclose(f);
298+
+ }
299+
+
300+
+ std::vector<std::string> result;
301+
+ ASSERT_LEVELDB_OK(env_->GetChildren(test_dir, &result));
302+
+ ASSERT_EQ(childFilesCount + 2, result.size()); // "." and ".." are returned.
303+
+}
304+
+
305+
} // namespace leveldb
306+
307+
int main(int argc, char** argv) {

0 commit comments

Comments
 (0)