unplugged-system/device/google/cuttlefish/host/commands/cvd/instance_lock.cpp

238 lines
7.6 KiB
C++

/*
* 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 <sys/file.h>
#include <algorithm>
#include <regex>
#include <sstream>
#include <string>
#include <unordered_map>
#include <android-base/file.h>
#include <android-base/parseint.h>
#include <android-base/strings.h>
#include <fruit/fruit.h>
#include "common/libs/fs/shared_fd.h"
#include "common/libs/utils/contains.h"
#include "common/libs/utils/environment.h"
#include "common/libs/utils/files.h"
#include "common/libs/utils/result.h"
namespace cuttlefish {
InstanceLockFile::InstanceLockFile(LockFile&& lock_file, const int instance_num)
: lock_file_(std::move(lock_file)), instance_num_(instance_num) {}
int InstanceLockFile::Instance() const { return instance_num_; }
Result<InUseState> InstanceLockFile::Status() const {
auto in_use_state = CF_EXPECT(lock_file_.Status());
return in_use_state;
}
Result<void> InstanceLockFile::Status(InUseState state) {
CF_EXPECT(lock_file_.Status(state));
return {};
}
bool InstanceLockFile::operator<(const InstanceLockFile& other) const {
if (instance_num_ != other.instance_num_) {
return instance_num_ < other.instance_num_;
}
return lock_file_ < other.lock_file_;
}
InstanceLockFileManager::InstanceLockFileManager() {}
Result<std::string> InstanceLockFileManager::LockFilePath(int instance_num) {
std::stringstream path;
path << TempDir() << "/acloud_cvd_temp/";
CF_EXPECT(EnsureDirectoryExists(path.str()));
path << "local-instance-" << instance_num << ".lock";
return path.str();
}
Result<InstanceLockFile> InstanceLockFileManager::AcquireLock(
int instance_num) {
const auto lock_file_path = CF_EXPECT(LockFilePath(instance_num));
LockFile lock_file =
CF_EXPECT(lock_file_manager_.AcquireLock(lock_file_path));
return InstanceLockFile(std::move(lock_file), instance_num);
}
Result<std::set<InstanceLockFile>> InstanceLockFileManager::AcquireLocks(
const std::set<int>& instance_nums) {
std::set<InstanceLockFile> locks;
for (const auto& num : instance_nums) {
locks.emplace(CF_EXPECT(AcquireLock(num)));
}
return locks;
}
Result<std::optional<InstanceLockFile>> InstanceLockFileManager::TryAcquireLock(
int instance_num) {
const auto lock_file_path = CF_EXPECT(LockFilePath(instance_num));
std::optional<LockFile> lock_file_opt =
CF_EXPECT(lock_file_manager_.TryAcquireLock(lock_file_path));
if (!lock_file_opt) {
return std::nullopt;
}
return InstanceLockFile(std::move(*lock_file_opt), instance_num);
}
Result<std::set<InstanceLockFile>> InstanceLockFileManager::TryAcquireLocks(
const std::set<int>& instance_nums) {
std::set<InstanceLockFile> locks;
for (const auto& num : instance_nums) {
auto lock = CF_EXPECT(TryAcquireLock(num));
if (lock) {
locks.emplace(std::move(*lock));
}
}
return locks;
}
Result<std::vector<InstanceLockFile>>
InstanceLockFileManager::LockAllAvailable() {
if (!all_instance_nums_) {
all_instance_nums_ = CF_EXPECT(FindPotentialInstanceNumsFromNetDevices());
}
std::vector<InstanceLockFile> acquired_lock_files;
for (const auto num : *all_instance_nums_) {
auto lock = CF_EXPECT(TryAcquireLock(num));
if (!lock) {
continue;
}
auto status = CF_EXPECT(lock->Status());
if (status != InUseState::kNotInUse) {
continue;
}
acquired_lock_files.emplace_back(std::move(*lock));
}
return acquired_lock_files;
}
static std::string DevicePatternString(
const std::unordered_map<std::string, std::set<int>>& device_to_ids_map) {
std::string device_pattern_str("^[[:space:]]*cvd-(");
for (const auto& [key, _] : device_to_ids_map) {
device_pattern_str.append(key).append("|");
}
if (!device_to_ids_map.empty()) {
*device_pattern_str.rbegin() = ')';
}
device_pattern_str.append("-[0-9]+");
return device_pattern_str;
}
struct TypeAndId {
std::string device_type;
int id;
};
// call this if the line is a network device line
static Result<TypeAndId> ParseMatchedLine(
const std::smatch& device_string_match) {
std::string device_string = *device_string_match.begin();
auto tokens = android::base::Tokenize(device_string, "-");
CF_EXPECT_GE(tokens.size(), 3);
const auto cvd = tokens.front();
int id = 0;
CF_EXPECT(android::base::ParseInt(tokens.back(), &id));
// '-'.join(tokens[1:-1])
tokens.pop_back();
tokens.erase(tokens.begin());
const auto device_type = android::base::Join(tokens, "-");
return TypeAndId{.device_type = device_type, .id = id};
}
Result<std::set<int>>
InstanceLockFileManager::FindPotentialInstanceNumsFromNetDevices() {
// 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::unordered_map<std::string, std::set<int>> device_to_ids_map{
{"etap", std::set<int>{}},
{"mtap", std::set<int>{}},
{"wtap", std::set<int>{}},
{"wifiap", std::set<int>{}},
};
// "^[[:space:]]*cvd-(etap|mtap|wtap|wifiap)-[0-9]+"
std::string device_pattern_str = DevicePatternString(device_to_ids_map);
std::regex device_pattern(device_pattern_str);
for (const auto& line : lines) {
std::smatch device_string_match;
if (!std::regex_search(line, device_string_match, device_pattern)) {
continue;
}
const auto [device_type, id] =
CF_EXPECT(ParseMatchedLine(device_string_match));
CF_EXPECT(Contains(device_to_ids_map, device_type));
device_to_ids_map[device_type].insert(id);
}
std::set<int> result{device_to_ids_map["etap"]}; // any set except "wifiap"
for (const auto& [device_type, id_set] : device_to_ids_map) {
/*
* b/2457509
*
* Until the debian host packages are sufficiently up-to-date, the wifiap
* devices wouldn't show up in /proc/net/dev.
*/
if (device_type == "wifiap" && id_set.empty()) {
continue;
}
std::set<int> tmp;
std::set_intersection(result.begin(), result.end(), id_set.begin(),
id_set.end(), std::inserter(tmp, tmp.begin()));
result = std::move(tmp);
}
return result;
}
Result<std::optional<InstanceLockFile>>
InstanceLockFileManager::TryAcquireUnusedLock() {
if (!all_instance_nums_) {
all_instance_nums_ = CF_EXPECT(FindPotentialInstanceNumsFromNetDevices());
}
for (const auto num : *all_instance_nums_) {
auto lock = CF_EXPECT(TryAcquireLock(num));
if (lock && CF_EXPECT(lock->Status()) == InUseState::kNotInUse) {
return std::move(*lock);
}
}
return {};
}
} // namespace cuttlefish