1 /*
2 * Copyright (C) 2022 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 "host/commands/cvd/instance_lock.h"
18
19 #include <sys/file.h>
20
21 #include <algorithm>
22 #include <regex>
23 #include <sstream>
24 #include <string>
25 #include <unordered_map>
26
27 #include <android-base/file.h>
28 #include <android-base/parseint.h>
29 #include <android-base/strings.h>
30 #include <fruit/fruit.h>
31
32 #include "common/libs/fs/shared_fd.h"
33 #include "common/libs/utils/contains.h"
34 #include "common/libs/utils/environment.h"
35 #include "common/libs/utils/files.h"
36 #include "common/libs/utils/result.h"
37
38 namespace cuttlefish {
39
InstanceLockFile(LockFile && lock_file,const int instance_num)40 InstanceLockFile::InstanceLockFile(LockFile&& lock_file, const int instance_num)
41 : lock_file_(std::move(lock_file)), instance_num_(instance_num) {}
42
Instance() const43 int InstanceLockFile::Instance() const { return instance_num_; }
44
Status() const45 Result<InUseState> InstanceLockFile::Status() const {
46 auto in_use_state = CF_EXPECT(lock_file_.Status());
47 return in_use_state;
48 }
49
Status(InUseState state)50 Result<void> InstanceLockFile::Status(InUseState state) {
51 CF_EXPECT(lock_file_.Status(state));
52 return {};
53 }
54
operator <(const InstanceLockFile & other) const55 bool InstanceLockFile::operator<(const InstanceLockFile& other) const {
56 if (instance_num_ != other.instance_num_) {
57 return instance_num_ < other.instance_num_;
58 }
59 return lock_file_ < other.lock_file_;
60 }
61
InstanceLockFileManager()62 InstanceLockFileManager::InstanceLockFileManager() {}
63
LockFilePath(int instance_num)64 Result<std::string> InstanceLockFileManager::LockFilePath(int instance_num) {
65 std::stringstream path;
66 path << TempDir() << "/acloud_cvd_temp/";
67 CF_EXPECT(EnsureDirectoryExists(path.str()));
68 path << "local-instance-" << instance_num << ".lock";
69 return path.str();
70 }
71
AcquireLock(int instance_num)72 Result<InstanceLockFile> InstanceLockFileManager::AcquireLock(
73 int instance_num) {
74 const auto lock_file_path = CF_EXPECT(LockFilePath(instance_num));
75 LockFile lock_file =
76 CF_EXPECT(lock_file_manager_.AcquireLock(lock_file_path));
77 return InstanceLockFile(std::move(lock_file), instance_num);
78 }
79
AcquireLocks(const std::set<int> & instance_nums)80 Result<std::set<InstanceLockFile>> InstanceLockFileManager::AcquireLocks(
81 const std::set<int>& instance_nums) {
82 std::set<InstanceLockFile> locks;
83 for (const auto& num : instance_nums) {
84 locks.emplace(CF_EXPECT(AcquireLock(num)));
85 }
86 return locks;
87 }
88
TryAcquireLock(int instance_num)89 Result<std::optional<InstanceLockFile>> InstanceLockFileManager::TryAcquireLock(
90 int instance_num) {
91 const auto lock_file_path = CF_EXPECT(LockFilePath(instance_num));
92 std::optional<LockFile> lock_file_opt =
93 CF_EXPECT(lock_file_manager_.TryAcquireLock(lock_file_path));
94 if (!lock_file_opt) {
95 return std::nullopt;
96 }
97 return InstanceLockFile(std::move(*lock_file_opt), instance_num);
98 }
99
TryAcquireLocks(const std::set<int> & instance_nums)100 Result<std::set<InstanceLockFile>> InstanceLockFileManager::TryAcquireLocks(
101 const std::set<int>& instance_nums) {
102 std::set<InstanceLockFile> locks;
103 for (const auto& num : instance_nums) {
104 auto lock = CF_EXPECT(TryAcquireLock(num));
105 if (lock) {
106 locks.emplace(std::move(*lock));
107 }
108 }
109 return locks;
110 }
111
112 Result<std::vector<InstanceLockFile>>
LockAllAvailable()113 InstanceLockFileManager::LockAllAvailable() {
114 if (!all_instance_nums_) {
115 all_instance_nums_ = CF_EXPECT(FindPotentialInstanceNumsFromNetDevices());
116 }
117
118 std::vector<InstanceLockFile> acquired_lock_files;
119 for (const auto num : *all_instance_nums_) {
120 auto lock = CF_EXPECT(TryAcquireLock(num));
121 if (!lock) {
122 continue;
123 }
124 auto status = CF_EXPECT(lock->Status());
125 if (status != InUseState::kNotInUse) {
126 continue;
127 }
128 acquired_lock_files.emplace_back(std::move(*lock));
129 }
130 return acquired_lock_files;
131 }
132
DevicePatternString(const std::unordered_map<std::string,std::set<int>> & device_to_ids_map)133 static std::string DevicePatternString(
134 const std::unordered_map<std::string, std::set<int>>& device_to_ids_map) {
135 std::string device_pattern_str("^[[:space:]]*cvd-(");
136 for (const auto& [key, _] : device_to_ids_map) {
137 device_pattern_str.append(key).append("|");
138 }
139 if (!device_to_ids_map.empty()) {
140 *device_pattern_str.rbegin() = ')';
141 }
142 device_pattern_str.append("-[0-9]+");
143 return device_pattern_str;
144 }
145
146 struct TypeAndId {
147 std::string device_type;
148 int id;
149 };
150 // call this if the line is a network device line
ParseMatchedLine(const std::smatch & device_string_match)151 static Result<TypeAndId> ParseMatchedLine(
152 const std::smatch& device_string_match) {
153 std::string device_string = *device_string_match.begin();
154 auto tokens = android::base::Tokenize(device_string, "-");
155 CF_EXPECT_GE(tokens.size(), 3);
156 const auto cvd = tokens.front();
157 int id = 0;
158 CF_EXPECT(android::base::ParseInt(tokens.back(), &id));
159 // '-'.join(tokens[1:-1])
160 tokens.pop_back();
161 tokens.erase(tokens.begin());
162 const auto device_type = android::base::Join(tokens, "-");
163 return TypeAndId{.device_type = device_type, .id = id};
164 }
165
166 Result<std::set<int>>
FindPotentialInstanceNumsFromNetDevices()167 InstanceLockFileManager::FindPotentialInstanceNumsFromNetDevices() {
168 // Estimate this by looking at available tap devices
169 // clang-format off
170 /** Sample format:
171 Inter-| Receive | Transmit
172 face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed
173 cvd-wtap-02: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
174 */
175 // clang-format on
176 static constexpr char kPath[] = "/proc/net/dev";
177 std::string proc_net_dev;
178 using android::base::ReadFileToString;
179 CF_EXPECT(ReadFileToString(kPath, &proc_net_dev, /* follow_symlinks */ true));
180
181 auto lines = android::base::Split(proc_net_dev, "\n");
182 std::unordered_map<std::string, std::set<int>> device_to_ids_map{
183 {"etap", std::set<int>{}},
184 {"mtap", std::set<int>{}},
185 {"wtap", std::set<int>{}},
186 {"wifiap", std::set<int>{}},
187 };
188 // "^[[:space:]]*cvd-(etap|mtap|wtap|wifiap)-[0-9]+"
189 std::string device_pattern_str = DevicePatternString(device_to_ids_map);
190
191 std::regex device_pattern(device_pattern_str);
192 for (const auto& line : lines) {
193 std::smatch device_string_match;
194 if (!std::regex_search(line, device_string_match, device_pattern)) {
195 continue;
196 }
197 const auto [device_type, id] =
198 CF_EXPECT(ParseMatchedLine(device_string_match));
199 CF_EXPECT(Contains(device_to_ids_map, device_type));
200 device_to_ids_map[device_type].insert(id);
201 }
202
203 std::set<int> result{device_to_ids_map["etap"]}; // any set except "wifiap"
204 for (const auto& [device_type, id_set] : device_to_ids_map) {
205 /*
206 * b/2457509
207 *
208 * Until the debian host packages are sufficiently up-to-date, the wifiap
209 * devices wouldn't show up in /proc/net/dev.
210 */
211 if (device_type == "wifiap" && id_set.empty()) {
212 continue;
213 }
214 std::set<int> tmp;
215 std::set_intersection(result.begin(), result.end(), id_set.begin(),
216 id_set.end(), std::inserter(tmp, tmp.begin()));
217 result = std::move(tmp);
218 }
219 return result;
220 }
221
222 Result<std::optional<InstanceLockFile>>
TryAcquireUnusedLock()223 InstanceLockFileManager::TryAcquireUnusedLock() {
224 if (!all_instance_nums_) {
225 all_instance_nums_ = CF_EXPECT(FindPotentialInstanceNumsFromNetDevices());
226 }
227
228 for (const auto num : *all_instance_nums_) {
229 auto lock = CF_EXPECT(TryAcquireLock(num));
230 if (lock && CF_EXPECT(lock->Status()) == InUseState::kNotInUse) {
231 return std::move(*lock);
232 }
233 }
234 return {};
235 }
236
237 } // namespace cuttlefish
238