/* 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 #include #include #include #include #include #ifdef _WIN32 # include # include # include #else # include # include # include #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; }