KiCad PCB EDA Suite
Loading...
Searching...
No Matches
windows/io.cpp
Go to the documentation of this file.
1/*
2* This program source code file is part of KiCad, a free EDA CAD application.
3*
4* Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5*
6* This program is free software: you can redistribute it and/or modify it
7* under the terms of the GNU General Public License as published by the
8* Free Software Foundation, either version 3 of the License, or (at your
9* option) any later version.
10*
11* This program is distributed in the hope that it will be useful, but
12* WITHOUT ANY WARRANTY; without even the implied warranty of
13* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14* General Public License for more details.
15*
16* You should have received a copy of the GNU General Public License
17* along with this program. If not, see <https://www.gnu.org/licenses/>.
18*/
19
20#include <kiplatform/io.h>
21
22#include <wx/string.h>
23#include <wx/wxcrt.h>
24#include <wx/filename.h>
25
26#include <cstdio>
27#include <fcntl.h>
28#include <io.h>
29#include <stdexcept>
30#include <string>
31#include <vector>
32#include <windows.h>
33#include <shlwapi.h>
34#include <winternl.h>
35
36// NtQueryDirectoryFile-based directory enumeration for fast file listing.
37// This approach is based on git-for-windows fscache implementation:
38// https://github.com/git-for-windows/git/blob/main/compat/win32/fscache.c
39// Copyright (C) Johannes Schindelin and the Git for Windows project
40// Licensed under GPL v2.
41//
42// FILE_FULL_DIR_INFORMATION is documented in the Windows Driver Kit but not the SDK.
43
44#if !defined( __MINGW32__ ) // already defined in the included mingw header <winternl.h>
45 // So do not redefine it on mingw
61#endif
62
63typedef NTSTATUS( NTAPI* PFN_NtQueryDirectoryFile )( HANDLE, HANDLE, PIO_APC_ROUTINE, PVOID,
67
68#define FileFullDirectoryInformation ( (FILE_INFORMATION_CLASS) 2 )
69
70// Define USE_MSYS2_FALlBACK if the code for _MSC_VER does not compile on msys2
71//#define USE_MSYS2_FALLBACK
72
73FILE* KIPLATFORM::IO::SeqFOpen( const wxString& aPath, const wxString& aMode )
74{
75#if defined( _MSC_VER ) || !defined( USE_MSYS2_FALLBACK )
76 // We need to use the win32 api to setup a file handle with sequential scan flagged
77 // and pass it up the chain to create a normal FILE stream
78 HANDLE hFile = INVALID_HANDLE_VALUE;
79 hFile = CreateFileW( aPath.wc_str(),
80 GENERIC_READ,
81 FILE_SHARE_READ,
82 NULL,
83 OPEN_EXISTING,
84 FILE_FLAG_SEQUENTIAL_SCAN,
85 NULL );
86
87 if (hFile == INVALID_HANDLE_VALUE)
88 {
89 return NULL;
90 }
91
92 int fd = _open_osfhandle( reinterpret_cast<intptr_t>( hFile ), 0 );
93
94 if( fd == -1 )
95 {
96 // close the handle manually as the ownership didnt transfer
97 CloseHandle( hFile );
98 return NULL;
99 }
100
101 FILE* fp = _fdopen( fd, aMode.c_str() );
102
103 if( !fp )
104 {
105 // close the file descriptor manually as the ownership didnt transfer
106 _close( fd );
107 }
108
109 return fp;
110#else
111 // Fallback for MSYS2
112 return wxFopen( aPath, aMode );
113#endif
114}
115
116bool KIPLATFORM::IO::DuplicatePermissions( const wxString &aSrc, const wxString &aDest )
117{
118 // Only copy the DACL. Copying OWNER/GROUP would require SE_RESTORE_NAME when the
119 // target is owned by a different principal (common for files under ProgramData or
120 // on network shares), turning ACL preservation into a hard failure. The temp file
121 // is created by the current user, so leaving owner as the current user is correct.
122 const SECURITY_INFORMATION secInfo = DACL_SECURITY_INFORMATION;
123
124 // Size-probe call: required buffer size is returned via dwSize. By API contract this
125 // call fails with ERROR_INSUFFICIENT_BUFFER; the previous implementation wrapped it
126 // in an if() and therefore never executed the body, silently returning false on every
127 // save and surfacing as "Cannot copy permissions" once atomic save made the failure
128 // fatal.
129 DWORD dwSize = 0;
130 GetFileSecurityW( aSrc.wc_str(), secInfo, nullptr, 0, &dwSize );
131
132 if( dwSize == 0 || GetLastError() != ERROR_INSUFFICIENT_BUFFER )
133 return false;
134
135 std::vector<BYTE> sdBuffer( dwSize );
136 PSECURITY_DESCRIPTOR pSD = static_cast<PSECURITY_DESCRIPTOR>( sdBuffer.data() );
137
138 return GetFileSecurityW( aSrc.wc_str(), secInfo, pSD, dwSize, &dwSize )
139 && SetFileSecurityW( aDest.wc_str(), secInfo, pSD );
140}
141
142bool KIPLATFORM::IO::MakeWriteable( const wxString& aFilePath )
143{
144 DWORD attrs = GetFileAttributesW( aFilePath.wc_str() );
145
146 if( attrs == INVALID_FILE_ATTRIBUTES )
147 return false;
148
149 // Remove read-only and hidden attributes if present. Both of these can prevent file
150 // operations on Windows. Hidden files in particular can cause issues when files are
151 // synced via cloud services like OneDrive.
152 DWORD attrsToRemove = FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN;
153
154 if( attrs & attrsToRemove )
155 {
156 attrs &= ~attrsToRemove;
157 return SetFileAttributesW( aFilePath.wc_str(), attrs ) != 0;
158 }
159
160 return true;
161}
162
164{
165 TARGET_ATTRS snapshot;
166 DWORD attrs = GetFileAttributesW( aPath.wc_str() );
167
168 if( attrs == INVALID_FILE_ATTRIBUTES )
169 return snapshot;
170
171 // Only preserve bits that SetFileSecurity (used by DuplicatePermissions) does not
172 // carry across a rename. Other attributes on the new file come from the temp's
173 // default creation attrs and should not be overwritten here.
174 snapshot.value = static_cast<std::uint32_t>( attrs )
175 & ( FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN );
176 snapshot.captured = true;
177 return snapshot;
178}
179
180
181bool KIPLATFORM::IO::ApplyTargetAttributes( const wxString& aPath, const TARGET_ATTRS& aAttrs )
182{
183 if( !aAttrs.captured )
184 return true;
185
186 DWORD current = GetFileAttributesW( aPath.wc_str() );
187
188 if( current == INVALID_FILE_ATTRIBUTES )
189 return false;
190
191 DWORD merged = current | static_cast<DWORD>( aAttrs.value );
192
193 if( merged == current )
194 return true;
195
196 return SetFileAttributesW( aPath.wc_str(), merged ) != 0;
197}
198
199
200FILE* KIPLATFORM::IO::OpenUniqueSiblingTempFile( const wxString& aTargetPath,
201 const wxString& aMode, wxString* aTempPathOut,
202 wxString* aError )
203{
204 // Exclusive-create closes the TOCTOU window: if another process pre-created a file
205 // at the candidate path, CreateFileW with CREATE_NEW fails and we retry.
206 for( unsigned attempt = 0; attempt < 32; ++attempt )
207 {
208 wxString candidate = MakeSiblingTempPath( aTargetPath );
209 HANDLE h = CreateFileW( candidate.wc_str(), GENERIC_WRITE, 0, nullptr, CREATE_NEW,
210 FILE_ATTRIBUTE_NORMAL, nullptr );
211
212 if( h != INVALID_HANDLE_VALUE )
213 {
214 // _wfdopen ignores the text/binary letter, so honour it here as fopen would
215 const int translation = aMode.Contains( wxT( "b" ) ) ? _O_BINARY : _O_TEXT;
216 int fd = _open_osfhandle( reinterpret_cast<intptr_t>( h ), _O_WRONLY | translation );
217
218 if( fd < 0 )
219 {
220 CloseHandle( h );
221 DeleteFileW( candidate.wc_str() );
222
223 if( aError )
224 {
225 *aError = wxString::Format( wxT( "_open_osfhandle failed for '%s'" ),
226 candidate );
227 }
228
229 return nullptr;
230 }
231
232 FILE* fp = _wfdopen( fd, aMode.wc_str() );
233
234 if( !fp )
235 {
236 _close( fd ); // also closes the HANDLE
237 DeleteFileW( candidate.wc_str() );
238
239 if( aError )
240 *aError = wxString::Format( wxT( "_wfdopen failed for '%s'" ), candidate );
241
242 return nullptr;
243 }
244
245 if( aTempPathOut )
246 *aTempPathOut = candidate;
247
248 return fp;
249 }
250
251 DWORD err = GetLastError();
252
253 if( err != ERROR_FILE_EXISTS && err != ERROR_ALREADY_EXISTS )
254 {
255 if( aError )
256 *aError = wxString::Format( wxT( "CreateFile failed for '%s' (Win32 %lu)" ),
257 candidate, err );
258
259 return nullptr;
260 }
261 }
262
263 if( aError )
264 *aError = wxT( "Exhausted temp-file retry budget" );
265
266 return nullptr;
267}
268
269
270wxString KIPLATFORM::IO::ResolveSymlinkTarget( const wxString& aPath )
271{
272 // MoveFileExW replaces a symlink rather than writing through it. Only the symlink tag
273 // is resolved, so cloud placeholders and other reparse points are never opened.
274 WIN32_FIND_DATAW findData;
275 HANDLE find = FindFirstFileW( aPath.wc_str(), &findData );
276
277 if( find == INVALID_HANDLE_VALUE )
278 return aPath;
279
280 FindClose( find );
281
282 if( !( findData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT )
283 || findData.dwReserved0 != IO_REPARSE_TAG_SYMLINK )
284 {
285 return aPath;
286 }
287
288 HANDLE file = CreateFileW( aPath.wc_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
289 nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr );
290
291 if( file == INVALID_HANDLE_VALUE )
292 return aPath;
293
294 std::vector<wchar_t> buffer( 32768 );
295 DWORD length = GetFinalPathNameByHandleW( file, buffer.data(), static_cast<DWORD>( buffer.size() ),
296 FILE_NAME_NORMALIZED | VOLUME_NAME_DOS );
297 CloseHandle( file );
298
299 if( length == 0 || length >= buffer.size() )
300 return aPath;
301
302 wxString resolved( buffer.data(), length );
303
304 if( resolved.StartsWith( wxT( "\\\\?\\UNC\\" ) ) )
305 resolved = wxT( "\\\\" ) + resolved.Mid( 8 );
306 else if( resolved.StartsWith( wxT( "\\\\?\\" ) ) )
307 resolved = resolved.Mid( 4 );
308
309 return resolved;
310}
311
312
313bool KIPLATFORM::IO::IsFileHidden( const wxString& aFileName )
314{
315 const DWORD attributes = GetFileAttributesW( aFileName.fn_str() );
316
317 return attributes != INVALID_FILE_ATTRIBUTES
318 && ( attributes & FILE_ATTRIBUTE_HIDDEN ) != 0;
319}
320
321
322void KIPLATFORM::IO::LongPathAdjustment( wxFileName& aFilename )
323{
324 // dont shortcut this for shorter lengths as there are uses like directory
325 // paths that exceed the path length when you start traversing their subdirectories
326 // so we want to start with the long path prefix all the time
327
328 if( aFilename.GetVolume().Length() == 1 )
329 // assume single letter == drive volume
330 aFilename.SetVolume( "\\\\?\\" + aFilename.GetVolume() + ":" );
331 else if( aFilename.GetVolume().Length() > 1
332 && aFilename.GetVolume().StartsWith( wxT( "\\\\" ) )
333 && !aFilename.GetVolume().StartsWith( wxT( "\\\\?" ) ) )
334 // unc path aka network share, wx returns with \\ already
335 // so skip the first slash and combine with the prefix
336 // which in the case of UNCs is actually \\?\UNC<server><share>
337 // where UNC is literally the text UNC
338 aFilename.SetVolume( "\\\\?\\UNC" + aFilename.GetVolume().Mid( 1 ) );
339 else if( aFilename.GetVolume().StartsWith( wxT( "\\\\?" ) )
340 && aFilename.GetDirs().size() >= 2
341 && aFilename.GetDirs()[0] == "UNC" )
342 {
343 // wxWidgets can parse \\?\UNC<server> into a mess
344 // UNC gets stored into a directory
345 // volume gets reduced to just \\?
346 // so we need to repair it
347 aFilename.SetVolume( "\\\\?\\UNC\\" + aFilename.GetDirs()[1] );
348 aFilename.RemoveDir( 0 );
349 aFilename.RemoveDir( 0 );
350 }
351}
352
353
354long long KIPLATFORM::IO::TimestampDir( const wxString& aDirPath, const wxString& aFilespec )
355{
356 long long timestamp = 0;
357
358 // Use NtQueryDirectoryFile for fast directory enumeration (same approach as git-for-windows).
359 // This retrieves multiple directory entries per syscall into a large buffer, reducing
360 // kernel transitions compared to FindFirstFile/FindNextFile.
361 static PFN_NtQueryDirectoryFile pNtQueryDirectoryFile = nullptr;
362
363 if( !pNtQueryDirectoryFile )
364 {
365 HMODULE ntdll = GetModuleHandleW( L"ntdll.dll" );
366
367 if( ntdll )
368 {
369 pNtQueryDirectoryFile =
370 (PFN_NtQueryDirectoryFile) GetProcAddress( ntdll, "NtQueryDirectoryFile" );
371 }
372 }
373
374 if( !pNtQueryDirectoryFile )
375 return timestamp;
376
377 std::wstring dirPath( aDirPath.t_str() );
378
379 if( !dirPath.empty() && dirPath.back() != L'\\' )
380 dirPath += L'\\';
381
382 // Prefix with \\?\ for long path support, handling UNC paths specially
383 std::wstring ntPath;
384
385 if( dirPath.size() >= 2 && dirPath[0] == L'\\' && dirPath[1] == L'\\' )
386 {
387 if( dirPath.size() >= 4 && dirPath[2] == L'?' && dirPath[3] == L'\\' )
388 {
389 // Already has \\?\ prefix
390 ntPath = dirPath;
391 }
392 else
393 {
394 // UNC path: \\server\share -> \\?\UNC\server\share
395 ntPath = L"\\\\?\\UNC\\" + dirPath.substr( 2 );
396 }
397 }
398 else
399 {
400 // Local path: C:\foo -> \\?\C:\foo
401 ntPath = L"\\\\?\\" + dirPath;
402 }
403
404 HANDLE hDir = CreateFileW( ntPath.c_str(), FILE_LIST_DIRECTORY,
405 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
406 OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr );
407
408 if( hDir == INVALID_HANDLE_VALUE )
409 return timestamp;
410
411 std::wstring pattern( aFilespec.t_str() );
412
413 // 64KB buffer for directory entries (same size as git-for-windows)
414 alignas( sizeof( LONGLONG ) ) char buffer[64 * 1024];
415
416 IO_STATUS_BLOCK iosb;
417 NTSTATUS status;
418 bool firstQuery = true;
419
420 for( ;; )
421 {
422 status = pNtQueryDirectoryFile( hDir, nullptr, nullptr, nullptr, &iosb, buffer,
423 sizeof( buffer ), FileFullDirectoryInformation, FALSE,
424 nullptr, firstQuery ? TRUE : FALSE );
425 firstQuery = false;
426
427 if( status != 0 )
428 break;
429
431
432 for( ;; )
433 {
434 // Extract null-terminated filename
435 std::wstring fileName( dirInfo->FileName, dirInfo->FileNameLength / sizeof( WCHAR ) );
436
437 // Skip directories and match against pattern
438 if( !( dirInfo->FileAttributes & FILE_ATTRIBUTE_DIRECTORY )
439 && PathMatchSpecW( fileName.c_str(), pattern.c_str() ) )
440 {
441 // Shift right by 13 (~0.8ms resolution) to avoid overflow when summing many files
442 timestamp += dirInfo->LastWriteTime.QuadPart >> 13;
443 timestamp += dirInfo->EndOfFile.LowPart;
444 }
445
446 if( dirInfo->NextEntryOffset == 0 )
447 break;
448
449 dirInfo = (PFILE_FULL_DIR_INFORMATION) ( (char*) dirInfo + dirInfo->NextEntryOffset );
450 }
451 }
452
453 CloseHandle( hDir );
454
455 return timestamp;
456}
457
458
459bool KIPLATFORM::IO::FlushToDisk( FILE* aFp )
460{
461 if( !aFp )
462 return false;
463
464 if( std::fflush( aFp ) != 0 )
465 return false;
466
467 int fd = _fileno( aFp );
468
469 if( fd < 0 )
470 return false;
471
472 HANDLE h = reinterpret_cast<HANDLE>( _get_osfhandle( fd ) );
473
474 if( h == INVALID_HANDLE_VALUE )
475 return false;
476
477 return FlushFileBuffers( h ) != 0;
478}
479
480
481bool KIPLATFORM::IO::FlushDirectory( const wxString& aDirPath )
482{
483 // NTFS metadata journaling commits rename operations durably on its own, so there is
484 // no equivalent of POSIX dir-fsync. Report success unconditionally.
485 (void) aDirPath;
486 return true;
487}
488
489
490bool KIPLATFORM::IO::AtomicRename( const wxString& aSrc, const wxString& aDst, wxString* aError )
491{
492 // Try MoveFileEx first. MOVEFILE_WRITE_THROUGH ensures the rename is committed before
493 // return, so a power loss after success does not lose the replacement. MOVEFILE_REPLACE_EXISTING
494 // allows overwriting the destination (the caller has already verified this is the intent).
495 // A brief retry loop absorbs transient antivirus / indexer / cloud-sync locks that would
496 // otherwise surface as ERROR_SHARING_VIOLATION or ERROR_ACCESS_DENIED.
497 DWORD lastError = 0;
498
499 for( int attempt = 0; attempt < 10; ++attempt )
500 {
501 if( MoveFileExW( aSrc.wc_str(), aDst.wc_str(),
502 MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH ) )
503 return true;
504
505 lastError = GetLastError();
506
507 if( lastError != ERROR_SHARING_VIOLATION && lastError != ERROR_ACCESS_DENIED
508 && lastError != ERROR_LOCK_VIOLATION )
509 break;
510
511 Sleep( 50 );
512 }
513
514 // Fall back to ReplaceFileW, which handles some share-mode cases MoveFileEx cannot
515 // (for instance when the destination is open for reading with FILE_SHARE_DELETE).
516 if( ReplaceFileW( aDst.wc_str(), aSrc.wc_str(), nullptr, REPLACEFILE_WRITE_THROUGH, nullptr,
517 nullptr ) )
518 return true;
519
520 DWORD fallbackError = GetLastError();
521
522 if( aError )
523 {
524 wchar_t* msg = nullptr;
525 FormatMessageW( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM
526 | FORMAT_MESSAGE_IGNORE_INSERTS,
527 nullptr, fallbackError ? fallbackError : lastError,
528 MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), reinterpret_cast<LPWSTR>( &msg ),
529 0, nullptr );
530
531 if( msg )
532 {
533 *aError = wxString( msg );
534 LocalFree( msg );
535 }
536 else
537 {
538 *aError = wxString::Format( wxT( "Win32 error %lu" ), fallbackError ? fallbackError
539 : lastError );
540 }
541 }
542
543 return false;
544}
545
546
547KIPLATFORM::IO::MAPPED_FILE::MAPPED_FILE( const wxString& aFileName )
548{
549 m_fileHandle = CreateFileW( aFileName.wc_str(), GENERIC_READ, FILE_SHARE_READ,
550 nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr );
551
552 if( m_fileHandle == INVALID_HANDLE_VALUE )
553 {
554 m_fileHandle = nullptr;
555 throw std::runtime_error( std::string( "Cannot open file: " )
556 + aFileName.ToStdString() );
557 }
558
559 LARGE_INTEGER fileSize;
560
561 if( !GetFileSizeEx( m_fileHandle, &fileSize ) )
562 {
563 CloseHandle( m_fileHandle );
564 m_fileHandle = nullptr;
565 throw std::runtime_error( std::string( "Cannot determine file size: " )
566 + aFileName.ToStdString() );
567 }
568
569 m_size = static_cast<size_t>( fileSize.QuadPart );
570
571 if( m_size == 0 )
572 {
573 CloseHandle( m_fileHandle );
574 m_fileHandle = nullptr;
575 return;
576 }
577
578 m_mapHandle = CreateFileMappingW( m_fileHandle, nullptr, PAGE_READONLY, 0, 0, nullptr );
579
580 if( !m_mapHandle )
581 {
582 CloseHandle( m_fileHandle );
583 m_fileHandle = nullptr;
584 readIntoBuffer( aFileName );
585 return;
586 }
587
588 void* ptr = MapViewOfFile( m_mapHandle, FILE_MAP_READ, 0, 0, 0 );
589
590 if( !ptr )
591 {
592 CloseHandle( m_mapHandle );
593 m_mapHandle = nullptr;
594 CloseHandle( m_fileHandle );
595 m_fileHandle = nullptr;
596 readIntoBuffer( aFileName );
597 return;
598 }
599
600 m_data = static_cast<const uint8_t*>( ptr );
601}
602
603
605{
606 if( m_data && m_mapHandle )
607 UnmapViewOfFile( m_data );
608
609 if( m_mapHandle )
610 CloseHandle( m_mapHandle );
611
612 if( m_fileHandle )
613 CloseHandle( m_fileHandle );
614}
615
616
617
618// Past any content, so reading a held lock's owner still works; SQLite's PENDING_BYTE offset
619static constexpr DWORD LOCK_BYTE_OFFSET = 0x40000000;
620
621
623 bool& aCreated )
624{
625 Release();
626
627 // FILE_SHARE_DELETE lets the owner remove the lock file while we still hold it open
628 auto openFile = [&]( DWORD aAccess, DWORD aDisposition )
629 {
630 return CreateFileW( aPath.wc_str(), aAccess,
631 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
632 aDisposition, FILE_ATTRIBUTE_NORMAL, nullptr );
633 };
634
635 HANDLE handle = openFile( GENERIC_READ | GENERIC_WRITE, CREATE_NEW );
636
637 aCreated = handle != INVALID_HANDLE_VALUE;
638
639 if( !aCreated )
640 handle = openFile( GENERIC_READ | GENERIC_WRITE, OPEN_EXISTING );
641
642 if( handle == INVALID_HANDLE_VALUE )
643 {
644 // Fall back to read-only so we can still report the lock owner
645 handle = openFile( GENERIC_READ, OPEN_EXISTING );
646
647 if( handle != INVALID_HANDLE_VALUE )
648 {
649 m_handle = handle;
650 m_state = STATE::UNSUPPORTED;
651 }
652
653 return m_state;
654 }
655
656 m_handle = handle;
657
658 OVERLAPPED overlapped = {};
659 overlapped.Offset = LOCK_BYTE_OFFSET;
660
661 if( LockFileEx( handle, LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY, 0, 1, 0,
662 &overlapped ) )
663 {
664 m_state = STATE::HELD;
665 }
666 else if( GetLastError() == ERROR_LOCK_VIOLATION || GetLastError() == ERROR_IO_PENDING )
667 {
668 m_state = STATE::BUSY;
669 }
670 else
671 {
672 m_state = STATE::UNSUPPORTED;
673 }
674
675 return m_state;
676}
677
678
679bool KIPLATFORM::IO::FILE_LOCK::OpenForInspect( const wxString& aPath, bool& aHeldByAnother )
680{
681 Release();
682
683 aHeldByAnother = false;
684
685 HANDLE handle = CreateFileW( aPath.wc_str(), GENERIC_READ,
686 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
687 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr );
688
689 if( handle == INVALID_HANDLE_VALUE )
690 return false;
691
692 m_handle = handle;
693
694 OVERLAPPED overlapped = {};
695 overlapped.Offset = LOCK_BYTE_OFFSET;
696
697 // Briefly take the lock to test for a holder, then release; m_state stays NONE
698 if( LockFileEx( handle, LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY, 0, 1, 0,
699 &overlapped ) )
700 {
701 UnlockFileEx( handle, 0, 1, 0, &overlapped );
702 }
703 else if( GetLastError() == ERROR_LOCK_VIOLATION || GetLastError() == ERROR_IO_PENDING )
704 {
705 aHeldByAnother = true;
706 }
707
708 return true;
709}
710
711
713{
714 return m_handle != nullptr;
715}
716
717
718bool KIPLATFORM::IO::FILE_LOCK::ReadAll( std::string& aContents ) const
719{
720 if( !IsOpen() )
721 return false;
722
723 LARGE_INTEGER zero = {};
724
725 if( !SetFilePointerEx( m_handle, zero, nullptr, FILE_BEGIN ) )
726 return false;
727
728 aContents.clear();
729
730 char buffer[4096];
731 DWORD read = 0;
732
733 while( ReadFile( m_handle, buffer, sizeof( buffer ), &read, nullptr ) && read > 0 )
734 aContents.append( buffer, read );
735
736 return true;
737}
738
739
740bool KIPLATFORM::IO::FILE_LOCK::Rewrite( const std::string& aContents )
741{
742 if( !IsOpen() )
743 return false;
744
745 LARGE_INTEGER zero = {};
746
747 if( !SetFilePointerEx( m_handle, zero, nullptr, FILE_BEGIN ) || !SetEndOfFile( m_handle ) )
748 return false;
749
750 DWORD written = 0;
751
752 if( !WriteFile( m_handle, aContents.data(), static_cast<DWORD>( aContents.size() ), &written,
753 nullptr ) )
754 {
755 return false;
756 }
757
758 return written == aContents.size();
759}
760
761
763{
764 if( IsOpen() )
765 {
766 // Closing the handle releases the lock, same as process death would
767 CloseHandle( m_handle );
768 m_handle = nullptr;
769 }
770
771 m_state = STATE::NONE;
772}
bool ReadAll(std::string &aContents) const
Read the whole file through the descriptor we hold.
void Release()
Release the lock and close the file.
bool Rewrite(const std::string &aContents)
Replace the file contents through the descriptor we hold, keeping the same inode.
STATE Acquire(const wxString &aPath, bool &aCreated)
Open aPath, creating it if it does not exist, and try to take the lock without ever blocking on it.
bool OpenForInspect(const wxString &aPath, bool &aHeldByAnother)
Open an existing file and report whether another process holds its lock, creating nothing and keeping...
MAPPED_FILE(const wxString &aFileName)
Definition unix/io.cpp:199
void readIntoBuffer(const wxString &aFileName)
const uint8_t * m_data
Definition io.h:57
wxString MakeSiblingTempPath(const wxString &aTargetPath)
Returns a unique sibling path of aTargetPath suitable as an atomic-save temp file.
Definition common/io.cpp:46
bool FlushDirectory(const wxString &aDirPath)
Forces a directory entry's metadata to stable storage.
void LongPathAdjustment(wxFileName &aFilename)
Adjusts a filename to be a long path compatible.
Definition unix/io.cpp:117
FILE * SeqFOpen(const wxString &aPath, const wxString &mode)
Opens the file like fopen but sets flags (if available) for sequential read hinting.
Definition unix/io.cpp:39
TARGET_ATTRS CaptureTargetAttributes(const wxString &aPath)
Captures attributes of an existing aPath that must survive an atomic rename.
Definition unix/io.cpp:94
bool DuplicatePermissions(const wxString &aSrc, const wxString &aDest)
Duplicates the file security data from one file to another ensuring that they are the same between bo...
Definition unix/io.cpp:55
wxString ResolveSymlinkTarget(const wxString &aPath)
If aPath is a symlink, returns the canonical path of its referent so atomic-save operations replace t...
bool IsFileHidden(const wxString &aFileName)
Helper function to determine the status of the 'Hidden' file attribute.
Definition unix/io.cpp:109
bool AtomicRename(const wxString &aSrc, const wxString &aDst, wxString *aError=nullptr)
Atomically replaces aDst with aSrc.
FILE * OpenUniqueSiblingTempFile(const wxString &aTargetPath, const wxString &aMode, wxString *aTempPathOut, wxString *aError=nullptr)
Opens a fresh sibling temp file next to aTargetPath with exclusive-create semantics (POSIX O_CREAT|O_...
Definition common/io.cpp:67
bool MakeWriteable(const wxString &aFilePath)
Ensures that a file has write permissions.
Definition unix/io.cpp:78
bool ApplyTargetAttributes(const wxString &aPath, const TARGET_ATTRS &aAttrs)
Re-applies attributes previously captured by CaptureTargetAttributes.
Definition unix/io.cpp:103
long long TimestampDir(const wxString &aDirPath, const wxString &aFilespec)
Computes a hash of modification times and sizes for files matching a pattern.
Definition unix/io.cpp:123
bool FlushToDisk(FILE *aFp)
Flushes user-space buffers for aFp and forces the kernel/filesystem to commit the file's data blocks ...
Definition unix/io.cpp:182
Opaque snapshot of filesystem attributes that MakeWriteable may alter and that the atomic rename sequ...
Definition io.h:302
typedef PUNICODE_STRING
static constexpr DWORD LOCK_BYTE_OFFSET
typedef FILE_INFORMATION_CLASS
typedef BOOLEAN
struct _FILE_FULL_DIR_INFORMATION FILE_FULL_DIR_INFORMATION
typedef NTSTATUS(NTAPI *PFN_NtQueryDirectoryFile)(HANDLE
typedef PIO_STATUS_BLOCK
typedef PIO_APC_ROUTINE
typedef HANDLE
typedef PVOID
struct _FILE_FULL_DIR_INFORMATION * PFILE_FULL_DIR_INFORMATION
typedef ULONG
#define FileFullDirectoryInformation