Serenity Operating System
1/*
2 * Copyright (c) 2020, Sergey Bugaev <bugaevc@serenityos.org>
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include <Kernel/FileSystem/Plan9FS/FileSystem.h>
8#include <Kernel/FileSystem/Plan9FS/Inode.h>
9#include <Kernel/Process.h>
10
11namespace Kernel {
12
13ErrorOr<NonnullLockRefPtr<FileSystem>> Plan9FS::try_create(OpenFileDescription& file_description)
14{
15 return TRY(adopt_nonnull_lock_ref_or_enomem(new (nothrow) Plan9FS(file_description)));
16}
17
18Plan9FS::Plan9FS(OpenFileDescription& file_description)
19 : FileBackedFileSystem(file_description)
20 , m_completion_blocker(*this)
21{
22}
23
24ErrorOr<void> Plan9FS::prepare_to_clear_last_mount()
25{
26 // FIXME: Do proper cleaning here.
27 return {};
28}
29
30Plan9FS::~Plan9FS()
31{
32 // Make sure to destroy the root inode before the FS gets destroyed.
33 if (m_root_inode) {
34 VERIFY(m_root_inode->ref_count() == 1);
35 m_root_inode = nullptr;
36 }
37}
38
39bool Plan9FS::is_initialized_while_locked()
40{
41 VERIFY(m_lock.is_locked());
42 return !m_root_inode.is_null();
43}
44
45ErrorOr<void> Plan9FS::initialize_while_locked()
46{
47 VERIFY(m_lock.is_locked());
48 VERIFY(!is_initialized_while_locked());
49
50 ensure_thread();
51
52 Plan9FSMessage version_message { *this, Plan9FSMessage::Type::Tversion };
53 version_message << (u32)m_max_message_size << "9P2000.L"sv;
54
55 TRY(post_message_and_wait_for_a_reply(version_message));
56
57 u32 msize;
58 StringView remote_protocol_version;
59 version_message >> msize >> remote_protocol_version;
60 dbgln("Remote supports msize={} and protocol version {}", msize, remote_protocol_version);
61 m_remote_protocol_version = parse_protocol_version(remote_protocol_version);
62 m_max_message_size = min(m_max_message_size, (size_t)msize);
63
64 // TODO: auth
65
66 u32 root_fid = allocate_fid();
67 Plan9FSMessage attach_message { *this, Plan9FSMessage::Type::Tattach };
68 // FIXME: This needs a user name and an "export" name; but how do we get them?
69 // Perhaps initialize() should accept a string of FS-specific options...
70 attach_message << root_fid << (u32)-1 << "sergey"sv
71 << "/"sv;
72 if (m_remote_protocol_version >= ProtocolVersion::v9P2000u)
73 attach_message << (u32)-1;
74
75 TRY(post_message_and_wait_for_a_reply(attach_message));
76 m_root_inode = TRY(Plan9FSInode::try_create(*this, root_fid));
77 return {};
78}
79
80Plan9FS::ProtocolVersion Plan9FS::parse_protocol_version(StringView s) const
81{
82 if (s == "9P2000.L")
83 return ProtocolVersion::v9P2000L;
84 if (s == "9P2000.u")
85 return ProtocolVersion::v9P2000u;
86 return ProtocolVersion::v9P2000;
87}
88
89Inode& Plan9FS::root_inode()
90{
91 return *m_root_inode;
92}
93
94Plan9FS::ReceiveCompletion::ReceiveCompletion(u16 tag)
95 : tag(tag)
96{
97}
98
99Plan9FS::ReceiveCompletion::~ReceiveCompletion() = default;
100
101bool Plan9FS::Blocker::unblock(u16 tag)
102{
103 {
104 SpinlockLocker lock(m_lock);
105 if (m_did_unblock)
106 return false;
107 m_did_unblock = true;
108
109 if (m_completion->tag != tag)
110 return false;
111 if (!m_completion->result.is_error())
112 m_message = move(*m_completion->message);
113 }
114 return unblock();
115}
116
117bool Plan9FS::Blocker::setup_blocker()
118{
119 return add_to_blocker_set(m_fs.m_completion_blocker);
120}
121
122void Plan9FS::Blocker::will_unblock_immediately_without_blocking(UnblockImmediatelyReason)
123{
124 {
125 SpinlockLocker lock(m_lock);
126 if (m_did_unblock)
127 return;
128 }
129
130 m_fs.m_completion_blocker.try_unblock(*this);
131}
132
133bool Plan9FS::Blocker::is_completed() const
134{
135 SpinlockLocker lock(m_completion->lock);
136 return m_completion->completed;
137}
138
139bool Plan9FS::Plan9FSBlockerSet::should_add_blocker(Thread::Blocker& b, void*)
140{
141 // NOTE: m_lock is held already!
142 auto& blocker = static_cast<Blocker&>(b);
143 return !blocker.is_completed();
144}
145
146void Plan9FS::Plan9FSBlockerSet::unblock_completed(u16 tag)
147{
148 unblock_all_blockers_whose_conditions_are_met([&](Thread::Blocker& b, void*, bool&) {
149 VERIFY(b.blocker_type() == Thread::Blocker::Type::Plan9FS);
150 auto& blocker = static_cast<Blocker&>(b);
151 return blocker.unblock(tag);
152 });
153}
154
155void Plan9FS::Plan9FSBlockerSet::unblock_all()
156{
157 unblock_all_blockers_whose_conditions_are_met([&](Thread::Blocker& b, void*, bool&) {
158 VERIFY(b.blocker_type() == Thread::Blocker::Type::Plan9FS);
159 auto& blocker = static_cast<Blocker&>(b);
160 return blocker.unblock();
161 });
162}
163
164void Plan9FS::Plan9FSBlockerSet::try_unblock(Plan9FS::Blocker& blocker)
165{
166 if (m_fs.is_complete(*blocker.completion())) {
167 SpinlockLocker lock(m_lock);
168 blocker.unblock(blocker.completion()->tag);
169 }
170}
171
172bool Plan9FS::is_complete(ReceiveCompletion const& completion)
173{
174 MutexLocker locker(m_lock);
175 if (m_completions.contains(completion.tag)) {
176 // If it's still in the map then it can't be complete
177 VERIFY(!completion.completed);
178 return false;
179 }
180
181 // if it's not in the map anymore, it must be complete. But we MUST
182 // hold m_lock to be able to check completion.completed!
183 VERIFY(completion.completed);
184 return true;
185}
186
187ErrorOr<void> Plan9FS::post_message(Plan9FSMessage& message, LockRefPtr<ReceiveCompletion> completion)
188{
189 auto const& buffer = message.build();
190 u8 const* data = buffer.data();
191 size_t size = buffer.size();
192 auto& description = file_description();
193
194 MutexLocker locker(m_send_lock);
195
196 if (completion) {
197 // Save the completion record *before* we send the message. This
198 // ensures that it exists when the thread reads the response
199 MutexLocker locker(m_lock);
200 auto tag = completion->tag;
201 m_completions.set(tag, completion.release_nonnull());
202 // TODO: What if there is a collision? Do we need to wait until
203 // the existing record with the tag completes before queueing
204 // this one?
205 }
206
207 while (size > 0) {
208 if (!description.can_write()) {
209 auto unblock_flags = Thread::FileBlocker::BlockFlags::None;
210 if (Thread::current()->block<Thread::WriteBlocker>({}, description, unblock_flags).was_interrupted())
211 return EINTR;
212 }
213 auto data_buffer = UserOrKernelBuffer::for_kernel_buffer(const_cast<u8*>(data));
214 auto nwritten = TRY(description.write(data_buffer, size));
215 data += nwritten;
216 size -= nwritten;
217 }
218
219 return {};
220}
221
222ErrorOr<void> Plan9FS::do_read(u8* data, size_t size)
223{
224 auto& description = file_description();
225 while (size > 0) {
226 if (!description.can_read()) {
227 auto unblock_flags = Thread::FileBlocker::BlockFlags::None;
228 if (Thread::current()->block<Thread::ReadBlocker>({}, description, unblock_flags).was_interrupted())
229 return EINTR;
230 }
231 auto data_buffer = UserOrKernelBuffer::for_kernel_buffer(data);
232 auto nread = TRY(description.read(data_buffer, size));
233 if (nread == 0)
234 return EIO;
235 data += nread;
236 size -= nread;
237 }
238 return {};
239}
240
241ErrorOr<void> Plan9FS::read_and_dispatch_one_message()
242{
243 struct [[gnu::packed]] Header {
244 u32 size;
245 u8 type;
246 u16 tag;
247 };
248 Header header;
249 TRY(do_read(reinterpret_cast<u8*>(&header), sizeof(header)));
250
251 auto buffer = TRY(KBuffer::try_create_with_size("Plan9FS: Plan9FSMessage read buffer"sv, header.size, Memory::Region::Access::ReadWrite));
252 // Copy the already read header into the buffer.
253 memcpy(buffer->data(), &header, sizeof(header));
254 TRY(do_read(buffer->data() + sizeof(header), header.size - sizeof(header)));
255
256 MutexLocker locker(m_lock);
257
258 auto optional_completion = m_completions.get(header.tag);
259 if (optional_completion.has_value()) {
260 auto* completion = optional_completion.value();
261 SpinlockLocker lock(completion->lock);
262 completion->result = {};
263 completion->message = adopt_own_if_nonnull(new (nothrow) Plan9FSMessage { move(buffer) });
264 completion->completed = true;
265
266 m_completions.remove(header.tag);
267 m_completion_blocker.unblock_completed(header.tag);
268 } else {
269 dbgln("Received a 9p message of type {} with an unexpected tag {}, dropping", header.type, header.tag);
270 }
271
272 return {};
273}
274
275ErrorOr<void> Plan9FS::post_message_and_explicitly_ignore_reply(Plan9FSMessage& message)
276{
277 return post_message(message, {});
278}
279
280ErrorOr<void> Plan9FS::post_message_and_wait_for_a_reply(Plan9FSMessage& message)
281{
282 auto request_type = message.type();
283 auto tag = message.tag();
284 auto completion = adopt_lock_ref(*new ReceiveCompletion(tag));
285 TRY(post_message(message, completion));
286 if (Thread::current()->block<Plan9FS::Blocker>({}, *this, message, completion).was_interrupted())
287 return EINTR;
288
289 if (completion->result.is_error()) {
290 dbgln("Plan9FS: Plan9FSMessage was aborted with error {}", completion->result.error());
291 return EIO;
292 }
293
294 auto reply_type = message.type();
295
296 if (reply_type == Plan9FSMessage::Type::Rlerror) {
297 // Contains a numerical Linux errno; hopefully our errno numbers match.
298 u32 error_code;
299 message >> error_code;
300 return Error::from_errno((ErrnoCode)error_code);
301 }
302 if (reply_type == Plan9FSMessage::Type::Rerror) {
303 // Contains an error message. We could attempt to parse it, but for now
304 // we simply return EIO instead. In 9P200.u, it can also contain a
305 // numerical errno in an unspecified encoding; we ignore those too.
306 StringView error_name;
307 message >> error_name;
308 dbgln("Plan9FS: Received error name {}", error_name);
309 return EIO;
310 }
311 if ((u8)reply_type != (u8)request_type + 1) {
312 // Other than those error messages. we only expect the matching reply
313 // message type.
314 dbgln("Plan9FS: Received unexpected message type {} in response to {}", (u8)reply_type, (u8)request_type);
315 return EIO;
316 }
317
318 return {};
319}
320
321size_t Plan9FS::adjust_buffer_size(size_t size) const
322{
323 size_t max_size = m_max_message_size - Plan9FSMessage::max_header_size;
324 return min(size, max_size);
325}
326
327void Plan9FS::thread_main()
328{
329 dbgln("Plan9FS: Thread running");
330 do {
331 auto result = read_and_dispatch_one_message();
332 if (result.is_error()) {
333 // If we fail to read, wake up everyone with an error.
334 MutexLocker locker(m_lock);
335
336 for (auto& it : m_completions) {
337 it.value->result = Error::copy(result.error());
338 it.value->completed = true;
339 }
340 m_completions.clear();
341 m_completion_blocker.unblock_all();
342 dbgln("Plan9FS: Thread terminating, error reading");
343 return;
344 }
345 } while (!m_thread_shutdown);
346 dbgln("Plan9FS: Thread terminating");
347}
348
349void Plan9FS::ensure_thread()
350{
351 SpinlockLocker lock(m_thread_lock);
352 if (!m_thread_running.exchange(true, AK::MemoryOrder::memory_order_acq_rel)) {
353 auto process_name = KString::try_create("Plan9FS"sv);
354 if (process_name.is_error())
355 TODO();
356 (void)Process::create_kernel_process(m_thread, process_name.release_value(), [&]() {
357 thread_main();
358 m_thread_running.store(false, AK::MemoryOrder::memory_order_release);
359 });
360 }
361}
362
363}