blob: 710293b5a2cf72ee3d0687148713f61fb1927605 [file] [log] [blame]
/* 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/eintr_wrapper.h"
#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
// Set to 1 to enable debug traces here.
#if 0
#define D(...) printf(__VA_ARGS__), printf("\n"), fflush(stdout)
#else
#define D(...) ((void)0)
#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(), this 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 made 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. The directory will contain a file named 'pid' that
** contains the locking process' PID.
**
**/
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;
FILE* f = NULL;
char pid[8];
struct stat st_temp;
temp_fd = mkstemp(lock->temp);
if (temp_fd < 0) {
D("Cannot create locking temp file '%s'", lock->temp);
goto Fail;
}
snprintf(pid, sizeof pid, "%d", getpid());
ret = HANDLE_EINTR(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;
rc = HANDLE_EINTR(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 */
int sleep_duration_us = 0;
for (tries = 4; tries > 0; tries--)
{
const int kSleepDurationUsMax = 2000000; // 2 seconds.
const int kSleepDurationUsIncrement = 200000; // 0.2 seconds
if (sleep_duration_us > 0) {
if (sleep_duration_us > kSleepDurationUsMax) {
D("Cannot acquire lock file '%s'", lock->lock);
goto Fail;
}
usleep(sleep_duration_us);
}
sleep_duration_us += kSleepDurationUsIncrement;
// The return value of link() is buggy on NFS, so ignore it.
// and use lstat() to look at the result.
rc = HANDLE_EINTR(link(lock->temp, lock->lock));
struct stat st_lock;
rc = HANDLE_EINTR(lstat(lock->lock, &st_lock));
if (rc != 0) {
// Try again after sleeping a little.
continue;
}
if (st_temp.st_rdev == st_lock.st_rdev &&
st_temp.st_ino == st_lock.st_ino ) {
/* The link() operation suceeded */
lock->locked = 1;
rc = HANDLE_EINTR(unlink(lock->temp));
return 0;
}
if (S_ISDIR(st_lock.st_mode)) {
// The .lock file is a directory. This can only happen
// when the AVD was previously used by a Win32 emulator
// instance running under Wine on the same machine.
fprintf(stderr,
"Stale Win32 lock file detected: %s\n",
lock->lock);
goto Fail;
}
/* if we get there, it means that the link() call failed */
/* check the lockfile to see if it is stale */
typedef enum {
FRESHNESS_UNKNOWN = 0,
FRESHNESS_FRESH,
FRESHNESS_STALE,
} Freshness;
Freshness freshness = FRESHNESS_UNKNOWN;
struct stat st;
time_t now;
rc = HANDLE_EINTR(time(&now));
st.st_mtime = now - 120;
int lockpid = 0;
int lockfd = HANDLE_EINTR(open(lock->lock,O_RDONLY));
if (lockfd >= 0) {
char buf[16];
int len = HANDLE_EINTR(read(lockfd, buf, sizeof(buf) - 1U));
if (len < 0) {
len = 0;
}
buf[len] = 0;
lockpid = atoi(buf);
rc = HANDLE_EINTR(fstat(lockfd, &st));
if (rc == 0) {
now = st.st_atime;
}
IGNORE_EINTR(close(lockfd));
}
/* if there is a PID, check that it is still alive */
if (lockpid > 0) {
rc = HANDLE_EINTR(kill(lockpid, 0));
if (rc == 0 || errno == EPERM) {
freshness = FRESHNESS_FRESH;
} else if (rc < 0 && errno == ESRCH) {
freshness = FRESHNESS_STALE;
}
}
if (freshness == FRESHNESS_UNKNOWN) {
/* no pid, stale if the file is older than 1 minute */
freshness = (now >= st.st_mtime + 60) ?
FRESHNESS_STALE :
FRESHNESS_FRESH;
}
if (freshness == FRESHNESS_STALE) {
D("Removing stale lockfile '%s'", lock->lock);
rc = HANDLE_EINTR(unlink(lock->lock));
sleep_duration_us = 0;
tries++;
}
}
D("file '%s' is already in use by another process", lock->file);
Fail:
if (f) {
fclose(f);
}
if (temp_fd >= 0) {
IGNORE_EINTR(close(temp_fd));
}
if (lock_fd >= 0) {
IGNORE_EINTR(close(lock_fd));
}
HANDLE_EINTR(unlink(lock->lock));
HANDLE_EINTR(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
snprintf((char*)lock->temp, temp_len, "%s%s", lock->file, TEMP_NAME);
#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;
}