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
24
25#ifndef INCLUDE__LOCK_FILE_H_
26#define INCLUDE__LOCK_FILE_H_
27
28#include <wx/wx.h>
29#include <wx/filefn.h>
30#include <wx/log.h>
31#include <wx/filename.h>
32#include <json_common.h>
34#include <kiplatform/io.h>
36
37#include <cstdint>
38#include <random>
39#include <string>
40
48static const wxChar* traceLockFile = wxT( "KICAD_LOCKING" );
49
60{
61public:
62 LOCKFILE( const wxString &filename, bool aRemoveOnRelease = true ) :
63 m_removeOnRelease( aRemoveOnRelease )
64 {
65 if( filename.IsEmpty() )
66 return;
67
68 wxLogTrace( traceLockFile, "Trying to lock %s", filename );
69 m_lockFilename = LockPathFor( filename );
70
71 if( !wxFileName( m_lockFilename ).IsDirWritable() )
72 {
73 wxLogTrace( traceLockFile, "File is not writable: %s", filename );
74 m_status = true;
75 m_removeOnRelease = false;
76 return;
77 }
78
79 bool created = false;
80 KIPLATFORM::IO::FILE_LOCK::STATE state = m_lock.Acquire( m_lockFilename, created );
81
82 if( !m_lock.IsOpen() )
83 {
84 wxLogTrace( traceLockFile, "Could not open a lock file for %s", filename );
85 return;
86 }
87
88 // Creating the file does not make the lock ours, since another process can take the lock
89 // between our create and our own attempt. Exclusive creation decides only where the
90 // filesystem cannot lock at all.
91 if( created && state != KIPLATFORM::IO::FILE_LOCK::STATE::BUSY )
92 {
93 claim();
94 wxLogTrace( traceLockFile, "Locked %s", filename );
95 return;
96 }
97
98 readOwner();
99
100 // Whoever wrote this lock holds it until their process dies, so a lock we can take is
101 // one nobody is using.
104 {
105 claim();
106 wxLogTrace( traceLockFile, "Reclaimed the abandoned lock on %s", filename );
107 return;
108 }
109
110 wxLogTrace( traceLockFile, "Existing Lock for %s", filename );
111 }
112
119 static LOCKFILE Inspect( const wxString& aFilename )
120 {
121 LOCKFILE probe;
122 bool heldByAnother = false;
123
124 if( !aFilename.IsEmpty() )
125 {
126 probe.m_lockFilename = LockPathFor( aFilename );
127
128 if( probe.m_lock.OpenForInspect( probe.m_lockFilename, heldByAnother ) )
129 probe.readOwner();
130 }
131
132 probe.m_status = !heldByAnother;
133
134 return probe;
135 }
136
140 static wxString LockPathFor( const wxString& aFilename )
141 {
142 wxFileName fn( aFilename );
143 fn.SetName( FILEEXT::LockFilePrefix + fn.GetName() );
144 fn.SetExt( fn.GetExt() + '.' + FILEEXT::LockFileExtension );
145
146 return fn.GetFullPath();
147 }
148
149 LOCKFILE( LOCKFILE&& other ) noexcept :
150 m_lockFilename( std::move( other.m_lockFilename ) ),
151 m_username( std::move( other.m_username ) ),
152 m_hostname( std::move( other.m_hostname ) ),
153 m_token( std::move( other.m_token ) ),
154 m_lock( std::move( other.m_lock ) ),
155 m_owned( other.m_owned ),
156 m_status( other.m_status ),
157 m_removeOnRelease( other.m_removeOnRelease )
158 {
159 // Disable unlock in the moved-from object
160 other.m_owned = false;
161 }
162
164 {
165 UnlockFile();
166 }
167
172 {
173 wxLogTrace( traceLockFile, "Unlocking %s", m_lockFilename );
174
175 // Remove the file before dropping the lock, so that the window in which another
176 // process could adopt a lock we are abandoning never opens
177 if( m_owned && stillOwnLock() )
178 {
180 wxRemoveFile( m_lockFilename );
181
182 m_owned = false;
183 m_status = false;
184 }
185
186 m_lock.Release();
187 }
188
194 bool OverrideLock( bool aRemoveOnRelease = true )
195 {
196 wxLogTrace( traceLockFile, "Overriding lock on %s", m_lockFilename );
197
198 if( !m_owned && m_lock.IsOpen() )
199 claim();
200
201 m_removeOnRelease = aRemoveOnRelease;
202
203 return m_status;
204 }
205
207 {
208 // Empty owner means the lock file could not be parsed (e.g. partial cloud sync).
209 // We cannot prove another user owns it, so treat it as reclaimable.
210 if( m_username.IsEmpty() && m_hostname.IsEmpty() )
211 return true;
212
213 return m_username == wxGetUserId() && m_hostname == wxGetHostName();
214 }
215
220 wxString GetUsername(){ return m_username; }
221
226 wxString GetHostname(){ return m_hostname; }
227
228 bool Locked() const
229 {
230 return m_owned;
231 }
232
233 bool Valid() const
234 {
235 return m_status;
236 }
237
238private:
239 // Only Inspect() builds a lock that holds nothing
240 LOCKFILE() = default;
241
243 wxString m_username;
244 wxString m_hostname;
245 wxString m_token;
247 bool m_owned = false;
248 bool m_status = false;
249 bool m_removeOnRelease = false;
250
255 static wxString newToken()
256 {
257 std::random_device rd;
258
259 uint64_t high = ( static_cast<uint64_t>( rd() ) << 32 ) | rd();
260 uint64_t low = ( static_cast<uint64_t>( rd() ) << 32 ) | rd();
261
262 return wxString::Format( wxS( "%016llx%016llx" ),
263 static_cast<unsigned long long>( high ),
264 static_cast<unsigned long long>( low ) );
265 }
266
267 void claim()
268 {
269 m_username = wxGetUserId();
270 m_hostname = wxGetHostName();
271 m_token = newToken();
272
273 nlohmann::json j;
274 j["username"] = std::string( m_username.mb_str() );
275 j["hostname"] = std::string( m_hostname.mb_str() );
276 j["token"] = std::string( m_token.mb_str() );
277
278 if( m_lock.Rewrite( j.dump() ) )
279 {
280 m_owned = true;
281 m_status = true;
282 }
283 else
284 {
285 wxLogTrace( traceLockFile, "Could not write the lock record for %s", m_lockFilename );
286 }
287 }
288
294 bool readRecord( nlohmann::json& aRecord ) const
295 {
296 std::string contents;
297
298 if( !m_lock.ReadAll( contents ) )
299 return false;
300
301 aRecord = nlohmann::json::parse( contents, nullptr, false );
302
303 if( aRecord.is_discarded() )
304 {
305 wxLogTrace( traceLockFile, "Unreadable lock contents for %s", m_lockFilename );
306 return false;
307 }
308
309 return true;
310 }
311
313 {
314 nlohmann::json record;
315
316 if( readRecord( record ) )
317 {
318 m_username = wxString( record.value( "username", std::string() ) );
319 m_hostname = wxString( record.value( "hostname", std::string() ) );
320 m_token = wxString( record.value( "token", std::string() ) );
321 }
322 else
323 {
324 m_username = wxEmptyString;
325 m_hostname = wxEmptyString;
326 m_token = wxEmptyString;
327 }
328 }
329
335 {
336 nlohmann::json record;
337
338 if( m_token.IsEmpty() || !readRecord( record ) )
339 return false;
340
341 if( m_token == wxString( record.value( "token", std::string() ) ) )
342 return true;
343
344 wxLogTrace( traceLockFile, "Lock on %s is no longer ours", m_lockFilename );
345
346 return false;
347 }
348};
349
350
351#endif // INCLUDE__LOCK_FILE_H_
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:334
bool m_removeOnRelease
Definition lockfile.h:249
LOCKFILE(const wxString &filename, bool aRemoveOnRelease=true)
Definition lockfile.h:62
static wxString LockPathFor(const wxString &aFilename)
Definition lockfile.h:140
LOCKFILE(LOCKFILE &&other) noexcept
Definition lockfile.h:149
KIPLATFORM::IO::FILE_LOCK m_lock
Definition lockfile.h:246
LOCKFILE()=default
bool m_status
Definition lockfile.h:248
wxString m_lockFilename
Definition lockfile.h:242
bool OverrideLock(bool aRemoveOnRelease=true)
Force the lock, overwriting the data that existed already.
Definition lockfile.h:194
bool Valid() const
Definition lockfile.h:233
void readOwner()
Definition lockfile.h:312
~LOCKFILE()
Definition lockfile.h:163
bool m_owned
Definition lockfile.h:247
wxString m_token
Definition lockfile.h:245
wxString m_hostname
Definition lockfile.h:244
wxString GetUsername()
Definition lockfile.h:220
wxString GetHostname()
Definition lockfile.h:226
void claim()
Definition lockfile.h:267
bool Locked() const
Definition lockfile.h:228
wxString m_username
Definition lockfile.h:243
bool IsLockedByMe()
Definition lockfile.h:206
static wxString newToken()
Definition lockfile.h:255
bool readRecord(nlohmann::json &aRecord) const
Definition lockfile.h:294
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:119
void UnlockFile()
Unlock and remove the file from the filesystem as long as we still own it.
Definition lockfile.h:171
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:48
bool IsRemovablePath(const wxString &aPath)
Definition of file extensions used in Kicad.