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