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#include <paths.h>
57
58void QuoteString( wxString& string )
59{
60 if( !string.StartsWith( wxT( "\"" ) ) )
61 {
62 string.Prepend ( wxT( "\"" ) );
63 string.Append ( wxT( "\"" ) );
64 }
65}
66
67
68wxString FindKicadFile( const wxString& shortname )
69{
70 // Test the presence of the file in the directory shortname of
71 // the KiCad binary path.
72#ifndef __WXMAC__
73 wxString fullFileName = Pgm().GetExecutablePath() + shortname;
74#else
75 wxString fullFileName = Pgm().GetExecutablePath() + wxT( "Contents/MacOS/" ) + shortname;
76#endif
77 if( wxFileExists( fullFileName ) )
78 return fullFileName;
79
81 {
82 wxFileName buildDir( Pgm().GetExecutablePath(), shortname );
83
84#ifdef __WXMAC__
85 if( !buildDir.GetDirs().IsEmpty()
86 && buildDir.GetDirs().Last().Lower().EndsWith( wxT( ".app" ) ) )
87 {
88 buildDir.RemoveLastDir();
89 }
90#endif
91 buildDir.RemoveLastDir();
92#ifndef __WXMSW__
93 buildDir.AppendDir( shortname );
94#else
95 buildDir.AppendDir( shortname.BeforeLast( '.' ) );
96#endif
97
98 if( buildDir.GetDirs().Last() == "pl_editor" )
99 {
100 buildDir.RemoveLastDir();
101 buildDir.AppendDir( "pagelayout_editor" );
102 }
103
104#ifdef __WXMAC__
105 buildDir.AppendDir( shortname + wxT( ".app" ) );
106 buildDir.AppendDir( wxT( "Contents" ) );
107 buildDir.AppendDir( wxT( "MacOS" ) );
108#endif
109
110 if( wxFileExists( buildDir.GetFullPath() ) )
111 return buildDir.GetFullPath();
112 }
113
114 // Test the presence of the file in the directory shortname
115 // defined by the environment variable KiCad.
116 if( Pgm().IsKicadEnvVariableDefined() )
117 {
118 fullFileName = Pgm().GetKicadEnvVariable() + shortname;
119
120 if( wxFileExists( fullFileName ) )
121 return fullFileName;
122 }
123
124#if defined( __WINDOWS__ )
125 // KiCad can be installed highly portably on Windows, anywhere and concurrently
126 // either the "kicad file" is immediately adjacent to the exe or it's not a valid install
127 return shortname;
128#else
129
130 // Path list for KiCad binary files
131 const static wxChar* possibilities[] = {
132#if defined( __WXMAC__ )
133 // all internal paths are relative to main bundle kicad.app
134 wxT( "Contents/Applications/pcbnew.app/Contents/MacOS/" ),
135 wxT( "Contents/Applications/eeschema.app/Contents/MacOS/" ),
136 wxT( "Contents/Applications/gerbview.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 // Preserve Windows PATH spelling while putting KiCad's Python first.
284 wxString binPath = wxFileName( wxStandardPaths::Get().GetExecutablePath() ).GetPath();
285 bool foundPath = false;
286
287 for( auto& entry : env.env )
288 {
289 if( entry.first.CmpNoCase( wxS( "PATH" ) ) == 0 )
290 {
291 entry.second = binPath + wxS( ';' ) + entry.second;
292 foundPath = true;
293 break;
294 }
295 }
296
297 if( !foundPath )
298 env.env["PATH"] = binPath;
299
300 // The array form of wxExecute is unusable with cmd.exe. wx joins the argv elements back into a
301 // single command line, wrapping any element containing spaces in double quotes and escaping
302 // embedded quotes with backslashes. cmd.exe does not understand backslash-escaped quotes and
303 // applies its own quote-stripping rules to the /c argument, which mangles absolute paths that
304 // contain spaces or quotes. Build the command line ourselves and let cmd.exe's /s rule strip
305 // exactly the outer quote pair, passing everything between through verbatim. /d disables any
306 // AutoRun registry commands so job execution is not machine-dependent.
307 wxString shellCmd = wxS( "cmd.exe /d /s /c \"" ) + aCommand + wxS( "\"" );
308
309 return static_cast<int>( wxExecute( shellCmd, wxEXEC_SYNC, aProcess, &env ) );
310#else
311 // Invoke /bin/sh -c so glob expansion, pipes, and other shell features work. The string form of
312 // wxExecute would call execvp() directly, bypassing the shell. Hold the wchar buffers in named
313 // locals so the argv pointers stay valid on wxUSE_UNICODE_UTF8 builds where wc_str() is a temp.
314 wxWCharBuffer shell = wxString( wxS( "/bin/sh" ) ).wc_str();
315 wxWCharBuffer flag = wxString( wxS( "-c" ) ).wc_str();
316 wxWCharBuffer command = aCommand.wc_str();
317
318 const wchar_t* argv[] = { shell.data(), flag.data(), command.data(), nullptr };
319
320 return static_cast<int>( wxExecute( argv, wxEXEC_SYNC, aProcess ) );
321#endif
322}
323
324
325bool OpenPDF( const wxString& file )
326{
327 wxString msg;
328 wxString filename = file;
329
331
332 if( Pgm().UseSystemPdfBrowser() )
333 {
334 if( !LaunchExternal( filename ) )
335 {
336 msg.Printf( _( "Unable to find a PDF viewer for '%s'." ), filename );
337 DisplayErrorMessage( nullptr, msg );
338 return false;
339 }
340 }
341 else
342 {
343 const wchar_t* args[3];
344
345 args[0] = Pgm().GetPdfBrowserName().wc_str();
346 args[1] = filename.wc_str();
347 args[2] = nullptr;
348
349 if( wxExecute( const_cast<wchar_t**>( args ) ) == -1 )
350 {
351 msg.Printf( _( "Problem while running the PDF viewer '%s'." ), args[0] );
352 DisplayErrorMessage( nullptr, msg );
353 return false;
354 }
355 }
356
357 return true;
358}
359
360
361void KiCopyFile( const wxString& aSrcPath, const wxString& aDestPath, wxString& aErrors )
362{
363 if( !wxCopyFile( aSrcPath, aDestPath ) )
364 {
365 wxString msg;
366
367 if( !aErrors.IsEmpty() )
368 aErrors += "\n";
369
370 msg.Printf( _( "Cannot copy file '%s'." ), aDestPath );
371 aErrors += msg;
372 }
373}
374
375
376static void traverseSEXPR( SEXPR::SEXPR* aNode, const std::function<void( SEXPR::SEXPR* )>& aVisitor )
377{
378 aVisitor( aNode );
379
380 if( aNode->IsList() )
381 {
382 for( unsigned i = 0; i < aNode->GetNumberOfChildren(); i++ )
383 traverseSEXPR( aNode->GetChild( i ), aVisitor );
384 }
385}
386
387
388void CopySexprFile( const wxString& aSrcPath, const wxString& aDestPath,
389 std::function<bool( const std::string& token, wxString& value )> aCallback,
390 wxString& aErrors )
391{
392 bool success = false;
393
394 try
395 {
396 SEXPR::PARSER parser;
397 std::unique_ptr<SEXPR::SEXPR> sexpr( parser.ParseFromFile( TO_UTF8( aSrcPath ) ) );
398
399 traverseSEXPR( sexpr.get(),
400 [&]( SEXPR::SEXPR* node )
401 {
402 if( node->IsList() && node->GetNumberOfChildren() > 1 && node->GetChild( 0 )->IsSymbol() )
403 {
404 std::string token = node->GetChild( 0 )->GetSymbol();
405 SEXPR::SEXPR_STRING* pathNode = dynamic_cast<SEXPR::SEXPR_STRING*>( node->GetChild( 1 ) );
406 SEXPR::SEXPR_SYMBOL* symNode = dynamic_cast<SEXPR::SEXPR_SYMBOL*>( node->GetChild( 1 ) );
407 wxString path;
408
409 if( pathNode )
410 path = pathNode->m_value;
411 else if( symNode )
412 path = symNode->m_value;
413
414 if( aCallback( token, path ) )
415 {
416 if( pathNode )
417 pathNode->m_value = path;
418 else if( symNode )
419 symNode->m_value = path;
420 }
421 }
422 } );
423
424
425 // Pass through the pretifier to ensure format is the same as when a file is saved by a frame
426 PRETTIFIED_FILE_OUTPUTFORMATTER formatter( aDestPath );
427
428 // Format as a string to prevent format string attacks
429 formatter.Print( "%s", sexpr->AsString( 0 ).c_str() );
430
431 success = formatter.Finish();
432 }
433 catch( ... )
434 {
435 success = false;
436 }
437
438 if( !success )
439 {
440 wxString msg;
441
442 if( !aErrors.empty() )
443 aErrors += wxS( "\n" );
444
445 msg.Printf( _( "Cannot copy file '%s'." ), aDestPath );
446 aErrors += msg;
447 }
448}
449
450
451wxString QuoteFullPath( wxFileName& fn, wxPathFormat format )
452{
453 return wxT( "\"" ) + fn.GetFullPath( format ) + wxT( "\"" );
454}
455
456
457bool RmDirRecursive( const wxString& aFileName, wxString* aErrors )
458{
459 namespace fs = std::filesystem;
460
461 std::string rmDir = aFileName.ToStdString();
462
463 if( rmDir.length() < 3 )
464 {
465 if( aErrors )
466 *aErrors = _( "Invalid directory name, cannot remove root" );
467
468 return false;
469 }
470
471 if( !fs::exists( rmDir ) )
472 {
473 if( aErrors )
474 *aErrors = wxString::Format( _( "Directory '%s' does not exist" ), aFileName );
475
476 return false;
477 }
478
479 fs::path path( rmDir );
480
481 if( !fs::is_directory( path ) )
482 {
483 if( aErrors )
484 *aErrors = wxString::Format( _( "'%s' is not a directory" ), aFileName );
485
486 return false;
487 }
488
489 try
490 {
491 fs::remove_all( path );
492 }
493 catch( const fs::filesystem_error& e )
494 {
495 if( aErrors )
496 *aErrors = wxString::Format( _( "Error removing directory '%s': %s" ), aFileName, e.what() );
497
498 return false;
499 }
500
501 return true;
502}
503
504
505bool CopyDirectory( const wxString& aSourceDir, const wxString& aDestDir,
506 const std::vector<wxString>& aPathsWithOverwriteDisallowed, wxString& aErrors )
507{
508 wxDir dir( aSourceDir );
509
510 if( !dir.IsOpened() )
511 {
512 aErrors += wxString::Format( _( "Could not open source directory: %s" ), aSourceDir );
513 aErrors += wxT( "\n" );
514 return false;
515 }
516
517 if( !wxFileName::Mkdir( aDestDir, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
518 {
519 aErrors += wxString::Format( _( "Could not create destination directory: %s" ), aDestDir );
520 aErrors += wxT( "\n" );
521 return false;
522 }
523
524 wxString filename;
525 bool cont = dir.GetFirst( &filename );
526
527 while( cont )
528 {
529 wxString sourcePath = dir.GetNameWithSep() + filename;
530 wxString destPath = aDestDir + wxFileName::GetPathSeparator() + filename;
531
532 if( wxFileName::DirExists( sourcePath ) )
533 {
534 // Recursively copy subdirectories
535 if( !CopyDirectory( sourcePath, destPath, aPathsWithOverwriteDisallowed, aErrors ) )
536 return false;
537 }
538 else
539 {
540 // Copy files
541 if( alg::contains( aPathsWithOverwriteDisallowed, sourcePath ) && wxFileExists( destPath ) )
542 {
543 // Presumably user does not want an error on a no-overwrite condition....
544 }
545 else if( !wxCopyFile( sourcePath, destPath ) )
546 {
547 aErrors += wxString::Format( _( "Could not copy file: %s to %s" ), sourcePath, destPath );
548 return false;
549 }
550 }
551
552 cont = dir.GetNext( &filename );
553 }
554
555 return true;
556}
557
558
559bool CopyFilesOrDirectory( const wxString& aSourcePath, const wxString& aDestDir, bool aAllowOverwrites,
560 wxString& aErrors, std::vector<wxString>& aPathsWritten )
561{
562 // Parse source path and determine if it's a directory
563 wxFileName sourceFn( aSourcePath );
564 wxString sourcePath = sourceFn.GetFullPath();
565 bool isSourceDirectory = wxFileName::DirExists( sourcePath );
566 wxString baseDestDir = aDestDir;
567
568 auto performCopy =
569 [&]( const wxString& src, const wxString& dest ) -> bool
570 {
571 if( wxCopyFile( src, dest, aAllowOverwrites ) )
572 {
573 aPathsWritten.push_back( dest );
574 return true;
575 }
576
577 aErrors += wxString::Format( _( "Could not copy file: %s to %s" ), src, dest );
578 aErrors += wxT( "\n" );
579 return false;
580 };
581
582 auto processEntries =
583 [&]( const wxString& srcDir, const wxString& pattern, const wxString& destDir ) -> bool
584 {
585 wxDir dir( srcDir );
586
587 if( !dir.IsOpened() )
588 {
589 aErrors += wxString::Format( _( "Could not open source directory: %s" ), srcDir );
590 aErrors += wxT( "\n" );
591 return false;
592 }
593
594 wxString filename;
595 bool success = true;
596
597 // Find all entries matching pattern (files + directories + hidden items)
598 bool cont = dir.GetFirst( &filename, pattern, wxDIR_FILES | wxDIR_DIRS | wxDIR_HIDDEN );
599
600 while( cont )
601 {
602 const wxString entrySrc = srcDir + wxFileName::GetPathSeparator() + filename;
603 const wxString entryDest = destDir + wxFileName::GetPathSeparator() + filename;
604
605 if( !filename.Matches( wxT( "~*.lck" ) ) && !filename.Matches( wxT( "*.lck" ) ) )
606 {
607 if( wxFileName::DirExists( entrySrc ) )
608 {
609 // Recursively process subdirectories
610 if( !CopyFilesOrDirectory( entrySrc, destDir, aAllowOverwrites, aErrors, aPathsWritten ) )
611 {
612 aErrors += wxString::Format( _( "Could not copy directory: %s to %s" ),
613 entrySrc, entryDest );
614 aErrors += wxT( "\n" );
615
616 success = false;
617 }
618 }
619 else
620 {
621 // Copy individual files
622 if( !performCopy( entrySrc, entryDest ) )
623 {
624 success = false;
625 }
626 }
627 }
628
629 cont = dir.GetNext( &filename );
630 }
631
632 return success;
633 };
634
635 // If copying a directory, append its name to destination path
636 if( isSourceDirectory )
637 {
638 wxString sourceDirName = sourceFn.GetFullName();
639 baseDestDir = wxFileName( aDestDir, sourceDirName ).GetFullPath();
640 }
641
642 // Create destination directory hierarchy
643 if( !wxFileName::Mkdir( baseDestDir, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
644 {
645 aErrors += wxString::Format( _( "Could not create destination directory: %s" ), baseDestDir );
646 aErrors += wxT( "\n" );
647
648 return false;
649 }
650
651 // Execute appropriate copy operation based on source type
652 if( !isSourceDirectory )
653 {
654 const wxString fileName = sourceFn.GetFullName();
655
656 // Handle wildcard patterns in filenames
657 if( fileName.Contains( '*' ) || fileName.Contains( '?' ) )
658 {
659 const wxString dirPath = sourceFn.GetPath();
660
661 if( !wxFileName::DirExists( dirPath ) )
662 {
663 aErrors += wxString::Format( _( "Source directory does not exist: %s" ), dirPath );
664 aErrors += wxT( "\n" );
665
666 return false;
667 }
668
669 // Process all matching files in source directory
670 return processEntries( dirPath, fileName, baseDestDir );
671 }
672
673 // Single file copy operation
674 return performCopy( sourcePath, wxFileName( baseDestDir, fileName ).GetFullPath() );
675 }
676
677 // Full directory copy operation
678 return processEntries( sourcePath, wxEmptyString, baseDestDir );
679}
680
681
682bool AddDirectoryToZip( wxZipOutputStream& aZip, const wxString& aSourceDir, wxString& aErrors,
683 const wxString& aParentDir )
684{
685 wxDir dir( aSourceDir );
686
687 if( !dir.IsOpened() )
688 {
689 aErrors += wxString::Format( _( "Could not open source directory: %s" ), aSourceDir );
690 aErrors += "\n";
691 return false;
692 }
693
694 wxString filename;
695 bool cont = dir.GetFirst( &filename );
696
697 while( cont )
698 {
699 wxString sourcePath = aSourceDir + wxFileName::GetPathSeparator() + filename;
700 wxString zipPath = aParentDir + filename;
701
702 if( wxFileName::DirExists( sourcePath ) )
703 {
704 // Add directory entry to the ZIP file
705 aZip.PutNextDirEntry( zipPath + "/" );
706
707 // Recursively add subdirectories
708 if( !AddDirectoryToZip( aZip, sourcePath, aErrors, zipPath + "/" ) )
709 return false;
710 }
711 else
712 {
713 // Add file entry to the ZIP file
714 aZip.PutNextEntry( zipPath );
715 wxFFileInputStream fileStream( sourcePath );
716
717 if( !fileStream.IsOk() )
718 {
719 aErrors += wxString::Format( _( "Could not read file: %s" ), sourcePath );
720 return false;
721 }
722
723 aZip.Write( fileStream );
724 }
725
726 cont = dir.GetNext( &filename );
727 }
728
729 return true;
730}
731
732
733namespace
734{
735struct ARCHIVE_SOURCE
736{
737 wxString m_path;
738 wxString m_name;
739 wxString m_key;
740 bool m_isDirectory;
741};
742
743
744bool collectArchiveSources( const wxString& aSourceDir, const wxString& aParent,
745 DIR_LOOP_GUARD& aGuard, std::vector<ARCHIVE_SOURCE>& aSources )
746{
747 wxDir dir( aSourceDir );
748
749 if( !dir.IsOpened() )
750 return false;
751
752 wxString name;
753 bool cont = dir.GetFirst( &name, wxEmptyString, wxDIR_DEFAULT );
754
755 while( cont )
756 {
757 wxString path = wxFileName( aSourceDir, name ).GetFullPath();
758 wxString relative = aParent.IsEmpty() ? name : aParent + wxS( "/" ) + name;
759 bool isDirectory = wxDirExists( path );
760 wxString key = relative + ( isDirectory ? wxS( "/" ) : wxS( "" ) );
761 aSources.push_back( { path, relative, key, isDirectory } );
762
763 if( isDirectory )
764 {
765 if( !aGuard.ShouldDescend( path ) || !collectArchiveSources( path, relative, aGuard, aSources ) )
766 return false;
767 }
768
769 cont = dir.GetNext( &name );
770 }
771
772 return true;
773}
774
775
776bool writeArchiveContents( wxArchiveOutputStream& aArchive, const std::vector<ARCHIVE_SOURCE>& aSources )
777{
778 for( const ARCHIVE_SOURCE& source : aSources )
779 {
780 if( source.m_isDirectory )
781 {
782 if( !aArchive.PutNextDirEntry( source.m_name ) )
783 return false;
784
785 continue;
786 }
787
788 wxFFileInputStream input( source.m_path );
789 wxFileOffset expectedLength = input.IsOk() ? input.GetLength() : wxInvalidOffset;
790
791 if( !input.IsOk() || !aArchive.PutNextEntry( source.m_name, wxDateTime::Now(), expectedLength ) )
792 return false;
793
794 input.Read( aArchive );
795
796 if( input.TellI() != expectedLength || !aArchive.IsOk() || aArchive.GetLastError() != wxSTREAM_NO_ERROR
797 || !aArchive.CloseEntry() )
798 return false;
799 }
800
801 return true;
802}
803} // namespace
804
805
806bool WriteDirectoryArchive( const wxString& aSourceDir, wxOutputStream& aOut,
807 ARCHIVE_FORMAT aFormat, const wxString& aTopDir )
808{
809 if( !aOut.IsOk() || !wxDirExists( aSourceDir ) )
810 return false;
811
812 DIR_LOOP_GUARD guard( aSourceDir );
813 std::vector<ARCHIVE_SOURCE> sources;
814
815 if( !guard.IsRooted() || !collectArchiveSources( aSourceDir, aTopDir, guard, sources ) )
816 return false;
817
818 std::sort( sources.begin(), sources.end(), []( const ARCHIVE_SOURCE& a, const ARCHIVE_SOURCE& b )
819 {
820 return a.m_key < b.m_key;
821 } );
822
823 if( aFormat == ARCHIVE_FORMAT::TGZ )
824 {
825 wxZlibOutputStream gzip( aOut, -1, wxZLIB_GZIP );
826 wxTarOutputStream archive( gzip, wxTAR_PAX, wxConvUTF8 );
827 bool ok = ( aTopDir.IsEmpty() || archive.PutNextDirEntry( aTopDir ) )
828 && writeArchiveContents( archive, sources );
829 bool archiveOk = archive.IsOk() && archive.GetLastError() == wxSTREAM_NO_ERROR;
830 bool archiveClosed = archive.Close();
831 bool gzipClosed = gzip.Close();
832 return ok && archiveOk && archiveClosed && gzipClosed && aOut.IsOk();
833 }
834
835 wxZipOutputStream archive( aOut );
836 bool ok = ( aTopDir.IsEmpty() || archive.PutNextDirEntry( aTopDir ) )
837 && writeArchiveContents( archive, sources );
838 bool archiveOk = archive.IsOk() && archive.GetLastError() == wxSTREAM_NO_ERROR;
839 bool archiveClosed = archive.Close();
840 return ok && archiveOk && archiveClosed && aOut.IsOk();
841}
842
843
844bool WriteDirectoryArchive( const wxString& aSourceDir, const wxString& aArchive,
845 ARCHIVE_FORMAT aFormat, const wxString& aTopDir, wxString* aError,
846 const ARCHIVE_STREAM_FACTORY& aOpen )
847{
848 wxString tempPath;
849 wxString error;
850 FILE* fp = KIPLATFORM::IO::OpenUniqueSiblingTempFile( aArchive, wxS( "wb" ), &tempPath, &error );
851
852 if( !fp )
853 {
854 if( aError )
855 *aError = error.IsEmpty() ? _( "Cannot create temporary archive file" ) : error;
856
857 return false;
858 }
859
860 // Use a large I/O buffer for cloud-synced folders
861 // See KIPLATFORM::IO::CLOUD_SYNC_BUFFER_SIZE
862 setvbuf( fp, nullptr, _IOFBF, KIPLATFORM::IO::CLOUD_SYNC_BUFFER_SIZE );
863
864 bool ok = false;
865
866 {
867 wxFFile file( fp );
868
869 try
870 {
871 std::unique_ptr<wxOutputStream> output = aOpen ? aOpen( file )
872 : std::make_unique<wxFFileOutputStream>( file );
873
874 if( output )
875 ok = WriteDirectoryArchive( aSourceDir, *output, aFormat, aTopDir );
876 }
877 catch( const std::exception& exception )
878 {
879 ok = false;
880 error = wxString::FromUTF8( exception.what() );
881 }
882
883 bool writeOk = ok;
884 bool streamError = file.Error();
885 FILE* raw = file.Detach();
886 bool flushed = KIPLATFORM::IO::FlushToDisk( raw );
887 bool closed = fclose( raw ) == 0;
888 ok = writeOk && !streamError && flushed && closed;
889
890 if( !writeOk || streamError )
891 {
892 if( error.IsEmpty() )
893 error = _( "Write error" );
894 }
895 else if( !flushed || !closed )
896 {
897 error = _( "Cannot flush or close temporary file" );
898 }
899 }
900
901 if( ok )
902 {
903 ok = KIPLATFORM::IO::CommitTempFile( tempPath, aArchive, &error );
904
905 if( !ok && error.IsEmpty() )
906 error = _( "Cannot replace archive" );
907 }
908
909 if( !ok && wxFileExists( tempPath ) )
910 {
911 if( !wxRemoveFile( tempPath ) )
912 error += _( "\nCannot remove temporary archive file" );
913 }
914
915 if( aError )
916 *aError = error;
917
918 return ok;
919}
920
921
922std::filesystem::path ToFsPath( const wxString& aPath )
923{
924#ifdef __WXMSW__
925 return std::filesystem::path( std::wstring( aPath.wc_str() ) );
926#else
927 return std::filesystem::path( aPath.utf8_string() );
928#endif
929}
930
931
932wxString FromFsPath( const std::filesystem::path& aPath )
933{
934#ifdef __WXMSW__
935 return wxString( aPath.wstring() );
936#else
937 std::u8string utf8 = aPath.u8string();
938 return wxString::FromUTF8( reinterpret_cast<const char*>( utf8.data() ), utf8.size() );
939#endif
940}
941
942
944{
945 if( wxDirExists( m_path ) )
946 wxFileName::Rmdir( m_path, wxPATH_RMDIR_RECURSIVE );
947 else if( wxFileExists( m_path ) )
948 wxRemoveFile( m_path );
949}
950
951
952bool SCOPED_TEMP_PATH::MakeFile( const wxString& aPrefix )
953{
954 m_path = wxFileName::CreateTempFileName( aPrefix );
955 return !m_path.IsEmpty();
956}
957
958
959bool SCOPED_TEMP_PATH::MakeDirectory( const wxString& aPrefix, int aPermissions )
960{
961 SCOPED_TEMP_PATH tempFile;
962
963 if( !tempFile.MakeFile( aPrefix ) || !wxRemoveFile( tempFile.Path() ) )
964 return false;
965
966 wxString path = tempFile.Path();
967
968 // Stop owning the name before mkdir so a collision is never removed.
969 tempFile.m_path.clear();
970
971 // Non-recursive creation rejects an existing file, directory or symlink.
972 if( !wxFileName::Mkdir( path, aPermissions, 0 ) )
973 return false;
974
975 m_path = path;
976 return true;
977}
978
979
980namespace
981{
982
983
984// Best-effort canonicalisation. Empty path means "couldn't resolve"
985// (broken symlink, ELOOP, etc.); callers treat that as "skip rather than
986// risk recursing".
987std::filesystem::path canonicalPath( const std::filesystem::path& aPath )
988{
989 std::error_code ec;
990 std::filesystem::path canon = std::filesystem::weakly_canonical( aPath, ec );
991
992 return ec ? std::filesystem::path() : canon;
993}
994
995
996// True if @p aAncestor is equal to or an ancestor of @p aDescendant. Both
997// must already be canonical so that "/a/b" and "/a/b/c" share a prefix
998// component-wise.
999bool isAncestorOrSame( const std::filesystem::path& aAncestor,
1000 const std::filesystem::path& aDescendant )
1001{
1002 auto a = aAncestor.begin();
1003 auto d = aDescendant.begin();
1004
1005 for( ; a != aAncestor.end() && d != aDescendant.end(); ++a, ++d )
1006 {
1007 if( *a != *d )
1008 return false;
1009 }
1010
1011 return a == aAncestor.end();
1012}
1013
1014
1015// records files dirs or both into aOutput loop-safe via DIR_LOOP_GUARD
1016class LOOP_SAFE_COLLECTOR : public wxDirTraverser
1017{
1018public:
1019 LOOP_SAFE_COLLECTOR( wxArrayString& aOutput, const wxString& aRoot, bool aCollectFiles,
1020 bool aCollectDirs ) :
1021 m_output( aOutput ),
1022 m_guard( aRoot, DIR_LOOP_POLICY::BLOCK_ROOT_ESCAPE ),
1023 m_collectFiles( aCollectFiles ),
1024 m_collectDirs( aCollectDirs )
1025 {
1026 }
1027
1028 wxDirTraverseResult OnFile( const wxString& aFilename ) override
1029 {
1030 if( m_collectFiles )
1031 m_output.Add( aFilename );
1032
1033 return wxDIR_CONTINUE;
1034 }
1035
1036 wxDirTraverseResult OnDir( const wxString& aDirname ) override
1037 {
1038 if( !m_guard.ShouldDescend( aDirname ) )
1039 return wxDIR_IGNORE;
1040
1041 if( m_collectDirs )
1042 m_output.Add( aDirname );
1043
1044 return wxDIR_CONTINUE;
1045 }
1046
1047private:
1048 wxArrayString& m_output;
1049 DIR_LOOP_GUARD m_guard;
1050 bool m_collectFiles;
1051 bool m_collectDirs;
1052};
1053
1054
1055void traverseLoopSafe( const wxString& aRoot, wxArrayString& aOutput, bool aCollectFiles,
1056 bool aCollectDirs, const wxString& aFileSpec, int aFlags )
1057{
1058 wxDir dir( aRoot );
1059
1060 if( !dir.IsOpened() )
1061 return;
1062
1063 LOOP_SAFE_COLLECTOR collector( aOutput, aRoot, aCollectFiles, aCollectDirs );
1064 dir.Traverse( collector, aFileSpec, aFlags );
1065}
1066
1067} // namespace
1068
1069
1070DIR_LOOP_GUARD::DIR_LOOP_GUARD( const wxString& aRoot, DIR_LOOP_POLICY aPolicy ) :
1071 m_root( canonicalPath( ToFsPath( aRoot ) ) ),
1072 m_policy( aPolicy )
1073{
1074 m_visited.reserve( 256 );
1075
1076 if( !m_root.empty() )
1077 m_visited.insert( m_root.generic_string() );
1078}
1079
1080
1081bool DIR_LOOP_GUARD::ShouldDescend( const wxString& aDir )
1082{
1083 const std::filesystem::path raw = ToFsPath( aDir );
1084 std::filesystem::path key;
1085
1087 {
1088 // confine fears any resolution outside the subtree so resolve every candidate
1089 // a symlinked ancestor could otherwise smuggle an ordinary looking child out
1090 const std::filesystem::path canon = canonicalPath( raw );
1091
1092 if( canon.empty() )
1093 return false;
1094
1095 if( !m_root.empty() && !isAncestorOrSame( m_root, canon ) )
1096 return false;
1097
1098 key = canon;
1099 }
1100 else
1101 {
1102 // escape only fears an upward link a real subdir cant be an ancestor of root
1103 // so skip the per-component resolve and canonicalize actual links only
1104 std::error_code ec;
1105 const bool isLink = std::filesystem::is_symlink( raw, ec );
1106
1107 if( isLink && !ec )
1108 {
1109 const std::filesystem::path canon = canonicalPath( raw );
1110
1111 if( canon.empty() )
1112 return false;
1113
1114 if( !m_root.empty() && isAncestorOrSame( canon, m_root ) )
1115 return false;
1116
1117 key = canon;
1118 }
1119 else
1120 {
1121 key = raw.lexically_normal();
1122 }
1123 }
1124
1125 return m_visited.insert( key.generic_string() ).second;
1126}
1127
1128
1129void CollectFilesLoopSafe( const wxString& aRoot, wxArrayString& aFiles, const wxString& aFileSpec,
1130 int aFlags )
1131{
1132 // Force wxDIR_FILES so files are reported and wxDIR_DIRS so Traverse descends
1133 // into subdirectories; the collector keeps directories out of the file list
1134 // and breaks loops. aFlags carries the caller's wxDIR_HIDDEN choice.
1135 traverseLoopSafe( aRoot, aFiles, true, false, aFileSpec, aFlags | wxDIR_FILES | wxDIR_DIRS );
1136}
1137
1138
1139void CollectSubdirsLoopSafe( const wxString& aRoot, wxArrayString& aDirs, int aFlags )
1140{
1141 traverseLoopSafe( aRoot, aDirs, false, true, wxEmptyString, aFlags | wxDIR_DIRS );
1142}
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:272
std::unordered_set< std::string > m_visited
Definition gestfich.h:273
bool IsRooted() const
Definition gestfich.h:266
DIR_LOOP_GUARD(const wxString &aRoot, DIR_LOOP_POLICY aPolicy=DIR_LOOP_POLICY::CONFINE_TO_ROOT)
std::filesystem::path m_root
Definition gestfich.h:271
int PRINTF_FUNC_N Print(int nestLevel, const char *fmt,...)
Format and write text to the output stream.
Definition richio.cpp:432
static bool RunningFromBuildDir()
True when KiCad runs from its build tree or KICAD_RUN_FROM_BUILD_DIR is set.
Definition paths.cpp:698
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:894
virtual const wxString & GetPdfBrowserName() const
Definition pgm_base.h:176
virtual const wxString & GetExecutablePath() const
Definition pgm_base.cpp:888
bool Finish() override
Runs prettification over the buffered bytes, writes them to the sibling temp file,...
Definition richio.cpp:710
bool MakeDirectory(const wxString &aPrefix, int aPermissions=wxS_DIR_DEFAULT)
Create an empty directory with a unique name after aPrefix.
Definition gestfich.cpp:959
bool MakeFile(const wxString &aPrefix)
Create an empty file with a unique name after aPrefix.
Definition gestfich.cpp:952
SCOPED_TEMP_PATH()=default
wxString m_path
Definition gestfich.h:79
const wxString & Path() const
Definition gestfich.h:76
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:932
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:806
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:68
wxString QuoteFullPath(wxFileName &fn, wxPathFormat format)
Quote return value of wxFileName::GetFullPath().
Definition gestfich.cpp:451
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:388
bool OpenPDF(const wxString &file)
Run the PDF viewer and display a PDF file.
Definition gestfich.cpp:325
void KiCopyFile(const wxString &aSrcPath, const wxString &aDestPath, wxString &aErrors)
Definition gestfich.cpp:361
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:457
void QuoteString(wxString &string)
Add un " to the start and the end of string (if not already done).
Definition gestfich.cpp:58
bool CopyFilesOrDirectory(const wxString &aSourcePath, const wxString &aDestDir, bool aAllowOverwrites, wxString &aErrors, std::vector< wxString > &aPathsWritten)
Definition gestfich.cpp:559
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:922
static void traverseSEXPR(SEXPR::SEXPR *aNode, const std::function< void(SEXPR::SEXPR *)> &aVisitor)
Definition gestfich.cpp:376
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:505
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:682
ARCHIVE_FORMAT
Definition gestfich.h:50
std::function< std::unique_ptr< wxOutputStream >(wxFFile &)> ARCHIVE_STREAM_FACTORY
Definition gestfich.h:82
KICOMMON_API std::filesystem::path ToFsPath(const wxString &aPath)
Give aPath to std::filesystem without a detour through the C locale.
Definition gestfich.cpp:922
DIR_LOOP_POLICY
Definition gestfich.h:251
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