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