aboutsummaryrefslogtreecommitdiffstats
path: root/android/utils/filelock.c
diff options
context:
space:
mode:
Diffstat (limited to 'android/utils/filelock.c')
-rw-r--r--android/utils/filelock.c414
1 files changed, 414 insertions, 0 deletions
diff --git a/android/utils/filelock.c b/android/utils/filelock.c
new file mode 100644
index 0000000..a8b18da
--- /dev/null
+++ b/android/utils/filelock.c
@@ -0,0 +1,414 @@
+/* Copyright (C) 2007-2008 The Android Open Source Project
+**
+** This software is licensed under the terms of the GNU General Public
+** License version 2, as published by the Free Software Foundation, and
+** may be copied, distributed, and modified under those terms.
+**
+** This program is distributed in the hope that it will be useful,
+** but WITHOUT ANY WARRANTY; without even the implied warranty of
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+** GNU General Public License for more details.
+*/
+
+#include "android/utils/filelock.h"
+#include "android/utils/path.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <fcntl.h>
+#ifdef _WIN32
+# include <process.h>
+# include <windows.h>
+# include <tlhelp32.h>
+#else
+# include <sys/types.h>
+# include <unistd.h>
+# include <signal.h>
+#endif
+
+#define D(...) ((void)0)
+
+#ifndef CHECKED
+# ifdef _WIN32
+# define CHECKED(ret, call) (ret) = (call)
+# else
+# define CHECKED(ret, call) do { (ret) = (call); } while ((ret) < 0 && errno == EINTR)
+# endif
+#endif
+
+/** FILE LOCKS SUPPORT
+ **
+ ** a FileLock is useful to prevent several emulator instances from using the same
+ ** writable file (e.g. the userdata.img disk images).
+ **
+ ** create a FileLock object with filelock_create(), ithis function should return NULL
+ ** only if the corresponding file path could not be locked.
+ **
+ ** all file locks are automatically released and destroyed when the program exits.
+ ** the filelock_lock() function can also detect stale file locks that can linger
+ ** when the emulator crashes unexpectedly, and will happily clean them for you
+ **
+ ** here's how it works, three files are used:
+ ** file - the data file accessed by the emulator
+ ** lock - a lock file (file + '.lock')
+ ** temp - a temporary file make unique with mkstemp
+ **
+ ** when locking:
+ ** create 'temp' and store our pid in it
+ ** attemp to link 'lock' to 'temp'
+ ** if the link succeeds, we obtain the lock
+ ** unlink 'temp'
+ **
+ ** when unlocking:
+ ** unlink 'lock'
+ **
+ **
+ ** on Windows, 'lock' is a directory name. locking is equivalent to
+ ** creating it...
+ **
+ **/
+
+struct FileLock
+{
+ const char* file;
+ const char* lock;
+ char* temp;
+ int locked;
+ FileLock* next;
+};
+
+/* used to cleanup all locks at emulator exit */
+static FileLock* _all_filelocks;
+
+
+#define LOCK_NAME ".lock"
+#define TEMP_NAME ".tmp-XXXXXX"
+
+#ifdef _WIN32
+#define PIDFILE_NAME "pid"
+#endif
+
+/* returns 0 on success, -1 on failure */
+static int
+filelock_lock( FileLock* lock )
+{
+ int ret;
+#ifdef _WIN32
+ int pidfile_fd = -1;
+
+ ret = _mkdir( lock->lock );
+ if (ret < 0) {
+ if (errno == ENOENT) {
+ D( "could not access directory '%s', check path elements", lock->lock );
+ return -1;
+ } else if (errno != EEXIST) {
+ D( "_mkdir(%s): %s", lock->lock, strerror(errno) );
+ return -1;
+ }
+
+ /* if we get here, it's because the .lock directory already exists */
+ /* check to see if there is a pid file in it */
+ D("directory '%s' already exist, waiting a bit to ensure that no other emulator instance is starting", lock->lock );
+ {
+ int _sleep = 200;
+ int tries;
+
+ for ( tries = 4; tries > 0; tries-- )
+ {
+ pidfile_fd = open( lock->temp, O_RDONLY );
+
+ if (pidfile_fd >= 0)
+ break;
+
+ Sleep( _sleep );
+ _sleep *= 2;
+ }
+ }
+
+ if (pidfile_fd < 0) {
+ D( "no pid file in '%s', assuming stale directory", lock->lock );
+ }
+ else
+ {
+ /* read the pidfile, and check wether the corresponding process is still running */
+ char buf[16];
+ int len, lockpid;
+ HANDLE processSnapshot;
+ PROCESSENTRY32 pe32;
+ int is_locked = 0;
+
+ len = read( pidfile_fd, buf, sizeof(buf)-1 );
+ if (len < 0) {
+ D( "could not read pid file '%s'", lock->temp );
+ close( pidfile_fd );
+ return -1;
+ }
+ buf[len] = 0;
+ lockpid = atoi(buf);
+
+ /* PID 0 is the IDLE process, and 0 is returned in case of invalid input */
+ if (lockpid == 0)
+ lockpid = -1;
+
+ close( pidfile_fd );
+
+ pe32.dwSize = sizeof( PROCESSENTRY32 );
+ processSnapshot = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 );
+
+ if ( processSnapshot == INVALID_HANDLE_VALUE ) {
+ D( "could not retrieve the list of currently active processes\n" );
+ is_locked = 1;
+ }
+ else if ( !Process32First( processSnapshot, &pe32 ) )
+ {
+ D( "could not retrieve first process id\n" );
+ CloseHandle( processSnapshot );
+ is_locked = 1;
+ }
+ else
+ {
+ do {
+ if (pe32.th32ProcessID == lockpid) {
+ is_locked = 1;
+ break;
+ }
+ } while (Process32Next( processSnapshot, &pe32 ) );
+
+ CloseHandle( processSnapshot );
+ }
+
+ if (is_locked) {
+ D( "the file '%s' is locked by process ID %d\n", lock->file, lockpid );
+ return -1;
+ }
+ }
+ }
+
+ /* write our PID into the pid file */
+ pidfile_fd = open( lock->temp, O_WRONLY | O_CREAT | O_TRUNC );
+ if (pidfile_fd < 0) {
+ if (errno == EACCES) {
+ if ( path_delete_file( lock->temp ) < 0 ) {
+ D( "could not remove '%s': %s\n", lock->temp, strerror(errno) );
+ return -1;
+ }
+ pidfile_fd = open( lock->temp, O_WRONLY | O_CREAT | O_TRUNC );
+ }
+ if (pidfile_fd < 0) {
+ D( "could not create '%s': %s\n", lock->temp, strerror(errno) );
+ return -1;
+ }
+ }
+
+ {
+ char buf[16];
+ sprintf( buf, "%ld", GetCurrentProcessId() );
+ ret = write( pidfile_fd, buf, strlen(buf) );
+ close(pidfile_fd);
+ if (ret < 0) {
+ D( "could not write PID to '%s'\n", lock->temp );
+ return -1;
+ }
+ }
+
+ lock->locked = 1;
+ return 0;
+#else
+ int temp_fd = -1;
+ int lock_fd = -1;
+ int rc, tries, _sleep;
+ FILE* f = NULL;
+ char pid[8];
+ struct stat st_temp;
+
+ strcpy( lock->temp, lock->file );
+ strcat( lock->temp, TEMP_NAME );
+ temp_fd = mkstemp( lock->temp );
+
+ if (temp_fd < 0) {
+ D("cannot create locking temp file '%s'", lock->temp );
+ goto Fail;
+ }
+
+ sprintf( pid, "%d", getpid() );
+ ret = write( temp_fd, pid, strlen(pid)+1 );
+ if (ret < 0) {
+ D( "cannot write to locking temp file '%s'", lock->temp);
+ goto Fail;
+ }
+ close( temp_fd );
+ temp_fd = -1;
+
+ CHECKED(rc, lstat( lock->temp, &st_temp ));
+ if (rc < 0) {
+ D( "can't properly stat our locking temp file '%s'", lock->temp );
+ goto Fail;
+ }
+
+ /* now attempt to link the temp file to the lock file */
+ _sleep = 0;
+ for ( tries = 4; tries > 0; tries-- )
+ {
+ struct stat st_lock;
+ int rc;
+
+ if (_sleep > 0) {
+ if (_sleep > 2000000) {
+ D( "cannot acquire lock file '%s'", lock->lock );
+ goto Fail;
+ }
+ usleep( _sleep );
+ }
+ _sleep += 200000;
+
+ /* the return value of link() is buggy on NFS */
+ CHECKED(rc, link( lock->temp, lock->lock ));
+
+ CHECKED(rc, lstat( lock->lock, &st_lock ));
+ if (rc == 0 &&
+ st_temp.st_rdev == st_lock.st_rdev &&
+ st_temp.st_ino == st_lock.st_ino )
+ {
+ /* SUCCESS */
+ lock->locked = 1;
+ CHECKED(rc, unlink( lock->temp ));
+ return 0;
+ }
+
+ /* if we get there, it means that the link() call failed */
+ /* check the lockfile to see if it is stale */
+ if (rc == 0) {
+ char buf[16];
+ time_t now;
+ int lockpid = 0;
+ int lockfd;
+ int stale = 2; /* means don't know */
+ struct stat st;
+
+ CHECKED(rc, time( &now));
+ st.st_mtime = now - 120;
+
+ CHECKED(lockfd, open( lock->lock,O_RDONLY ));
+ if ( lockfd >= 0 ) {
+ int len;
+
+ CHECKED(len, read( lockfd, buf, sizeof(buf)-1 ));
+ buf[len] = 0;
+ lockpid = atoi(buf);
+
+ CHECKED(rc, fstat( lockfd, &st ));
+ if (rc == 0)
+ now = st.st_atime;
+
+ CHECKED(rc, close(lockfd));
+ }
+ /* if there is a PID, check that it is still alive */
+ if (lockpid > 0) {
+ CHECKED(rc, kill( lockpid, 0 ));
+ if (rc == 0 || errno == EPERM) {
+ stale = 0;
+ } else if (rc < 0 && errno == ESRCH) {
+ stale = 1;
+ }
+ }
+ if (stale == 2) {
+ /* no pid, stale if the file is older than 1 minute */
+ stale = (now >= st.st_mtime + 60);
+ }
+
+ if (stale) {
+ D( "removing stale lockfile '%s'", lock->lock );
+ CHECKED(rc, unlink( lock->lock ));
+ _sleep = 0;
+ tries++;
+ }
+ }
+ }
+ D("file '%s' is already in use by another process", lock->file );
+
+Fail:
+ if (f)
+ fclose(f);
+
+ if (temp_fd >= 0) {
+ close(temp_fd);
+ }
+
+ if (lock_fd >= 0) {
+ close(lock_fd);
+ }
+
+ unlink( lock->lock );
+ unlink( lock->temp );
+ return -1;
+#endif
+}
+
+void
+filelock_release( FileLock* lock )
+{
+ if (lock->locked) {
+#ifdef _WIN32
+ path_delete_file( (char*)lock->temp );
+ rmdir( (char*)lock->lock );
+#else
+ unlink( (char*)lock->lock );
+#endif
+ lock->locked = 0;
+ }
+}
+
+static void
+filelock_atexit( void )
+{
+ FileLock* lock;
+
+ for (lock = _all_filelocks; lock != NULL; lock = lock->next)
+ filelock_release( lock );
+}
+
+/* create a file lock */
+FileLock*
+filelock_create( const char* file )
+{
+ int file_len = strlen(file);
+ int lock_len = file_len + sizeof(LOCK_NAME);
+#ifdef _WIN32
+ int temp_len = lock_len + 1 + sizeof(PIDFILE_NAME);
+#else
+ int temp_len = file_len + sizeof(TEMP_NAME);
+#endif
+ int total_len = sizeof(FileLock) + file_len + lock_len + temp_len + 3;
+
+ FileLock* lock = malloc(total_len);
+
+ lock->file = (const char*)(lock + 1);
+ memcpy( (char*)lock->file, file, file_len+1 );
+
+ lock->lock = lock->file + file_len + 1;
+ memcpy( (char*)lock->lock, file, file_len+1 );
+ strcat( (char*)lock->lock, LOCK_NAME );
+
+ lock->temp = (char*)lock->lock + lock_len + 1;
+#ifdef _WIN32
+ snprintf( (char*)lock->temp, temp_len, "%s\\" PIDFILE_NAME, lock->lock );
+#else
+ lock->temp[0] = 0;
+#endif
+ lock->locked = 0;
+
+ if (filelock_lock(lock) < 0) {
+ free(lock);
+ return NULL;
+ }
+
+ lock->next = _all_filelocks;
+ _all_filelocks = lock;
+
+ if (lock->next == NULL)
+ atexit( filelock_atexit );
+
+ return lock;
+}