The rounded corners look really dumb at this size.
[wxWidgets.git] / src / unix / snglinst.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: src/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 // Copyright: (c) 2001 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
9 // Licence: wxWindows licence
10 ///////////////////////////////////////////////////////////////////////////////
11
12 // ============================================================================
13 // declarations
14 // ============================================================================
15
16 // ----------------------------------------------------------------------------
17 // headers
18 // ----------------------------------------------------------------------------
19
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"
33 #include "wx/utils.h" // wxGetHomeDir()
34 #endif //WX_PRECOMP
35
36 #include "wx/file.h"
37
38 #include "wx/snglinst.h"
39
40 #include <unistd.h>
41 #include <sys/types.h>
42 #include <sys/stat.h> // for S_I[RW]USR
43 #include <signal.h> // for kill()
44 #include <errno.h>
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 // ----------------------------------------------------------------------------
56 // constants
57 // ----------------------------------------------------------------------------
58
59 // argument of wxLockFile()
60 enum LockOperation
61 {
62 LOCK,
63 UNLOCK
64 };
65
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
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
87 fl.l_start =
88 fl.l_len =
89 fl.l_whence = 0;
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
127 LockResult CreateLockFile();
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
146 LockResult wxSingleInstanceCheckerImpl::CreateLockFile()
147 {
148 // try to open the file
149 m_fdLock = open(m_nameLock.fn_str(),
150 O_WRONLY | O_CREAT | O_EXCL,
151 S_IRUSR | S_IWUSR);
152
153 if ( m_fdLock != -1 )
154 {
155 // try to lock it
156 if ( wxLockFile(m_fdLock, LOCK) == 0 )
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
164 int len = sprintf(buf, "%d", (int)m_pidLocker) + 1;
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
173 return LOCK_ERROR;
174 }
175
176 fsync(m_fdLock);
177
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();
185
186 return LOCK_ERROR;
187 }
188
189 return LOCK_CREATED;
190 }
191 else // failure: see what exactly happened
192 {
193 close(m_fdLock);
194 m_fdLock = -1;
195
196 if ( errno != EACCES && errno != EAGAIN )
197 {
198 wxLogSysError(_("Failed to lock the lock file '%s'"),
199 m_nameLock.c_str());
200
201 unlink(m_nameLock.fn_str());
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 }
210 }
211
212 // we didn't create and lock the file
213 return LOCK_EXISTS;
214 }
215
216 bool wxSingleInstanceCheckerImpl::Create(const wxString& name)
217 {
218 m_nameLock = name;
219
220 switch ( CreateLockFile() )
221 {
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
228 return true;
229
230 case LOCK_ERROR:
231 // oops...
232 return false;
233 }
234
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:
238 wxStructStat stats;
239 if ( wxStat(name, &stats) != 0 )
240 {
241 wxLogSysError(_("Failed to inspect the lock file '%s'"), name.c_str());
242 return false;
243 }
244 if ( stats.st_uid != getuid() )
245 {
246 wxLogError(_("Lock file '%s' has incorrect owner."), name.c_str());
247 return false;
248 }
249 if ( stats.st_mode != (S_IFREG | S_IRUSR | S_IWUSR) )
250 {
251 wxLogError(_("Lock file '%s' has incorrect permissions."), name.c_str());
252 return false;
253 }
254
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
271 return false;
272 }
273
274 char buf[256];
275 ssize_t count = file.Read(buf, WXSIZEOF(buf));
276 if ( count == wxInvalidOffset )
277 {
278 wxLogError(_("Failed to read PID from lock file."));
279 }
280 else
281 {
282 if ( sscanf(buf, "%d", (int *)&m_pidLocker) == 1 )
283 {
284 if ( kill(m_pidLocker, 0) != 0 )
285 {
286 if ( unlink(name.fn_str()) != 0 )
287 {
288 wxLogError(_("Failed to remove stale lock file '%s'."),
289 name.c_str());
290
291 // return true in this case for now...
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 {
306 wxLogWarning(_("Invalid lock file '%s'."), name.c_str());
307 }
308 }
309
310 // return true if we could get the PID of the process owning the lock file
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 {
320 if ( unlink(m_nameLock.fn_str()) != 0 )
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,
350 wxT("calling wxSingleInstanceChecker::Create() twice?") );
351
352 // must have the file name to create a lock file
353 wxASSERT_MSG( !name.empty(), wxT("lock file name can't be empty") );
354
355 m_impl = new wxSingleInstanceCheckerImpl;
356
357 wxString fullname = path;
358 if ( fullname.empty() )
359 {
360 fullname = wxGetHomeDir();
361 }
362
363 if ( fullname.Last() != wxT('/') )
364 {
365 fullname += wxT('/');
366 }
367
368 fullname << name;
369
370 return m_impl->Create(fullname);
371 }
372
373 bool wxSingleInstanceChecker::DoIsAnotherRunning() const
374 {
375 wxCHECK_MSG( m_impl, false, wxT("must call Create() first") );
376
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
387 // if another instance is running, it must own the lock file - otherwise
388 // we have it and the locker PID is ours one
389 return lockerPid != getpid();
390 }
391
392 wxSingleInstanceChecker::~wxSingleInstanceChecker()
393 {
394 delete m_impl;
395 }
396
397 #endif // wxUSE_SNGLINST_CHECKER