• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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