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