23#include <wx/filename.h>
51 static std::atomic<unsigned> s_counter{ 0 };
53 unsigned counter = s_counter.fetch_add( 1, std::memory_order_relaxed );
56 unsigned pid =
static_cast<unsigned>( _getpid() );
58 unsigned pid =
static_cast<unsigned>( getpid() );
61 return aTargetPath + wxString::Format( wxT(
".kicad-save-%u-%u" ), pid, counter );
68 wxString* aTempPathOut, wxString* aError )
72 const mode_t createMode = wxFileName::FileExists( aTargetPath ) ? 0600 : 0666;
76 for(
unsigned attempt = 0; attempt < 32; ++attempt )
79 int fd = open( candidate.fn_str(), O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, createMode );
83 FILE* fp = fdopen( fd, aMode.mb_str() );
89 unlink( candidate.fn_str() );
93 *aError = wxString::Format( wxT(
"fdopen failed for temp file '%s': %s" ),
94 candidate, wxString::FromUTF8( strerror( err ) ) );
101 *aTempPathOut = candidate;
106 if( errno != EEXIST )
110 *aError = wxString::Format( wxT(
"Cannot create temp file '%s': %s" ), candidate,
111 wxString::FromUTF8( strerror( errno ) ) );
119 *aError = wxT(
"Exhausted temp-file retry budget" );
132 if( lstat( aPath.fn_str(), &st ) != 0 || !S_ISLNK( st.st_mode ) )
135 char resolved[PATH_MAX];
137 if( realpath( aPath.fn_str(), resolved ) )
138 return wxString::FromUTF8( resolved );
146 int fd = open( aDirPath.fn_str(), O_RDONLY
147#
if defined( O_DIRECTORY )
156 return errno == EINVAL || errno == ENOTSUP;
159 int rc = fsync( fd );
163 return rc == 0 || err == EINVAL;
169 if( rename( aSrc.fn_str(), aDst.fn_str() ) == 0 )
173 *aError = wxString::FromUTF8( strerror( errno ) );
185 const bool targetExists = wxFileName::FileExists( aTargetPath );
201 *aError = wxString::Format( wxT(
"Cannot copy permissions from '%s' to '%s'" ),
202 aTargetPath, aTempPath );
217 wxString renameError;
218 const bool renamed =
AtomicRename( aTempPath, aTargetPath, &renameError );
231 wxLogWarning( wxT(
"Could not restore file attributes on '%s' after save" ),
240 *aError = wxString::Format( wxT(
"Cannot rename temp file over '%s': %s" ),
241 aTargetPath, renameError );
247 wxFileName dst( aTargetPath );
248 wxString dirPath = dst.GetPath();
252 if( dirPath.IsEmpty() )
253 dirPath = wxT(
"." );
260 *aError = wxString::Format( wxT(
"Cannot flush directory '%s' to disk" ), dirPath );
279 if( aSize > 0 && std::fwrite( aData, 1, aSize, fp ) != aSize )
282 *aError = wxString::Format( wxT(
"Write failed to '%s'" ), tempPath );
285 wxRemoveFile( tempPath );
292 *aError = wxString::Format( wxT(
"fsync failed on '%s'" ), tempPath );
295 wxRemoveFile( tempPath );
301 if( std::fclose( fp ) != 0 )
307 *aError = wxString::Format( wxT(
"Cannot close temp file '%s': %s" ), tempPath,
308 wxString::FromUTF8( strerror( err ) ) );
311 wxRemoveFile( tempPath );
320 wxRemoveFile( tempPath );
333 FILE* fp = wxFopen( aFileName, wxS(
"rb" ) );
336 throw std::runtime_error( std::string(
"Cannot open file: " ) + aFileName.ToStdString() );
338 fseek( fp, 0, SEEK_END );
339 long len = ftell( fp );
344 throw std::runtime_error( std::string(
"Cannot determine file size: " )
345 + aFileName.ToStdString() );
349 fseek( fp, 0, SEEK_SET );
351 size_t bytesRead = fread(
m_fallbackBuffer.data(), 1,
static_cast<size_t>( len ), fp );
354 if( bytesRead !=
static_cast<size_t>( len ) )
356 throw std::runtime_error( std::string(
"Failed to read file: " )
357 + aFileName.ToStdString() );
365#if !defined( _WIN32 )
373 int fd = open( aPath.fn_str(), O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC, 0666 );
378 fd = open( aPath.fn_str(), O_RDWR | O_CLOEXEC );
383 m_fd = open( aPath.fn_str(), O_RDONLY | O_CLOEXEC );
393 if( flock( fd, LOCK_EX | LOCK_NB ) == 0 )
395 else if( errno == EWOULDBLOCK )
408 aHeldByAnother =
false;
410 m_fd = open( aPath.fn_str(), O_RDONLY | O_CLOEXEC );
416 if( flock(
m_fd, LOCK_EX | LOCK_NB ) == 0 )
417 flock(
m_fd, LOCK_UN );
418 else if( errno == EWOULDBLOCK )
419 aHeldByAnother =
true;
433 if( !
IsOpen() || lseek(
m_fd, 0, SEEK_SET ) < 0 )
441 while( ( bytes = read(
m_fd, buffer,
sizeof( buffer ) ) ) > 0 )
442 aContents.append( buffer,
static_cast<size_t>( bytes ) );
450 if( !
IsOpen() || ftruncate(
m_fd, 0 ) < 0 || lseek(
m_fd, 0, SEEK_SET ) < 0 )
455 while( written < aContents.size() )
457 ssize_t bytes = write(
m_fd, aContents.data() + written, aContents.size() - written );
462 written +=
static_cast<size_t>( bytes );
493 *
this = std::move( aOther );
499 if(
this == &aOther )
505 m_handle = aOther.m_handle;
506 aOther.m_handle =
nullptr;