diff options
Diffstat (limited to 'android/utils/filelock.c')
-rw-r--r-- | android/utils/filelock.c | 414 |
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; +} |