• 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 <sstream>
23 #include <string>
24 
25 #include <android-base/file.h>
26 #include <android-base/strings.h>
27 #include <fruit/fruit.h>
28 
29 #include "common/libs/fs/shared_fd.h"
30 #include "common/libs/utils/environment.h"
31 #include "common/libs/utils/files.h"
32 #include "common/libs/utils/result.h"
33 
34 namespace cuttlefish {
35 
InstanceLockFile(SharedFD fd,int instance_num)36 InstanceLockFile::InstanceLockFile(SharedFD fd, int instance_num)
37     : fd_(fd), instance_num_(instance_num) {}
38 
Instance() const39 int InstanceLockFile::Instance() const { return instance_num_; }
40 
Status() const41 Result<InUseState> InstanceLockFile::Status() const {
42   CF_EXPECT(fd_->LSeek(0, SEEK_SET) == 0, fd_->StrError());
43   char state_char = static_cast<char>(InUseState::kNotInUse);
44   CF_EXPECT(fd_->Read(&state_char, 1) >= 0, fd_->StrError());
45   switch (state_char) {
46     case static_cast<char>(InUseState::kInUse):
47       return InUseState::kInUse;
48     case static_cast<char>(InUseState::kNotInUse):
49       return InUseState::kNotInUse;
50     default:
51       return CF_ERR("Unexpected state value \"" << state_char << "\"");
52   }
53 }
54 
Status(InUseState state)55 Result<void> InstanceLockFile::Status(InUseState state) {
56   CF_EXPECT(fd_->LSeek(0, SEEK_SET) == 0, fd_->StrError());
57   char state_char = static_cast<char>(state);
58   CF_EXPECT(fd_->Write(&state_char, 1) == 1, fd_->StrError());
59   return {};
60 }
61 
operator <(const InstanceLockFile & other) const62 bool InstanceLockFile::operator<(const InstanceLockFile& other) const {
63   if (instance_num_ != other.instance_num_) {
64     return instance_num_ < other.instance_num_;
65   }
66   return fd_ < other.fd_;
67 }
68 
69 InstanceLockFileManager::InstanceLockFileManager() = default;
70 
71 // Replicates tempfile.gettempdir() in Python
TempDir()72 std::string TempDir() {
73   std::vector<std::string> try_dirs = {
74       StringFromEnv("TMPDIR", ""),
75       StringFromEnv("TEMP", ""),
76       StringFromEnv("TMP", ""),
77       "/tmp",
78       "/var/tmp",
79       "/usr/tmp",
80   };
81   for (const auto& try_dir : try_dirs) {
82     if (DirectoryExists(try_dir)) {
83       return try_dir;
84     }
85   }
86   return CurrentDirectory();
87 }
88 
OpenLockFile(int instance_num)89 static Result<SharedFD> OpenLockFile(int instance_num) {
90   std::stringstream path;
91   path << TempDir() << "/acloud_cvd_temp/";
92   CF_EXPECT(EnsureDirectoryExists(path.str()));
93   path << "local-instance-" << instance_num << ".lock";
94   auto fd = SharedFD::Open(path.str(), O_CREAT | O_RDWR, 0666);
95   CF_EXPECT(fd->IsOpen(), "open(\"" << path.str() << "\"): " << fd->StrError());
96   return fd;
97 }
98 
AcquireLock(int instance_num)99 Result<InstanceLockFile> InstanceLockFileManager::AcquireLock(
100     int instance_num) {
101   auto fd = CF_EXPECT(OpenLockFile(instance_num));
102   CF_EXPECT(fd->Flock(LOCK_EX), fd->StrError());
103   return InstanceLockFile(fd, instance_num);
104 }
105 
AcquireLocks(const std::set<int> & instance_nums)106 Result<std::set<InstanceLockFile>> InstanceLockFileManager::AcquireLocks(
107     const std::set<int>& instance_nums) {
108   std::set<InstanceLockFile> locks;
109   for (const auto& num : instance_nums) {
110     locks.emplace(CF_EXPECT(AcquireLock(num)));
111   }
112   return locks;
113 }
114 
TryAcquireLock(int instance_num)115 Result<std::optional<InstanceLockFile>> InstanceLockFileManager::TryAcquireLock(
116     int instance_num) {
117   auto fd = CF_EXPECT(OpenLockFile(instance_num));
118   int flock_result = fd->Flock(LOCK_EX | LOCK_NB);
119   if (flock_result == 0) {
120     return InstanceLockFile(fd, instance_num);
121   } else if (flock_result == -1 && fd->GetErrno() == EWOULDBLOCK) {
122     return {};
123   }
124   return CF_ERR("flock " << instance_num << " failed: " << fd->StrError());
125 }
126 
TryAcquireLocks(const std::set<int> & instance_nums)127 Result<std::set<InstanceLockFile>> InstanceLockFileManager::TryAcquireLocks(
128     const std::set<int>& instance_nums) {
129   std::set<InstanceLockFile> locks;
130   for (const auto& num : instance_nums) {
131     auto lock = CF_EXPECT(TryAcquireLock(num));
132     if (lock) {
133       locks.emplace(std::move(*lock));
134     }
135   }
136   return locks;
137 }
138 
AllInstanceNums()139 static Result<std::set<int>> AllInstanceNums() {
140   // Estimate this by looking at available tap devices
141   // clang-format off
142   /** Sample format:
143 Inter-|   Receive                                                |  Transmit
144  face |bytes    packets errs drop fifo frame compressed multicast|bytes    packets errs drop fifo colls carrier compressed
145 cvd-wtap-02:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0
146   */
147   // clang-format on
148   static constexpr char kPath[] = "/proc/net/dev";
149   std::string proc_net_dev;
150   using android::base::ReadFileToString;
151   CF_EXPECT(ReadFileToString(kPath, &proc_net_dev, /* follow_symlinks */ true));
152   auto lines = android::base::Split(proc_net_dev, "\n");
153   std::set<int> etaps, mtaps, wtaps;
154   for (const auto& line : lines) {
155     std::set<int>* tap_set = nullptr;
156     if (android::base::StartsWith(line, "cvd-etap-")) {
157       tap_set = &etaps;
158     } else if (android::base::StartsWith(line, "cvd-mtap-")) {
159       tap_set = &mtaps;
160     } else if (android::base::StartsWith(line, "cvd-wtap-")) {
161       tap_set = &wtaps;
162     } else {
163       continue;
164     }
165     tap_set->insert(std::stoi(line.substr(std::string{"cvd-etap-"}.size())));
166   }
167   std::set<int> emtaps;
168   std::set_intersection(etaps.begin(), etaps.end(), mtaps.begin(), mtaps.end(),
169                         std::inserter(emtaps, emtaps.begin()));
170   std::set<int> emwtaps;
171   std::set_intersection(emtaps.begin(), emtaps.end(), wtaps.begin(),
172                         wtaps.end(), std::inserter(emwtaps, emwtaps.begin()));
173   return emwtaps;
174 }
175 
176 Result<std::optional<InstanceLockFile>>
TryAcquireUnusedLock()177 InstanceLockFileManager::TryAcquireUnusedLock() {
178   auto nums = CF_EXPECT(AllInstanceNums());
179   for (const auto& num : nums) {
180     auto lock = CF_EXPECT(TryAcquireLock(num));
181     if (lock && CF_EXPECT(lock->Status()) == InUseState::kNotInUse) {
182       return std::move(*lock);
183     }
184   }
185   return {};
186 }
187 
188 }  // namespace cuttlefish
189