]>
Commit | Line | Data |
---|---|---|
4f3c5f06 | 1 | /////////////////////////////////////////////////////////////////////////////// |
de6185e2 | 2 | // Name: src/unix/snglinst.cpp |
4f3c5f06 VZ |
3 | // Purpose: implements wxSingleInstanceChecker class for Unix using |
4 | // lock files with fcntl(2) or flock(2) | |
5 | // Author: Vadim Zeitlin | |
6 | // Modified by: | |
7 | // Created: 09.06.01 | |
4f3c5f06 | 8 | // Copyright: (c) 2001 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr> |
526954c5 | 9 | // Licence: wxWindows licence |
4f3c5f06 VZ |
10 | /////////////////////////////////////////////////////////////////////////////// |
11 | ||
12 | // ============================================================================ | |
13 | // declarations | |
14 | // ============================================================================ | |
15 | ||
16 | // ---------------------------------------------------------------------------- | |
17 | // headers | |
18 | // ---------------------------------------------------------------------------- | |
19 | ||
4f3c5f06 VZ |
20 | // For compilers that support precompilation, includes "wx.h". |
21 | #include "wx/wxprec.h" | |
22 | ||
23 | #ifdef __BORLANDC__ | |
24 | #pragma hdrstop | |
25 | #endif | |
26 | ||
27 | #if wxUSE_SNGLINST_CHECKER | |
28 | ||
29 | #ifndef WX_PRECOMP | |
30 | #include "wx/string.h" | |
31 | #include "wx/log.h" | |
32 | #include "wx/intl.h" | |
de6185e2 | 33 | #include "wx/utils.h" // wxGetHomeDir() |
4f3c5f06 VZ |
34 | #endif //WX_PRECOMP |
35 | ||
6874238c | 36 | #include "wx/file.h" |
4f3c5f06 VZ |
37 | |
38 | #include "wx/snglinst.h" | |
39 | ||
40 | #include <unistd.h> | |
41 | #include <sys/types.h> | |
776862b4 | 42 | #include <sys/stat.h> // for S_I[RW]USR |
4f3c5f06 | 43 | #include <signal.h> // for kill() |
b5299791 | 44 | #include <errno.h> |
4f3c5f06 VZ |
45 | |
46 | #ifdef HAVE_FCNTL | |
47 | #include <fcntl.h> | |
48 | #elif defined(HAVE_FLOCK) | |
49 | #include <sys/file.h> | |
50 | #else | |
51 | // normally, wxUSE_SNGLINST_CHECKER must have been reset by configure | |
52 | #error "wxSingleInstanceChecker can't be compiled on this platform" | |
53 | #endif // fcntl()/flock() | |
54 | ||
55 | // ---------------------------------------------------------------------------- | |
b5299791 | 56 | // constants |
4f3c5f06 VZ |
57 | // ---------------------------------------------------------------------------- |
58 | ||
b5299791 | 59 | // argument of wxLockFile() |
4f3c5f06 VZ |
60 | enum LockOperation |
61 | { | |
62 | LOCK, | |
63 | UNLOCK | |
64 | }; | |
65 | ||
b5299791 VZ |
66 | // return value of CreateLockFile() |
67 | enum LockResult | |
68 | { | |
69 | LOCK_ERROR = -1, | |
70 | LOCK_EXISTS, | |
71 | LOCK_CREATED | |
72 | }; | |
73 | ||
74 | // ---------------------------------------------------------------------------- | |
75 | // private functions: (exclusively) lock/unlock the file | |
76 | // ---------------------------------------------------------------------------- | |
77 | ||
4f3c5f06 VZ |
78 | #ifdef HAVE_FCNTL |
79 | ||
80 | static int wxLockFile(int fd, LockOperation lock) | |
81 | { | |
82 | // init the flock parameter struct | |
83 | struct flock fl; | |
84 | fl.l_type = lock == LOCK ? F_WRLCK : F_UNLCK; | |
85 | ||
86 | // lock the entire file | |
4f3c5f06 | 87 | fl.l_start = |
f64cd1e6 VZ |
88 | fl.l_len = |
89 | fl.l_whence = 0; | |
4f3c5f06 VZ |
90 | |
91 | // is this needed? | |
92 | fl.l_pid = getpid(); | |
93 | ||
94 | return fcntl(fd, F_SETLK, &fl); | |
95 | } | |
96 | ||
97 | #else // HAVE_FLOCK | |
98 | ||
99 | static int wxLockFile(int fd, LockOperation lock) | |
100 | { | |
101 | return flock(fd, lock == LOCK ? LOCK_EX | LOCK_NB : LOCK_UN); | |
102 | } | |
103 | ||
104 | #endif // fcntl()/flock() | |
105 | ||
106 | // ---------------------------------------------------------------------------- | |
107 | // wxSingleInstanceCheckerImpl: the real implementation class | |
108 | // ---------------------------------------------------------------------------- | |
109 | ||
110 | class wxSingleInstanceCheckerImpl | |
111 | { | |
112 | public: | |
113 | wxSingleInstanceCheckerImpl() | |
114 | { | |
115 | m_fdLock = -1; | |
116 | m_pidLocker = 0; | |
117 | } | |
118 | ||
119 | bool Create(const wxString& name); | |
120 | ||
121 | pid_t GetLockerPID() const { return m_pidLocker; } | |
122 | ||
123 | ~wxSingleInstanceCheckerImpl() { Unlock(); } | |
124 | ||
125 | private: | |
126 | // try to create and lock the file | |
b5299791 | 127 | LockResult CreateLockFile(); |
4f3c5f06 VZ |
128 | |
129 | // unlock and remove the lock file | |
130 | void Unlock(); | |
131 | ||
132 | // the descriptor of our lock file, -1 if none | |
133 | int m_fdLock; | |
134 | ||
135 | // pid of the process owning the lock file | |
136 | pid_t m_pidLocker; | |
137 | ||
138 | // the name of the lock file | |
139 | wxString m_nameLock; | |
140 | }; | |
141 | ||
142 | // ============================================================================ | |
143 | // wxSingleInstanceCheckerImpl implementation | |
144 | // ============================================================================ | |
145 | ||
b5299791 | 146 | LockResult wxSingleInstanceCheckerImpl::CreateLockFile() |
4f3c5f06 VZ |
147 | { |
148 | // try to open the file | |
401eb3de | 149 | m_fdLock = open(m_nameLock.fn_str(), |
4f3c5f06 | 150 | O_WRONLY | O_CREAT | O_EXCL, |
776862b4 | 151 | S_IRUSR | S_IWUSR); |
4f3c5f06 VZ |
152 | |
153 | if ( m_fdLock != -1 ) | |
154 | { | |
155 | // try to lock it | |
f4d7250e | 156 | if ( wxLockFile(m_fdLock, LOCK) == 0 ) |
4f3c5f06 VZ |
157 | { |
158 | // fine, we have the exclusive lock to the file, write our PID | |
159 | // into it | |
160 | m_pidLocker = getpid(); | |
161 | ||
162 | // use char here, not wxChar! | |
163 | char buf[256]; // enough for any PID size | |
cd0401c8 | 164 | int len = sprintf(buf, "%d", (int)m_pidLocker) + 1; |
4f3c5f06 VZ |
165 | |
166 | if ( write(m_fdLock, buf, len) != len ) | |
167 | { | |
168 | wxLogSysError(_("Failed to write to lock file '%s'"), | |
169 | m_nameLock.c_str()); | |
170 | ||
171 | Unlock(); | |
172 | ||
b5299791 | 173 | return LOCK_ERROR; |
4f3c5f06 VZ |
174 | } |
175 | ||
176 | fsync(m_fdLock); | |
177 | ||
3c5487b1 VS |
178 | // change file's permission so that only this user can access it: |
179 | if ( chmod(m_nameLock.fn_str(), S_IRUSR | S_IWUSR) != 0 ) | |
180 | { | |
181 | wxLogSysError(_("Failed to set permissions on lock file '%s'"), | |
182 | m_nameLock.c_str()); | |
183 | ||
184 | Unlock(); | |
de6185e2 | 185 | |
3c5487b1 VS |
186 | return LOCK_ERROR; |
187 | } | |
188 | ||
b5299791 | 189 | return LOCK_CREATED; |
4f3c5f06 | 190 | } |
b5299791 VZ |
191 | else // failure: see what exactly happened |
192 | { | |
193 | close(m_fdLock); | |
194 | m_fdLock = -1; | |
4f3c5f06 | 195 | |
f4d7250e | 196 | if ( errno != EACCES && errno != EAGAIN ) |
b5299791 VZ |
197 | { |
198 | wxLogSysError(_("Failed to lock the lock file '%s'"), | |
199 | m_nameLock.c_str()); | |
200 | ||
401eb3de | 201 | unlink(m_nameLock.fn_str()); |
b5299791 VZ |
202 | |
203 | return LOCK_ERROR; | |
204 | } | |
205 | //else: couldn't lock because the lock is held by another process: | |
206 | // this might have happened because of a race condition: | |
207 | // maybe another instance opened and locked the file between | |
208 | // our calls to open() and flock(), so don't give an error | |
209 | } | |
4f3c5f06 VZ |
210 | } |
211 | ||
212 | // we didn't create and lock the file | |
b5299791 | 213 | return LOCK_EXISTS; |
4f3c5f06 VZ |
214 | } |
215 | ||
216 | bool wxSingleInstanceCheckerImpl::Create(const wxString& name) | |
217 | { | |
218 | m_nameLock = name; | |
219 | ||
b5299791 | 220 | switch ( CreateLockFile() ) |
4f3c5f06 | 221 | { |
b5299791 VZ |
222 | case LOCK_EXISTS: |
223 | // there is a lock file, check below if it is still valid | |
224 | break; | |
225 | ||
226 | case LOCK_CREATED: | |
227 | // nothing more to do | |
de6185e2 | 228 | return true; |
b5299791 VZ |
229 | |
230 | case LOCK_ERROR: | |
231 | // oops... | |
de6185e2 | 232 | return false; |
4f3c5f06 VZ |
233 | } |
234 | ||
3c5487b1 VS |
235 | // Check if the file is owned by current user and has 0600 permissions. |
236 | // If it doesn't, it's a fake file, possibly meant as a DoS attack, and | |
237 | // so we refuse to touch it: | |
9c4fc03f VS |
238 | wxStructStat stats; |
239 | if ( wxStat(name, &stats) != 0 ) | |
3c5487b1 VS |
240 | { |
241 | wxLogSysError(_("Failed to inspect the lock file '%s'"), name.c_str()); | |
242 | return false; | |
243 | } | |
9c4fc03f | 244 | if ( stats.st_uid != getuid() ) |
3c5487b1 VS |
245 | { |
246 | wxLogError(_("Lock file '%s' has incorrect owner."), name.c_str()); | |
247 | return false; | |
248 | } | |
9c4fc03f | 249 | if ( stats.st_mode != (S_IFREG | S_IRUSR | S_IWUSR) ) |
3c5487b1 VS |
250 | { |
251 | wxLogError(_("Lock file '%s' has incorrect permissions."), name.c_str()); | |
252 | return false; | |
253 | } | |
254 | ||
4f3c5f06 VZ |
255 | // try to open the file for reading and get the PID of the process |
256 | // which has it | |
257 | wxFile file(name, wxFile::read); | |
258 | if ( !file.IsOpened() ) | |
259 | { | |
260 | // well, this is really weird - file doesn't exist and we can't | |
261 | // create it | |
262 | // | |
263 | // normally, this just means that we don't have write access to | |
264 | // the directory where we try to create it, so return failure, | |
265 | // even it might also be a rare case of a race condition when | |
266 | // another process managed to open and lock the file and terminate | |
267 | // (erasing it) before we got here, but this should happen so | |
268 | // rarely in practice that we don't care | |
269 | wxLogError(_("Failed to access lock file.")); | |
270 | ||
de6185e2 | 271 | return false; |
4f3c5f06 VZ |
272 | } |
273 | ||
274 | char buf[256]; | |
f8a586e0 RL |
275 | ssize_t count = file.Read(buf, WXSIZEOF(buf)); |
276 | if ( count == wxInvalidOffset ) | |
4f3c5f06 VZ |
277 | { |
278 | wxLogError(_("Failed to read PID from lock file.")); | |
279 | } | |
280 | else | |
281 | { | |
cd0401c8 | 282 | if ( sscanf(buf, "%d", (int *)&m_pidLocker) == 1 ) |
4f3c5f06 VZ |
283 | { |
284 | if ( kill(m_pidLocker, 0) != 0 ) | |
285 | { | |
401eb3de | 286 | if ( unlink(name.fn_str()) != 0 ) |
4f3c5f06 VZ |
287 | { |
288 | wxLogError(_("Failed to remove stale lock file '%s'."), | |
289 | name.c_str()); | |
290 | ||
de6185e2 | 291 | // return true in this case for now... |
4f3c5f06 VZ |
292 | } |
293 | else | |
294 | { | |
295 | wxLogMessage(_("Deleted stale lock file '%s'."), | |
296 | name.c_str()); | |
297 | ||
298 | // retry now | |
299 | (void)CreateLockFile(); | |
300 | } | |
301 | } | |
302 | //else: the other process is running | |
303 | } | |
304 | else | |
305 | { | |
b5299791 | 306 | wxLogWarning(_("Invalid lock file '%s'."), name.c_str()); |
4f3c5f06 VZ |
307 | } |
308 | } | |
309 | ||
de6185e2 | 310 | // return true if we could get the PID of the process owning the lock file |
4f3c5f06 VZ |
311 | // (whether it is still running or not), FALSE otherwise as it is |
312 | // unexpected | |
313 | return m_pidLocker != 0; | |
314 | } | |
315 | ||
316 | void wxSingleInstanceCheckerImpl::Unlock() | |
317 | { | |
318 | if ( m_fdLock != -1 ) | |
319 | { | |
401eb3de | 320 | if ( unlink(m_nameLock.fn_str()) != 0 ) |
4f3c5f06 VZ |
321 | { |
322 | wxLogSysError(_("Failed to remove lock file '%s'"), | |
323 | m_nameLock.c_str()); | |
324 | } | |
325 | ||
326 | if ( wxLockFile(m_fdLock, UNLOCK) != 0 ) | |
327 | { | |
328 | wxLogSysError(_("Failed to unlock lock file '%s'"), | |
329 | m_nameLock.c_str()); | |
330 | } | |
331 | ||
332 | if ( close(m_fdLock) != 0 ) | |
333 | { | |
334 | wxLogSysError(_("Failed to close lock file '%s'"), | |
335 | m_nameLock.c_str()); | |
336 | } | |
337 | } | |
338 | ||
339 | m_pidLocker = 0; | |
340 | } | |
341 | ||
342 | // ============================================================================ | |
343 | // wxSingleInstanceChecker implementation | |
344 | // ============================================================================ | |
345 | ||
346 | bool wxSingleInstanceChecker::Create(const wxString& name, | |
347 | const wxString& path) | |
348 | { | |
349 | wxASSERT_MSG( !m_impl, | |
9a83f860 | 350 | wxT("calling wxSingleInstanceChecker::Create() twice?") ); |
4f3c5f06 VZ |
351 | |
352 | // must have the file name to create a lock file | |
9a83f860 | 353 | wxASSERT_MSG( !name.empty(), wxT("lock file name can't be empty") ); |
4f3c5f06 VZ |
354 | |
355 | m_impl = new wxSingleInstanceCheckerImpl; | |
356 | ||
357 | wxString fullname = path; | |
358 | if ( fullname.empty() ) | |
359 | { | |
f4d7250e VZ |
360 | fullname = wxGetHomeDir(); |
361 | } | |
362 | ||
9a83f860 | 363 | if ( fullname.Last() != wxT('/') ) |
f4d7250e | 364 | { |
9a83f860 | 365 | fullname += wxT('/'); |
4f3c5f06 VZ |
366 | } |
367 | ||
368 | fullname << name; | |
369 | ||
370 | return m_impl->Create(fullname); | |
371 | } | |
372 | ||
956b3d92 | 373 | bool wxSingleInstanceChecker::DoIsAnotherRunning() const |
4f3c5f06 | 374 | { |
9a83f860 | 375 | wxCHECK_MSG( m_impl, false, wxT("must call Create() first") ); |
4f3c5f06 | 376 | |
d86901f9 VZ |
377 | const pid_t lockerPid = m_impl->GetLockerPID(); |
378 | ||
379 | if ( !lockerPid ) | |
380 | { | |
381 | // we failed to open the lock file, return false as we're definitely | |
382 | // not sure that another our process is running and so it's better not | |
383 | // to prevent this one from starting up | |
384 | return false; | |
385 | } | |
386 | ||
4f3c5f06 VZ |
387 | // if another instance is running, it must own the lock file - otherwise |
388 | // we have it and the locker PID is ours one | |
d86901f9 | 389 | return lockerPid != getpid(); |
4f3c5f06 VZ |
390 | } |
391 | ||
392 | wxSingleInstanceChecker::~wxSingleInstanceChecker() | |
393 | { | |
394 | delete m_impl; | |
395 | } | |
396 | ||
397 | #endif // wxUSE_SNGLINST_CHECKER |