KiCad PCB EDA Suite
Loading...
Searching...
No Matches
file_utils.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
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU 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 "qa_utils/file_utils.h"
21
22#include <cstdio>
23#include <filesystem>
24#include <stdexcept>
25#include <string>
26
27#include <wx/ffile.h>
28#include <wx/filename.h>
29#include <wx/tokenzr.h>
30#include <wx/utils.h>
31#include <wx_filename.h>
32
35
36
37using namespace KI_TEST;
38
39
40namespace
41{
48void reportTempDir( const wxString& aMessage )
49{
50 fprintf( stderr, "%s\n", aMessage.utf8_string().c_str() );
51}
52
53
58void safeRemoveTempDir( const std::filesystem::path& aPath ) noexcept
59{
60 std::error_code ec;
61 std::filesystem::remove_all( aPath, ec ); // use the noexcept version
62
63 if( !ec )
64 return;
65
66 try
67 {
68 reportTempDir( wxString::Format( wxT( "Cannot remove temporary directory '%s': %s" ),
69 wxString::FromUTF8( aPath.string() ), wxString::FromUTF8( ec.message() ) ) );
70 }
71 catch( ... )
72 {
73 // Even a log failure must not throw during unwinding.
74 }
75}
76
77
85class TEMP_DIR_REMOVER
86{
87public:
88 explicit TEMP_DIR_REMOVER( std::filesystem::path aPath ) noexcept :
89 m_path( std::move( aPath ) )
90 {
91 }
92
93 ~TEMP_DIR_REMOVER()
94 {
95 if( !m_released )
96 {
97 // Destructor-safe removal
98 safeRemoveTempDir( m_path );
99 }
100 }
101
102 TEMP_DIR_REMOVER( const TEMP_DIR_REMOVER& ) = delete;
103 TEMP_DIR_REMOVER& operator=( const TEMP_DIR_REMOVER& ) = delete;
104
105 const std::filesystem::path& Path() const noexcept { return m_path; }
106
107 void Release() noexcept { m_released = true; }
108
109private:
110 std::filesystem::path m_path;
111 bool m_released = false;
112};
113
114
115bool shouldKeepTemp( const std::filesystem::path& aPath, const wxString& aKeepEnvValue )
116{
117 const wxString envVar = wxT( "KICAD_QA_KEEP_TEMP" );
118 wxString keepEnv;
119
120 if( !wxGetEnv( envVar, &keepEnv ) || keepEnv.IsEmpty() )
121 return false;
122
123 // Split the environment variable on commas (like WXTRACE)
124 wxStringTokenizer tokenizer( keepEnv, wxT( "," ) );
125
126 while( tokenizer.HasMoreTokens() )
127 {
128 wxString token = tokenizer.GetNextToken();
129
130 if( token == aKeepEnvValue || token == wxT( "ALL" ) )
131 {
132 // Probably do want to see this, because if you are keeping the temp dirs, you may
133 // want to know which ones they are.
134 reportTempDir( wxString::Format( wxT( "Keeping temporary directory '%s' because %s is set to '%s'" ),
135 wxString::FromUTF8( aPath.string() ), envVar, keepEnv ) );
136 return true;
137 }
138 }
139
140 return false;
141}
142
143
144std::filesystem::path createTempDir( const wxString& aPrefix, const wxString& aSuffix )
145{
146 std::error_code ec;
147 const std::filesystem::path tempRoot = std::filesystem::temp_directory_path( ec );
148
149 if( ec )
150 throw std::runtime_error( "Cannot determine the temporary directory: " + ec.message() );
151
152 // Claim the directory with a single mkdir. This avoids the race window
153 // associated with reserving a name using wxFileName::CreateTempFileName().
154 constexpr size_t k_maxAttempts = 1000;
155 for( size_t i = 0; i < k_maxAttempts; ++i )
156 {
157 const std::filesystem::path candidate =
158 tempRoot / ( aPrefix.utf8_string() + "_" + std::to_string( i ) + aSuffix.utf8_string() );
159
160 if( std::filesystem::create_directory( candidate, ec ) )
161 return candidate;
162
163 // Any error except "file exists" is fatal. "File exists" just
164 // means we should try the next candidate name.
165 if( ec && ec != std::errc::file_exists )
166 throw std::runtime_error( "Cannot create temporary directory '" + candidate.string()
167 + "': " + ec.message() );
168 }
169
170 // Ran out of attempts to find a free temporary directory name.
171 throw std::runtime_error( "Cannot find a free temporary directory name with prefix/suffix '"
172 + std::string( aPrefix.utf8_str() ) + "/" + std::string( aSuffix.utf8_str() ) + "'" );
173}
174
175} // namespace
176
177
179
180
181SCOPED_TEMP_DIR::SCOPED_TEMP_DIR( const wxString& aPrefix, const wxString& aSuffix )
182{
183 // Protect the temporary directory by creating a remover that will delete it
184 // if something goes wrong before the SCOPED_TEMP_DIR takes ownership.
185 TEMP_DIR_REMOVER remover( createTempDir( aPrefix, aSuffix ) );
186
187 m_keep = shouldKeepTemp( remover.Path(), aPrefix );
188 m_path = remover.Path();
189
190 if( m_keep )
192
193 // Finally, relinquish ownership to this SCOPED_TEMP_DIR instance.
194 remover.Release();
195}
196
197
199{
200 if( !m_keep )
201 {
202 // Destructor-safe removal of the temporary directory.
203 safeRemoveTempDir( m_path );
204 }
205}
206
207
209{
210 m_keep = true;
212
213 reportTempDir(
214 wxString::Format( wxT( "Keeping temporary directory '%s'" ), wxString::FromUTF8( m_path.string() ) ) );
215}
216
217
218void expectSafeChildPath( const wxString& aChild )
219{
220 if( !WX_FILENAME::IsSafeChildPath( aChild ) )
221 throw std::runtime_error( "Unsafe child path '" + std::string( aChild.utf8_str() ) + "'" );
222}
223
224
225wxString SCOPED_TEMP_DIR::ChildPathStr( const wxString& aName ) const
226{
227 expectSafeChildPath( aName );
228
229 return wxString::FromUTF8( ( m_path / aName.utf8_string() ).string() );
230}
231
232
233std::filesystem::path SCOPED_TEMP_DIR::CreateChildDir( const wxString& aName ) const
234{
235 expectSafeChildPath( aName );
236
237 std::filesystem::path childPath = m_path / aName.utf8_string();
238
239 if( !std::filesystem::create_directory( childPath ) )
240 throw std::runtime_error( "Cannot create temporary child directory '" + childPath.string() + "'" );
241
242 return childPath;
243}
244
245
246wxString SCOPED_TEMP_DIR::CreateChildDirStr( const wxString& aName ) const
247{
248 return wxString::FromUTF8( CreateChildDir( aName ).string() );
249}
250
251
252std::filesystem::path SCOPED_TEMP_DIR::CreateChildFile( const wxString& aName ) const
253{
254 expectSafeChildPath( aName );
255
256 std::filesystem::path childPath = m_path / aName.utf8_string();
257 wxFFile file( wxString::FromUTF8( childPath.string() ), wxT( "w" ) );
258
259 if( !file.IsOpened() )
260 throw std::runtime_error( "Cannot create temporary child file '" + childPath.string() + "'" );
261
262 return childPath;
263}
264
265
266wxString SCOPED_TEMP_DIR::CreateChildFileStr( const wxString& aName ) const
267{
268 return wxString::FromUTF8( CreateChildFile( aName ).string() );
269}
270
271
273 const wxString& aName ) :
274 m_dir( aPrefix ),
275 m_manager( aManager ),
276 m_project( nullptr )
277{
278 wxFileName projectFile( m_dir.PathStr(), aName, FILEEXT::ProjectFileExtension );
279
280 m_manager.LoadProject( projectFile.GetFullPath() );
281 m_project = m_manager.GetProject( projectFile.GetFullPath() );
282
283 if( !m_project )
284 {
285 throw std::runtime_error( "Cannot load temporary project '"
286 + std::string( projectFile.GetFullPath().utf8_str() ) + "'" );
287 }
288}
289
290
292{
293 m_manager.UnloadProject( m_project, false );
294}
295
296
298 m_dir( aPrefix )
299{
300 const wxString envValue = m_dir.PathStr();
301
302 wxSetEnv( wxT( "TMPDIR" ), envValue ); // This is the POSIX one
303 wxSetEnv( wxT( "TEMP" ), envValue );
304 wxSetEnv( wxT( "TMP" ), envValue );
305}
306
307
309{
310 // If any temporary directory has been retained, we must retain this one too
312 m_dir.Retain();
313}
SCOPED_PROCESS_TEMP_DIR(const wxString &aPrefix)
std::filesystem::path m_path
Definition file_utils.h:125
static bool s_anyTempDirRetained
Definition file_utils.h:129
wxString CreateChildFileStr(const wxString &aName) const
Create and return the path to a direct child file as a wxString.
void Retain()
Keep this directory after all.
static bool AnyTempDirRetained()
Definition file_utils.h:122
wxString ChildPathStr(const wxString &aName) const
Get the path to a direct child of the temporary directory as a wxString, without creating the child.
wxString CreateChildDirStr(const wxString &aName) const
Create and return the path to a direct child directory as a wxString.
std::filesystem::path CreateChildFile(const wxString &aName) const
Create and return the path to a direct child file.
SCOPED_TEMP_DIR(const wxString &aPrefix, const wxString &aSuffix=wxEmptyString)
Create a temporary directory with a unique name based on the given prefix and optional suffix.
std::filesystem::path CreateChildDir(const wxString &aName) const
Create and return the path to a direct child directory.
SETTINGS_MANAGER & m_manager
Definition file_utils.h:156
SCOPED_TEMP_PROJECT(SETTINGS_MANAGER &aManager, const wxString &aPrefix, const wxString &aName)
static bool IsSafeChildPath(const wxString &aChild)
Check that a child path is safe to append to a parent directory.
void expectSafeChildPath(const wxString &aChild)
static const std::string ProjectFileExtension
Test utilities for timestamps.
Definition of file extensions used in Kicad.