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