| /* 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/base/system/System.h" |
| #include "android/utils/eintr_wrapper.h" |
| #include "android/utils/lock.h" |
| #include "android/utils/path.h" |
| |
| #include <assert.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <stdbool.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <sys/stat.h> |
| #include <time.h> |
| #ifdef _WIN32 |
| # include "android/base/memory/ScopedPtr.h" |
| # include "android/base/system/Win32UnicodeString.h" |
| # ifndef WIN32_LEAN_AND_MEAN |
| # define WIN32_LEAN_AND_MEAN |
| # endif |
| # include <windows.h> |
| #else |
| # include <sys/types.h> |
| # include <unistd.h> |
| # include <signal.h> |
| #endif |
| |
| using android::base::System; |
| |
| // 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' |
| ** |
| **/ |
| |
| |
| /* Thread safety: |
| * _all_filelocks_tl: Hold this when accessing |_all_filelocks|. |
| * This is locked at exit, so hold this for very, *very* |
| * short. |
| */ |
| static CLock* _all_filelocks_tl; |
| /* This is set to true during exit. |
| * All accesses of _all_filelocks should strictly follow this scheme: |
| * android_lock_acquire(_all_filelocks_tl); |
| * if (!_is_exiting) { |
| * // Safe to access _all_filelocks now. |
| * } |
| * android_lock_release(_all_filelocks_tl); |
| */ |
| static bool _is_exiting = false; |
| |
| struct FileLock |
| { |
| const char* file; |
| const char* lock; |
| char* temp; |
| #ifdef _WIN32 |
| HANDLE lock_handle; |
| #endif |
| 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" |
| #define WIN_PIDFILE_NAME "pid" |
| |
| void |
| filelock_init() { |
| _all_filelocks_tl = android_lock_new(); |
| } |
| |
| #ifdef _WIN32 |
| template <class Func> |
| static bool retry(Func func, int tries = 4, int timeoutMs = 100) { |
| for (;;) { |
| if (func()) { |
| return true; |
| } |
| if (--tries == 0) { |
| return false; |
| } |
| System::get()->sleepMs(timeoutMs); |
| } |
| } |
| |
| static bool delete_file(const android::base::Win32UnicodeString& name) { |
| return retry([&name]() { |
| return ::DeleteFileW(name.c_str()) != 0 || |
| ::GetLastError() == ERROR_FILE_NOT_FOUND; |
| }); |
| } |
| |
| static bool delete_dir(const android::base::Win32UnicodeString& name) { |
| return retry([&name]() { |
| return ::RemoveDirectoryW(name.c_str()) != 0 || |
| ::GetLastError() == ERROR_FILE_NOT_FOUND; |
| }); |
| } |
| |
| /* returns 0 on success, -1 on failure */ |
| static int filelock_lock(FileLock* lock) { |
| lock->lock_handle = nullptr; |
| const auto unicodeDir = android::base::Win32UnicodeString(lock->lock); |
| const auto unicodeName = android::base::Win32UnicodeString(lock->temp); |
| |
| HANDLE lockHandle = INVALID_HANDLE_VALUE; |
| const bool createFileResult = retry( |
| [&lockHandle, &unicodeDir, &unicodeName]() { |
| if (!::CreateDirectoryW(unicodeDir.c_str(), nullptr) && |
| ::GetLastError() != ERROR_ALREADY_EXISTS) { |
| return false; |
| } |
| |
| // Open/create a lock file with a flags combination like: |
| // - open for writing only |
| // - allow only one process to open file for writing (our process) |
| // Together, this guarantees that the file can only be opened when |
| // our process is alive and keeps a handle to it. |
| // As soon as our process ends or we close/delete it - other lock |
| // can be acquired. |
| // Note: FILE_FLAG_DELETE_ON_CLOSE doesn't work well here, as |
| // everyone else would need to open the file in FILE_SHARE_DELETE |
| // mode, and both Android Studio and cmd.exe don't use it. Fix |
| // at least the Android Studio part before trying to add this flag |
| // instead of the manual deletion code. |
| lockHandle = ::CreateFileW( |
| unicodeName.c_str(), |
| GENERIC_WRITE, // open only for writing |
| FILE_SHARE_READ, // allow others to read the file, but not |
| // to write to it or not to delete it |
| nullptr, // no special security attributes |
| CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY, |
| nullptr); // no template file |
| return lockHandle != INVALID_HANDLE_VALUE; |
| }, |
| 5, // tries |
| 200); // sleep timeout between tries |
| |
| if (!createFileResult) { |
| assert(lockHandle == INVALID_HANDLE_VALUE); |
| return -1; |
| } |
| |
| assert(lockHandle != INVALID_HANDLE_VALUE); |
| |
| // Make sure we kill the file/directory on any failure. |
| auto fileDeleter = android::base::makeCustomScopedPtr( |
| lockHandle, [&unicodeName, &unicodeDir](HANDLE h) { |
| ::CloseHandle(h); |
| delete_file(unicodeName); |
| delete_dir(unicodeDir); |
| }); |
| |
| // We're good. Now write down the process ID as Android Studio relies on it. |
| const DWORD pid = ::GetCurrentProcessId(); |
| char pidBuf[12]; |
| const int len = snprintf(pidBuf, sizeof(pidBuf), "%lu", pid); |
| assert(len > 0 && len < (int)sizeof(pidBuf)); |
| DWORD bytesWritten; |
| if (!::WriteFile(lockHandle, pidBuf, static_cast<DWORD>(len), &bytesWritten, |
| nullptr) || |
| bytesWritten != static_cast<DWORD>(len)) { |
| D("Failed to write the current PID into the lock file"); |
| return -1; |
| } |
| lock->locked = 1; |
| lock->lock_handle = fileDeleter.release(); |
| return 0; // we're all good |
| } |
| #else |
| /* returns 0 on success, -1 on failure */ |
| static int filelock_lock(FileLock* lock) { |
| int ret; |
| int temp_fd = -1; |
| int lock_fd = -1; |
| int rc, tries; |
| FILE* f = NULL; |
| char pid[8]; |
| struct stat st_temp; |
| int sleep_duration_ms = 0; |
| |
| 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 */ |
| for (tries = 4; tries > 0; tries--) |
| { |
| const int kSleepDurationMsMax = 2000; // 2 seconds. |
| const int kSleepDurationMsIncrement = 50; |
| |
| if (sleep_duration_ms > 0) { |
| if (sleep_duration_ms > kSleepDurationMsMax) { |
| D("Cannot acquire lock file '%s'", lock->lock); |
| goto Fail; |
| } |
| System::get()->sleepMs(sleep_duration_ms); |
| } |
| sleep_duration_ms += kSleepDurationMsIncrement; |
| |
| // 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)) { |
| char *win_pid; |
| int win_pid_len; |
| // 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); |
| |
| /* Try deleting the pid file dropped in windows. |
| * Ignore error -- try blowing away the directory anyway. |
| */ |
| win_pid_len = strlen(lock->lock) + 1 + sizeof(WIN_PIDFILE_NAME); |
| win_pid = (char *) malloc(win_pid_len); |
| snprintf(win_pid, win_pid_len, "%s/" WIN_PIDFILE_NAME, lock->lock); |
| HANDLE_EINTR(unlink(win_pid)); |
| free(win_pid); |
| |
| rc = HANDLE_EINTR(rmdir(lock->lock)); |
| if (rc != 0) { |
| D("Removing stale Win32 lockfile '%s' failed (%s)", |
| lock->lock, strerror(errno)); |
| } |
| |
| 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_ms = 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->temp)); |
| return -1; |
| } |
| #endif |
| |
| void |
| filelock_release( FileLock* lock ) |
| { |
| if (lock->locked) { |
| #ifdef _WIN32 |
| ::CloseHandle(lock->lock_handle); |
| lock->lock_handle = nullptr; |
| delete_file(android::base::Win32UnicodeString(lock->temp)); |
| delete_dir(android::base::Win32UnicodeString(lock->lock)); |
| #else |
| unlink( (char*)lock->lock ); |
| #endif |
| lock->locked = 0; |
| } |
| } |
| |
| static void |
| filelock_atexit( void ) |
| { |
| FileLock* lock; |
| |
| android_lock_acquire(_all_filelocks_tl); |
| if (!_is_exiting) { |
| for (lock = _all_filelocks; lock != NULL; lock = lock->next) { |
| filelock_release( lock ); |
| } |
| } |
| _is_exiting = true; |
| android_lock_release(_all_filelocks_tl); |
| // We leak |_all_filelocks_tl| here. We can never guarantee that another |
| // thread isn't trying to use filelock concurrently, so we can not safely |
| // clean up the mutex. See b.android.com/209635 |
| } |
| |
| /* 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(WIN_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 = (FileLock*)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\\" WIN_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; |
| } |
| |
| android_lock_acquire(_all_filelocks_tl); |
| if (_is_exiting) { |
| android_lock_release(_all_filelocks_tl); |
| return NULL; |
| } |
| |
| lock->next = _all_filelocks; |
| _all_filelocks = lock; |
| android_lock_release(_all_filelocks_tl); |
| |
| if (lock->next == NULL) |
| atexit( filelock_atexit ); |
| |
| return lock; |
| } |