1 /*
2  * Copyright (C) 2023 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/lock_file.h"
18 
19 #include <sys/file.h>
20 
21 #include <algorithm>
22 #include <cstring>
23 #include <sstream>
24 #include <vector>
25 
26 #include <android-base/file.h>
27 #include <android-base/strings.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 namespace cvd_impl {
36 
LockFile(SharedFD fd,const std::string & lock_file_path)37 LockFile::LockFile(SharedFD fd, const std::string& lock_file_path)
38     : fd_(std::move(fd)), lock_file_path_(lock_file_path) {}
39 
Status() const40 Result<InUseState> LockFile::Status() const {
41   CF_EXPECT(fd_->LSeek(0, SEEK_SET) == 0, fd_->StrError());
42   char state_char = static_cast<char>(InUseState::kNotInUse);
43   CF_EXPECT(fd_->Read(&state_char, 1) >= 0, fd_->StrError());
44   switch (state_char) {
45     case static_cast<char>(InUseState::kInUse):
46       return InUseState::kInUse;
47     case static_cast<char>(InUseState::kNotInUse):
48       return InUseState::kNotInUse;
49     default:
50       return CF_ERR("Unexpected state value \"" << state_char << "\"");
51   }
52 }
53 
Status(InUseState state)54 Result<void> LockFile::Status(InUseState state) {
55   CF_EXPECT(fd_->LSeek(0, SEEK_SET) == 0, fd_->StrError());
56   char state_char = static_cast<char>(state);
57   CF_EXPECT(fd_->Write(&state_char, 1) == 1, fd_->StrError());
58   return {};
59 }
60 
operator <(const LockFile & other) const61 bool LockFile::operator<(const LockFile& other) const {
62   if (this == std::addressof(other)) {
63     return false;
64   }
65   if (LockFilePath() == other.LockFilePath()) {
66     return fd_ < other.fd_;
67   }
68   // operator< for std::string will be gone as of C++20
69   return (strncmp(lock_file_path_.data(), other.LockFilePath().data(),
70                   std::max(lock_file_path_.size(),
71                            other.LockFilePath().size())) < 0);
72 }
73 
OpenLockFile(const std::string & file_path)74 Result<SharedFD> LockFileManager::OpenLockFile(const std::string& file_path) {
75   auto parent_dir = android::base::Dirname(file_path);
76   CF_EXPECT(EnsureDirectoryExists(parent_dir));
77   auto fd = SharedFD::Open(file_path.data(), O_CREAT | O_RDWR, 0666);
78   CF_EXPECT(fd->IsOpen(), "open(\"" << file_path << "\"): " << fd->StrError());
79   return fd;
80 }
81 
AcquireLock(const std::string & lock_file_path)82 Result<LockFile> LockFileManager::AcquireLock(
83     const std::string& lock_file_path) {
84   auto fd = CF_EXPECT(OpenLockFile(lock_file_path));
85   CF_EXPECT(fd->Flock(LOCK_EX));
86   return LockFile(fd, lock_file_path);
87 }
88 
AcquireLocks(const std::set<std::string> & lock_file_paths)89 Result<std::set<LockFile>> LockFileManager::AcquireLocks(
90     const std::set<std::string>& lock_file_paths) {
91   std::set<LockFile> locks;
92   for (const auto& lock_file_path : lock_file_paths) {
93     locks.emplace(CF_EXPECT(AcquireLock(lock_file_path)));
94   }
95   return locks;
96 }
97 
TryAcquireLock(const std::string & lock_file_path)98 Result<std::optional<LockFile>> LockFileManager::TryAcquireLock(
99     const std::string& lock_file_path) {
100   auto fd = CF_EXPECT(OpenLockFile(lock_file_path));
101   auto flock_result = fd->Flock(LOCK_EX | LOCK_NB);
102   if (flock_result.ok()) {
103     return std::optional<LockFile>(LockFile(fd, lock_file_path));
104     // TODO(schuffelen): Include the error code in the Result
105   } else if (!flock_result.ok() && fd->GetErrno() == EWOULDBLOCK) {
106     return {};
107   }
108   CF_EXPECT(std::move(flock_result));
109   return {};
110 }
111 
TryAcquireLocks(const std::set<std::string> & lock_file_paths)112 Result<std::set<LockFile>> LockFileManager::TryAcquireLocks(
113     const std::set<std::string>& lock_file_paths) {
114   std::set<LockFile> locks;
115   for (const auto& lock_file_path : lock_file_paths) {
116     auto lock = CF_EXPECT(TryAcquireLock(lock_file_path));
117     if (lock) {
118       locks.emplace(std::move(*lock));
119     }
120   }
121   return locks;
122 }
123 
124 }  // namespace cvd_impl
125 
126 // Replicates tempfile.gettempdir() in Python
TempDir()127 std::string TempDir() {
128   std::vector<std::string> try_dirs = {
129       StringFromEnv("TMPDIR", ""),
130       StringFromEnv("TEMP", ""),
131       StringFromEnv("TMP", ""),
132       "/tmp",
133       "/var/tmp",
134       "/usr/tmp",
135   };
136   for (const auto& try_dir : try_dirs) {
137     if (DirectoryExists(try_dir)) {
138       return try_dir;
139     }
140   }
141   return CurrentDirectory();
142 }
143 
144 }  // namespace cuttlefish
145