KiCad PCB EDA Suite
Loading...
Searching...
No Matches
gestfich.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 (C) 2004 Jean-Pierre Charras, [email protected]
5 * Copyright (C) 2008 Wayne Stambaugh <[email protected]>
6 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 2
11 * of the License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <https://www.gnu.org/licenses/>.
20 */
21
26
27#include <wx/mimetype.h>
28#include <wx/dir.h>
29#include <wx/stdpaths.h>
30
31#include <pgm_base.h>
32#include <kiplatform/io.h>
33#include <confirm.h>
34#include <core/arraydim.h>
35#include <gestfich.h>
36#include <string_utils.h>
37#include <launch_ext.h>
38#include "wx/tokenzr.h"
39#include <richio.h>
40#include <sexpr/sexpr.h>
41#include <sexpr/sexpr_parser.h>
42
43#include <wx/wfstream.h>
44#include <wx/fs_zip.h>
45#include <wx/zipstrm.h>
46#include <wx/tarstrm.h>
47#include <wx/zstream.h>
48
49#include <algorithm>
50#include <filesystem>
51#include <string>
52#include <system_error>
53#include <unordered_set>
54#include <vector>
55#include <core/kicad_algo.h>
56
57void QuoteString( wxString& string )
58{
59 if( !string.StartsWith( wxT( "\"" ) ) )
60 {
61 string.Prepend ( wxT( "\"" ) );
62 string.Append ( wxT( "\"" ) );
63 }
64}
65
66
67wxString FindKicadFile( const wxString& shortname )
68{
69 // Test the presence of the file in the directory shortname of
70 // the KiCad binary path.
71#ifndef __WXMAC__
72 wxString fullFileName = Pgm().GetExecutablePath() + shortname;
73#else
74 wxString fullFileName = Pgm().GetExecutablePath() + wxT( "Contents/MacOS/" ) + shortname;
75#endif
76 if( wxFileExists( fullFileName ) )
77 return fullFileName;
78
79 if( wxGetEnv( wxT( "KICAD_RUN_FROM_BUILD_DIR" ), nullptr ) )
80 {
81 wxFileName buildDir( Pgm().GetExecutablePath(), shortname );
82
83#ifdef __WXMAC__
84 if( !buildDir.GetDirs().IsEmpty()
85 && buildDir.GetDirs().Last().Lower().EndsWith( wxT( ".app" ) ) )
86 {
87 buildDir.RemoveLastDir();
88 }
89#endif
90 buildDir.RemoveLastDir();
91#ifndef __WXMSW__
92 buildDir.AppendDir( shortname );
93#else
94 buildDir.AppendDir( shortname.BeforeLast( '.' ) );
95#endif
96
97 if( buildDir.GetDirs().Last() == "pl_editor" )
98 {
99 buildDir.RemoveLastDir();
100 buildDir.AppendDir( "pagelayout_editor" );
101 }
102
103#ifdef __WXMAC__
104 buildDir.AppendDir( shortname + wxT( ".app" ) );
105 buildDir.AppendDir( wxT( "Contents" ) );
106 buildDir.AppendDir( wxT( "MacOS" ) );
107#endif
108
109 if( wxFileExists( buildDir.GetFullPath() ) )
110 return buildDir.GetFullPath();
111 }
112
113 // Test the presence of the file in the directory shortname
114 // defined by the environment variable KiCad.
115 if( Pgm().IsKicadEnvVariableDefined() )
116 {
117 fullFileName = Pgm().GetKicadEnvVariable() + shortname;
118
119 if( wxFileExists( fullFileName ) )
120 return fullFileName;
121 }
122
123#if defined( __WINDOWS__ )
124 // KiCad can be installed highly portably on Windows, anywhere and concurrently
125 // either the "kicad file" is immediately adjacent to the exe or it's not a valid install
126 return shortname;
127#else
128
129 // Path list for KiCad binary files
130 const static wxChar* possibilities[] = {
131#if defined( __WXMAC__ )
132 // all internal paths are relative to main bundle kicad.app
133 wxT( "Contents/Applications/pcbnew.app/Contents/MacOS/" ),
134 wxT( "Contents/Applications/eeschema.app/Contents/MacOS/" ),
135 wxT( "Contents/Applications/gerbview.app/Contents/MacOS/" ),
136 wxT( "Contents/Applications/bitmap2component.app/Contents/MacOS/" ),
137 wxT( "Contents/Applications/pcb_calculator.app/Contents/MacOS/" ),
138 wxT( "Contents/Applications/pl_editor.app/Contents/MacOS/" ),
139#else
140 wxT( "/usr/bin/" ),
141 wxT( "/usr/local/bin/" ),
142 wxT( "/usr/local/kicad/bin/" ),
143#endif
144 };
145
146 // find binary file from possibilities list:
147 for( unsigned i=0; i<arrayDim(possibilities); ++i )
148 {
149#ifndef __WXMAC__
150 fullFileName = possibilities[i] + shortname;
151#else
152 // make relative paths absolute
153 fullFileName = Pgm().GetExecutablePath() + possibilities[i] + shortname;
154#endif
155
156 if( wxFileExists( fullFileName ) )
157 return fullFileName;
158 }
159
160 return shortname;
161
162#endif
163}
164
165
166int ExecuteFile( const wxString& aEditorName, const wxString& aFileName, wxProcess* aCallback,
167 bool aFileForKicad )
168{
169 wxString fullEditorName;
170 std::vector<wxString> params;
171
172#ifdef __UNIX__
173 wxString param;
174 bool inSingleQuotes = false;
175 bool inDoubleQuotes = false;
176
177 auto pushParam =
178 [&]()
179 {
180 if( !param.IsEmpty() )
181 {
182 params.push_back( param );
183 param.clear();
184 }
185 };
186
187 for( wxUniChar ch : aEditorName )
188 {
189 if( inSingleQuotes )
190 {
191 if( ch == '\'' )
192 {
193 pushParam();
194 inSingleQuotes = false;
195 continue;
196 }
197 else
198 {
199 param += ch;
200 }
201 }
202 else if( inDoubleQuotes )
203 {
204 if( ch == '"' )
205 {
206 pushParam();
207 inDoubleQuotes = false;
208 }
209 else
210 {
211 param += ch;
212 }
213 }
214 else if( ch == '\'' )
215 {
216 pushParam();
217 inSingleQuotes = true;
218 }
219 else if( ch == '"' )
220 {
221 pushParam();
222 inDoubleQuotes = true;
223 }
224 else if( ch == ' ' )
225 {
226 pushParam();
227 }
228 else
229 {
230 param += ch;
231 }
232 }
233
234 pushParam();
235
236 if( aFileForKicad )
237 fullEditorName = FindKicadFile( params[0] );
238 else
239 fullEditorName = params[0];
240
241 params.erase( params.begin() );
242#else
243
244 if( aFileForKicad )
245 fullEditorName = FindKicadFile( aEditorName );
246 else
247 fullEditorName = aEditorName;
248#endif
249
250 if( wxFileExists( fullEditorName ) )
251 {
252 std::vector<const wchar_t*> args;
253
254 args.emplace_back( fullEditorName.wc_str() );
255
256 if( !params.empty() )
257 {
258 for( const wxString& p : params )
259 args.emplace_back( p.wc_str() );
260 }
261
262 if( !aFileName.IsEmpty() )
263 args.emplace_back( aFileName.wc_str() );
264
265 args.emplace_back( nullptr );
266
267 return wxExecute( const_cast<wchar_t**>( args.data() ), wxEXEC_ASYNC, aCallback );
268 }
269
270 wxString msg;
271 msg.Printf( _( "Command '%s' could not be found." ), fullEditorName );
272 DisplayErrorMessage( nullptr, msg );
273 return -1;
274}
275
276
277int ExecuteCommandThroughShell( const wxString& aCommand, wxProcess* aProcess )
278{
279#ifdef __WXMSW__
280 wxExecuteEnv env;
281 wxGetEnvMap( &env.env );
282
283 // Prepend the app bin path so that KiCad's python is used by default
284 wxString binPath = wxFileName( wxStandardPaths::Get().GetExecutablePath() ).GetPath();
285 env.env["PATH"] = binPath + wxS( ';' ) + env.env["PATH"];
286
287 // The array form of wxExecute is unusable with cmd.exe. wx joins the argv elements back into a
288 // single command line, wrapping any element containing spaces in double quotes and escaping
289 // embedded quotes with backslashes. cmd.exe does not understand backslash-escaped quotes and
290 // applies its own quote-stripping rules to the /c argument, which mangles absolute paths that
291 // contain spaces or quotes. Build the command line ourselves and let cmd.exe's /s rule strip
292 // exactly the outer quote pair, passing everything between through verbatim. /d disables any
293 // AutoRun registry commands so job execution is not machine-dependent.
294 wxString shellCmd = wxS( "cmd.exe /d /s /c \"" ) + aCommand + wxS( "\"" );
295
296 return static_cast<int>( wxExecute( shellCmd, wxEXEC_SYNC, aProcess, &env ) );
297#else
298 // Invoke /bin/sh -c so glob expansion, pipes, and other shell features work. The string form of
299 // wxExecute would call execvp() directly, bypassing the shell. Hold the wchar buffers in named
300 // locals so the argv pointers stay valid on wxUSE_UNICODE_UTF8 builds where wc_str() is a temp.
301 wxWCharBuffer shell = wxString( wxS( "/bin/sh" ) ).wc_str();
302 wxWCharBuffer flag = wxString( wxS( "-c" ) ).wc_str();
303 wxWCharBuffer command = aCommand.wc_str();
304
305 const wchar_t* argv[] = { shell.data(), flag.data(), command.data(), nullptr };
306
307 return static_cast<int>( wxExecute( argv, wxEXEC_SYNC, aProcess ) );
308#endif
309}
310
311
312bool OpenPDF( const wxString& file )
313{
314 wxString msg;
315 wxString filename = file;
316
318
319 if( Pgm().UseSystemPdfBrowser() )
320 {
321 if( !LaunchExternal( filename ) )
322 {
323 msg.Printf( _( "Unable to find a PDF viewer for '%s'." ), filename );
324 DisplayErrorMessage( nullptr, msg );
325 return false;
326 }
327 }
328 else
329 {
330 const wchar_t* args[3];
331
332 args[0] = Pgm().GetPdfBrowserName().wc_str();
333 args[1] = filename.wc_str();
334 args[2] = nullptr;
335
336 if( wxExecute( const_cast<wchar_t**>( args ) ) == -1 )
337 {
338 msg.Printf( _( "Problem while running the PDF viewer '%s'." ), args[0] );
339 DisplayErrorMessage( nullptr, msg );
340 return false;
341 }
342 }
343
344 return true;
345}
346
347
348void KiCopyFile( const wxString& aSrcPath, const wxString& aDestPath, wxString& aErrors )
349{
350 if( !wxCopyFile( aSrcPath, aDestPath ) )
351 {
352 wxString msg;
353
354 if( !aErrors.IsEmpty() )
355 aErrors += "\n";
356
357 msg.Printf( _( "Cannot copy file '%s'." ), aDestPath );
358 aErrors += msg;
359 }
360}
361
362
363static void traverseSEXPR( SEXPR::SEXPR* aNode, const std::function<void( SEXPR::SEXPR* )>& aVisitor )
364{
365 aVisitor( aNode );
366
367 if( aNode->IsList() )
368 {
369 for( unsigned i = 0; i < aNode->GetNumberOfChildren(); i++ )
370 traverseSEXPR( aNode->GetChild( i ), aVisitor );
371 }
372}
373
374
375void CopySexprFile( const wxString& aSrcPath, const wxString& aDestPath,
376 std::function<bool( const std::string& token, wxString& value )> aCallback,
377 wxString& aErrors )
378{
379 bool success = false;
380
381 try
382 {
383 SEXPR::PARSER parser;
384 std::unique_ptr<SEXPR::SEXPR> sexpr( parser.ParseFromFile( TO_UTF8( aSrcPath ) ) );
385
386 traverseSEXPR( sexpr.get(),
387 [&]( SEXPR::SEXPR* node )
388 {
389 if( node->IsList() && node->GetNumberOfChildren() > 1 && node->GetChild( 0 )->IsSymbol() )
390 {
391 std::string token = node->GetChild( 0 )->GetSymbol();
392 SEXPR::SEXPR_STRING* pathNode = dynamic_cast<SEXPR::SEXPR_STRING*>( node->GetChild( 1 ) );
393 SEXPR::SEXPR_SYMBOL* symNode = dynamic_cast<SEXPR::SEXPR_SYMBOL*>( node->GetChild( 1 ) );
394 wxString path;
395
396 if( pathNode )
397 path = pathNode->m_value;
398 else if( symNode )
399 path = symNode->m_value;
400
401 if( aCallback( token, path ) )
402 {
403 if( pathNode )
404 pathNode->m_value = path;
405 else if( symNode )
406 symNode->m_value = path;
407 }
408 }
409 } );
410
411
412 // Pass through the pretifier to ensure format is the same as when a file is saved by a frame
413 PRETTIFIED_FILE_OUTPUTFORMATTER formatter( aDestPath );
414
415 // Format as a string to prevent format string attacks
416 formatter.Print( "%s", sexpr->AsString( 0 ).c_str() );
417
418 success = formatter.Finish();
419 }
420 catch( ... )
421 {
422 success = false;
423 }
424
425 if( !success )
426 {
427 wxString msg;
428
429 if( !aErrors.empty() )
430 aErrors += wxS( "\n" );
431
432 msg.Printf( _( "Cannot copy file '%s'." ), aDestPath );
433 aErrors += msg;
434 }
435}
436
437
438wxString QuoteFullPath( wxFileName& fn, wxPathFormat format )
439{
440 return wxT( "\"" ) + fn.GetFullPath( format ) + wxT( "\"" );
441}
442
443
444bool RmDirRecursive( const wxString& aFileName, wxString* aErrors )
445{
446 namespace fs = std::filesystem;
447
448 std::string rmDir = aFileName.ToStdString();
449
450 if( rmDir.length() < 3 )
451 {
452 if( aErrors )
453 *aErrors = _( "Invalid directory name, cannot remove root" );
454
455 return false;
456 }
457
458 if( !fs::exists( rmDir ) )
459 {
460 if( aErrors )
461 *aErrors = wxString::Format( _( "Directory '%s' does not exist" ), aFileName );
462
463 return false;
464 }
465
466 fs::path path( rmDir );
467
468 if( !fs::is_directory( path ) )
469 {
470 if( aErrors )
471 *aErrors = wxString::Format( _( "'%s' is not a directory" ), aFileName );
472
473 return false;
474 }
475
476 try
477 {
478 fs::remove_all( path );
479 }
480 catch( const fs::filesystem_error& e )
481 {
482 if( aErrors )
483 *aErrors = wxString::Format( _( "Error removing directory '%s': %s" ), aFileName, e.what() );
484
485 return false;
486 }
487
488 return true;
489}
490
491
492bool CopyDirectory( const wxString& aSourceDir, const wxString& aDestDir,
493 const std::vector<wxString>& aPathsWithOverwriteDisallowed, wxString& aErrors )
494{
495 wxDir dir( aSourceDir );
496
497 if( !dir.IsOpened() )
498 {
499 aErrors += wxString::Format( _( "Could not open source directory: %s" ), aSourceDir );
500 aErrors += wxT( "\n" );
501 return false;
502 }
503
504 if( !wxFileName::Mkdir( aDestDir, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
505 {
506 aErrors += wxString::Format( _( "Could not create destination directory: %s" ), aDestDir );
507 aErrors += wxT( "\n" );
508 return false;
509 }
510
511 wxString filename;
512 bool cont = dir.GetFirst( &filename );
513
514 while( cont )
515 {
516 wxString sourcePath = dir.GetNameWithSep() + filename;
517 wxString destPath = aDestDir + wxFileName::GetPathSeparator() + filename;
518
519 if( wxFileName::DirExists( sourcePath ) )
520 {
521 // Recursively copy subdirectories
522 if( !CopyDirectory( sourcePath, destPath, aPathsWithOverwriteDisallowed, aErrors ) )
523 return false;
524 }
525 else
526 {
527 // Copy files
528 if( alg::contains( aPathsWithOverwriteDisallowed, sourcePath ) && wxFileExists( destPath ) )
529 {
530 // Presumably user does not want an error on a no-overwrite condition....
531 }
532 else if( !wxCopyFile( sourcePath, destPath ) )
533 {
534 aErrors += wxString::Format( _( "Could not copy file: %s to %s" ), sourcePath, destPath );
535 return false;
536 }
537 }
538
539 cont = dir.GetNext( &filename );
540 }
541
542 return true;
543}
544
545
546bool CopyFilesOrDirectory( const wxString& aSourcePath, const wxString& aDestDir, bool aAllowOverwrites,
547 wxString& aErrors, std::vector<wxString>& aPathsWritten )
548{
549 // Parse source path and determine if it's a directory
550 wxFileName sourceFn( aSourcePath );
551 wxString sourcePath = sourceFn.GetFullPath();
552 bool isSourceDirectory = wxFileName::DirExists( sourcePath );
553 wxString baseDestDir = aDestDir;
554
555 auto performCopy =
556 [&]( const wxString& src, const wxString& dest ) -> bool
557 {
558 if( wxCopyFile( src, dest, aAllowOverwrites ) )
559 {
560 aPathsWritten.push_back( dest );
561 return true;
562 }
563
564 aErrors += wxString::Format( _( "Could not copy file: %s to %s" ), src, dest );
565 aErrors += wxT( "\n" );
566 return false;
567 };
568
569 auto processEntries =
570 [&]( const wxString& srcDir, const wxString& pattern, const wxString& destDir ) -> bool
571 {
572 wxDir dir( srcDir );
573
574 if( !dir.IsOpened() )
575 {
576 aErrors += wxString::Format( _( "Could not open source directory: %s" ), srcDir );
577 aErrors += wxT( "\n" );
578 return false;
579 }
580
581 wxString filename;
582 bool success = true;
583
584 // Find all entries matching pattern (files + directories + hidden items)
585 bool cont = dir.GetFirst( &filename, pattern, wxDIR_FILES | wxDIR_DIRS | wxDIR_HIDDEN );
586
587 while( cont )
588 {
589 const wxString entrySrc = srcDir + wxFileName::GetPathSeparator() + filename;
590 const wxString entryDest = destDir + wxFileName::GetPathSeparator() + filename;
591
592 if( !filename.Matches( wxT( "~*.lck" ) ) && !filename.Matches( wxT( "*.lck" ) ) )
593 {
594 if( wxFileName::DirExists( entrySrc ) )
595 {
596 // Recursively process subdirectories
597 if( !CopyFilesOrDirectory( entrySrc, destDir, aAllowOverwrites, aErrors, aPathsWritten ) )
598 {
599 aErrors += wxString::Format( _( "Could not copy directory: %s to %s" ),
600 entrySrc, entryDest );
601 aErrors += wxT( "\n" );
602
603 success = false;
604 }
605 }
606 else
607 {
608 // Copy individual files
609 if( !performCopy( entrySrc, entryDest ) )
610 {
611 success = false;
612 }
613 }
614 }
615
616 cont = dir.GetNext( &filename );
617 }
618
619 return success;
620 };
621
622 // If copying a directory, append its name to destination path
623 if( isSourceDirectory )
624 {
625 wxString sourceDirName = sourceFn.GetFullName();
626 baseDestDir = wxFileName( aDestDir, sourceDirName ).GetFullPath();
627 }
628
629 // Create destination directory hierarchy
630 if( !wxFileName::Mkdir( baseDestDir, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
631 {
632 aErrors += wxString::Format( _( "Could not create destination directory: %s" ), baseDestDir );
633 aErrors += wxT( "\n" );
634
635 return false;
636 }
637
638 // Execute appropriate copy operation based on source type
639 if( !isSourceDirectory )
640 {
641 const wxString fileName = sourceFn.GetFullName();
642
643 // Handle wildcard patterns in filenames
644 if( fileName.Contains( '*' ) || fileName.Contains( '?' ) )
645 {
646 const wxString dirPath = sourceFn.GetPath();
647
648 if( !wxFileName::DirExists( dirPath ) )
649 {
650 aErrors += wxString::Format( _( "Source directory does not exist: %s" ), dirPath );
651 aErrors += wxT( "\n" );
652
653 return false;
654 }
655
656 // Process all matching files in source directory
657 return processEntries( dirPath, fileName, baseDestDir );
658 }
659
660 // Single file copy operation
661 return performCopy( sourcePath, wxFileName( baseDestDir, fileName ).GetFullPath() );
662 }
663
664 // Full directory copy operation
665 return processEntries( sourcePath, wxEmptyString, baseDestDir );
666}
667
668
669bool AddDirectoryToZip( wxZipOutputStream& aZip, const wxString& aSourceDir, wxString& aErrors,
670 const wxString& aParentDir )
671{
672 wxDir dir( aSourceDir );
673
674 if( !dir.IsOpened() )
675 {
676 aErrors += wxString::Format( _( "Could not open source directory: %s" ), aSourceDir );
677 aErrors += "\n";
678 return false;
679 }
680
681 wxString filename;
682 bool cont = dir.GetFirst( &filename );
683
684 while( cont )
685 {
686 wxString sourcePath = aSourceDir + wxFileName::GetPathSeparator() + filename;
687 wxString zipPath = aParentDir + filename;
688
689 if( wxFileName::DirExists( sourcePath ) )
690 {
691 // Add directory entry to the ZIP file
692 aZip.PutNextDirEntry( zipPath + "/" );
693
694 // Recursively add subdirectories
695 if( !AddDirectoryToZip( aZip, sourcePath, aErrors, zipPath + "/" ) )
696 return false;
697 }
698 else
699 {
700 // Add file entry to the ZIP file
701 aZip.PutNextEntry( zipPath );
702 wxFFileInputStream fileStream( sourcePath );
703
704 if( !fileStream.IsOk() )
705 {
706 aErrors += wxString::Format( _( "Could not read file: %s" ), sourcePath );
707 return false;
708 }
709
710 aZip.Write( fileStream );
711 }
712
713 cont = dir.GetNext( &filename );
714 }
715
716 return true;
717}
718
719
720namespace
721{
722struct ARCHIVE_SOURCE
723{
724 wxString m_path;
725 wxString m_name;
726 wxString m_key;
727 bool m_isDirectory;
728};
729
730
731bool collectArchiveSources( const wxString& aSourceDir, const wxString& aParent,
732 DIR_LOOP_GUARD& aGuard, std::vector<ARCHIVE_SOURCE>& aSources )
733{
734 wxDir dir( aSourceDir );
735
736 if( !dir.IsOpened() )
737 return false;
738
739 wxString name;
740 bool cont = dir.GetFirst( &name, wxEmptyString, wxDIR_DEFAULT );
741
742 while( cont )
743 {
744 wxString path = wxFileName( aSourceDir, name ).GetFullPath();
745 wxString relative = aParent.IsEmpty() ? name : aParent + wxS( "/" ) + name;
746 bool isDirectory = wxDirExists( path );
747 wxString key = relative + ( isDirectory ? wxS( "/" ) : wxS( "" ) );
748 aSources.push_back( { path, relative, key, isDirectory } );
749
750 if( isDirectory )
751 {
752 if( !aGuard.ShouldDescend( path ) || !collectArchiveSources( path, relative, aGuard, aSources ) )
753 return false;
754 }
755
756 cont = dir.GetNext( &name );
757 }
758
759 return true;
760}
761
762
763bool writeArchiveContents( wxArchiveOutputStream& aArchive, const std::vector<ARCHIVE_SOURCE>& aSources )
764{
765 for( const ARCHIVE_SOURCE& source : aSources )
766 {
767 if( source.m_isDirectory )
768 {
769 if( !aArchive.PutNextDirEntry( source.m_name ) )
770 return false;
771
772 continue;
773 }
774
775 wxFFileInputStream input( source.m_path );
776 wxFileOffset expectedLength = input.IsOk() ? input.GetLength() : wxInvalidOffset;
777
778 if( !input.IsOk() || !aArchive.PutNextEntry( source.m_name, wxDateTime::Now(), expectedLength ) )
779 return false;
780
781 input.Read( aArchive );
782
783 if( input.TellI() != expectedLength || !aArchive.IsOk() || aArchive.GetLastError() != wxSTREAM_NO_ERROR
784 || !aArchive.CloseEntry() )
785 return false;
786 }
787
788 return true;
789}
790} // namespace
791
792
793bool WriteDirectoryArchive( const wxString& aSourceDir, wxOutputStream& aOut,
794 ARCHIVE_FORMAT aFormat, const wxString& aTopDir )
795{
796 if( !aOut.IsOk() || !wxDirExists( aSourceDir ) )
797 return false;
798
799 DIR_LOOP_GUARD guard( aSourceDir );
800 std::vector<ARCHIVE_SOURCE> sources;
801
802 if( !guard.IsRooted() || !collectArchiveSources( aSourceDir, aTopDir, guard, sources ) )
803 return false;
804
805 std::sort( sources.begin(), sources.end(), []( const ARCHIVE_SOURCE& a, const ARCHIVE_SOURCE& b )
806 {
807 return a.m_key < b.m_key;
808 } );
809
810 if( aFormat == ARCHIVE_FORMAT::TGZ )
811 {
812 wxZlibOutputStream gzip( aOut, -1, wxZLIB_GZIP );
813 wxTarOutputStream archive( gzip, wxTAR_PAX, wxConvUTF8 );
814 bool ok = ( aTopDir.IsEmpty() || archive.PutNextDirEntry( aTopDir ) )
815 && writeArchiveContents( archive, sources );
816 bool archiveOk = archive.IsOk() && archive.GetLastError() == wxSTREAM_NO_ERROR;
817 bool archiveClosed = archive.Close();
818 bool gzipClosed = gzip.Close();
819 return ok && archiveOk && archiveClosed && gzipClosed && aOut.IsOk();
820 }
821
822 wxZipOutputStream archive( aOut );
823 bool ok = ( aTopDir.IsEmpty() || archive.PutNextDirEntry( aTopDir ) )
824 && writeArchiveContents( archive, sources );
825 bool archiveOk = archive.IsOk() && archive.GetLastError() == wxSTREAM_NO_ERROR;
826 bool archiveClosed = archive.Close();
827 return ok && archiveOk && archiveClosed && aOut.IsOk();
828}
829
830
831bool WriteDirectoryArchive( const wxString& aSourceDir, const wxString& aArchive,
832 ARCHIVE_FORMAT aFormat, const wxString& aTopDir, wxString* aError,
833 const ARCHIVE_STREAM_FACTORY& aOpen )
834{
835 wxString tempPath;
836 wxString error;
837 FILE* fp = KIPLATFORM::IO::OpenUniqueSiblingTempFile( aArchive, wxS( "wb" ), &tempPath, &error );
838
839 if( !fp )
840 {
841 if( aError )
842 *aError = error.IsEmpty() ? _( "Cannot create temporary archive file" ) : error;
843
844 return false;
845 }
846
847 // Use a large I/O buffer for cloud-synced folders
848 // See KIPLATFORM::IO::CLOUD_SYNC_BUFFER_SIZE
849 setvbuf( fp, nullptr, _IOFBF, KIPLATFORM::IO::CLOUD_SYNC_BUFFER_SIZE );
850
851 bool ok = false;
852
853 {
854 wxFFile file( fp );
855
856 try
857 {
858 std::unique_ptr<wxOutputStream> output = aOpen ? aOpen( file )
859 : std::make_unique<wxFFileOutputStream>( file );
860
861 if( output )
862 ok = WriteDirectoryArchive( aSourceDir, *output, aFormat, aTopDir );
863 }
864 catch( const std::exception& exception )
865 {
866 ok = false;
867 error = wxString::FromUTF8( exception.what() );
868 }
869
870 bool writeOk = ok;
871 bool streamError = file.Error();
872 FILE* raw = file.Detach();
873 bool flushed = KIPLATFORM::IO::FlushToDisk( raw );
874 bool closed = fclose( raw ) == 0;
875 ok = writeOk && !streamError && flushed && closed;
876
877 if( !writeOk || streamError )
878 {
879 if( error.IsEmpty() )
880 error = _( "Write error" );
881 }
882 else if( !flushed || !closed )
883 {
884 error = _( "Cannot flush or close temporary file" );
885 }
886 }
887
888 if( ok )
889 {
890 ok = KIPLATFORM::IO::CommitTempFile( tempPath, aArchive, &error );
891
892 if( !ok && error.IsEmpty() )
893 error = _( "Cannot replace archive" );
894 }
895
896 if( !ok && wxFileExists( tempPath ) )
897 {
898 if( !wxRemoveFile( tempPath ) )
899 error += _( "\nCannot remove temporary archive file" );
900 }
901
902 if( aError )
903 *aError = error;
904
905 return ok;
906}
907
908
909std::filesystem::path ToFsPath( const wxString& aPath )
910{
911#ifdef __WXMSW__
912 return std::filesystem::path( std::wstring( aPath.wc_str() ) );
913#else
914 return std::filesystem::path( aPath.utf8_string() );
915#endif
916}
917
918
919wxString FromFsPath( const std::filesystem::path& aPath )
920{
921#ifdef __WXMSW__
922 return wxString( aPath.wstring() );
923#else
924 std::u8string utf8 = aPath.u8string();
925 return wxString::FromUTF8( reinterpret_cast<const char*>( utf8.data() ), utf8.size() );
926#endif
927}
928
929
931{
932 if( wxDirExists( m_path ) )
933 wxFileName::Rmdir( m_path, wxPATH_RMDIR_RECURSIVE );
934 else if( wxFileExists( m_path ) )
935 wxRemoveFile( m_path );
936}
937
938
939bool SCOPED_TEMP_PATH::MakeFile( const wxString& aPrefix )
940{
941 m_path = wxFileName::CreateTempFileName( aPrefix );
942 return !m_path.IsEmpty();
943}
944
945
946bool SCOPED_TEMP_PATH::MakeDirectory( const wxString& aPrefix )
947{
948 if( !MakeFile( aPrefix ) || !wxRemoveFile( m_path ) )
949 return false;
950
951 // A non-recursive Mkdir fails on a leaf that someone created after the remove
952 if( wxFileName::Mkdir( m_path, wxS_DIR_DEFAULT, 0 ) )
953 return true;
954
955 // That leaf is not ours to delete
956 m_path.clear();
957 return false;
958}
959
960
961namespace
962{
963
964
965// Best-effort canonicalisation. Empty path means "couldn't resolve"
966// (broken symlink, ELOOP, etc.); callers treat that as "skip rather than
967// risk recursing".
968std::filesystem::path canonicalPath( const std::filesystem::path& aPath )
969{
970 std::error_code ec;
971 std::filesystem::path canon = std::filesystem::weakly_canonical( aPath, ec );
972
973 return ec ? std::filesystem::path() : canon;
974}
975
976
977// True if @p aAncestor is equal to or an ancestor of @p aDescendant. Both
978// must already be canonical so that "/a/b" and "/a/b/c" share a prefix
979// component-wise.
980bool isAncestorOrSame( const std::filesystem::path& aAncestor,
981 const std::filesystem::path& aDescendant )
982{
983 auto a = aAncestor.begin();
984 auto d = aDescendant.begin();
985
986 for( ; a != aAncestor.end() && d != aDescendant.end(); ++a, ++d )
987 {
988 if( *a != *d )
989 return false;
990 }
991
992 return a == aAncestor.end();
993}
994
995
996// records files dirs or both into aOutput loop-safe via DIR_LOOP_GUARD
997class LOOP_SAFE_COLLECTOR : public wxDirTraverser
998{
999public:
1000 LOOP_SAFE_COLLECTOR( wxArrayString& aOutput, const wxString& aRoot, bool aCollectFiles,
1001 bool aCollectDirs ) :
1002 m_output( aOutput ),
1003 m_guard( aRoot, DIR_LOOP_POLICY::BLOCK_ROOT_ESCAPE ),
1004 m_collectFiles( aCollectFiles ),
1005 m_collectDirs( aCollectDirs )
1006 {
1007 }
1008
1009 wxDirTraverseResult OnFile( const wxString& aFilename ) override
1010 {
1011 if( m_collectFiles )
1012 m_output.Add( aFilename );
1013
1014 return wxDIR_CONTINUE;
1015 }
1016
1017 wxDirTraverseResult OnDir( const wxString& aDirname ) override
1018 {
1019 if( !m_guard.ShouldDescend( aDirname ) )
1020 return wxDIR_IGNORE;
1021
1022 if( m_collectDirs )
1023 m_output.Add( aDirname );
1024
1025 return wxDIR_CONTINUE;
1026 }
1027
1028private:
1029 wxArrayString& m_output;
1030 DIR_LOOP_GUARD m_guard;
1031 bool m_collectFiles;
1032 bool m_collectDirs;
1033};
1034
1035
1036void traverseLoopSafe( const wxString& aRoot, wxArrayString& aOutput, bool aCollectFiles,
1037 bool aCollectDirs, const wxString& aFileSpec, int aFlags )
1038{
1039 wxDir dir( aRoot );
1040
1041 if( !dir.IsOpened() )
1042 return;
1043
1044 LOOP_SAFE_COLLECTOR collector( aOutput, aRoot, aCollectFiles, aCollectDirs );
1045 dir.Traverse( collector, aFileSpec, aFlags );
1046}
1047
1048} // namespace
1049
1050
1051DIR_LOOP_GUARD::DIR_LOOP_GUARD( const wxString& aRoot, DIR_LOOP_POLICY aPolicy ) :
1052 m_root( canonicalPath( ToFsPath( aRoot ) ) ),
1053 m_policy( aPolicy )
1054{
1055 m_visited.reserve( 256 );
1056
1057 if( !m_root.empty() )
1058 m_visited.insert( m_root.generic_string() );
1059}
1060
1061
1062bool DIR_LOOP_GUARD::ShouldDescend( const wxString& aDir )
1063{
1064 const std::filesystem::path raw = ToFsPath( aDir );
1065 std::filesystem::path key;
1066
1068 {
1069 // confine fears any resolution outside the subtree so resolve every candidate
1070 // a symlinked ancestor could otherwise smuggle an ordinary looking child out
1071 const std::filesystem::path canon = canonicalPath( raw );
1072
1073 if( canon.empty() )
1074 return false;
1075
1076 if( !m_root.empty() && !isAncestorOrSame( m_root, canon ) )
1077 return false;
1078
1079 key = canon;
1080 }
1081 else
1082 {
1083 // escape only fears an upward link a real subdir cant be an ancestor of root
1084 // so skip the per-component resolve and canonicalize actual links only
1085 std::error_code ec;
1086 const bool isLink = std::filesystem::is_symlink( raw, ec );
1087
1088 if( isLink && !ec )
1089 {
1090 const std::filesystem::path canon = canonicalPath( raw );
1091
1092 if( canon.empty() )
1093 return false;
1094
1095 if( !m_root.empty() && isAncestorOrSame( canon, m_root ) )
1096 return false;
1097
1098 key = canon;
1099 }
1100 else
1101 {
1102 key = raw.lexically_normal();
1103 }
1104 }
1105
1106 return m_visited.insert( key.generic_string() ).second;
1107}
1108
1109
1110void CollectFilesLoopSafe( const wxString& aRoot, wxArrayString& aFiles, const wxString& aFileSpec,
1111 int aFlags )
1112{
1113 // Force wxDIR_FILES so files are reported and wxDIR_DIRS so Traverse descends
1114 // into subdirectories; the collector keeps directories out of the file list
1115 // and breaks loops. aFlags carries the caller's wxDIR_HIDDEN choice.
1116 traverseLoopSafe( aRoot, aFiles, true, false, aFileSpec, aFlags | wxDIR_FILES | wxDIR_DIRS );
1117}
1118
1119
1120void CollectSubdirsLoopSafe( const wxString& aRoot, wxArrayString& aDirs, int aFlags )
1121{
1122 traverseLoopSafe( aRoot, aDirs, false, true, wxEmptyString, aFlags | wxDIR_DIRS );
1123}
const char * name
constexpr std::size_t arrayDim(T const (&)[N]) noexcept
Returns # of elements in an array.
Definition arraydim.h:27
bool ShouldDescend(const wxString &aDir)
DIR_LOOP_POLICY m_policy
Definition gestfich.h:271
std::unordered_set< std::string > m_visited
Definition gestfich.h:272
bool IsRooted() const
Definition gestfich.h:265
DIR_LOOP_GUARD(const wxString &aRoot, DIR_LOOP_POLICY aPolicy=DIR_LOOP_POLICY::CONFINE_TO_ROOT)
std::filesystem::path m_root
Definition gestfich.h:270
int PRINTF_FUNC_N Print(int nestLevel, const char *fmt,...)
Format and write text to the output stream.
Definition richio.cpp:432
virtual const wxString & GetKicadEnvVariable() const
Definition pgm_base.h:170
virtual void ReadPdfBrowserInfos()
Read the PDF browser choice from the common configuration.
Definition pgm_base.cpp:878
virtual const wxString & GetPdfBrowserName() const
Definition pgm_base.h:176
virtual const wxString & GetExecutablePath() const
Definition pgm_base.cpp:872
bool Finish() override
Runs prettification over the buffered bytes, writes them to the sibling temp file,...
Definition richio.cpp:710
bool MakeFile(const wxString &aPrefix)
Create an empty file with a unique name after aPrefix.
Definition gestfich.cpp:939
bool MakeDirectory(const wxString &aPrefix)
Create an empty directory with a unique name after aPrefix.
Definition gestfich.cpp:946
wxString m_path
Definition gestfich.h:78
std::unique_ptr< SEXPR > ParseFromFile(const std::string &aFilename)
size_t GetNumberOfChildren() const
Definition sexpr.cpp:72
bool IsList() const
Definition sexpr.h:49
SEXPR * GetChild(size_t aIndex) const
Definition sexpr.cpp:50
void DisplayErrorMessage(wxWindow *aParent, const wxString &aText, const wxString &aExtraInfo)
Display an error message with aMessage.
Definition confirm.cpp:217
This file is part of the common library.
#define _(s)
wxString FromFsPath(const std::filesystem::path &aPath)
Definition gestfich.cpp:919
bool WriteDirectoryArchive(const wxString &aSourceDir, wxOutputStream &aOut, ARCHIVE_FORMAT aFormat, const wxString &aTopDir)
Write directory entries in sorted archive path order, using the archive root when aTopDir is empty.
Definition gestfich.cpp:793
wxString FindKicadFile(const wxString &shortname)
Search the executable file shortname in KiCad binary path and return full file name if found or short...
Definition gestfich.cpp:67
wxString QuoteFullPath(wxFileName &fn, wxPathFormat format)
Quote return value of wxFileName::GetFullPath().
Definition gestfich.cpp:438
int ExecuteCommandThroughShell(const wxString &aCommand, wxProcess *aProcess)
Run a user-supplied command line through the platform shell so glob expansion, pipes,...
Definition gestfich.cpp:277
void CopySexprFile(const wxString &aSrcPath, const wxString &aDestPath, std::function< bool(const std::string &token, wxString &value)> aCallback, wxString &aErrors)
Definition gestfich.cpp:375
bool OpenPDF(const wxString &file)
Run the PDF viewer and display a PDF file.
Definition gestfich.cpp:312
void KiCopyFile(const wxString &aSrcPath, const wxString &aDestPath, wxString &aErrors)
Definition gestfich.cpp:348
int ExecuteFile(const wxString &aEditorName, const wxString &aFileName, wxProcess *aCallback, bool aFileForKicad)
Call the executable file aEditorName with the parameter aFileName.
Definition gestfich.cpp:166
bool RmDirRecursive(const wxString &aFileName, wxString *aErrors)
Remove the directory aDirName and all its contents including subdirectories and their files.
Definition gestfich.cpp:444
void QuoteString(wxString &string)
Add un " to the start and the end of string (if not already done).
Definition gestfich.cpp:57
bool CopyFilesOrDirectory(const wxString &aSourcePath, const wxString &aDestDir, bool aAllowOverwrites, wxString &aErrors, std::vector< wxString > &aPathsWritten)
Definition gestfich.cpp:546
void CollectSubdirsLoopSafe(const wxString &aRoot, wxArrayString &aDirs, int aFlags)
Recursively collect every subdirectory under aRoot using the same loop detection as CollectFilesLoopS...
void CollectFilesLoopSafe(const wxString &aRoot, wxArrayString &aFiles, const wxString &aFileSpec, int aFlags)
Recursively collect every file under aRoot, deduplicating subdirectories by their resolved path.
std::filesystem::path ToFsPath(const wxString &aPath)
Give aPath to std::filesystem without a detour through the C locale.
Definition gestfich.cpp:909
static void traverseSEXPR(SEXPR::SEXPR *aNode, const std::function< void(SEXPR::SEXPR *)> &aVisitor)
Definition gestfich.cpp:363
bool CopyDirectory(const wxString &aSourceDir, const wxString &aDestDir, const std::vector< wxString > &aPathsWithOverwriteDisallowed, wxString &aErrors)
Copy a directory and its contents to another directory.
Definition gestfich.cpp:492
bool AddDirectoryToZip(wxZipOutputStream &aZip, const wxString &aSourceDir, wxString &aErrors, const wxString &aParentDir)
Add a directory and its contents to a zip file.
Definition gestfich.cpp:669
ARCHIVE_FORMAT
Definition gestfich.h:50
std::function< std::unique_ptr< wxOutputStream >(wxFFile &)> ARCHIVE_STREAM_FACTORY
Definition gestfich.h:81
KICOMMON_API std::filesystem::path ToFsPath(const wxString &aPath)
Give aPath to std::filesystem without a detour through the C locale.
Definition gestfich.cpp:909
DIR_LOOP_POLICY
Definition gestfich.h:250
std::string source
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
Definition idf_helpers.h:37
bool LaunchExternal(const wxString &aPath)
Launches the given file or folder in the host OS.
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 CommitTempFile(const wxString &aTempPath, const wxString &aTargetPath, wxString *aError=nullptr)
Completes an atomic save.
static constexpr size_t CLOUD_SYNC_BUFFER_SIZE
Buffer size for file I/O operations on cloud-synced folders.
Definition io.h:169
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
bool contains(const _Container &__container, _Value __value)
Returns true if the container contains the given value.
Definition kicad_algo.h:96
PGM_BASE & Pgm()
The global program "get" accessor.
see class PGM_BASE
std::string path