@@ -77,11 +77,16 @@ static jint open_file(const char *utf8Path,
7777 }
7878 HANDLE h = CreateFileW (wide , desiredAccess , shareMode , NULL ,
7979 creationDisposition , flagsAndAttributes , NULL );
80- free (wide );
8180 if (h == INVALID_HANDLE_VALUE ) {
81+ // Save the CreateFileW failure code BEFORE free(): the thread's
82+ // last-error value may be overwritten by any subsequent API/CRT call
83+ // (HeapFree can SetLastError), and callers such as fsyncDir0 rely on
84+ // the saved value being the CreateFileW error.
8285 SaveLastError ();
86+ free (wide );
8387 return -1 ;
8488 }
89+ free (wide );
8590 return HANDLE_TO_FD (h );
8691}
8792
@@ -103,6 +108,15 @@ JNIEXPORT jint JNICALL Java_io_questdb_client_std_Files_openRW0
103108 FILE_ATTRIBUTE_NORMAL );
104109}
105110
111+ JNIEXPORT jint JNICALL Java_io_questdb_client_std_Files_openRWExclusive0
112+ (JNIEnv * e , jclass cl , jlong lpszName ) {
113+ return open_file ((const char * ) (uintptr_t ) lpszName ,
114+ GENERIC_READ | GENERIC_WRITE ,
115+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE ,
116+ CREATE_NEW ,
117+ FILE_ATTRIBUTE_NORMAL );
118+ }
119+
106120JNIEXPORT jint JNICALL Java_io_questdb_client_std_Files_openAppend0
107121 (JNIEnv * e , jclass cl , jlong lpszName ) {
108122 jint fd = open_file ((const char * ) (uintptr_t ) lpszName ,
@@ -215,6 +229,52 @@ JNIEXPORT jint JNICALL Java_io_questdb_client_std_Files_fsync
215229 return 0 ;
216230}
217231
232+ JNIEXPORT jint JNICALL Java_io_questdb_client_std_Files_fsyncDir0
233+ (JNIEnv * e , jclass cl , jlong lpszName ) {
234+ jint fd = open_file ((const char * ) (uintptr_t ) lpszName ,
235+ GENERIC_READ | GENERIC_WRITE ,
236+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE ,
237+ OPEN_EXISTING ,
238+ FILE_FLAG_BACKUP_SEMANTICS );
239+ if (fd < 0 ) {
240+ // A directory can be crash-consistent yet not openable for write:
241+ // some ACL/filesystem configurations refuse a GENERIC_WRITE open of a
242+ // directory handle with ERROR_ACCESS_DENIED. Directory-entry durability
243+ // on NTFS is provided by metadata journaling ($LogFile), so degrade to
244+ // best-effort success here rather than hard-failing the SF durability
245+ // path. Consult the error open_file() saved at the CreateFileW failure
246+ // point rather than the live GetLastError(): the intervening free()
247+ // and return path inside open_file() are not guaranteed to preserve
248+ // the thread's last-error value. A genuine failure such as a missing
249+ // directory (ERROR_PATH_NOT_FOUND) still propagates as fatal.
250+ if (GetSavedLastError () == ERROR_ACCESS_DENIED ) {
251+ return 0 ;
252+ }
253+ return -1 ;
254+ }
255+ if (!FlushFileBuffers (FD_TO_HANDLE (fd ))) {
256+ DWORD err = GetLastError ();
257+ CloseHandle (FD_TO_HANDLE (fd ));
258+ // FlushFileBuffers is documented for file/volume handles; NTFS refuses
259+ // it on a directory handle (typically ERROR_ACCESS_DENIED, and
260+ // ERROR_INVALID_FUNCTION on filesystems that do not implement it).
261+ // create/rename/unlink of directory entries are made crash consistent
262+ // by NTFS metadata journaling, so treat those "not supported"
263+ // signatures as best-effort success. This mirrors libgit2/PostgreSQL/
264+ // SQLite, which do not rely on directory fsync on Windows, and keeps
265+ // the SF manifest-create, slot-lock, and trim/unlink barriers from
266+ // hard-failing. Any other error is a real I/O failure and stays fatal.
267+ if (err == ERROR_ACCESS_DENIED || err == ERROR_INVALID_FUNCTION ) {
268+ return 0 ;
269+ }
270+ SetLastError (err );
271+ SaveLastError ();
272+ return -1 ;
273+ }
274+ CloseHandle (FD_TO_HANDLE (fd ));
275+ return 0 ;
276+ }
277+
218278JNIEXPORT jboolean JNICALL Java_io_questdb_client_std_Files_truncate
219279 (JNIEnv * e , jclass cl , jint fd , jlong size ) {
220280 FILE_END_OF_FILE_INFO eof ;
@@ -300,18 +360,24 @@ JNIEXPORT jlong JNICALL Java_io_questdb_client_std_Files_length0
300360 OPEN_EXISTING ,
301361 FILE_ATTRIBUTE_NORMAL ,
302362 NULL );
303- free (wide );
304363 if (h == INVALID_HANDLE_VALUE ) {
364+ /* Save the CreateFileW failure code BEFORE free(): any subsequent
365+ * API/CRT call (HeapFree can SetLastError) may overwrite the
366+ * thread's last-error value -- same pattern as open_file(). */
305367 SaveLastError ();
368+ free (wide );
306369 return -1 ;
307370 }
371+ free (wide );
308372 LARGE_INTEGER sz ;
309373 BOOL ok = GetFileSizeEx (h , & sz );
310- CloseHandle (h );
311374 if (!ok ) {
375+ /* Save before CloseHandle() can clobber the last-error value. */
312376 SaveLastError ();
377+ CloseHandle (h );
313378 return -1 ;
314379 }
380+ CloseHandle (h );
315381 return (jlong ) sz .QuadPart ;
316382}
317383
@@ -331,6 +397,20 @@ JNIEXPORT jint JNICALL Java_io_questdb_client_std_Files_lock
331397 return 0 ;
332398}
333399
400+ JNIEXPORT jint JNICALL Java_io_questdb_client_cutlass_qwp_client_sf_cursor_SlotLock_release0
401+ (JNIEnv * e , jclass cl , jint fd ) {
402+ OVERLAPPED ov ;
403+ memset (& ov , 0 , sizeof (ov ));
404+ if (!UnlockFileEx (FD_TO_HANDLE (fd ), 0 , MAXDWORD , MAXDWORD , & ov )) {
405+ SaveLastError ();
406+ return -1 ;
407+ }
408+ /* Unlock success confirms that the slot is reusable. Match POSIX by
409+ * closing once without making handle cleanup part of that signal. */
410+ (void ) CloseHandle (FD_TO_HANDLE (fd ));
411+ return 0 ;
412+ }
413+
334414JNIEXPORT jint JNICALL Java_io_questdb_client_std_Files_mkdir0
335415 (JNIEnv * e , jclass cl , jlong lpszPath , jint mode ) {
336416 (void ) mode ;
@@ -340,11 +420,13 @@ JNIEXPORT jint JNICALL Java_io_questdb_client_std_Files_mkdir0
340420 return -1 ;
341421 }
342422 BOOL ok = CreateDirectoryW (wide , NULL );
343- free (wide );
344423 if (!ok ) {
424+ /* Save before free() can clobber the last-error value. */
345425 SaveLastError ();
426+ free (wide );
346427 return -1 ;
347428 }
429+ free (wide );
348430 return 0 ;
349431}
350432
@@ -373,11 +455,13 @@ JNIEXPORT jboolean JNICALL Java_io_questdb_client_std_Files_remove0
373455 } else {
374456 ok = DeleteFileW (wide );
375457 }
376- free (wide );
377458 if (!ok ) {
459+ /* Save before free() can clobber the last-error value. */
378460 SaveLastError ();
461+ free (wide );
379462 return JNI_FALSE ;
380463 }
464+ free (wide );
381465 return JNI_TRUE ;
382466}
383467
@@ -395,12 +479,15 @@ JNIEXPORT jint JNICALL Java_io_questdb_client_std_Files_rename0
395479 return -1 ;
396480 }
397481 BOOL ok = MoveFileExW (oldW , newW , MOVEFILE_REPLACE_EXISTING );
398- free (oldW );
399- free (newW );
400482 if (!ok ) {
483+ /* Save before free() can clobber the last-error value. */
401484 SaveLastError ();
485+ free (oldW );
486+ free (newW );
402487 return -1 ;
403488 }
489+ free (oldW );
490+ free (newW );
404491 return 0 ;
405492}
406493
@@ -438,24 +525,28 @@ JNIEXPORT jlong JNICALL Java_io_questdb_client_std_Files_findFirst0
438525 pattern [pathLen ] = '\0' ;
439526
440527 wchar_t * wide = utf8_to_wide (pattern );
441- free (pattern );
442528 if (!wide ) {
529+ /* Save before free() can clobber the last-error value. */
443530 SaveLastError ();
531+ free (pattern );
444532 return 0 ;
445533 }
534+ free (pattern );
446535
447536 qdb_find_t * find = (qdb_find_t * ) malloc (sizeof (qdb_find_t ));
448537 if (!find ) {
449538 free (wide );
450539 return 0 ;
451540 }
452541 find -> handle = FindFirstFileW (wide , & find -> data );
453- free (wide );
454542 if (find -> handle == INVALID_HANDLE_VALUE ) {
543+ /* Save before free() can clobber the last-error value. */
455544 SaveLastError ();
545+ free (wide );
456546 free (find );
457547 return 0 ;
458548 }
549+ free (wide );
459550 find -> hasEntry = 1 ;
460551 win_findname_to_utf8 (find );
461552 return (jlong ) (uintptr_t ) find ;
@@ -615,3 +706,16 @@ JNIEXPORT jint JNICALL Java_io_questdb_client_std_Files_msync
615706 (void ) async ;
616707 return 0 ;
617708}
709+
710+ /* Best-effort page pin. VirtualLock is quota-bound to the process working-set
711+ * minimum; callers treat any refusal as a soft downgrade, so no
712+ * SaveLastError() -- the refusal is not surfaced as an error. */
713+ JNIEXPORT jint JNICALL Java_io_questdb_client_std_Files_mlock0
714+ (JNIEnv * e , jclass cl , jlong addr , jlong len ) {
715+ return VirtualLock ((LPVOID ) (uintptr_t ) addr , (SIZE_T ) len ) ? 0 : -1 ;
716+ }
717+
718+ JNIEXPORT jint JNICALL Java_io_questdb_client_std_Files_munlock0
719+ (JNIEnv * e , jclass cl , jlong addr , jlong len ) {
720+ return VirtualUnlock ((LPVOID ) (uintptr_t ) addr , (SIZE_T ) len ) ? 0 : -1 ;
721+ }
0 commit comments