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 along
17* 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 <stdexcept>
27#include <string>
28#include <windows.h>
29#include <shlwapi.h>
30#include <winternl.h>
31
32// NtQueryDirectoryFile-based directory enumeration for fast file listing.
33// This approach is based on git-for-windows fscache implementation:
34// https://github.com/git-for-windows/git/blob/main/compat/win32/fscache.c
35// Copyright (C) Johannes Schindelin and the Git for Windows project
36// Licensed under GPL v2.
37//
38// FILE_FULL_DIR_INFORMATION is documented in the Windows Driver Kit but not the SDK.
39
40#if !defined( __MINGW32__ ) // already defined in the included mingw header <winternl.h>
41 // So do not redefine it on mingw
57#endif
58
59typedef NTSTATUS( NTAPI* PFN_NtQueryDirectoryFile )( HANDLE, HANDLE, PIO_APC_ROUTINE, PVOID,
63
64#define FileFullDirectoryInformation ( (FILE_INFORMATION_CLASS) 2 )
65
66// Define USE_MSYS2_FALlBACK if the code for _MSC_VER does not compile on msys2
67//#define USE_MSYS2_FALLBACK
68
69FILE* KIPLATFORM::IO::SeqFOpen( const wxString& aPath, const wxString& aMode )
70{
71#if defined( _MSC_VER ) || !defined( USE_MSYS2_FALLBACK )
72 // We need to use the win32 api to setup a file handle with sequential scan flagged
73 // and pass it up the chain to create a normal FILE stream
74 HANDLE hFile = INVALID_HANDLE_VALUE;
75 hFile = CreateFileW( aPath.wc_str(),
76 GENERIC_READ,
77 FILE_SHARE_READ,
78 NULL,
79 OPEN_EXISTING,
80 FILE_FLAG_SEQUENTIAL_SCAN,
81 NULL );
82
83 if (hFile == INVALID_HANDLE_VALUE)
84 {
85 return NULL;
86 }
87
88 int fd = _open_osfhandle( reinterpret_cast<intptr_t>( hFile ), 0 );
89
90 if( fd == -1 )
91 {
92 // close the handle manually as the ownership didnt transfer
93 CloseHandle( hFile );
94 return NULL;
95 }
96
97 FILE* fp = _fdopen( fd, aMode.c_str() );
98
99 if( !fp )
100 {
101 // close the file descriptor manually as the ownership didnt transfer
102 _close( fd );
103 }
104
105 return fp;
106#else
107 // Fallback for MSYS2
108 return wxFopen( aPath, aMode );
109#endif
110}
111
112bool KIPLATFORM::IO::DuplicatePermissions( const wxString &aSrc, const wxString &aDest )
113{
114 bool retval = false;
115 DWORD dwSize = 0;
116
117 // Retrieve the security descriptor from the source file
118 if( GetFileSecurity( aSrc.wc_str(),
119 OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
120 NULL, 0, &dwSize ) )
121 {
122 #ifdef __MINGW32__
123 // pSD is used as PSECURITY_DESCRIPTOR, aka void* pointer
124 // it create an annoying warning on gcc with "delete[] pSD;" :
125 // "warning: deleting 'PSECURITY_DESCRIPTOR' {aka 'void*'} is undefined"
126 // so use a BYTE* pointer (do not cast it to a void pointer)
127 BYTE* pSD = new BYTE[dwSize];
128 #else
129 PSECURITY_DESCRIPTOR pSD = static_cast<PSECURITY_DESCRIPTOR>( new BYTE[dwSize] );
130 #endif
131
132 if( !pSD )
133 return false;
134
135 if( !GetFileSecurity( aSrc.wc_str(),
136 OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION
137 | DACL_SECURITY_INFORMATION, pSD, dwSize, &dwSize ) )
138 {
139 delete[] pSD;
140 return false;
141 }
142
143 // Assign the retrieved security descriptor to the destination file
144 if( !SetFileSecurity( aDest.wc_str(),
145 OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION
146 | DACL_SECURITY_INFORMATION, pSD ) )
147 {
148 retval = false;
149 }
150
151 delete[] pSD;
152 }
153
154 return retval;
155}
156
157bool KIPLATFORM::IO::MakeWriteable( const wxString& aFilePath )
158{
159 DWORD attrs = GetFileAttributesW( aFilePath.wc_str() );
160
161 if( attrs == INVALID_FILE_ATTRIBUTES )
162 return false;
163
164 // Remove read-only and hidden attributes if present. Both of these can prevent file
165 // operations on Windows. Hidden files in particular can cause issues when files are
166 // synced via cloud services like OneDrive.
167 DWORD attrsToRemove = FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN;
168
169 if( attrs & attrsToRemove )
170 {
171 attrs &= ~attrsToRemove;
172 return SetFileAttributesW( aFilePath.wc_str(), attrs ) != 0;
173 }
174
175 return true;
176}
177
178bool KIPLATFORM::IO::IsFileHidden( const wxString& aFileName )
179{
180 bool result = false;
181
182 if( ( GetFileAttributesW( aFileName.fn_str() ) & FILE_ATTRIBUTE_HIDDEN ) )
183 result = true;
184
185 return result;
186}
187
188
189void KIPLATFORM::IO::LongPathAdjustment( wxFileName& aFilename )
190{
191 // dont shortcut this for shorter lengths as there are uses like directory
192 // paths that exceed the path length when you start traversing their subdirectories
193 // so we want to start with the long path prefix all the time
194
195 if( aFilename.GetVolume().Length() == 1 )
196 // assume single letter == drive volume
197 aFilename.SetVolume( "\\\\?\\" + aFilename.GetVolume() + ":" );
198 else if( aFilename.GetVolume().Length() > 1
199 && aFilename.GetVolume().StartsWith( wxT( "\\\\" ) )
200 && !aFilename.GetVolume().StartsWith( wxT( "\\\\?" ) ) )
201 // unc path aka network share, wx returns with \\ already
202 // so skip the first slash and combine with the prefix
203 // which in the case of UNCs is actually \\?\UNC<server><share>
204 // where UNC is literally the text UNC
205 aFilename.SetVolume( "\\\\?\\UNC" + aFilename.GetVolume().Mid( 1 ) );
206 else if( aFilename.GetVolume().StartsWith( wxT( "\\\\?" ) )
207 && aFilename.GetDirs().size() >= 2
208 && aFilename.GetDirs()[0] == "UNC" )
209 {
210 // wxWidgets can parse \\?\UNC<server> into a mess
211 // UNC gets stored into a directory
212 // volume gets reduced to just \\?
213 // so we need to repair it
214 aFilename.SetVolume( "\\\\?\\UNC\\" + aFilename.GetDirs()[1] );
215 aFilename.RemoveDir( 0 );
216 aFilename.RemoveDir( 0 );
217 }
218}
219
220
221long long KIPLATFORM::IO::TimestampDir( const wxString& aDirPath, const wxString& aFilespec )
222{
223 long long timestamp = 0;
224
225 // Use NtQueryDirectoryFile for fast directory enumeration (same approach as git-for-windows).
226 // This retrieves multiple directory entries per syscall into a large buffer, reducing
227 // kernel transitions compared to FindFirstFile/FindNextFile.
228 static PFN_NtQueryDirectoryFile pNtQueryDirectoryFile = nullptr;
229
230 if( !pNtQueryDirectoryFile )
231 {
232 HMODULE ntdll = GetModuleHandleW( L"ntdll.dll" );
233
234 if( ntdll )
235 {
236 pNtQueryDirectoryFile =
237 (PFN_NtQueryDirectoryFile) GetProcAddress( ntdll, "NtQueryDirectoryFile" );
238 }
239 }
240
241 if( !pNtQueryDirectoryFile )
242 return timestamp;
243
244 std::wstring dirPath( aDirPath.t_str() );
245
246 if( !dirPath.empty() && dirPath.back() != L'\\' )
247 dirPath += L'\\';
248
249 // Prefix with \\?\ for long path support, handling UNC paths specially
250 std::wstring ntPath;
251
252 if( dirPath.size() >= 2 && dirPath[0] == L'\\' && dirPath[1] == L'\\' )
253 {
254 if( dirPath.size() >= 4 && dirPath[2] == L'?' && dirPath[3] == L'\\' )
255 {
256 // Already has \\?\ prefix
257 ntPath = dirPath;
258 }
259 else
260 {
261 // UNC path: \\server\share -> \\?\UNC\server\share
262 ntPath = L"\\\\?\\UNC\\" + dirPath.substr( 2 );
263 }
264 }
265 else
266 {
267 // Local path: C:\foo -> \\?\C:\foo
268 ntPath = L"\\\\?\\" + dirPath;
269 }
270
271 HANDLE hDir = CreateFileW( ntPath.c_str(), FILE_LIST_DIRECTORY,
272 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
273 OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr );
274
275 if( hDir == INVALID_HANDLE_VALUE )
276 return timestamp;
277
278 std::wstring pattern( aFilespec.t_str() );
279
280 // 64KB buffer for directory entries (same size as git-for-windows)
281 alignas( sizeof( LONGLONG ) ) char buffer[64 * 1024];
282
283 IO_STATUS_BLOCK iosb;
284 NTSTATUS status;
285 bool firstQuery = true;
286
287 for( ;; )
288 {
289 status = pNtQueryDirectoryFile( hDir, nullptr, nullptr, nullptr, &iosb, buffer,
290 sizeof( buffer ), FileFullDirectoryInformation, FALSE,
291 nullptr, firstQuery ? TRUE : FALSE );
292 firstQuery = false;
293
294 if( status != 0 )
295 break;
296
298
299 for( ;; )
300 {
301 // Extract null-terminated filename
302 std::wstring fileName( dirInfo->FileName, dirInfo->FileNameLength / sizeof( WCHAR ) );
303
304 // Skip directories and match against pattern
305 if( !( dirInfo->FileAttributes & FILE_ATTRIBUTE_DIRECTORY )
306 && PathMatchSpecW( fileName.c_str(), pattern.c_str() ) )
307 {
308 // Shift right by 13 (~0.8ms resolution) to avoid overflow when summing many files
309 timestamp += dirInfo->LastWriteTime.QuadPart >> 13;
310 timestamp += dirInfo->EndOfFile.LowPart;
311 }
312
313 if( dirInfo->NextEntryOffset == 0 )
314 break;
315
316 dirInfo = (PFILE_FULL_DIR_INFORMATION) ( (char*) dirInfo + dirInfo->NextEntryOffset );
317 }
318 }
319
320 CloseHandle( hDir );
321
322 return timestamp;
323}
324
325
326KIPLATFORM::IO::MAPPED_FILE::MAPPED_FILE( const wxString& aFileName )
327{
328 m_fileHandle = CreateFileW( aFileName.wc_str(), GENERIC_READ, FILE_SHARE_READ,
329 nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr );
330
331 if( m_fileHandle == INVALID_HANDLE_VALUE )
332 {
333 m_fileHandle = nullptr;
334 throw std::runtime_error( std::string( "Cannot open file: " )
335 + aFileName.ToStdString() );
336 }
337
338 LARGE_INTEGER fileSize;
339
340 if( !GetFileSizeEx( m_fileHandle, &fileSize ) )
341 {
342 CloseHandle( m_fileHandle );
343 m_fileHandle = nullptr;
344 throw std::runtime_error( std::string( "Cannot determine file size: " )
345 + aFileName.ToStdString() );
346 }
347
348 m_size = static_cast<size_t>( fileSize.QuadPart );
349
350 if( m_size == 0 )
351 {
352 CloseHandle( m_fileHandle );
353 m_fileHandle = nullptr;
354 return;
355 }
356
357 m_mapHandle = CreateFileMappingW( m_fileHandle, nullptr, PAGE_READONLY, 0, 0, nullptr );
358
359 if( !m_mapHandle )
360 {
361 CloseHandle( m_fileHandle );
362 m_fileHandle = nullptr;
363 readIntoBuffer( aFileName );
364 return;
365 }
366
367 void* ptr = MapViewOfFile( m_mapHandle, FILE_MAP_READ, 0, 0, 0 );
368
369 if( !ptr )
370 {
371 CloseHandle( m_mapHandle );
372 m_mapHandle = nullptr;
373 CloseHandle( m_fileHandle );
374 m_fileHandle = nullptr;
375 readIntoBuffer( aFileName );
376 return;
377 }
378
379 m_data = static_cast<const uint8_t*>( ptr );
380}
381
382
384{
385 if( m_data && m_mapHandle )
386 UnmapViewOfFile( m_data );
387
388 if( m_mapHandle )
389 CloseHandle( m_mapHandle );
390
391 if( m_fileHandle )
392 CloseHandle( m_fileHandle );
393}
MAPPED_FILE(const wxString &aFileName)
Definition unix/io.cpp:163
void readIntoBuffer(const wxString &aFileName)
Definition common/io.cpp:29
const uint8_t * m_data
Definition io.h:56
void LongPathAdjustment(wxFileName &aFilename)
Adjusts a filename to be a long path compatible.
Definition unix/io.cpp:98
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:36
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:52
bool IsFileHidden(const wxString &aFileName)
Helper function to determine the status of the 'Hidden' file attribute.
Definition unix/io.cpp:90
bool MakeWriteable(const wxString &aFilePath)
Ensures that a file has write permissions.
Definition unix/io.cpp:75
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:104
wxString result
Test unit parsing edge cases and error handling.
typedef PUNICODE_STRING
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