1/* Copyright (C) 2007-2008 The Android Open Source Project
2**
3** This software is licensed under the terms of the GNU General Public
4** License version 2, as published by the Free Software Foundation, and
5** may be copied, distributed, and modified under those terms.
6**
7** This program is distributed in the hope that it will be useful,
8** but WITHOUT ANY WARRANTY; without even the implied warranty of
9** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10** GNU General Public License for more details.
11*/
12
13#include "android/utils/filelock.h"
14#include "android/utils/path.h"
15#include <stdio.h>
16#include <stdlib.h>
17#include <errno.h>
18#include <sys/stat.h>
19#include <time.h>
20#include <fcntl.h>
21#ifdef _WIN32
22#  include <process.h>
23#  include <windows.h>
24#  include <tlhelp32.h>
25#else
26#  include <sys/types.h>
27#  include <unistd.h>
28#  include <signal.h>
29#endif
30
31#define  D(...)  ((void)0)
32
33#ifndef CHECKED
34#  ifdef _WIN32
35#    define   CHECKED(ret, call)    (ret) = (call)
36#  else
37#    define   CHECKED(ret, call)    do { (ret) = (call); } while ((ret) < 0 && errno == EINTR)
38#  endif
39#endif
40
41/** FILE LOCKS SUPPORT
42 **
43 ** a FileLock is useful to prevent several emulator instances from using the same
44 ** writable file (e.g. the userdata.img disk images).
45 **
46 ** create a FileLock object with filelock_create(), ithis function should return NULL
47 ** only if the corresponding file path could not be locked.
48 **
49 ** all file locks are automatically released and destroyed when the program exits.
50 ** the filelock_lock() function can also detect stale file locks that can linger
51 ** when the emulator crashes unexpectedly, and will happily clean them for you
52 **
53 **  here's how it works, three files are used:
54 **     file  - the data file accessed by the emulator
55 **     lock  - a lock file  (file + '.lock')
56 **     temp  - a temporary file make unique with mkstemp
57 **
58 **  when locking:
59 **      create 'temp' and store our pid in it
60 **      attemp to link 'lock' to 'temp'
61 **         if the link succeeds, we obtain the lock
62 **      unlink 'temp'
63 **
64 **  when unlocking:
65 **      unlink 'lock'
66 **
67 **
68 **  on Windows, 'lock' is a directory name. locking is equivalent to
69 **  creating it...
70 **
71 **/
72
73struct FileLock
74{
75  const char*  file;
76  const char*  lock;
77  char*        temp;
78  int          locked;
79  FileLock*    next;
80};
81
82/* used to cleanup all locks at emulator exit */
83static FileLock*   _all_filelocks;
84
85
86#define  LOCK_NAME   ".lock"
87#define  TEMP_NAME   ".tmp-XXXXXX"
88
89#ifdef _WIN32
90#define  PIDFILE_NAME  "pid"
91#endif
92
93/* returns 0 on success, -1 on failure */
94static int
95filelock_lock( FileLock*  lock )
96{
97    int    ret;
98#ifdef _WIN32
99    int  pidfile_fd = -1;
100
101    ret = _mkdir( lock->lock );
102    if (ret < 0) {
103        if (errno == ENOENT) {
104            D( "could not access directory '%s', check path elements", lock->lock );
105            return -1;
106        } else if (errno != EEXIST) {
107            D( "_mkdir(%s): %s", lock->lock, strerror(errno) );
108            return -1;
109        }
110
111        /* if we get here, it's because the .lock directory already exists */
112        /* check to see if there is a pid file in it                       */
113        D("directory '%s' already exist, waiting a bit to ensure that no other emulator instance is starting", lock->lock );
114        {
115            int  _sleep = 200;
116            int  tries;
117
118            for ( tries = 4; tries > 0; tries-- )
119            {
120                pidfile_fd = open( lock->temp, O_RDONLY );
121
122                if (pidfile_fd >= 0)
123                    break;
124
125                Sleep( _sleep );
126                _sleep *= 2;
127            }
128        }
129
130        if (pidfile_fd < 0) {
131            D( "no pid file in '%s', assuming stale directory", lock->lock );
132        }
133        else
134        {
135            /* read the pidfile, and check wether the corresponding process is still running */
136            char            buf[16];
137            int             len, lockpid;
138            HANDLE          processSnapshot;
139            PROCESSENTRY32  pe32;
140            int             is_locked = 0;
141
142            len = read( pidfile_fd, buf, sizeof(buf)-1 );
143            if (len < 0) {
144                D( "could not read pid file '%s'", lock->temp );
145                close( pidfile_fd );
146                return -1;
147            }
148            buf[len] = 0;
149            lockpid  = atoi(buf);
150
151            /* PID 0 is the IDLE process, and 0 is returned in case of invalid input */
152            if (lockpid == 0)
153                lockpid = -1;
154
155            close( pidfile_fd );
156
157            pe32.dwSize     = sizeof( PROCESSENTRY32 );
158            processSnapshot = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 );
159
160            if ( processSnapshot == INVALID_HANDLE_VALUE ) {
161                D( "could not retrieve the list of currently active processes\n" );
162                is_locked = 1;
163            }
164            else if ( !Process32First( processSnapshot, &pe32 ) )
165            {
166                D( "could not retrieve first process id\n" );
167                CloseHandle( processSnapshot );
168                is_locked = 1;
169            }
170            else
171            {
172                do {
173                    if (pe32.th32ProcessID == lockpid) {
174                        is_locked = 1;
175                        break;
176                    }
177                } while (Process32Next( processSnapshot, &pe32 ) );
178
179                CloseHandle( processSnapshot );
180            }
181
182            if (is_locked) {
183                D( "the file '%s' is locked by process ID %d\n", lock->file, lockpid );
184                return -1;
185            }
186        }
187    }
188
189    /* write our PID into the pid file */
190    pidfile_fd = open( lock->temp, O_WRONLY | O_CREAT | O_TRUNC );
191    if (pidfile_fd < 0) {
192        if (errno == EACCES) {
193            if ( path_delete_file( lock->temp ) < 0 ) {
194                D( "could not remove '%s': %s\n", lock->temp, strerror(errno) );
195                return -1;
196            }
197            pidfile_fd = open( lock->temp, O_WRONLY | O_CREAT | O_TRUNC );
198        }
199        if (pidfile_fd < 0) {
200            D( "could not create '%s': %s\n", lock->temp, strerror(errno) );
201            return -1;
202        }
203    }
204
205    {
206        char  buf[16];
207        sprintf( buf, "%ld", GetCurrentProcessId() );
208        ret = write( pidfile_fd, buf, strlen(buf) );
209        close(pidfile_fd);
210        if (ret < 0) {
211            D( "could not write PID to '%s'\n", lock->temp );
212            return -1;
213        }
214    }
215
216    lock->locked = 1;
217    return 0;
218#else
219    int    temp_fd = -1;
220    int    lock_fd = -1;
221    int    rc, tries, _sleep;
222    FILE*  f = NULL;
223    char   pid[8];
224    struct stat  st_temp;
225
226    strcpy( lock->temp, lock->file );
227    strcat( lock->temp, TEMP_NAME );
228    temp_fd = mkstemp( lock->temp );
229
230    if (temp_fd < 0) {
231        D("cannot create locking temp file '%s'", lock->temp );
232        goto Fail;
233    }
234
235    sprintf( pid, "%d", getpid() );
236    ret = write( temp_fd, pid, strlen(pid)+1 );
237    if (ret < 0) {
238        D( "cannot write to locking temp file '%s'", lock->temp);
239        goto Fail;
240    }
241    close( temp_fd );
242    temp_fd = -1;
243
244    CHECKED(rc, lstat( lock->temp, &st_temp ));
245    if (rc < 0) {
246        D( "can't properly stat our locking temp file '%s'", lock->temp );
247        goto Fail;
248    }
249
250    /* now attempt to link the temp file to the lock file */
251    _sleep = 0;
252    for ( tries = 4; tries > 0; tries-- )
253    {
254        struct stat  st_lock;
255        int          rc;
256
257        if (_sleep > 0) {
258            if (_sleep > 2000000) {
259                D( "cannot acquire lock file '%s'", lock->lock );
260                goto Fail;
261            }
262            usleep( _sleep );
263        }
264        _sleep += 200000;
265
266        /* the return value of link() is buggy on NFS */
267        CHECKED(rc, link( lock->temp, lock->lock ));
268
269        CHECKED(rc, lstat( lock->lock, &st_lock ));
270        if (rc == 0 &&
271            st_temp.st_rdev == st_lock.st_rdev &&
272            st_temp.st_ino  == st_lock.st_ino  )
273        {
274            /* SUCCESS */
275            lock->locked = 1;
276            CHECKED(rc, unlink( lock->temp ));
277            return 0;
278        }
279
280        /* if we get there, it means that the link() call failed */
281        /* check the lockfile to see if it is stale              */
282        if (rc == 0) {
283            char    buf[16];
284            time_t  now;
285            int     lockpid = 0;
286            int     lockfd;
287            int     stale = 2;  /* means don't know */
288            struct stat  st;
289
290            CHECKED(rc, time( &now));
291            st.st_mtime = now - 120;
292
293            CHECKED(lockfd, open( lock->lock,O_RDONLY ));
294            if ( lockfd >= 0 ) {
295                int  len;
296
297                CHECKED(len, read( lockfd, buf, sizeof(buf)-1 ));
298                buf[len] = 0;
299                lockpid = atoi(buf);
300
301                CHECKED(rc, fstat( lockfd, &st ));
302                if (rc == 0)
303                  now = st.st_atime;
304
305                CHECKED(rc, close(lockfd));
306            }
307            /* if there is a PID, check that it is still alive */
308            if (lockpid > 0) {
309                CHECKED(rc, kill( lockpid, 0 ));
310                if (rc == 0 || errno == EPERM) {
311                    stale = 0;
312                } else if (rc < 0 && errno == ESRCH) {
313                    stale = 1;
314                }
315            }
316            if (stale == 2) {
317                /* no pid, stale if the file is older than 1 minute */
318                stale = (now >= st.st_mtime + 60);
319            }
320
321            if (stale) {
322                D( "removing stale lockfile '%s'", lock->lock );
323                CHECKED(rc, unlink( lock->lock ));
324                _sleep = 0;
325                tries++;
326            }
327        }
328    }
329    D("file '%s' is already in use by another process", lock->file );
330
331Fail:
332    if (f)
333        fclose(f);
334
335    if (temp_fd >= 0) {
336        close(temp_fd);
337    }
338
339    if (lock_fd >= 0) {
340        close(lock_fd);
341    }
342
343    unlink( lock->lock );
344    unlink( lock->temp );
345    return -1;
346#endif
347}
348
349void
350filelock_release( FileLock*  lock )
351{
352    if (lock->locked) {
353#ifdef _WIN32
354        path_delete_file( (char*)lock->temp );
355        rmdir( (char*)lock->lock );
356#else
357        unlink( (char*)lock->lock );
358#endif
359        lock->locked = 0;
360    }
361}
362
363static void
364filelock_atexit( void )
365{
366  FileLock*  lock;
367
368  for (lock = _all_filelocks; lock != NULL; lock = lock->next)
369     filelock_release( lock );
370}
371
372/* create a file lock */
373FileLock*
374filelock_create( const char*  file )
375{
376    int    file_len = strlen(file);
377    int    lock_len = file_len + sizeof(LOCK_NAME);
378#ifdef _WIN32
379    int    temp_len = lock_len + 1 + sizeof(PIDFILE_NAME);
380#else
381    int    temp_len = file_len + sizeof(TEMP_NAME);
382#endif
383    int    total_len = sizeof(FileLock) + file_len + lock_len + temp_len + 3;
384
385    FileLock*  lock = malloc(total_len);
386
387    lock->file = (const char*)(lock + 1);
388    memcpy( (char*)lock->file, file, file_len+1 );
389
390    lock->lock = lock->file + file_len + 1;
391    memcpy( (char*)lock->lock, file, file_len+1 );
392    strcat( (char*)lock->lock, LOCK_NAME );
393
394    lock->temp    = (char*)lock->lock + lock_len + 1;
395#ifdef _WIN32
396    snprintf( (char*)lock->temp, temp_len, "%s\\" PIDFILE_NAME, lock->lock );
397#else
398    lock->temp[0] = 0;
399#endif
400    lock->locked = 0;
401
402    if (filelock_lock(lock) < 0) {
403        free(lock);
404        return NULL;
405    }
406
407    lock->next     = _all_filelocks;
408    _all_filelocks = lock;
409
410    if (lock->next == NULL)
411        atexit( filelock_atexit );
412
413    return lock;
414}
415