]>
Commit | Line | Data |
---|---|---|
7c673cae | 1 | // Copyright (c) 2011-present, Facebook, Inc. All rights reserved. |
11fdf7f2 TL |
2 | // This source code is licensed under both the GPLv2 (found in the |
3 | // COPYING file in the root directory) and Apache 2.0 License | |
4 | // (found in the LICENSE.Apache file in the root directory). | |
7c673cae FG |
5 | // |
6 | // Copyright (c) 2011 The LevelDB Authors. All rights reserved. | |
7 | // Use of this source code is governed by a BSD-style license that can be | |
8 | // found in the LICENSE file. See the AUTHORS file for names of contributors. | |
9 | ||
10 | #include "port/win/env_win.h" | |
11 | #include "port/win/win_thread.h" | |
12 | #include <algorithm> | |
13 | #include <ctime> | |
14 | #include <thread> | |
15 | ||
16 | #include <errno.h> | |
17 | #include <process.h> // _getpid | |
18 | #include <io.h> // _access | |
19 | #include <direct.h> // _rmdir, _mkdir, _getcwd | |
20 | #include <sys/types.h> | |
21 | #include <sys/stat.h> | |
22 | ||
23 | #include "rocksdb/env.h" | |
24 | #include "rocksdb/slice.h" | |
25 | ||
26 | #include "port/port.h" | |
494da23a | 27 | #include "port/port_dirent.h" |
7c673cae FG |
28 | #include "port/win/win_logger.h" |
29 | #include "port/win/io_win.h" | |
30 | ||
31 | #include "monitoring/iostats_context_imp.h" | |
32 | ||
33 | #include "monitoring/thread_status_updater.h" | |
34 | #include "monitoring/thread_status_util.h" | |
35 | ||
11fdf7f2 TL |
36 | #include <rpc.h> // for uuid generation |
37 | #include <windows.h> | |
38 | #include <shlwapi.h> | |
39 | #include "strsafe.h" | |
40 | ||
41 | #include <algorithm> | |
7c673cae | 42 | |
f67539c2 | 43 | namespace ROCKSDB_NAMESPACE { |
7c673cae FG |
44 | |
45 | ThreadStatusUpdater* CreateThreadStatusUpdater() { | |
46 | return new ThreadStatusUpdater(); | |
47 | } | |
48 | ||
49 | namespace { | |
50 | ||
494da23a TL |
51 | // Sector size used when physical sector size cannot be obtained from device. |
52 | static const size_t kSectorSize = 512; | |
11fdf7f2 | 53 | |
7c673cae FG |
54 | // RAII helpers for HANDLEs |
55 | const auto CloseHandleFunc = [](HANDLE h) { ::CloseHandle(h); }; | |
56 | typedef std::unique_ptr<void, decltype(CloseHandleFunc)> UniqueCloseHandlePtr; | |
57 | ||
11fdf7f2 TL |
58 | const auto FindCloseFunc = [](HANDLE h) { ::FindClose(h); }; |
59 | typedef std::unique_ptr<void, decltype(FindCloseFunc)> UniqueFindClosePtr; | |
60 | ||
7c673cae FG |
61 | void WinthreadCall(const char* label, std::error_code result) { |
62 | if (0 != result.value()) { | |
63 | fprintf(stderr, "pthread %s: %s\n", label, strerror(result.value())); | |
64 | abort(); | |
65 | } | |
66 | } | |
67 | ||
68 | } | |
69 | ||
70 | namespace port { | |
71 | ||
72 | WinEnvIO::WinEnvIO(Env* hosted_env) | |
494da23a | 73 | : hosted_env_(hosted_env), |
11fdf7f2 | 74 | page_size_(4 * 1024), |
7c673cae FG |
75 | allocation_granularity_(page_size_), |
76 | perf_counter_frequency_(0), | |
494da23a | 77 | nano_seconds_per_period_(0), |
7c673cae FG |
78 | GetSystemTimePreciseAsFileTime_(NULL) { |
79 | ||
80 | SYSTEM_INFO sinfo; | |
81 | GetSystemInfo(&sinfo); | |
82 | ||
83 | page_size_ = sinfo.dwPageSize; | |
84 | allocation_granularity_ = sinfo.dwAllocationGranularity; | |
85 | ||
86 | { | |
87 | LARGE_INTEGER qpf; | |
11fdf7f2 TL |
88 | BOOL ret __attribute__((__unused__)); |
89 | ret = QueryPerformanceFrequency(&qpf); | |
7c673cae FG |
90 | assert(ret == TRUE); |
91 | perf_counter_frequency_ = qpf.QuadPart; | |
494da23a TL |
92 | |
93 | if (std::nano::den % perf_counter_frequency_ == 0) { | |
94 | nano_seconds_per_period_ = std::nano::den / perf_counter_frequency_; | |
95 | } | |
7c673cae FG |
96 | } |
97 | ||
98 | HMODULE module = GetModuleHandle("kernel32.dll"); | |
99 | if (module != NULL) { | |
494da23a TL |
100 | GetSystemTimePreciseAsFileTime_ = |
101 | (FnGetSystemTimePreciseAsFileTime)GetProcAddress( | |
102 | module, "GetSystemTimePreciseAsFileTime"); | |
7c673cae FG |
103 | } |
104 | } | |
105 | ||
106 | WinEnvIO::~WinEnvIO() { | |
107 | } | |
108 | ||
109 | Status WinEnvIO::DeleteFile(const std::string& fname) { | |
110 | Status result; | |
111 | ||
494da23a TL |
112 | BOOL ret = RX_DeleteFile(RX_FN(fname).c_str()); |
113 | ||
11fdf7f2 TL |
114 | if(!ret) { |
115 | auto lastError = GetLastError(); | |
116 | result = IOErrorFromWindowsError("Failed to delete: " + fname, | |
494da23a | 117 | lastError); |
7c673cae FG |
118 | } |
119 | ||
120 | return result; | |
121 | } | |
122 | ||
11fdf7f2 TL |
123 | Status WinEnvIO::Truncate(const std::string& fname, size_t size) { |
124 | Status s; | |
f67539c2 | 125 | int result = ROCKSDB_NAMESPACE::port::Truncate(fname, size); |
11fdf7f2 TL |
126 | if (result != 0) { |
127 | s = IOError("Failed to truncate: " + fname, errno); | |
128 | } | |
129 | return s; | |
130 | } | |
131 | ||
7c673cae FG |
132 | Status WinEnvIO::GetCurrentTime(int64_t* unix_time) { |
133 | time_t time = std::time(nullptr); | |
134 | if (time == (time_t)(-1)) { | |
135 | return Status::NotSupported("Failed to get time"); | |
136 | } | |
137 | ||
138 | *unix_time = time; | |
139 | return Status::OK(); | |
140 | } | |
141 | ||
142 | Status WinEnvIO::NewSequentialFile(const std::string& fname, | |
494da23a TL |
143 | std::unique_ptr<SequentialFile>* result, |
144 | const EnvOptions& options) { | |
7c673cae FG |
145 | Status s; |
146 | ||
147 | result->reset(); | |
148 | ||
149 | // Corruption test needs to rename and delete files of these kind | |
150 | // while they are still open with another handle. For that reason we | |
151 | // allow share_write and delete(allows rename). | |
152 | HANDLE hFile = INVALID_HANDLE_VALUE; | |
153 | ||
154 | DWORD fileFlags = FILE_ATTRIBUTE_READONLY; | |
155 | ||
156 | if (options.use_direct_reads && !options.use_mmap_reads) { | |
157 | fileFlags |= FILE_FLAG_NO_BUFFERING; | |
158 | } | |
159 | ||
160 | { | |
161 | IOSTATS_TIMER_GUARD(open_nanos); | |
494da23a TL |
162 | hFile = RX_CreateFile( |
163 | RX_FN(fname).c_str(), GENERIC_READ, | |
164 | FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, | |
165 | OPEN_EXISTING, // Original fopen mode is "rb" | |
166 | fileFlags, NULL); | |
7c673cae FG |
167 | } |
168 | ||
169 | if (INVALID_HANDLE_VALUE == hFile) { | |
170 | auto lastError = GetLastError(); | |
171 | s = IOErrorFromWindowsError("Failed to open NewSequentialFile" + fname, | |
494da23a | 172 | lastError); |
7c673cae FG |
173 | } else { |
174 | result->reset(new WinSequentialFile(fname, hFile, options)); | |
175 | } | |
176 | return s; | |
177 | } | |
178 | ||
179 | Status WinEnvIO::NewRandomAccessFile(const std::string& fname, | |
494da23a TL |
180 | std::unique_ptr<RandomAccessFile>* result, |
181 | const EnvOptions& options) { | |
7c673cae FG |
182 | result->reset(); |
183 | Status s; | |
184 | ||
185 | // Open the file for read-only random access | |
186 | // Random access is to disable read-ahead as the system reads too much data | |
187 | DWORD fileFlags = FILE_ATTRIBUTE_READONLY; | |
188 | ||
189 | if (options.use_direct_reads && !options.use_mmap_reads) { | |
190 | fileFlags |= FILE_FLAG_NO_BUFFERING; | |
191 | } else { | |
192 | fileFlags |= FILE_FLAG_RANDOM_ACCESS; | |
193 | } | |
194 | ||
195 | /// Shared access is necessary for corruption test to pass | |
196 | // almost all tests would work with a possible exception of fault_injection | |
197 | HANDLE hFile = 0; | |
198 | { | |
199 | IOSTATS_TIMER_GUARD(open_nanos); | |
494da23a TL |
200 | hFile = RX_CreateFile( |
201 | RX_FN(fname).c_str(), GENERIC_READ, | |
202 | FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, | |
203 | NULL, OPEN_EXISTING, fileFlags, NULL); | |
7c673cae FG |
204 | } |
205 | ||
206 | if (INVALID_HANDLE_VALUE == hFile) { | |
207 | auto lastError = GetLastError(); | |
208 | return IOErrorFromWindowsError( | |
494da23a | 209 | "NewRandomAccessFile failed to Create/Open: " + fname, lastError); |
7c673cae FG |
210 | } |
211 | ||
212 | UniqueCloseHandlePtr fileGuard(hFile, CloseHandleFunc); | |
213 | ||
214 | // CAUTION! This will map the entire file into the process address space | |
215 | if (options.use_mmap_reads && sizeof(void*) >= 8) { | |
216 | // Use mmap when virtual address-space is plentiful. | |
217 | uint64_t fileSize; | |
218 | ||
219 | s = GetFileSize(fname, &fileSize); | |
220 | ||
221 | if (s.ok()) { | |
222 | // Will not map empty files | |
223 | if (fileSize == 0) { | |
224 | return IOError( | |
494da23a | 225 | "NewRandomAccessFile failed to map empty file: " + fname, EINVAL); |
7c673cae FG |
226 | } |
227 | ||
494da23a TL |
228 | HANDLE hMap = RX_CreateFileMapping(hFile, NULL, PAGE_READONLY, |
229 | 0, // At its present length | |
230 | 0, | |
231 | NULL); // Mapping name | |
7c673cae FG |
232 | |
233 | if (!hMap) { | |
234 | auto lastError = GetLastError(); | |
235 | return IOErrorFromWindowsError( | |
494da23a TL |
236 | "Failed to create file mapping for NewRandomAccessFile: " + fname, |
237 | lastError); | |
7c673cae FG |
238 | } |
239 | ||
240 | UniqueCloseHandlePtr mapGuard(hMap, CloseHandleFunc); | |
241 | ||
242 | const void* mapped_region = | |
243 | MapViewOfFileEx(hMap, FILE_MAP_READ, | |
494da23a TL |
244 | 0, // High DWORD of access start |
245 | 0, // Low DWORD | |
246 | static_cast<SIZE_T>(fileSize), | |
247 | NULL); // Let the OS choose the mapping | |
7c673cae FG |
248 | |
249 | if (!mapped_region) { | |
250 | auto lastError = GetLastError(); | |
251 | return IOErrorFromWindowsError( | |
494da23a TL |
252 | "Failed to MapViewOfFile for NewRandomAccessFile: " + fname, |
253 | lastError); | |
7c673cae FG |
254 | } |
255 | ||
256 | result->reset(new WinMmapReadableFile(fname, hFile, hMap, mapped_region, | |
494da23a | 257 | static_cast<size_t>(fileSize))); |
7c673cae FG |
258 | |
259 | mapGuard.release(); | |
260 | fileGuard.release(); | |
261 | } | |
262 | } else { | |
494da23a TL |
263 | result->reset(new WinRandomAccessFile(fname, hFile, |
264 | std::max(GetSectorSize(fname), | |
265 | page_size_), | |
266 | options)); | |
7c673cae FG |
267 | fileGuard.release(); |
268 | } | |
269 | return s; | |
270 | } | |
271 | ||
11fdf7f2 | 272 | Status WinEnvIO::OpenWritableFile(const std::string& fname, |
494da23a TL |
273 | std::unique_ptr<WritableFile>* result, |
274 | const EnvOptions& options, | |
275 | bool reopen) { | |
11fdf7f2 | 276 | |
7c673cae FG |
277 | const size_t c_BufferCapacity = 64 * 1024; |
278 | ||
279 | EnvOptions local_options(options); | |
280 | ||
281 | result->reset(); | |
282 | Status s; | |
283 | ||
284 | DWORD fileFlags = FILE_ATTRIBUTE_NORMAL; | |
285 | ||
286 | if (local_options.use_direct_writes && !local_options.use_mmap_writes) { | |
11fdf7f2 | 287 | fileFlags = FILE_FLAG_NO_BUFFERING | FILE_FLAG_WRITE_THROUGH; |
7c673cae FG |
288 | } |
289 | ||
290 | // Desired access. We are want to write only here but if we want to memory | |
291 | // map | |
292 | // the file then there is no write only mode so we have to create it | |
293 | // Read/Write | |
294 | // However, MapViewOfFile specifies only Write only | |
295 | DWORD desired_access = GENERIC_WRITE; | |
296 | DWORD shared_mode = FILE_SHARE_READ; | |
297 | ||
298 | if (local_options.use_mmap_writes) { | |
299 | desired_access |= GENERIC_READ; | |
300 | } else { | |
301 | // Adding this solely for tests to pass (fault_injection_test, | |
302 | // wal_manager_test). | |
303 | shared_mode |= (FILE_SHARE_WRITE | FILE_SHARE_DELETE); | |
304 | } | |
305 | ||
11fdf7f2 TL |
306 | // This will always truncate the file |
307 | DWORD creation_disposition = CREATE_ALWAYS; | |
308 | if (reopen) { | |
309 | creation_disposition = OPEN_ALWAYS; | |
310 | } | |
311 | ||
7c673cae FG |
312 | HANDLE hFile = 0; |
313 | { | |
314 | IOSTATS_TIMER_GUARD(open_nanos); | |
494da23a TL |
315 | hFile = RX_CreateFile( |
316 | RX_FN(fname).c_str(), | |
317 | desired_access, // Access desired | |
318 | shared_mode, | |
319 | NULL, // Security attributes | |
320 | // Posix env says (reopen) ? (O_CREATE | O_APPEND) : O_CREAT | O_TRUNC | |
321 | creation_disposition, | |
322 | fileFlags, // Flags | |
323 | NULL); // Template File | |
7c673cae FG |
324 | } |
325 | ||
326 | if (INVALID_HANDLE_VALUE == hFile) { | |
327 | auto lastError = GetLastError(); | |
328 | return IOErrorFromWindowsError( | |
494da23a | 329 | "Failed to create a NewWriteableFile: " + fname, lastError); |
7c673cae FG |
330 | } |
331 | ||
11fdf7f2 TL |
332 | // We will start writing at the end, appending |
333 | if (reopen) { | |
334 | LARGE_INTEGER zero_move; | |
335 | zero_move.QuadPart = 0; | |
336 | BOOL ret = SetFilePointerEx(hFile, zero_move, NULL, FILE_END); | |
337 | if (!ret) { | |
338 | auto lastError = GetLastError(); | |
339 | return IOErrorFromWindowsError( | |
494da23a TL |
340 | "Failed to create a ReopenWritableFile move to the end: " + fname, |
341 | lastError); | |
11fdf7f2 TL |
342 | } |
343 | } | |
344 | ||
7c673cae FG |
345 | if (options.use_mmap_writes) { |
346 | // We usually do not use mmmapping on SSD and thus we pass memory | |
347 | // page_size | |
348 | result->reset(new WinMmapFile(fname, hFile, page_size_, | |
494da23a | 349 | allocation_granularity_, local_options)); |
7c673cae FG |
350 | } else { |
351 | // Here we want the buffer allocation to be aligned by the SSD page size | |
352 | // and to be a multiple of it | |
494da23a TL |
353 | result->reset(new WinWritableFile(fname, hFile, |
354 | std::max(GetSectorSize(fname), | |
355 | GetPageSize()), | |
356 | c_BufferCapacity, local_options)); | |
7c673cae FG |
357 | } |
358 | return s; | |
359 | } | |
360 | ||
361 | Status WinEnvIO::NewRandomRWFile(const std::string & fname, | |
494da23a TL |
362 | std::unique_ptr<RandomRWFile>* result, |
363 | const EnvOptions & options) { | |
7c673cae FG |
364 | |
365 | Status s; | |
366 | ||
367 | // Open the file for read-only random access | |
368 | // Random access is to disable read-ahead as the system reads too much data | |
369 | DWORD desired_access = GENERIC_READ | GENERIC_WRITE; | |
370 | DWORD shared_mode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; | |
11fdf7f2 | 371 | DWORD creation_disposition = OPEN_EXISTING; // Fail if file does not exist |
7c673cae FG |
372 | DWORD file_flags = FILE_FLAG_RANDOM_ACCESS; |
373 | ||
374 | if (options.use_direct_reads && options.use_direct_writes) { | |
375 | file_flags |= FILE_FLAG_NO_BUFFERING; | |
376 | } | |
377 | ||
378 | /// Shared access is necessary for corruption test to pass | |
379 | // almost all tests would work with a possible exception of fault_injection | |
380 | HANDLE hFile = 0; | |
381 | { | |
382 | IOSTATS_TIMER_GUARD(open_nanos); | |
383 | hFile = | |
494da23a TL |
384 | RX_CreateFile(RX_FN(fname).c_str(), |
385 | desired_access, | |
386 | shared_mode, | |
387 | NULL, // Security attributes | |
388 | creation_disposition, | |
389 | file_flags, | |
390 | NULL); | |
7c673cae FG |
391 | } |
392 | ||
393 | if (INVALID_HANDLE_VALUE == hFile) { | |
394 | auto lastError = GetLastError(); | |
395 | return IOErrorFromWindowsError( | |
396 | "NewRandomRWFile failed to Create/Open: " + fname, lastError); | |
397 | } | |
398 | ||
399 | UniqueCloseHandlePtr fileGuard(hFile, CloseHandleFunc); | |
494da23a TL |
400 | result->reset(new WinRandomRWFile(fname, hFile, |
401 | std::max(GetSectorSize(fname), | |
402 | GetPageSize()), | |
403 | options)); | |
11fdf7f2 TL |
404 | fileGuard.release(); |
405 | ||
406 | return s; | |
407 | } | |
408 | ||
494da23a TL |
409 | Status WinEnvIO::NewMemoryMappedFileBuffer( |
410 | const std::string & fname, | |
411 | std::unique_ptr<MemoryMappedFileBuffer>* result) { | |
11fdf7f2 TL |
412 | Status s; |
413 | result->reset(); | |
414 | ||
415 | DWORD fileFlags = FILE_ATTRIBUTE_READONLY; | |
416 | ||
417 | HANDLE hFile = INVALID_HANDLE_VALUE; | |
418 | { | |
419 | IOSTATS_TIMER_GUARD(open_nanos); | |
494da23a TL |
420 | hFile = RX_CreateFile( |
421 | RX_FN(fname).c_str(), GENERIC_READ | GENERIC_WRITE, | |
422 | FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, | |
423 | NULL, | |
424 | OPEN_EXISTING, // Open only if it exists | |
425 | fileFlags, | |
426 | NULL); | |
11fdf7f2 TL |
427 | } |
428 | ||
429 | if (INVALID_HANDLE_VALUE == hFile) { | |
430 | auto lastError = GetLastError(); | |
494da23a TL |
431 | s = IOErrorFromWindowsError( |
432 | "Failed to open NewMemoryMappedFileBuffer: " + fname, lastError); | |
11fdf7f2 TL |
433 | return s; |
434 | } | |
435 | UniqueCloseHandlePtr fileGuard(hFile, CloseHandleFunc); | |
436 | ||
437 | uint64_t fileSize = 0; | |
438 | s = GetFileSize(fname, &fileSize); | |
439 | if (!s.ok()) { | |
440 | return s; | |
441 | } | |
442 | // Will not map empty files | |
443 | if (fileSize == 0) { | |
494da23a TL |
444 | return Status::NotSupported( |
445 | "NewMemoryMappedFileBuffer can not map zero length files: " + fname); | |
11fdf7f2 TL |
446 | } |
447 | ||
448 | // size_t is 32-bit with 32-bit builds | |
449 | if (fileSize > std::numeric_limits<size_t>::max()) { | |
450 | return Status::NotSupported( | |
494da23a TL |
451 | "The specified file size does not fit into 32-bit memory addressing: " |
452 | + fname); | |
11fdf7f2 TL |
453 | } |
454 | ||
494da23a TL |
455 | HANDLE hMap = RX_CreateFileMapping(hFile, NULL, PAGE_READWRITE, |
456 | 0, // Whole file at its present length | |
457 | 0, | |
458 | NULL); // Mapping name | |
11fdf7f2 TL |
459 | |
460 | if (!hMap) { | |
461 | auto lastError = GetLastError(); | |
462 | return IOErrorFromWindowsError( | |
494da23a | 463 | "Failed to create file mapping for: " + fname, lastError); |
11fdf7f2 TL |
464 | } |
465 | UniqueCloseHandlePtr mapGuard(hMap, CloseHandleFunc); | |
466 | ||
467 | void* base = MapViewOfFileEx(hMap, FILE_MAP_WRITE, | |
494da23a TL |
468 | 0, // High DWORD of access start |
469 | 0, // Low DWORD | |
470 | static_cast<SIZE_T>(fileSize), | |
471 | NULL); // Let the OS choose the mapping | |
11fdf7f2 TL |
472 | |
473 | if (!base) { | |
474 | auto lastError = GetLastError(); | |
475 | return IOErrorFromWindowsError( | |
494da23a TL |
476 | "Failed to MapViewOfFile for NewMemoryMappedFileBuffer: " + fname, |
477 | lastError); | |
11fdf7f2 TL |
478 | } |
479 | ||
494da23a TL |
480 | result->reset(new WinMemoryMappedBuffer(hFile, hMap, base, |
481 | static_cast<size_t>(fileSize))); | |
11fdf7f2 TL |
482 | |
483 | mapGuard.release(); | |
7c673cae FG |
484 | fileGuard.release(); |
485 | ||
486 | return s; | |
487 | } | |
488 | ||
489 | Status WinEnvIO::NewDirectory(const std::string& name, | |
494da23a | 490 | std::unique_ptr<Directory>* result) { |
7c673cae FG |
491 | Status s; |
492 | // Must be nullptr on failure | |
493 | result->reset(); | |
11fdf7f2 | 494 | |
7c673cae | 495 | if (!DirExists(name)) { |
11fdf7f2 | 496 | s = IOErrorFromWindowsError( |
494da23a | 497 | "open folder: " + name, ERROR_DIRECTORY); |
11fdf7f2 TL |
498 | return s; |
499 | } | |
500 | ||
501 | HANDLE handle = INVALID_HANDLE_VALUE; | |
502 | // 0 - for access means read metadata | |
503 | { | |
7c673cae | 504 | IOSTATS_TIMER_GUARD(open_nanos); |
494da23a TL |
505 | handle = RX_CreateFile( |
506 | RX_FN(name).c_str(), 0, | |
507 | FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, | |
508 | NULL, | |
509 | OPEN_EXISTING, | |
510 | FILE_FLAG_BACKUP_SEMANTICS, // make opening folders possible | |
511 | NULL); | |
11fdf7f2 TL |
512 | } |
513 | ||
514 | if (INVALID_HANDLE_VALUE == handle) { | |
515 | auto lastError = GetLastError(); | |
494da23a | 516 | s = IOErrorFromWindowsError("open folder: " + name, lastError); |
11fdf7f2 | 517 | return s; |
7c673cae | 518 | } |
11fdf7f2 TL |
519 | |
520 | result->reset(new WinDirectory(handle)); | |
521 | ||
7c673cae FG |
522 | return s; |
523 | } | |
524 | ||
525 | Status WinEnvIO::FileExists(const std::string& fname) { | |
11fdf7f2 TL |
526 | Status s; |
527 | // TODO: This does not follow symbolic links at this point | |
528 | // which is consistent with _access() impl on windows | |
529 | // but can be added | |
530 | WIN32_FILE_ATTRIBUTE_DATA attrs; | |
494da23a TL |
531 | if (FALSE == RX_GetFileAttributesEx(RX_FN(fname).c_str(), |
532 | GetFileExInfoStandard, &attrs)) { | |
11fdf7f2 TL |
533 | auto lastError = GetLastError(); |
534 | switch (lastError) { | |
535 | case ERROR_ACCESS_DENIED: | |
536 | case ERROR_NOT_FOUND: | |
537 | case ERROR_FILE_NOT_FOUND: | |
538 | case ERROR_PATH_NOT_FOUND: | |
539 | s = Status::NotFound(); | |
540 | break; | |
541 | default: | |
542 | s = IOErrorFromWindowsError("Unexpected error for: " + fname, | |
494da23a | 543 | lastError); |
11fdf7f2 TL |
544 | break; |
545 | } | |
546 | } | |
547 | return s; | |
7c673cae FG |
548 | } |
549 | ||
550 | Status WinEnvIO::GetChildren(const std::string& dir, | |
494da23a | 551 | std::vector<std::string>* result) { |
7c673cae | 552 | |
11fdf7f2 | 553 | Status status; |
7c673cae FG |
554 | result->clear(); |
555 | std::vector<std::string> output; | |
556 | ||
494da23a TL |
557 | RX_WIN32_FIND_DATA data; |
558 | memset(&data, 0, sizeof(data)); | |
11fdf7f2 TL |
559 | std::string pattern(dir); |
560 | pattern.append("\\").append("*"); | |
7c673cae | 561 | |
494da23a TL |
562 | HANDLE handle = RX_FindFirstFileEx(RX_FN(pattern).c_str(), |
563 | // Do not want alternative name | |
564 | FindExInfoBasic, | |
565 | &data, | |
566 | FindExSearchNameMatch, | |
567 | NULL, // lpSearchFilter | |
568 | 0); | |
7c673cae | 569 | |
11fdf7f2 TL |
570 | if (handle == INVALID_HANDLE_VALUE) { |
571 | auto lastError = GetLastError(); | |
572 | switch (lastError) { | |
573 | case ERROR_NOT_FOUND: | |
574 | case ERROR_ACCESS_DENIED: | |
575 | case ERROR_FILE_NOT_FOUND: | |
576 | case ERROR_PATH_NOT_FOUND: | |
577 | status = Status::NotFound(); | |
578 | break; | |
579 | default: | |
580 | status = IOErrorFromWindowsError( | |
494da23a | 581 | "Failed to GetChhildren for: " + dir, lastError); |
7c673cae | 582 | } |
11fdf7f2 | 583 | return status; |
7c673cae FG |
584 | } |
585 | ||
11fdf7f2 TL |
586 | UniqueFindClosePtr fc(handle, FindCloseFunc); |
587 | ||
588 | if (result->capacity() > 0) { | |
589 | output.reserve(result->capacity()); | |
590 | } | |
7c673cae | 591 | |
11fdf7f2 TL |
592 | // For safety |
593 | data.cFileName[MAX_PATH - 1] = 0; | |
594 | ||
595 | while (true) { | |
494da23a TL |
596 | auto x = RX_FILESTRING(data.cFileName, RX_FNLEN(data.cFileName)); |
597 | output.emplace_back(FN_TO_RX(x)); | |
598 | BOOL ret =- RX_FindNextFile(handle, &data); | |
11fdf7f2 TL |
599 | // If the function fails the return value is zero |
600 | // and non-zero otherwise. Not TRUE or FALSE. | |
601 | if (ret == FALSE) { | |
602 | // Posix does not care why we stopped | |
603 | break; | |
604 | } | |
605 | data.cFileName[MAX_PATH - 1] = 0; | |
606 | } | |
607 | output.swap(*result); | |
7c673cae FG |
608 | return status; |
609 | } | |
610 | ||
611 | Status WinEnvIO::CreateDir(const std::string& name) { | |
612 | Status result; | |
494da23a | 613 | BOOL ret = RX_CreateDirectory(RX_FN(name).c_str(), NULL); |
11fdf7f2 TL |
614 | if (!ret) { |
615 | auto lastError = GetLastError(); | |
616 | result = IOErrorFromWindowsError( | |
617 | "Failed to create a directory: " + name, lastError); | |
7c673cae FG |
618 | } |
619 | ||
620 | return result; | |
621 | } | |
622 | ||
623 | Status WinEnvIO::CreateDirIfMissing(const std::string& name) { | |
624 | Status result; | |
625 | ||
626 | if (DirExists(name)) { | |
627 | return result; | |
628 | } | |
629 | ||
494da23a | 630 | BOOL ret = RX_CreateDirectory(RX_FN(name).c_str(), NULL); |
11fdf7f2 TL |
631 | if (!ret) { |
632 | auto lastError = GetLastError(); | |
633 | if (lastError != ERROR_ALREADY_EXISTS) { | |
634 | result = IOErrorFromWindowsError( | |
494da23a | 635 | "Failed to create a directory: " + name, lastError); |
7c673cae | 636 | } else { |
11fdf7f2 | 637 | result = |
494da23a | 638 | Status::IOError(name + ": exists but is not a directory"); |
7c673cae FG |
639 | } |
640 | } | |
7c673cae FG |
641 | return result; |
642 | } | |
643 | ||
644 | Status WinEnvIO::DeleteDir(const std::string& name) { | |
645 | Status result; | |
494da23a | 646 | BOOL ret = RX_RemoveDirectory(RX_FN(name).c_str()); |
11fdf7f2 TL |
647 | if (!ret) { |
648 | auto lastError = GetLastError(); | |
494da23a TL |
649 | result = IOErrorFromWindowsError("Failed to remove dir: " + name, |
650 | lastError); | |
7c673cae FG |
651 | } |
652 | return result; | |
653 | } | |
654 | ||
655 | Status WinEnvIO::GetFileSize(const std::string& fname, | |
656 | uint64_t* size) { | |
657 | Status s; | |
658 | ||
659 | WIN32_FILE_ATTRIBUTE_DATA attrs; | |
494da23a TL |
660 | if (RX_GetFileAttributesEx(RX_FN(fname).c_str(), GetFileExInfoStandard, |
661 | &attrs)) { | |
7c673cae FG |
662 | ULARGE_INTEGER file_size; |
663 | file_size.HighPart = attrs.nFileSizeHigh; | |
664 | file_size.LowPart = attrs.nFileSizeLow; | |
665 | *size = file_size.QuadPart; | |
666 | } else { | |
667 | auto lastError = GetLastError(); | |
668 | s = IOErrorFromWindowsError("Can not get size for: " + fname, lastError); | |
669 | } | |
670 | return s; | |
671 | } | |
672 | ||
673 | uint64_t WinEnvIO::FileTimeToUnixTime(const FILETIME& ftTime) { | |
674 | const uint64_t c_FileTimePerSecond = 10000000U; | |
675 | // UNIX epoch starts on 1970-01-01T00:00:00Z | |
676 | // Windows FILETIME starts on 1601-01-01T00:00:00Z | |
677 | // Therefore, we need to subtract the below number of seconds from | |
678 | // the seconds that we obtain from FILETIME with an obvious loss of | |
679 | // precision | |
680 | const uint64_t c_SecondBeforeUnixEpoch = 11644473600U; | |
681 | ||
682 | ULARGE_INTEGER li; | |
683 | li.HighPart = ftTime.dwHighDateTime; | |
684 | li.LowPart = ftTime.dwLowDateTime; | |
685 | ||
686 | uint64_t result = | |
687 | (li.QuadPart / c_FileTimePerSecond) - c_SecondBeforeUnixEpoch; | |
688 | return result; | |
689 | } | |
690 | ||
691 | Status WinEnvIO::GetFileModificationTime(const std::string& fname, | |
692 | uint64_t* file_mtime) { | |
693 | Status s; | |
694 | ||
695 | WIN32_FILE_ATTRIBUTE_DATA attrs; | |
494da23a TL |
696 | if (RX_GetFileAttributesEx(RX_FN(fname).c_str(), GetFileExInfoStandard, |
697 | &attrs)) { | |
7c673cae FG |
698 | *file_mtime = FileTimeToUnixTime(attrs.ftLastWriteTime); |
699 | } else { | |
700 | auto lastError = GetLastError(); | |
701 | s = IOErrorFromWindowsError( | |
702 | "Can not get file modification time for: " + fname, lastError); | |
703 | *file_mtime = 0; | |
704 | } | |
705 | ||
706 | return s; | |
707 | } | |
708 | ||
709 | Status WinEnvIO::RenameFile(const std::string& src, | |
710 | const std::string& target) { | |
711 | Status result; | |
712 | ||
713 | // rename() is not capable of replacing the existing file as on Linux | |
714 | // so use OS API directly | |
494da23a TL |
715 | if (!RX_MoveFileEx(RX_FN(src).c_str(), RX_FN(target).c_str(), |
716 | MOVEFILE_REPLACE_EXISTING)) { | |
7c673cae FG |
717 | DWORD lastError = GetLastError(); |
718 | ||
719 | std::string text("Failed to rename: "); | |
720 | text.append(src).append(" to: ").append(target); | |
721 | ||
722 | result = IOErrorFromWindowsError(text, lastError); | |
723 | } | |
724 | ||
725 | return result; | |
726 | } | |
727 | ||
728 | Status WinEnvIO::LinkFile(const std::string& src, | |
729 | const std::string& target) { | |
730 | Status result; | |
731 | ||
494da23a | 732 | if (!RX_CreateHardLink(RX_FN(target).c_str(), RX_FN(src).c_str(), NULL)) { |
7c673cae | 733 | DWORD lastError = GetLastError(); |
11fdf7f2 TL |
734 | if (lastError == ERROR_NOT_SAME_DEVICE) { |
735 | return Status::NotSupported("No cross FS links allowed"); | |
736 | } | |
7c673cae FG |
737 | |
738 | std::string text("Failed to link: "); | |
739 | text.append(src).append(" to: ").append(target); | |
740 | ||
741 | result = IOErrorFromWindowsError(text, lastError); | |
742 | } | |
743 | ||
744 | return result; | |
745 | } | |
746 | ||
11fdf7f2 TL |
747 | Status WinEnvIO::NumFileLinks(const std::string& fname, uint64_t* count) { |
748 | Status s; | |
494da23a TL |
749 | HANDLE handle = RX_CreateFile( |
750 | RX_FN(fname).c_str(), 0, | |
751 | FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, | |
11fdf7f2 TL |
752 | NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); |
753 | ||
754 | if (INVALID_HANDLE_VALUE == handle) { | |
755 | auto lastError = GetLastError(); | |
756 | s = IOErrorFromWindowsError("NumFileLinks: " + fname, lastError); | |
757 | return s; | |
758 | } | |
759 | UniqueCloseHandlePtr handle_guard(handle, CloseHandleFunc); | |
760 | FILE_STANDARD_INFO standard_info; | |
761 | if (0 != GetFileInformationByHandleEx(handle, FileStandardInfo, | |
762 | &standard_info, | |
763 | sizeof(standard_info))) { | |
764 | *count = standard_info.NumberOfLinks; | |
765 | } else { | |
766 | auto lastError = GetLastError(); | |
767 | s = IOErrorFromWindowsError("GetFileInformationByHandleEx: " + fname, | |
768 | lastError); | |
769 | } | |
770 | return s; | |
771 | } | |
772 | ||
773 | Status WinEnvIO::AreFilesSame(const std::string& first, | |
774 | const std::string& second, bool* res) { | |
775 | // For MinGW builds | |
776 | #if (_WIN32_WINNT == _WIN32_WINNT_VISTA) | |
777 | Status s = Status::NotSupported(); | |
778 | #else | |
779 | assert(res != nullptr); | |
780 | Status s; | |
781 | if (res == nullptr) { | |
782 | s = Status::InvalidArgument("res"); | |
783 | return s; | |
784 | } | |
785 | ||
786 | // 0 - for access means read metadata | |
494da23a TL |
787 | HANDLE file_1 = RX_CreateFile( |
788 | RX_FN(first).c_str(), 0, | |
789 | FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, | |
790 | NULL, | |
791 | OPEN_EXISTING, | |
792 | FILE_FLAG_BACKUP_SEMANTICS, // make opening folders possible | |
793 | NULL); | |
11fdf7f2 TL |
794 | |
795 | if (INVALID_HANDLE_VALUE == file_1) { | |
796 | auto lastError = GetLastError(); | |
494da23a | 797 | s = IOErrorFromWindowsError("open file: " + first, lastError); |
11fdf7f2 TL |
798 | return s; |
799 | } | |
800 | UniqueCloseHandlePtr g_1(file_1, CloseHandleFunc); | |
801 | ||
494da23a TL |
802 | HANDLE file_2 = RX_CreateFile( |
803 | RX_FN(second).c_str(), 0, | |
804 | FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, | |
805 | NULL, OPEN_EXISTING, | |
806 | FILE_FLAG_BACKUP_SEMANTICS, // make opening folders possible | |
807 | NULL); | |
11fdf7f2 TL |
808 | |
809 | if (INVALID_HANDLE_VALUE == file_2) { | |
810 | auto lastError = GetLastError(); | |
494da23a | 811 | s = IOErrorFromWindowsError("open file: " + second, lastError); |
11fdf7f2 TL |
812 | return s; |
813 | } | |
814 | UniqueCloseHandlePtr g_2(file_2, CloseHandleFunc); | |
815 | ||
816 | FILE_ID_INFO FileInfo_1; | |
817 | BOOL result = GetFileInformationByHandleEx(file_1, FileIdInfo, &FileInfo_1, | |
494da23a | 818 | sizeof(FileInfo_1)); |
11fdf7f2 TL |
819 | |
820 | if (!result) { | |
821 | auto lastError = GetLastError(); | |
494da23a | 822 | s = IOErrorFromWindowsError("stat file: " + first, lastError); |
11fdf7f2 TL |
823 | return s; |
824 | } | |
825 | ||
826 | FILE_ID_INFO FileInfo_2; | |
827 | result = GetFileInformationByHandleEx(file_2, FileIdInfo, &FileInfo_2, | |
494da23a | 828 | sizeof(FileInfo_2)); |
11fdf7f2 TL |
829 | |
830 | if (!result) { | |
831 | auto lastError = GetLastError(); | |
494da23a | 832 | s = IOErrorFromWindowsError("stat file: " + second, lastError); |
11fdf7f2 TL |
833 | return s; |
834 | } | |
835 | ||
836 | if (FileInfo_1.VolumeSerialNumber == FileInfo_2.VolumeSerialNumber) { | |
494da23a TL |
837 | *res = (0 == memcmp(FileInfo_1.FileId.Identifier, |
838 | FileInfo_2.FileId.Identifier, | |
839 | sizeof(FileInfo_1.FileId.Identifier))); | |
11fdf7f2 TL |
840 | } else { |
841 | *res = false; | |
842 | } | |
843 | #endif | |
844 | return s; | |
845 | } | |
846 | ||
7c673cae | 847 | Status WinEnvIO::LockFile(const std::string& lockFname, |
494da23a | 848 | FileLock** lock) { |
7c673cae FG |
849 | assert(lock != nullptr); |
850 | ||
851 | *lock = NULL; | |
852 | Status result; | |
853 | ||
854 | // No-sharing, this is a LOCK file | |
855 | const DWORD ExclusiveAccessON = 0; | |
856 | ||
857 | // Obtain exclusive access to the LOCK file | |
858 | // Previously, instead of NORMAL attr we set DELETE on close and that worked | |
859 | // well except with fault_injection test that insists on deleting it. | |
860 | HANDLE hFile = 0; | |
861 | { | |
862 | IOSTATS_TIMER_GUARD(open_nanos); | |
494da23a TL |
863 | hFile = RX_CreateFile(RX_FN(lockFname).c_str(), |
864 | (GENERIC_READ | GENERIC_WRITE), | |
865 | ExclusiveAccessON, NULL, CREATE_ALWAYS, | |
866 | FILE_ATTRIBUTE_NORMAL, NULL); | |
7c673cae FG |
867 | } |
868 | ||
869 | if (INVALID_HANDLE_VALUE == hFile) { | |
870 | auto lastError = GetLastError(); | |
871 | result = IOErrorFromWindowsError( | |
494da23a | 872 | "Failed to create lock file: " + lockFname, lastError); |
7c673cae FG |
873 | } else { |
874 | *lock = new WinFileLock(hFile); | |
875 | } | |
876 | ||
877 | return result; | |
878 | } | |
879 | ||
880 | Status WinEnvIO::UnlockFile(FileLock* lock) { | |
881 | Status result; | |
882 | ||
883 | assert(lock != nullptr); | |
884 | ||
885 | delete lock; | |
886 | ||
887 | return result; | |
888 | } | |
889 | ||
890 | Status WinEnvIO::GetTestDirectory(std::string* result) { | |
11fdf7f2 | 891 | |
7c673cae FG |
892 | std::string output; |
893 | ||
894 | const char* env = getenv("TEST_TMPDIR"); | |
895 | if (env && env[0] != '\0') { | |
896 | output = env; | |
7c673cae FG |
897 | } else { |
898 | env = getenv("TMP"); | |
899 | ||
900 | if (env && env[0] != '\0') { | |
901 | output = env; | |
902 | } else { | |
903 | output = "c:\\tmp"; | |
904 | } | |
7c673cae | 905 | } |
11fdf7f2 | 906 | CreateDir(output); |
7c673cae FG |
907 | |
908 | output.append("\\testrocksdb-"); | |
909 | output.append(std::to_string(_getpid())); | |
910 | ||
911 | CreateDir(output); | |
912 | ||
913 | output.swap(*result); | |
914 | ||
915 | return Status::OK(); | |
916 | } | |
917 | ||
918 | Status WinEnvIO::NewLogger(const std::string& fname, | |
494da23a | 919 | std::shared_ptr<Logger>* result) { |
7c673cae FG |
920 | Status s; |
921 | ||
922 | result->reset(); | |
923 | ||
924 | HANDLE hFile = 0; | |
925 | { | |
926 | IOSTATS_TIMER_GUARD(open_nanos); | |
494da23a TL |
927 | hFile = RX_CreateFile( |
928 | RX_FN(fname).c_str(), GENERIC_WRITE, | |
929 | FILE_SHARE_READ | FILE_SHARE_DELETE, // In RocksDb log files are | |
930 | // renamed and deleted before | |
931 | // they are closed. This enables | |
932 | // doing so. | |
933 | NULL, | |
934 | CREATE_ALWAYS, // Original fopen mode is "w" | |
935 | FILE_ATTRIBUTE_NORMAL, NULL); | |
7c673cae FG |
936 | } |
937 | ||
938 | if (INVALID_HANDLE_VALUE == hFile) { | |
939 | auto lastError = GetLastError(); | |
940 | s = IOErrorFromWindowsError("Failed to open LogFile" + fname, lastError); | |
941 | } else { | |
942 | { | |
943 | // With log files we want to set the true creation time as of now | |
944 | // because the system | |
945 | // for some reason caches the attributes of the previous file that just | |
946 | // been renamed from | |
947 | // this name so auto_roll_logger_test fails | |
948 | FILETIME ft; | |
949 | GetSystemTimeAsFileTime(&ft); | |
950 | // Set creation, last access and last write time to the same value | |
951 | SetFileTime(hFile, &ft, &ft, &ft); | |
952 | } | |
953 | result->reset(new WinLogger(&WinEnvThreads::gettid, hosted_env_, hFile)); | |
954 | } | |
955 | return s; | |
956 | } | |
957 | ||
958 | uint64_t WinEnvIO::NowMicros() { | |
959 | ||
960 | if (GetSystemTimePreciseAsFileTime_ != NULL) { | |
961 | // all std::chrono clocks on windows proved to return | |
962 | // values that may repeat that is not good enough for some uses. | |
963 | const int64_t c_UnixEpochStartTicks = 116444736000000000LL; | |
964 | const int64_t c_FtToMicroSec = 10; | |
965 | ||
966 | // This interface needs to return system time and not | |
967 | // just any microseconds because it is often used as an argument | |
968 | // to TimedWait() on condition variable | |
969 | FILETIME ftSystemTime; | |
970 | GetSystemTimePreciseAsFileTime_(&ftSystemTime); | |
971 | ||
972 | LARGE_INTEGER li; | |
973 | li.LowPart = ftSystemTime.dwLowDateTime; | |
974 | li.HighPart = ftSystemTime.dwHighDateTime; | |
975 | // Subtract unix epoch start | |
976 | li.QuadPart -= c_UnixEpochStartTicks; | |
977 | // Convert to microsecs | |
978 | li.QuadPart /= c_FtToMicroSec; | |
979 | return li.QuadPart; | |
980 | } | |
981 | using namespace std::chrono; | |
f67539c2 TL |
982 | return duration_cast<microseconds>(system_clock::now().time_since_epoch()) |
983 | .count(); | |
7c673cae FG |
984 | } |
985 | ||
986 | uint64_t WinEnvIO::NowNanos() { | |
494da23a TL |
987 | if (nano_seconds_per_period_ != 0) { |
988 | // all std::chrono clocks on windows have the same resolution that is only | |
989 | // good enough for microseconds but not nanoseconds | |
990 | // On Windows 8 and Windows 2012 Server | |
991 | // GetSystemTimePreciseAsFileTime(¤t_time) can be used | |
992 | LARGE_INTEGER li; | |
993 | QueryPerformanceCounter(&li); | |
994 | // Convert performance counter to nanoseconds by precomputed ratio. | |
995 | // Directly multiply nano::den with li.QuadPart causes overflow. | |
996 | // Only do this when nano::den is divisible by perf_counter_frequency_, | |
997 | // which most likely is the case in reality. If it's not, fall back to | |
998 | // high_resolution_clock, which may be less precise under old compilers. | |
999 | li.QuadPart *= nano_seconds_per_period_; | |
1000 | return li.QuadPart; | |
1001 | } | |
1002 | using namespace std::chrono; | |
1003 | return duration_cast<nanoseconds>( | |
1004 | high_resolution_clock::now().time_since_epoch()).count(); | |
7c673cae FG |
1005 | } |
1006 | ||
1007 | Status WinEnvIO::GetHostName(char* name, uint64_t len) { | |
1008 | Status s; | |
1009 | DWORD nSize = static_cast<DWORD>( | |
1010 | std::min<uint64_t>(len, std::numeric_limits<DWORD>::max())); | |
1011 | ||
1012 | if (!::GetComputerNameA(name, &nSize)) { | |
1013 | auto lastError = GetLastError(); | |
1014 | s = IOErrorFromWindowsError("GetHostName", lastError); | |
1015 | } else { | |
1016 | name[nSize] = 0; | |
1017 | } | |
1018 | ||
1019 | return s; | |
1020 | } | |
1021 | ||
1022 | Status WinEnvIO::GetAbsolutePath(const std::string& db_path, | |
494da23a | 1023 | std::string* output_path) { |
7c673cae | 1024 | // Check if we already have an absolute path |
11fdf7f2 TL |
1025 | // For test compatibility we will consider starting slash as an |
1026 | // absolute path | |
1027 | if ((!db_path.empty() && (db_path[0] == '\\' || db_path[0] == '/')) || | |
494da23a | 1028 | !RX_PathIsRelative(RX_FN(db_path).c_str())) { |
7c673cae FG |
1029 | *output_path = db_path; |
1030 | return Status::OK(); | |
1031 | } | |
1032 | ||
494da23a | 1033 | RX_FILESTRING result; |
11fdf7f2 | 1034 | result.resize(MAX_PATH); |
7c673cae | 1035 | |
11fdf7f2 TL |
1036 | // Hopefully no changes the current directory while we do this |
1037 | // however _getcwd also suffers from the same limitation | |
494da23a | 1038 | DWORD len = RX_GetCurrentDirectory(MAX_PATH, &result[0]); |
11fdf7f2 TL |
1039 | if (len == 0) { |
1040 | auto lastError = GetLastError(); | |
1041 | return IOErrorFromWindowsError("Failed to get current working directory", | |
494da23a | 1042 | lastError); |
7c673cae FG |
1043 | } |
1044 | ||
11fdf7f2 | 1045 | result.resize(len); |
494da23a | 1046 | std::string res = FN_TO_RX(result); |
7c673cae | 1047 | |
494da23a | 1048 | res.swap(*output_path); |
7c673cae FG |
1049 | return Status::OK(); |
1050 | } | |
1051 | ||
1052 | std::string WinEnvIO::TimeToString(uint64_t secondsSince1970) { | |
1053 | std::string result; | |
1054 | ||
1055 | const time_t seconds = secondsSince1970; | |
1056 | const int maxsize = 64; | |
1057 | ||
1058 | struct tm t; | |
1059 | errno_t ret = localtime_s(&t, &seconds); | |
1060 | ||
1061 | if (ret) { | |
1062 | result = std::to_string(seconds); | |
1063 | } else { | |
1064 | result.resize(maxsize); | |
1065 | char* p = &result[0]; | |
1066 | ||
1067 | int len = snprintf(p, maxsize, "%04d/%02d/%02d-%02d:%02d:%02d ", | |
494da23a TL |
1068 | t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour, |
1069 | t.tm_min, t.tm_sec); | |
7c673cae FG |
1070 | assert(len > 0); |
1071 | ||
1072 | result.resize(len); | |
1073 | } | |
1074 | ||
1075 | return result; | |
1076 | } | |
1077 | ||
f67539c2 TL |
1078 | Status WinEnvIO::GetFreeSpace(const std::string& path, uint64_t* diskfree) { |
1079 | assert(diskfree != nullptr); | |
1080 | ULARGE_INTEGER freeBytes; | |
1081 | BOOL f = RX_GetDiskFreeSpaceEx(RX_FN(path).c_str(), &freeBytes, NULL, NULL); | |
1082 | if (f) { | |
1083 | *diskfree = freeBytes.QuadPart; | |
1084 | return Status::OK(); | |
1085 | } else { | |
1086 | DWORD lastError = GetLastError(); | |
1087 | return IOErrorFromWindowsError("Failed to get free space: " + path, | |
1088 | lastError); | |
1089 | } | |
1090 | } | |
1091 | ||
7c673cae | 1092 | EnvOptions WinEnvIO::OptimizeForLogWrite(const EnvOptions& env_options, |
494da23a | 1093 | const DBOptions& db_options) const { |
11fdf7f2 TL |
1094 | EnvOptions optimized(env_options); |
1095 | // These two the same as default optimizations | |
7c673cae | 1096 | optimized.bytes_per_sync = db_options.wal_bytes_per_sync; |
11fdf7f2 TL |
1097 | optimized.writable_file_max_buffer_size = |
1098 | db_options.writable_file_max_buffer_size; | |
1099 | ||
1100 | // This adversely affects %999 on windows | |
7c673cae | 1101 | optimized.use_mmap_writes = false; |
11fdf7f2 TL |
1102 | // Direct writes will produce a huge perf impact on |
1103 | // Windows. Pre-allocate space for WAL. | |
7c673cae | 1104 | optimized.use_direct_writes = false; |
7c673cae FG |
1105 | return optimized; |
1106 | } | |
1107 | ||
1108 | EnvOptions WinEnvIO::OptimizeForManifestWrite( | |
494da23a | 1109 | const EnvOptions& env_options) const { |
11fdf7f2 | 1110 | EnvOptions optimized(env_options); |
7c673cae | 1111 | optimized.use_mmap_writes = false; |
11fdf7f2 TL |
1112 | optimized.use_direct_reads = false; |
1113 | return optimized; | |
1114 | } | |
1115 | ||
1116 | EnvOptions WinEnvIO::OptimizeForManifestRead( | |
494da23a | 1117 | const EnvOptions& env_options) const { |
11fdf7f2 TL |
1118 | EnvOptions optimized(env_options); |
1119 | optimized.use_mmap_writes = false; | |
1120 | optimized.use_direct_reads = false; | |
7c673cae FG |
1121 | return optimized; |
1122 | } | |
1123 | ||
1124 | // Returns true iff the named directory exists and is a directory. | |
1125 | bool WinEnvIO::DirExists(const std::string& dname) { | |
1126 | WIN32_FILE_ATTRIBUTE_DATA attrs; | |
494da23a TL |
1127 | if (RX_GetFileAttributesEx(RX_FN(dname).c_str(), |
1128 | GetFileExInfoStandard, &attrs)) { | |
7c673cae FG |
1129 | return 0 != (attrs.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); |
1130 | } | |
1131 | return false; | |
1132 | } | |
1133 | ||
11fdf7f2 TL |
1134 | size_t WinEnvIO::GetSectorSize(const std::string& fname) { |
1135 | size_t sector_size = kSectorSize; | |
1136 | ||
494da23a | 1137 | if (RX_PathIsRelative(RX_FN(fname).c_str())) { |
11fdf7f2 TL |
1138 | return sector_size; |
1139 | } | |
1140 | ||
1141 | // obtain device handle | |
1142 | char devicename[7] = "\\\\.\\"; | |
1143 | int erresult = strncat_s(devicename, sizeof(devicename), fname.c_str(), 2); | |
1144 | ||
1145 | if (erresult) { | |
1146 | assert(false); | |
1147 | return sector_size; | |
1148 | } | |
1149 | ||
494da23a TL |
1150 | HANDLE hDevice = CreateFile(devicename, 0, 0, nullptr, OPEN_EXISTING, |
1151 | FILE_ATTRIBUTE_NORMAL, nullptr); | |
11fdf7f2 TL |
1152 | |
1153 | if (hDevice == INVALID_HANDLE_VALUE) { | |
1154 | return sector_size; | |
1155 | } | |
1156 | ||
1157 | STORAGE_PROPERTY_QUERY spropertyquery; | |
1158 | spropertyquery.PropertyId = StorageAccessAlignmentProperty; | |
1159 | spropertyquery.QueryType = PropertyStandardQuery; | |
1160 | ||
1161 | BYTE output_buffer[sizeof(STORAGE_ACCESS_ALIGNMENT_DESCRIPTOR)]; | |
1162 | DWORD output_bytes = 0; | |
1163 | ||
1164 | BOOL ret = DeviceIoControl(hDevice, IOCTL_STORAGE_QUERY_PROPERTY, | |
494da23a TL |
1165 | &spropertyquery, sizeof(spropertyquery), |
1166 | output_buffer, | |
1167 | sizeof(STORAGE_ACCESS_ALIGNMENT_DESCRIPTOR), | |
1168 | &output_bytes, nullptr); | |
11fdf7f2 TL |
1169 | |
1170 | if (ret) { | |
1171 | sector_size = ((STORAGE_ACCESS_ALIGNMENT_DESCRIPTOR *)output_buffer)->BytesPerLogicalSector; | |
1172 | } else { | |
1173 | // many devices do not support StorageProcessAlignmentProperty. Any failure here and we | |
1174 | // fall back to logical alignment | |
1175 | ||
1176 | DISK_GEOMETRY_EX geometry = { 0 }; | |
1177 | ret = DeviceIoControl(hDevice, IOCTL_DISK_GET_DRIVE_GEOMETRY, | |
1178 | nullptr, 0, &geometry, sizeof(geometry), &output_bytes, nullptr); | |
1179 | if (ret) { | |
1180 | sector_size = geometry.Geometry.BytesPerSector; | |
1181 | } | |
1182 | } | |
1183 | ||
1184 | if (hDevice != INVALID_HANDLE_VALUE) { | |
1185 | CloseHandle(hDevice); | |
1186 | } | |
1187 | ||
1188 | return sector_size; | |
1189 | } | |
1190 | ||
7c673cae FG |
1191 | //////////////////////////////////////////////////////////////////////// |
1192 | // WinEnvThreads | |
1193 | ||
494da23a TL |
1194 | WinEnvThreads::WinEnvThreads(Env* hosted_env) |
1195 | : hosted_env_(hosted_env), thread_pools_(Env::Priority::TOTAL) { | |
7c673cae FG |
1196 | |
1197 | for (int pool_id = 0; pool_id < Env::Priority::TOTAL; ++pool_id) { | |
1198 | thread_pools_[pool_id].SetThreadPriority( | |
1199 | static_cast<Env::Priority>(pool_id)); | |
1200 | // This allows later initializing the thread-local-env of each thread. | |
1201 | thread_pools_[pool_id].SetHostEnv(hosted_env); | |
1202 | } | |
1203 | } | |
1204 | ||
1205 | WinEnvThreads::~WinEnvThreads() { | |
1206 | ||
1207 | WaitForJoin(); | |
1208 | ||
1209 | for (auto& thpool : thread_pools_) { | |
1210 | thpool.JoinAllThreads(); | |
1211 | } | |
1212 | } | |
1213 | ||
494da23a TL |
1214 | void WinEnvThreads::Schedule(void(*function)(void*), void* arg, |
1215 | Env::Priority pri, void* tag, | |
1216 | void(*unschedFunction)(void* arg)) { | |
11fdf7f2 | 1217 | assert(pri >= Env::Priority::BOTTOM && pri <= Env::Priority::HIGH); |
7c673cae FG |
1218 | thread_pools_[pri].Schedule(function, arg, tag, unschedFunction); |
1219 | } | |
1220 | ||
1221 | int WinEnvThreads::UnSchedule(void* arg, Env::Priority pri) { | |
1222 | return thread_pools_[pri].UnSchedule(arg); | |
1223 | } | |
1224 | ||
1225 | namespace { | |
1226 | ||
1227 | struct StartThreadState { | |
1228 | void(*user_function)(void*); | |
1229 | void* arg; | |
1230 | }; | |
1231 | ||
1232 | void* StartThreadWrapper(void* arg) { | |
1233 | std::unique_ptr<StartThreadState> state( | |
1234 | reinterpret_cast<StartThreadState*>(arg)); | |
1235 | state->user_function(state->arg); | |
1236 | return nullptr; | |
1237 | } | |
1238 | ||
1239 | } | |
1240 | ||
1241 | void WinEnvThreads::StartThread(void(*function)(void* arg), void* arg) { | |
1242 | std::unique_ptr<StartThreadState> state(new StartThreadState); | |
1243 | state->user_function = function; | |
1244 | state->arg = arg; | |
1245 | try { | |
f67539c2 | 1246 | ROCKSDB_NAMESPACE::port::WindowsThread th(&StartThreadWrapper, state.get()); |
7c673cae FG |
1247 | state.release(); |
1248 | ||
1249 | std::lock_guard<std::mutex> lg(mu_); | |
1250 | threads_to_join_.push_back(std::move(th)); | |
1251 | ||
1252 | } catch (const std::system_error& ex) { | |
1253 | WinthreadCall("start thread", ex.code()); | |
1254 | } | |
1255 | } | |
1256 | ||
1257 | void WinEnvThreads::WaitForJoin() { | |
1258 | for (auto& th : threads_to_join_) { | |
1259 | th.join(); | |
1260 | } | |
1261 | threads_to_join_.clear(); | |
1262 | } | |
1263 | ||
1264 | unsigned int WinEnvThreads::GetThreadPoolQueueLen(Env::Priority pri) const { | |
11fdf7f2 | 1265 | assert(pri >= Env::Priority::BOTTOM && pri <= Env::Priority::HIGH); |
7c673cae FG |
1266 | return thread_pools_[pri].GetQueueLen(); |
1267 | } | |
1268 | ||
1269 | uint64_t WinEnvThreads::gettid() { | |
1270 | uint64_t thread_id = GetCurrentThreadId(); | |
1271 | return thread_id; | |
1272 | } | |
1273 | ||
1274 | uint64_t WinEnvThreads::GetThreadID() const { return gettid(); } | |
1275 | ||
1276 | void WinEnvThreads::SleepForMicroseconds(int micros) { | |
1277 | std::this_thread::sleep_for(std::chrono::microseconds(micros)); | |
1278 | } | |
1279 | ||
1280 | void WinEnvThreads::SetBackgroundThreads(int num, Env::Priority pri) { | |
11fdf7f2 | 1281 | assert(pri >= Env::Priority::BOTTOM && pri <= Env::Priority::HIGH); |
7c673cae FG |
1282 | thread_pools_[pri].SetBackgroundThreads(num); |
1283 | } | |
1284 | ||
11fdf7f2 TL |
1285 | int WinEnvThreads::GetBackgroundThreads(Env::Priority pri) { |
1286 | assert(pri >= Env::Priority::BOTTOM && pri <= Env::Priority::HIGH); | |
1287 | return thread_pools_[pri].GetBackgroundThreads(); | |
1288 | } | |
1289 | ||
7c673cae | 1290 | void WinEnvThreads::IncBackgroundThreadsIfNeeded(int num, Env::Priority pri) { |
11fdf7f2 | 1291 | assert(pri >= Env::Priority::BOTTOM && pri <= Env::Priority::HIGH); |
7c673cae FG |
1292 | thread_pools_[pri].IncBackgroundThreadsIfNeeded(num); |
1293 | } | |
1294 | ||
1295 | ///////////////////////////////////////////////////////////////////////// | |
1296 | // WinEnv | |
1297 | ||
1298 | WinEnv::WinEnv() : winenv_io_(this), winenv_threads_(this) { | |
1299 | // Protected member of the base class | |
1300 | thread_status_updater_ = CreateThreadStatusUpdater(); | |
1301 | } | |
1302 | ||
1303 | ||
1304 | WinEnv::~WinEnv() { | |
1305 | // All threads must be joined before the deletion of | |
1306 | // thread_status_updater_. | |
1307 | delete thread_status_updater_; | |
1308 | } | |
1309 | ||
494da23a | 1310 | Status WinEnv::GetThreadList(std::vector<ThreadStatus>* thread_list) { |
7c673cae FG |
1311 | assert(thread_status_updater_); |
1312 | return thread_status_updater_->GetThreadList(thread_list); | |
1313 | } | |
1314 | ||
1315 | Status WinEnv::DeleteFile(const std::string& fname) { | |
1316 | return winenv_io_.DeleteFile(fname); | |
1317 | } | |
1318 | ||
11fdf7f2 TL |
1319 | Status WinEnv::Truncate(const std::string& fname, size_t size) { |
1320 | return winenv_io_.Truncate(fname, size); | |
1321 | } | |
1322 | ||
7c673cae FG |
1323 | Status WinEnv::GetCurrentTime(int64_t* unix_time) { |
1324 | return winenv_io_.GetCurrentTime(unix_time); | |
1325 | } | |
1326 | ||
1327 | Status WinEnv::NewSequentialFile(const std::string& fname, | |
494da23a TL |
1328 | std::unique_ptr<SequentialFile>* result, |
1329 | const EnvOptions& options) { | |
7c673cae FG |
1330 | return winenv_io_.NewSequentialFile(fname, result, options); |
1331 | } | |
1332 | ||
1333 | Status WinEnv::NewRandomAccessFile(const std::string& fname, | |
494da23a TL |
1334 | std::unique_ptr<RandomAccessFile>* result, |
1335 | const EnvOptions& options) { | |
7c673cae FG |
1336 | return winenv_io_.NewRandomAccessFile(fname, result, options); |
1337 | } | |
1338 | ||
1339 | Status WinEnv::NewWritableFile(const std::string& fname, | |
1340 | std::unique_ptr<WritableFile>* result, | |
1341 | const EnvOptions& options) { | |
11fdf7f2 TL |
1342 | return winenv_io_.OpenWritableFile(fname, result, options, false); |
1343 | } | |
1344 | ||
1345 | Status WinEnv::ReopenWritableFile(const std::string& fname, | |
494da23a TL |
1346 | std::unique_ptr<WritableFile>* result, |
1347 | const EnvOptions& options) { | |
11fdf7f2 | 1348 | return winenv_io_.OpenWritableFile(fname, result, options, true); |
7c673cae FG |
1349 | } |
1350 | ||
1351 | Status WinEnv::NewRandomRWFile(const std::string & fname, | |
494da23a TL |
1352 | std::unique_ptr<RandomRWFile>* result, |
1353 | const EnvOptions & options) { | |
7c673cae FG |
1354 | return winenv_io_.NewRandomRWFile(fname, result, options); |
1355 | } | |
1356 | ||
494da23a TL |
1357 | Status WinEnv::NewMemoryMappedFileBuffer( |
1358 | const std::string& fname, | |
1359 | std::unique_ptr<MemoryMappedFileBuffer>* result) { | |
11fdf7f2 TL |
1360 | return winenv_io_.NewMemoryMappedFileBuffer(fname, result); |
1361 | } | |
1362 | ||
7c673cae | 1363 | Status WinEnv::NewDirectory(const std::string& name, |
494da23a | 1364 | std::unique_ptr<Directory>* result) { |
7c673cae FG |
1365 | return winenv_io_.NewDirectory(name, result); |
1366 | } | |
1367 | ||
1368 | Status WinEnv::FileExists(const std::string& fname) { | |
1369 | return winenv_io_.FileExists(fname); | |
1370 | } | |
1371 | ||
1372 | Status WinEnv::GetChildren(const std::string& dir, | |
494da23a | 1373 | std::vector<std::string>* result) { |
7c673cae FG |
1374 | return winenv_io_.GetChildren(dir, result); |
1375 | } | |
1376 | ||
1377 | Status WinEnv::CreateDir(const std::string& name) { | |
1378 | return winenv_io_.CreateDir(name); | |
1379 | } | |
1380 | ||
1381 | Status WinEnv::CreateDirIfMissing(const std::string& name) { | |
1382 | return winenv_io_.CreateDirIfMissing(name); | |
1383 | } | |
1384 | ||
1385 | Status WinEnv::DeleteDir(const std::string& name) { | |
1386 | return winenv_io_.DeleteDir(name); | |
1387 | } | |
1388 | ||
1389 | Status WinEnv::GetFileSize(const std::string& fname, | |
494da23a | 1390 | uint64_t* size) { |
7c673cae FG |
1391 | return winenv_io_.GetFileSize(fname, size); |
1392 | } | |
1393 | ||
1394 | Status WinEnv::GetFileModificationTime(const std::string& fname, | |
494da23a | 1395 | uint64_t* file_mtime) { |
7c673cae FG |
1396 | return winenv_io_.GetFileModificationTime(fname, file_mtime); |
1397 | } | |
1398 | ||
1399 | Status WinEnv::RenameFile(const std::string& src, | |
494da23a | 1400 | const std::string& target) { |
7c673cae FG |
1401 | return winenv_io_.RenameFile(src, target); |
1402 | } | |
1403 | ||
1404 | Status WinEnv::LinkFile(const std::string& src, | |
494da23a | 1405 | const std::string& target) { |
7c673cae FG |
1406 | return winenv_io_.LinkFile(src, target); |
1407 | } | |
1408 | ||
11fdf7f2 TL |
1409 | Status WinEnv::NumFileLinks(const std::string& fname, uint64_t* count) { |
1410 | return winenv_io_.NumFileLinks(fname, count); | |
1411 | } | |
1412 | ||
1413 | Status WinEnv::AreFilesSame(const std::string& first, | |
494da23a | 1414 | const std::string& second, bool* res) { |
11fdf7f2 TL |
1415 | return winenv_io_.AreFilesSame(first, second, res); |
1416 | } | |
1417 | ||
7c673cae FG |
1418 | Status WinEnv::LockFile(const std::string& lockFname, |
1419 | FileLock** lock) { | |
1420 | return winenv_io_.LockFile(lockFname, lock); | |
1421 | } | |
1422 | ||
1423 | Status WinEnv::UnlockFile(FileLock* lock) { | |
1424 | return winenv_io_.UnlockFile(lock); | |
1425 | } | |
1426 | ||
1427 | Status WinEnv::GetTestDirectory(std::string* result) { | |
1428 | return winenv_io_.GetTestDirectory(result); | |
1429 | } | |
1430 | ||
1431 | Status WinEnv::NewLogger(const std::string& fname, | |
494da23a | 1432 | std::shared_ptr<Logger>* result) { |
7c673cae FG |
1433 | return winenv_io_.NewLogger(fname, result); |
1434 | } | |
1435 | ||
1436 | uint64_t WinEnv::NowMicros() { | |
1437 | return winenv_io_.NowMicros(); | |
1438 | } | |
1439 | ||
1440 | uint64_t WinEnv::NowNanos() { | |
1441 | return winenv_io_.NowNanos(); | |
1442 | } | |
1443 | ||
1444 | Status WinEnv::GetHostName(char* name, uint64_t len) { | |
1445 | return winenv_io_.GetHostName(name, len); | |
1446 | } | |
1447 | ||
1448 | Status WinEnv::GetAbsolutePath(const std::string& db_path, | |
1449 | std::string* output_path) { | |
1450 | return winenv_io_.GetAbsolutePath(db_path, output_path); | |
1451 | } | |
1452 | ||
1453 | std::string WinEnv::TimeToString(uint64_t secondsSince1970) { | |
1454 | return winenv_io_.TimeToString(secondsSince1970); | |
1455 | } | |
1456 | ||
1457 | void WinEnv::Schedule(void(*function)(void*), void* arg, Env::Priority pri, | |
494da23a TL |
1458 | void* tag, |
1459 | void(*unschedFunction)(void* arg)) { | |
7c673cae FG |
1460 | return winenv_threads_.Schedule(function, arg, pri, tag, unschedFunction); |
1461 | } | |
1462 | ||
1463 | int WinEnv::UnSchedule(void* arg, Env::Priority pri) { | |
1464 | return winenv_threads_.UnSchedule(arg, pri); | |
1465 | } | |
1466 | ||
1467 | void WinEnv::StartThread(void(*function)(void* arg), void* arg) { | |
1468 | return winenv_threads_.StartThread(function, arg); | |
1469 | } | |
1470 | ||
1471 | void WinEnv::WaitForJoin() { | |
1472 | return winenv_threads_.WaitForJoin(); | |
1473 | } | |
1474 | ||
1475 | unsigned int WinEnv::GetThreadPoolQueueLen(Env::Priority pri) const { | |
1476 | return winenv_threads_.GetThreadPoolQueueLen(pri); | |
1477 | } | |
1478 | ||
1479 | uint64_t WinEnv::GetThreadID() const { | |
1480 | return winenv_threads_.GetThreadID(); | |
1481 | } | |
1482 | ||
f67539c2 TL |
1483 | Status WinEnv::GetFreeSpace(const std::string& path, uint64_t* diskfree) { |
1484 | return winenv_io_.GetFreeSpace(path, diskfree); | |
1485 | } | |
1486 | ||
7c673cae FG |
1487 | void WinEnv::SleepForMicroseconds(int micros) { |
1488 | return winenv_threads_.SleepForMicroseconds(micros); | |
1489 | } | |
1490 | ||
1491 | // Allow increasing the number of worker threads. | |
1492 | void WinEnv::SetBackgroundThreads(int num, Env::Priority pri) { | |
1493 | return winenv_threads_.SetBackgroundThreads(num, pri); | |
1494 | } | |
1495 | ||
11fdf7f2 TL |
1496 | int WinEnv::GetBackgroundThreads(Env::Priority pri) { |
1497 | return winenv_threads_.GetBackgroundThreads(pri); | |
1498 | } | |
1499 | ||
7c673cae FG |
1500 | void WinEnv::IncBackgroundThreadsIfNeeded(int num, Env::Priority pri) { |
1501 | return winenv_threads_.IncBackgroundThreadsIfNeeded(num, pri); | |
1502 | } | |
1503 | ||
11fdf7f2 | 1504 | EnvOptions WinEnv::OptimizeForManifestRead( |
494da23a | 1505 | const EnvOptions& env_options) const { |
11fdf7f2 TL |
1506 | return winenv_io_.OptimizeForManifestRead(env_options); |
1507 | } | |
1508 | ||
7c673cae | 1509 | EnvOptions WinEnv::OptimizeForLogWrite(const EnvOptions& env_options, |
494da23a | 1510 | const DBOptions& db_options) const { |
7c673cae FG |
1511 | return winenv_io_.OptimizeForLogWrite(env_options, db_options); |
1512 | } | |
1513 | ||
1514 | EnvOptions WinEnv::OptimizeForManifestWrite( | |
494da23a | 1515 | const EnvOptions& env_options) const { |
7c673cae FG |
1516 | return winenv_io_.OptimizeForManifestWrite(env_options); |
1517 | } | |
1518 | ||
1519 | } // namespace port | |
1520 | ||
1521 | std::string Env::GenerateUniqueId() { | |
1522 | std::string result; | |
1523 | ||
1524 | UUID uuid; | |
1525 | UuidCreateSequential(&uuid); | |
1526 | ||
1527 | RPC_CSTR rpc_str; | |
1528 | auto status = UuidToStringA(&uuid, &rpc_str); | |
11fdf7f2 | 1529 | (void)status; |
7c673cae FG |
1530 | assert(status == RPC_S_OK); |
1531 | ||
1532 | result = reinterpret_cast<char*>(rpc_str); | |
1533 | ||
1534 | status = RpcStringFreeA(&rpc_str); | |
1535 | assert(status == RPC_S_OK); | |
1536 | ||
1537 | return result; | |
1538 | } | |
1539 | ||
f67539c2 | 1540 | } // namespace ROCKSDB_NAMESPACE |