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 );
198 wxLogTrace( wxT(
"KICAD_ATOMIC_SAVE" ), wxT(
"Cannot copy permissions from '%s' to '%s'" ),
199 aTargetPath, aTempPath );
211 wxString renameError;
212 const bool renamed =
AtomicRename( aTempPath, aTargetPath, &renameError );
225 wxLogWarning( wxT(
"Could not restore file attributes on '%s' after save" ),
234 *aError = wxString::Format( wxT(
"Cannot rename temp file over '%s': %s" ),
235 aTargetPath, renameError );
241 wxFileName dst( aTargetPath );
242 wxString dirPath = dst.GetPath();
246 if( dirPath.IsEmpty() )
247 dirPath = wxT(
"." );
254 *aError = wxString::Format( wxT(
"Cannot flush directory '%s' to disk" ), dirPath );
273 if( aSize > 0 && std::fwrite( aData, 1, aSize, fp ) != aSize )
276 *aError = wxString::Format( wxT(
"Write failed to '%s'" ), tempPath );
279 wxRemoveFile( tempPath );
286 *aError = wxString::Format( wxT(
"fsync failed on '%s'" ), tempPath );
289 wxRemoveFile( tempPath );
295 if( std::fclose( fp ) != 0 )
301 *aError = wxString::Format( wxT(
"Cannot close temp file '%s': %s" ), tempPath,
302 wxString::FromUTF8( strerror( err ) ) );
305 wxRemoveFile( tempPath );
314 wxRemoveFile( tempPath );
327 FILE* fp = wxFopen( aFileName, wxS(
"rb" ) );
330 throw std::runtime_error( std::string(
"Cannot open file: " ) + aFileName.ToStdString() );
332 fseek( fp, 0, SEEK_END );
333 long len = ftell( fp );
338 throw std::runtime_error( std::string(
"Cannot determine file size: " )
339 + aFileName.ToStdString() );
343 fseek( fp, 0, SEEK_SET );
345 size_t bytesRead = fread(
m_fallbackBuffer.data(), 1,
static_cast<size_t>( len ), fp );
348 if( bytesRead !=
static_cast<size_t>( len ) )
350 throw std::runtime_error( std::string(
"Failed to read file: " )
351 + aFileName.ToStdString() );
359#if !defined( _WIN32 )
367 int fd = open( aPath.fn_str(), O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC, 0666 );
372 fd = open( aPath.fn_str(), O_RDWR | O_CLOEXEC );
377 m_fd = open( aPath.fn_str(), O_RDONLY | O_CLOEXEC );
387 if( flock( fd, LOCK_EX | LOCK_NB ) == 0 )
389 else if( errno == EWOULDBLOCK )
402 aHeldByAnother =
false;
404 m_fd = open( aPath.fn_str(), O_RDONLY | O_CLOEXEC );
410 if( flock(
m_fd, LOCK_EX | LOCK_NB ) == 0 )
411 flock(
m_fd, LOCK_UN );
412 else if( errno == EWOULDBLOCK )
413 aHeldByAnother =
true;
427 if( !
IsOpen() || lseek(
m_fd, 0, SEEK_SET ) < 0 )
435 while( (
bytes = read(
m_fd, buffer,
sizeof( buffer ) ) ) > 0 )
436 aContents.append( buffer,
static_cast<size_t>(
bytes ) );
444 if( !
IsOpen() || ftruncate(
m_fd, 0 ) < 0 || lseek(
m_fd, 0, SEEK_SET ) < 0 )
449 while( written < aContents.size() )
451 ssize_t
bytes = write(
m_fd, aContents.data() + written, aContents.size() - written );
456 written +=
static_cast<size_t>(
bytes );
487 *
this = std::move( aOther );
493 if(
this == &aOther )
499 m_handle = aOther.m_handle;
500 aOther.m_handle =
nullptr;
static std::vector< uint8_t > bytes(const std::string &aText)