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