KiCad PCB EDA Suite
Loading...
Searching...
No Matches
lockfile.h
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#pragma once
21
22#include <wx/wx.h>
23#include <wx/filefn.h>
24#include <wx/log.h>
25#include <wx/filename.h>
26#include <json_common.h>
28#include <kiplatform/io.h>
30
31#include <cstdint>
32#include <random>
33#include <string>
34
42static const wxChar* traceLockFile = wxT( "KICAD_LOCKING" );
43
54{
55public:
56 LOCKFILE( const wxString &filename, bool aRemoveOnRelease = true ) :
57 m_removeOnRelease( aRemoveOnRelease )
58 {
59 if( filename.IsEmpty() )
60 return;
61
62 wxLogTrace( traceLockFile, "Trying to lock %s", filename );
63 m_lockFilename = LockPathFor( filename );
64
65 if( !wxFileName( m_lockFilename ).IsDirWritable() )
66 {
67 wxLogTrace( traceLockFile, "File is not writable: %s", filename );
68 m_status = true;
69 m_removeOnRelease = false;
70 return;
71 }
72
73 bool created = false;
74 KIPLATFORM::IO::FILE_LOCK::STATE state = m_lock.Acquire( m_lockFilename, created );
75
76 if( !m_lock.IsOpen() )
77 {
78 wxLogTrace( traceLockFile, "Could not open a lock file for %s", filename );
79 return;
80 }
81
82 // Creating the file does not make the lock ours, since another process can take the lock
83 // between our create and our own attempt. Exclusive creation decides only where the
84 // filesystem cannot lock at all.
85 if( created && state != KIPLATFORM::IO::FILE_LOCK::STATE::BUSY )
86 {
87 claim();
88 wxLogTrace( traceLockFile, "Locked %s", filename );
89 return;
90 }
91
92 readOwner();
93
94 // Whoever wrote this lock holds it until their process dies, so a lock we can take is
95 // one nobody is using.
98 {
99 claim();
100 wxLogTrace( traceLockFile, "Reclaimed the abandoned lock on %s", filename );
101 return;
102 }
103
104 wxLogTrace( traceLockFile, "Existing Lock for %s", filename );
105 }
106
113 static LOCKFILE Inspect( const wxString& aFilename )
114 {
115 LOCKFILE probe;
116 bool heldByAnother = false;
117
118 if( !aFilename.IsEmpty() )
119 {
120 probe.m_lockFilename = LockPathFor( aFilename );
121
122 if( probe.m_lock.OpenForInspect( probe.m_lockFilename, heldByAnother ) )
123 probe.readOwner();
124 }
125
126 probe.m_status = !heldByAnother;
127
128 return probe;
129 }
130
134 static wxString LockPathFor( const wxString& aFilename )
135 {
136 wxFileName fn( aFilename );
137 fn.SetName( FILEEXT::LockFilePrefix + fn.GetName() );
138 fn.SetExt( fn.GetExt() + '.' + FILEEXT::LockFileExtension );
139
140 return fn.GetFullPath();
141 }
142
143 LOCKFILE( LOCKFILE&& other ) noexcept :
144 m_lockFilename( std::move( other.m_lockFilename ) ),
145 m_username( std::move( other.m_username ) ),
146 m_hostname( std::move( other.m_hostname ) ),
147 m_token( std::move( other.m_token ) ),
148 m_lock( std::move( other.m_lock ) ),
149 m_owned( other.m_owned ),
150 m_status( other.m_status ),
151 m_removeOnRelease( other.m_removeOnRelease )
152 {
153 // Disable unlock in the moved-from object
154 other.m_owned = false;
155 }
156
158 {
159 UnlockFile();
160 }
161
166 {
167 wxLogTrace( traceLockFile, "Unlocking %s", m_lockFilename );
168
169 // Remove the file before dropping the lock, so that the window in which another
170 // process could adopt a lock we are abandoning never opens
171 if( m_owned && stillOwnLock() )
172 {
174 wxRemoveFile( m_lockFilename );
175
176 m_owned = false;
177 m_status = false;
178 }
179
180 m_lock.Release();
181 }
182
188 bool OverrideLock( bool aRemoveOnRelease = true )
189 {
190 wxLogTrace( traceLockFile, "Overriding lock on %s", m_lockFilename );
191
192 if( !m_owned && m_lock.IsOpen() )
193 claim();
194
195 m_removeOnRelease = aRemoveOnRelease;
196
197 return m_status;
198 }
199
201 {
202 // Empty owner means the lock file could not be parsed (e.g. partial cloud sync).
203 // We cannot prove another user owns it, so treat it as reclaimable.
204 if( m_username.IsEmpty() && m_hostname.IsEmpty() )
205 return true;
206
207 return m_username == wxGetUserId() && m_hostname == wxGetHostName();
208 }
209
214 wxString GetUsername() { return m_username; }
215
220 wxString GetHostname() { return m_hostname; }
221
222 bool Locked() const { return m_owned; }
223
224 bool Valid() const { return m_status; }
225
226private:
227 // Only Inspect() builds a lock that holds nothing
228 LOCKFILE() = default;
229
231 wxString m_username;
232 wxString m_hostname;
233 wxString m_token;
235 bool m_owned = false;
236 bool m_status = false;
237 bool m_removeOnRelease = false;
238
243 static wxString newToken()
244 {
245 std::random_device rd;
246
247 uint64_t high = ( static_cast<uint64_t>( rd() ) << 32 ) | rd();
248 uint64_t low = ( static_cast<uint64_t>( rd() ) << 32 ) | rd();
249
250 return wxString::Format( wxS( "%016llx%016llx" ),
251 static_cast<unsigned long long>( high ),
252 static_cast<unsigned long long>( low ) );
253 }
254
255 void claim()
256 {
257 m_username = wxGetUserId();
258 m_hostname = wxGetHostName();
259 m_token = newToken();
260
261 nlohmann::json j;
262 j["username"] = std::string( m_username.mb_str() );
263 j["hostname"] = std::string( m_hostname.mb_str() );
264 j["token"] = std::string( m_token.mb_str() );
265
266 if( m_lock.Rewrite( j.dump() ) )
267 {
268 m_owned = true;
269 m_status = true;
270 }
271 else
272 {
273 wxLogTrace( traceLockFile, "Could not write the lock record for %s", m_lockFilename );
274 }
275 }
276
282 bool readRecord( nlohmann::json& aRecord ) const
283 {
284 std::string contents;
285
286 if( !m_lock.ReadAll( contents ) )
287 return false;
288
289 aRecord = nlohmann::json::parse( contents, nullptr, false /* exceptions allowed */ );
290
291 if( aRecord.is_discarded() )
292 {
293 wxLogTrace( traceLockFile, "Unreadable lock contents for %s", m_lockFilename );
294 return false;
295 }
296
297 return true;
298 }
299
301 {
302 m_username = wxEmptyString;
303 m_hostname = wxEmptyString;
304 m_token = wxEmptyString;
305
306 try
307 {
308 nlohmann::json record;
309
310 if( readRecord( record ) )
311 {
312 m_username = wxString( record.value( "username", std::string() ) );
313 m_hostname = wxString( record.value( "hostname", std::string() ) );
314 m_token = wxString( record.value( "token", std::string() ) );
315 }
316 }
317 catch(...)
318 {
319 // best efforts
320 }
321 }
322
328 {
329 if( m_token.IsEmpty() )
330 return false;
331
332 try
333 {
334 nlohmann::json record;
335
336 if( !readRecord( record ) )
337 return false;
338
339 if( m_token == wxString( record.value( "token", std::string() ) ) )
340 return true;
341 }
342 catch(...)
343 {
344 return false;
345 }
346
347 wxLogTrace( traceLockFile, "Lock on %s is no longer ours", m_lockFilename );
348
349 return false;
350 }
351};
An exclusive advisory lock on a file, held for the lifetime of this object.
Definition io.h:91
@ BUSY
Another process holds the lock.
Definition io.h:97
@ HELD
We hold the lock.
Definition io.h:96
bool OpenForInspect(const wxString &aPath, bool &aHeldByAnother)
Open an existing file and report whether another process holds its lock, creating nothing and keeping...
bool stillOwnLock()
Definition lockfile.h:327
bool m_removeOnRelease
Definition lockfile.h:237
LOCKFILE(const wxString &filename, bool aRemoveOnRelease=true)
Definition lockfile.h:56
static wxString LockPathFor(const wxString &aFilename)
Definition lockfile.h:134
LOCKFILE(LOCKFILE &&other) noexcept
Definition lockfile.h:143
KIPLATFORM::IO::FILE_LOCK m_lock
Definition lockfile.h:234
LOCKFILE()=default
bool m_status
Definition lockfile.h:236
wxString m_lockFilename
Definition lockfile.h:230
bool OverrideLock(bool aRemoveOnRelease=true)
Force the lock, overwriting the data that existed already.
Definition lockfile.h:188
bool Valid() const
Definition lockfile.h:224
void readOwner()
Definition lockfile.h:300
~LOCKFILE()
Definition lockfile.h:157
bool m_owned
Definition lockfile.h:235
wxString m_token
Definition lockfile.h:233
wxString m_hostname
Definition lockfile.h:232
wxString GetUsername()
Definition lockfile.h:214
wxString GetHostname()
Definition lockfile.h:220
void claim()
Definition lockfile.h:255
bool Locked() const
Definition lockfile.h:222
wxString m_username
Definition lockfile.h:231
bool IsLockedByMe()
Definition lockfile.h:200
static wxString newToken()
Definition lockfile.h:243
bool readRecord(nlohmann::json &aRecord) const
Definition lockfile.h:282
static LOCKFILE Inspect(const wxString &aFilename)
Look at a lock without taking it: nothing is created, nothing is claimed and nothing is removed on re...
Definition lockfile.h:113
void UnlockFile()
Unlock and remove the file from the filesystem as long as we still own it.
Definition lockfile.h:165
static const std::string LockFileExtension
static const std::string LockFilePrefix
static const wxChar * traceLockFile
Flag to enable project lock file debug tracing.
Definition lockfile.h:42
bool IsRemovablePath(const wxString &aPath)
Definition of file extensions used in Kicad.