/* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "host/commands/cvd/instance_lock.h" #include #include #include #include #include #include #include #include "common/libs/fs/shared_fd.h" #include "common/libs/utils/environment.h" #include "common/libs/utils/files.h" #include "common/libs/utils/result.h" namespace cuttlefish { InstanceLockFile::InstanceLockFile(SharedFD fd, int instance_num) : fd_(fd), instance_num_(instance_num) {} int InstanceLockFile::Instance() const { return instance_num_; } Result InstanceLockFile::Status() const { CF_EXPECT(fd_->LSeek(0, SEEK_SET) == 0, fd_->StrError()); char state_char = static_cast(InUseState::kNotInUse); CF_EXPECT(fd_->Read(&state_char, 1) >= 0, fd_->StrError()); switch (state_char) { case static_cast(InUseState::kInUse): return InUseState::kInUse; case static_cast(InUseState::kNotInUse): return InUseState::kNotInUse; default: return CF_ERR("Unexpected state value \"" << state_char << "\""); } } Result InstanceLockFile::Status(InUseState state) { CF_EXPECT(fd_->LSeek(0, SEEK_SET) == 0, fd_->StrError()); char state_char = static_cast(state); CF_EXPECT(fd_->Write(&state_char, 1) == 1, fd_->StrError()); return {}; } bool InstanceLockFile::operator<(const InstanceLockFile& other) const { if (instance_num_ != other.instance_num_) { return instance_num_ < other.instance_num_; } return fd_ < other.fd_; } InstanceLockFileManager::InstanceLockFileManager() = default; // Replicates tempfile.gettempdir() in Python std::string TempDir() { std::vector try_dirs = { StringFromEnv("TMPDIR", ""), StringFromEnv("TEMP", ""), StringFromEnv("TMP", ""), "/tmp", "/var/tmp", "/usr/tmp", }; for (const auto& try_dir : try_dirs) { if (DirectoryExists(try_dir)) { return try_dir; } } return CurrentDirectory(); } static Result OpenLockFile(int instance_num) { std::stringstream path; path << TempDir() << "/acloud_cvd_temp/"; CF_EXPECT(EnsureDirectoryExists(path.str())); path << "local-instance-" << instance_num << ".lock"; auto fd = SharedFD::Open(path.str(), O_CREAT | O_RDWR, 0666); CF_EXPECT(fd->IsOpen(), "open(\"" << path.str() << "\"): " << fd->StrError()); return fd; } Result InstanceLockFileManager::AcquireLock( int instance_num) { auto fd = CF_EXPECT(OpenLockFile(instance_num)); CF_EXPECT(fd->Flock(LOCK_EX), fd->StrError()); return InstanceLockFile(fd, instance_num); } Result> InstanceLockFileManager::AcquireLocks( const std::set& instance_nums) { std::set locks; for (const auto& num : instance_nums) { locks.emplace(CF_EXPECT(AcquireLock(num))); } return locks; } Result> InstanceLockFileManager::TryAcquireLock( int instance_num) { auto fd = CF_EXPECT(OpenLockFile(instance_num)); int flock_result = fd->Flock(LOCK_EX | LOCK_NB); if (flock_result == 0) { return InstanceLockFile(fd, instance_num); } else if (flock_result == -1 && fd->GetErrno() == EWOULDBLOCK) { return {}; } return CF_ERR("flock " << instance_num << " failed: " << fd->StrError()); } Result> InstanceLockFileManager::TryAcquireLocks( const std::set& instance_nums) { std::set locks; for (const auto& num : instance_nums) { auto lock = CF_EXPECT(TryAcquireLock(num)); if (lock) { locks.emplace(std::move(*lock)); } } return locks; } static Result> AllInstanceNums() { // Estimate this by looking at available tap devices // clang-format off /** Sample format: Inter-| Receive | Transmit face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed cvd-wtap-02: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 */ // clang-format on static constexpr char kPath[] = "/proc/net/dev"; std::string proc_net_dev; using android::base::ReadFileToString; CF_EXPECT(ReadFileToString(kPath, &proc_net_dev, /* follow_symlinks */ true)); auto lines = android::base::Split(proc_net_dev, "\n"); std::set etaps, mtaps, wtaps; for (const auto& line : lines) { std::set* tap_set = nullptr; if (android::base::StartsWith(line, "cvd-etap-")) { tap_set = &etaps; } else if (android::base::StartsWith(line, "cvd-mtap-")) { tap_set = &mtaps; } else if (android::base::StartsWith(line, "cvd-wtap-")) { tap_set = &wtaps; } else { continue; } tap_set->insert(std::stoi(line.substr(std::string{"cvd-etap-"}.size()))); } std::set emtaps; std::set_intersection(etaps.begin(), etaps.end(), mtaps.begin(), mtaps.end(), std::inserter(emtaps, emtaps.begin())); std::set emwtaps; std::set_intersection(emtaps.begin(), emtaps.end(), wtaps.begin(), wtaps.end(), std::inserter(emwtaps, emwtaps.begin())); return emwtaps; } Result> InstanceLockFileManager::TryAcquireUnusedLock() { auto nums = CF_EXPECT(AllInstanceNums()); for (const auto& num : nums) { auto lock = CF_EXPECT(TryAcquireLock(num)); if (lock && CF_EXPECT(lock->Status()) == InUseState::kNotInUse) { return std::move(*lock); } } return {}; } } // namespace cuttlefish