1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #include "scoped_flock.h" 18 19 #include <sys/file.h> 20 #include <sys/stat.h> 21 22 #include <android-base/logging.h> 23 #include <android-base/stringprintf.h> 24 25 #include "file_utils.h" 26 #include "unix_file/fd_file.h" 27 28 namespace art { 29 30 using android::base::StringPrintf; 31 32 /* static */ ScopedFlock LockedFile::Open(const char* filename, std::string* error_msg) { 33 return Open(filename, O_CREAT | O_RDWR, true, error_msg); 34 } 35 36 /* static */ ScopedFlock LockedFile::Open(const char* filename, int flags, bool block, 37 std::string* error_msg) { 38 #ifdef _WIN32 39 // TODO: implement file locking for Windows. 40 UNUSED(filename); 41 UNUSED(flags); 42 UNUSED(block); 43 *error_msg = "flock is unsupported on Windows"; 44 return nullptr; 45 #else 46 while (true) { 47 // NOTE: We don't check usage here because the ScopedFlock should *never* be 48 // responsible for flushing its underlying FD. Its only purpose should be 49 // to acquire a lock, and the unlock / close in the corresponding 50 // destructor. Callers should explicitly flush files they're writing to if 51 // that is the desired behaviour. 52 std::unique_ptr<File> file(OS::OpenFileWithFlags(filename, flags, /* auto_flush= */ false)); 53 if (file.get() == nullptr) { 54 *error_msg = StringPrintf("Failed to open file '%s': %s", filename, strerror(errno)); 55 return nullptr; 56 } 57 58 int operation = block ? LOCK_EX : (LOCK_EX | LOCK_NB); 59 int flock_result = TEMP_FAILURE_RETRY(flock(file->Fd(), operation)); 60 if (flock_result == EWOULDBLOCK) { 61 // File is locked by someone else and we are required not to block; 62 return nullptr; 63 } 64 if (flock_result != 0) { 65 *error_msg = StringPrintf("Failed to lock file '%s': %s", filename, strerror(errno)); 66 return nullptr; 67 } 68 struct stat fstat_stat; 69 int fstat_result = TEMP_FAILURE_RETRY(fstat(file->Fd(), &fstat_stat)); 70 if (fstat_result != 0) { 71 *error_msg = StringPrintf("Failed to fstat file '%s': %s", filename, strerror(errno)); 72 return nullptr; 73 } 74 struct stat stat_stat; 75 int stat_result = TEMP_FAILURE_RETRY(stat(filename, &stat_stat)); 76 if (stat_result != 0) { 77 PLOG(WARNING) << "Failed to stat, will retry: " << filename; 78 // ENOENT can happen if someone racing with us unlinks the file we created so just retry. 79 if (block) { 80 continue; 81 } else { 82 // Note that in theory we could race with someone here for a long time and end up retrying 83 // over and over again. This potential behavior does not fit well in the non-blocking 84 // semantics. Thus, if we are not require to block return failure when racing. 85 return nullptr; 86 } 87 } 88 if (fstat_stat.st_dev != stat_stat.st_dev || fstat_stat.st_ino != stat_stat.st_ino) { 89 LOG(WARNING) << "File changed while locking, will retry: " << filename; 90 if (block) { 91 continue; 92 } else { 93 // See comment above. 94 return nullptr; 95 } 96 } 97 98 return ScopedFlock(new LockedFile(std::move((*file.get())))); 99 } 100 #endif 101 } 102 103 ScopedFlock LockedFile::DupOf(const int fd, const std::string& path, 104 const bool read_only_mode, std::string* error_msg) { 105 #ifdef _WIN32 106 // TODO: implement file locking for Windows. 107 UNUSED(fd); 108 UNUSED(path); 109 UNUSED(read_only_mode); 110 *error_msg = "flock is unsupported on Windows."; 111 return nullptr; 112 #else 113 // NOTE: We don't check usage here because the ScopedFlock should *never* be 114 // responsible for flushing its underlying FD. Its only purpose should be 115 // to acquire a lock, and the unlock / close in the corresponding 116 // destructor. Callers should explicitly flush files they're writing to if 117 // that is the desired behaviour. 118 ScopedFlock locked_file( 119 new LockedFile(DupCloexec(fd), path, /* check_usage= */ false, read_only_mode)); 120 if (locked_file->Fd() == -1) { 121 *error_msg = StringPrintf("Failed to duplicate open file '%s': %s", 122 locked_file->GetPath().c_str(), strerror(errno)); 123 return nullptr; 124 } 125 if (0 != TEMP_FAILURE_RETRY(flock(locked_file->Fd(), LOCK_EX))) { 126 *error_msg = StringPrintf( 127 "Failed to lock file '%s': %s", locked_file->GetPath().c_str(), strerror(errno)); 128 return nullptr; 129 } 130 131 return locked_file; 132 #endif 133 } 134 135 void LockedFile::ReleaseLock() { 136 #ifndef _WIN32 137 if (this->Fd() != -1) { 138 int flock_result = TEMP_FAILURE_RETRY(flock(this->Fd(), LOCK_UN)); 139 if (flock_result != 0) { 140 // Only printing a warning is okay since this is only used with either: 141 // 1) a non-blocking Init call, or 142 // 2) as a part of a seperate binary (eg dex2oat) which has it's own timeout logic to prevent 143 // deadlocks. 144 // This means we can be sure that the warning won't cause a deadlock. 145 PLOG(WARNING) << "Unable to unlock file " << this->GetPath(); 146 } 147 } 148 #endif 149 } 150 151 } // namespace art 152