xref: /aosp_15_r20/external/pigweed/pw_digital_io_linux/mock_vfs.cc (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1 // Copyright 2024 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 #include "mock_vfs.h"
15 
16 #include <sys/eventfd.h>
17 #include <unistd.h>
18 
19 #include <cinttypes>
20 
21 #include "log_errno.h"
22 #include "pw_assert/check.h"
23 
24 extern "C" {
25 
26 decltype(close) __real_close;
27 decltype(read) __real_read;
28 #define __real_write ::write  // Not (yet) wraapped
29 
30 }  // extern "C"
31 
32 // TODO(b/328262654): Move this to a more appropriate module.
33 namespace pw::digital_io {
34 
GetMockVfs()35 MockVfs& GetMockVfs() {
36   static MockVfs vfs;
37   return vfs;
38 }
39 
40 // MockVfs
41 
GetFile(const int fd) const42 MockFile* MockVfs::GetFile(const int fd) const {
43   if (auto it = open_files_.find(fd); it != open_files_.end()) {
44     return it->second.get();
45   }
46   return nullptr;
47 }
48 
IsMockFd(const int fd)49 bool MockVfs::IsMockFd(const int fd) { return GetFile(fd) != nullptr; }
50 
GetEventFd()51 int MockVfs::GetEventFd() {
52   // All files are backed by a real (kernel) eventfd.
53   const int fd = ::eventfd(0, EFD_SEMAPHORE | EFD_CLOEXEC | EFD_NONBLOCK);
54   PW_CHECK_INT_GE(fd,
55                   0,
56                   "eventfd() failed: " ERRNO_FORMAT_STRING,
57                   ERRNO_FORMAT_ARGS(errno));
58 
59   // There should be no existing file registered with this eventfd.
60   PW_CHECK_PTR_EQ(GetFile(fd), nullptr);
61 
62   return fd;
63 }
64 
InstallFile(std::unique_ptr<MockFile> && file)65 int MockVfs::InstallFile(std::unique_ptr<MockFile>&& file) {
66   // All files are backed by a real (kernel) eventfd.
67   const int fd = file->eventfd();
68 
69   PW_LOG_DEBUG("Installing fd %d: \"%s\"", fd, file->name().c_str());
70 
71   auto [_, inserted] = open_files_.try_emplace(fd, std::move(file));
72   PW_CHECK(inserted);
73 
74   return fd;
75 }
76 
Reset()77 void MockVfs::Reset() {
78   for (const auto& [fd, file] : open_files_) {
79     file->Close();
80   }
81   open_files_.clear();
82 }
83 
AllFdsClosed() const84 bool MockVfs::AllFdsClosed() const { return open_files_.empty(); }
85 
mock_close(int fd)86 int MockVfs::mock_close(int fd) {
87   // Attempt to remove the file from the map.
88   auto node = open_files_.extract(fd);
89   if (!node) {
90     // TODO: https://pwbug.dev/338269682 - The return value of close(2) is
91     // frequently ignored, so provide a hook here to let consumers handle this
92     // error.
93     errno = EBADF;
94     return -1;
95   }
96   return node.mapped()->Close();
97 }
98 
mock_ioctl(int fd,unsigned long request,void * arg)99 int MockVfs::mock_ioctl(int fd, unsigned long request, void* arg) {
100   auto* file = GetFile(fd);
101   if (!file) {
102     errno = EBADF;
103     return -1;
104   }
105   return file->Ioctl(request, arg);
106 }
107 
mock_read(int fd,void * buf,size_t count)108 ssize_t MockVfs::mock_read(int fd, void* buf, size_t count) {
109   auto* file = GetFile(fd);
110   if (!file) {
111     errno = EBADF;
112     return -1;
113   }
114   return file->Read(buf, count);
115 }
116 
117 // MockFile
118 
Close()119 int MockFile::Close() {
120   int result = DoClose();
121 
122   // Close the real eventfd
123   PW_CHECK_INT_NE(eventfd_, kInvalidFd);
124   PW_CHECK_INT_EQ(__real_close(eventfd_), 0);
125   eventfd_ = kInvalidFd;
126 
127   return result;
128 }
129 
WriteEventfd(uint64_t add)130 void MockFile::WriteEventfd(uint64_t add) {
131   const ssize_t ret = __real_write(eventfd_, &add, sizeof(add));
132   PW_CHECK(ret == sizeof(add));
133 }
134 
ReadEventfd()135 uint64_t MockFile::ReadEventfd() {
136   uint64_t val;
137   ssize_t nread = __real_read(eventfd_, &val, sizeof(val));
138   if (nread == -1 && errno == EAGAIN) {
139     return 0;
140   }
141   PW_CHECK_INT_EQ(nread, sizeof(val));
142   return val;
143 }
144 
145 ////////////////////////////////////////////////////////////////////////////////
146 // Syscalls wrapped via --wrap
147 
148 #include <unistd.h>
149 
150 extern "C" {
151 
152 // close()
153 
154 decltype(close) __wrap_close;
155 
__wrap_close(int fd)156 int __wrap_close(int fd) {
157   auto& vfs = GetMockVfs();
158   if (vfs.IsMockFd(fd)) {
159     return vfs.mock_close(fd);
160   }
161   return __real_close(fd);
162 }
163 
164 // ioctl()
165 
166 // ioctl() is actually variadic (third arg is ...), but there's no way
167 // (https://c-faq.com/varargs/handoff.html) to forward the args when invoked
168 // that way, so we lie and use void*.
169 int __real_ioctl(int fd, unsigned long request, void* arg);
170 int __wrap_ioctl(int fd, unsigned long request, void* arg);
171 
__wrap_ioctl(int fd,unsigned long request,void * arg)172 int __wrap_ioctl(int fd, unsigned long request, void* arg) {
173   auto& vfs = GetMockVfs();
174   if (vfs.IsMockFd(fd)) {
175     return vfs.mock_ioctl(fd, request, arg);
176   }
177   return __real_ioctl(fd, request, arg);
178 }
179 
180 // read()
181 
182 decltype(read) __wrap_read;
183 
__wrap_read(int fd,void * buf,size_t nbytes)184 ssize_t __wrap_read(int fd, void* buf, size_t nbytes) {
185   auto& vfs = GetMockVfs();
186   if (vfs.IsMockFd(fd)) {
187     return vfs.mock_read(fd, buf, nbytes);
188   }
189   return __real_read(fd, buf, nbytes);
190 }
191 
192 }  // extern "C"
193 
194 }  // namespace pw::digital_io
195