Serenity Operating System
1/*
2 * Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
3 * Copyright (c) 2021, Spencer Dixon <spencercdixon@gmail.com>
4 * Copyright (c) 2021-2023, Liav A. <liavalb@hotmail.co.il>
5 *
6 * SPDX-License-Identifier: BSD-2-Clause
7 */
8
9#include <Kernel/FileSystem/ProcFS/Inode.h>
10#include <Kernel/Process.h>
11#include <Kernel/Time/TimeManagement.h>
12
13namespace Kernel {
14
15ProcFSInode::~ProcFSInode() = default;
16
17static mode_t determine_procfs_process_inode_mode(u32 subdirectory, u32 property)
18{
19 if (subdirectory == process_fd_subdirectory_root_entry.subdirectory)
20 return S_IFLNK | 0400;
21 if (subdirectory == process_stacks_subdirectory_root_entry.subdirectory)
22 return S_IFREG | 0400;
23 if (subdirectory == process_children_subdirectory_root_entry.subdirectory)
24 return S_IFLNK | 0400;
25 VERIFY(subdirectory == main_process_directory_root_entry.subdirectory);
26 if (property == process_exe_symlink_entry.property)
27 return S_IFLNK | 0777;
28 if (property == process_cwd_symlink_entry.property)
29 return S_IFLNK | 0777;
30 return S_IFREG | 0400;
31}
32
33static u16 extract_subdirectory_index_from_inode_index(InodeIndex inode_index)
34{
35 return (inode_index.value() >> 20) & 0xFFFF;
36}
37
38static u32 extract_property_index_from_inode_index(InodeIndex inode_index)
39{
40 return inode_index.value() & 0xFFFFF;
41}
42
43InodeIndex ProcFSInode::create_index_from_global_directory_entry(segmented_global_inode_index entry)
44{
45 u64 inode_index = 0;
46 VERIFY(entry.primary < 0x10000000);
47 u64 tmp = entry.primary;
48 inode_index |= tmp << 36;
49
50 // NOTE: The sub-directory part is already limited to 0xFFFF, so no need to VERIFY it.
51 tmp = entry.subdirectory;
52 inode_index |= tmp << 20;
53
54 VERIFY(entry.property < 0x100000);
55 inode_index |= entry.property;
56 return inode_index;
57}
58
59InodeIndex ProcFSInode::create_index_from_process_directory_entry(ProcessID pid, segmented_process_directory_entry entry)
60{
61 u64 inode_index = 0;
62 // NOTE: We use 0xFFFFFFF because PID part (bits 64-36) as 0 is reserved for global inodes.
63 VERIFY(pid.value() < 0xFFFFFFF);
64 u64 tmp = (pid.value() + 1);
65 inode_index |= tmp << 36;
66 // NOTE: The sub-directory part is already limited to 0xFFFF, so no need to VERIFY it.
67 tmp = entry.subdirectory;
68 inode_index |= tmp << 20;
69 VERIFY(entry.property < 0x100000);
70 inode_index |= entry.property;
71 return inode_index;
72}
73
74static Optional<ProcessID> extract_possible_pid_from_inode_index(InodeIndex inode_index)
75{
76 auto pid_part = inode_index.value() >> 36;
77 // NOTE: pid_part is set to 0 for global inodes.
78 if (pid_part == 0)
79 return {};
80 return pid_part - 1;
81}
82
83ProcFSInode::ProcFSInode(ProcFS const& procfs_instance, InodeIndex inode_index)
84 : Inode(const_cast<ProcFS&>(procfs_instance), inode_index)
85 , m_associated_pid(extract_possible_pid_from_inode_index(inode_index))
86 , m_subdirectory(extract_subdirectory_index_from_inode_index(inode_index))
87 , m_property(extract_property_index_from_inode_index(inode_index))
88{
89 if (inode_index == 1) {
90 m_type = Type::RootDirectory;
91 return;
92 }
93 if (inode_index == 2) {
94 m_type = Type::SelfProcessLink;
95 return;
96 }
97
98 if (m_property == 0) {
99 if (m_subdirectory > 0)
100 m_type = Type::ProcessSubdirectory;
101 else
102 m_type = Type::ProcessDirectory;
103 return;
104 }
105
106 m_type = Type::ProcessProperty;
107}
108
109ErrorOr<void> ProcFSInode::traverse_as_root_directory(Function<ErrorOr<void>(FileSystem::DirectoryEntryView const&)> callback) const
110{
111 TRY(callback({ "."sv, { fsid(), 1 }, 0 }));
112 TRY(callback({ ".."sv, { fsid(), 0 }, 0 }));
113 TRY(callback({ "self"sv, { fsid(), 2 }, 0 }));
114
115 return Process::for_each_in_same_jail([&](Process& process) -> ErrorOr<void> {
116 VERIFY(!(process.pid() < 0));
117 u64 process_id = (u64)process.pid().value();
118 InodeIdentifier identifier = { fsid(), static_cast<InodeIndex>(process_id << 36) };
119 auto process_id_string = TRY(KString::formatted("{:d}", process_id));
120 TRY(callback({ process_id_string->view(), identifier, 0 }));
121 return {};
122 });
123}
124
125ErrorOr<void> ProcFSInode::traverse_as_directory(Function<ErrorOr<void>(FileSystem::DirectoryEntryView const&)> callback) const
126{
127 MutexLocker locker(procfs().m_lock);
128 if (m_type == Type::ProcessSubdirectory) {
129 VERIFY(m_associated_pid.has_value());
130 auto process = Process::from_pid_in_same_jail(m_associated_pid.value());
131 if (!process)
132 return EINVAL;
133 switch (m_subdirectory) {
134 case process_fd_subdirectory_root_entry.subdirectory:
135 return process->traverse_file_descriptions_directory(procfs().fsid(), move(callback));
136 case process_stacks_subdirectory_root_entry.subdirectory:
137 return process->traverse_stacks_directory(procfs().fsid(), move(callback));
138 case process_children_subdirectory_root_entry.subdirectory:
139 return process->traverse_children_directory(procfs().fsid(), move(callback));
140 default:
141 VERIFY_NOT_REACHED();
142 }
143 VERIFY_NOT_REACHED();
144 }
145
146 if (m_type == Type::RootDirectory) {
147 return traverse_as_root_directory(move(callback));
148 }
149
150 VERIFY(m_type == Type::ProcessDirectory);
151 VERIFY(m_associated_pid.has_value());
152 auto process = Process::from_pid_in_same_jail(m_associated_pid.value());
153 if (!process)
154 return EINVAL;
155 return process->traverse_as_directory(procfs().fsid(), move(callback));
156}
157
158ErrorOr<NonnullRefPtr<Inode>> ProcFSInode::lookup_as_root_directory(StringView name)
159{
160 if (name == "self"sv)
161 return procfs().get_inode({ fsid(), 2 });
162
163 auto pid = name.to_uint<unsigned>();
164 if (!pid.has_value())
165 return ESRCH;
166 auto actual_pid = pid.value();
167
168 if (auto maybe_process = Process::from_pid_in_same_jail(actual_pid)) {
169 InodeIndex id = (static_cast<u64>(maybe_process->pid().value()) + 1) << 36;
170 return procfs().get_inode({ fsid(), id });
171 }
172 return ENOENT;
173}
174
175ErrorOr<NonnullRefPtr<Inode>> ProcFSInode::lookup(StringView name)
176{
177 MutexLocker locker(procfs().m_lock);
178 if (m_type == Type::ProcessSubdirectory) {
179 VERIFY(m_associated_pid.has_value());
180 auto process = Process::from_pid_in_same_jail(m_associated_pid.value());
181 if (!process)
182 return ESRCH;
183 switch (m_subdirectory) {
184 case process_fd_subdirectory_root_entry.subdirectory:
185 return process->lookup_file_descriptions_directory(procfs(), name);
186 case process_stacks_subdirectory_root_entry.subdirectory:
187 return process->lookup_stacks_directory(procfs(), name);
188 case process_children_subdirectory_root_entry.subdirectory:
189 return process->lookup_children_directory(procfs(), name);
190 default:
191 VERIFY_NOT_REACHED();
192 }
193 VERIFY_NOT_REACHED();
194 }
195
196 if (m_type == Type::RootDirectory) {
197 return lookup_as_root_directory(name);
198 }
199
200 VERIFY(m_type == Type::ProcessDirectory);
201 VERIFY(m_associated_pid.has_value());
202 auto process = Process::from_pid_in_same_jail(m_associated_pid.value());
203 if (!process)
204 return ESRCH;
205 return process->lookup_as_directory(procfs(), name);
206}
207
208ErrorOr<void> ProcFSInode::attach(OpenFileDescription& description)
209{
210 if (m_type == Type::RootDirectory || m_type == Type::SelfProcessLink || m_type == Type::ProcessDirectory || m_type == Type::ProcessSubdirectory)
211 return {};
212 VERIFY(m_type == Type::ProcessProperty);
213 return refresh_process_property_data(description);
214}
215
216void ProcFSInode::did_seek(OpenFileDescription& description, off_t offset)
217{
218 if (m_type == Type::SelfProcessLink) {
219 return;
220 }
221 VERIFY(m_type == Type::ProcessProperty);
222 if (offset != 0)
223 return;
224 (void)refresh_process_property_data(description);
225}
226
227ErrorOr<size_t> ProcFSInode::read_bytes_locked(off_t offset, size_t count, UserOrKernelBuffer& buffer, OpenFileDescription* description) const
228{
229 dbgln_if(PROCFS_DEBUG, "ProcFSInode: read_bytes_locked offset: {} count: {}", offset, count);
230 VERIFY(offset >= 0);
231 VERIFY(buffer.user_or_kernel_ptr());
232
233 if (m_type == Type::SelfProcessLink) {
234 auto builder = TRY(KBufferBuilder::try_create());
235 TRY(builder.appendff("{}", Process::current().pid().value()));
236 auto data_buffer = builder.build();
237 if (!data_buffer)
238 return Error::from_errno(EFAULT);
239 if ((size_t)offset >= data_buffer->size())
240 return 0;
241 ssize_t nread = min(static_cast<off_t>(data_buffer->size() - offset), static_cast<off_t>(count));
242 TRY(buffer.write(data_buffer->data() + offset, nread));
243 return nread;
244 }
245
246 VERIFY(m_type == Type::ProcessProperty);
247
248 if (!description) {
249 auto builder = TRY(KBufferBuilder::try_create());
250 VERIFY(m_associated_pid.has_value());
251 auto process = Process::from_pid_in_same_jail(m_associated_pid.value());
252 if (!process)
253 return Error::from_errno(ESRCH);
254 TRY(try_fetch_process_property_data(*process, builder));
255 auto data_buffer = builder.build();
256 if (!data_buffer)
257 return Error::from_errno(EFAULT);
258 if ((size_t)offset >= data_buffer->size())
259 return 0;
260 ssize_t nread = min(static_cast<off_t>(data_buffer->size() - offset), static_cast<off_t>(count));
261 TRY(buffer.write(data_buffer->data() + offset, nread));
262 return nread;
263 }
264
265 if (!description->data()) {
266 dbgln("ProcFS Process Information: Do not have cached data!");
267 return Error::from_errno(EIO);
268 }
269
270 MutexLocker locker(m_refresh_lock);
271
272 auto& typed_cached_data = static_cast<ProcFSInodeData&>(*description->data());
273 auto& data_buffer = typed_cached_data.buffer;
274
275 if (!data_buffer || (size_t)offset >= data_buffer->size())
276 return 0;
277
278 ssize_t nread = min(static_cast<off_t>(data_buffer->size() - offset), static_cast<off_t>(count));
279 TRY(buffer.write(data_buffer->data() + offset, nread));
280 return nread;
281}
282
283static ErrorOr<void> build_from_cached_data(KBufferBuilder& builder, ProcFSInodeData& cached_data)
284{
285 cached_data.buffer = builder.build();
286 if (!cached_data.buffer)
287 return ENOMEM;
288 return {};
289}
290
291ErrorOr<void> ProcFSInode::try_fetch_process_property_data(NonnullLockRefPtr<Process> process, KBufferBuilder& builder) const
292{
293 VERIFY(m_type == Type::ProcessProperty);
294 if (m_subdirectory == process_fd_subdirectory_root_entry.subdirectory) {
295 // NOTE: All property numbers should start from 1 as 0 is reserved for the directory itself.
296 // Therefore subtract 1 to get the actual correct fd number.
297 TRY(process->procfs_get_file_description_link(m_property - 1, builder));
298 return {};
299 }
300 if (m_subdirectory == process_stacks_subdirectory_root_entry.subdirectory) {
301 // NOTE: All property numbers should start from 1 as 0 is reserved for the directory itself.
302 // Therefore subtract 1 to get the actual correct thread stack number.
303 TRY(process->procfs_get_thread_stack(m_property - 1, builder));
304 return {};
305 }
306 if (m_subdirectory == process_children_subdirectory_root_entry.subdirectory) {
307 // NOTE: All property numbers should start from 1 as 0 is reserved for the directory itself.
308 // Therefore subtract 1 to get the actual correct child process index number for a correct symlink.
309 TRY(process->procfs_get_child_process_link(m_property - 1, builder));
310 return {};
311 }
312
313 VERIFY(m_subdirectory == main_process_directory_root_entry.subdirectory);
314 switch (m_property) {
315 case process_unveil_list_entry.property:
316 return process->procfs_get_unveil_stats(builder);
317 case process_pledge_list_entry.property:
318 return process->procfs_get_pledge_stats(builder);
319 case process_fds_list_entry.property:
320 return process->procfs_get_fds_stats(builder);
321 case process_exe_symlink_entry.property:
322 return process->procfs_get_binary_link(builder);
323 case process_cwd_symlink_entry.property:
324 return process->procfs_get_current_work_directory_link(builder);
325 case process_perf_events_entry.property:
326 return process->procfs_get_perf_events(builder);
327 case process_vm_entry.property:
328 return process->procfs_get_virtual_memory_stats(builder);
329 case process_cmdline_entry.property:
330 return process->procfs_get_command_line(builder);
331 default:
332 VERIFY_NOT_REACHED();
333 }
334}
335
336ErrorOr<void> ProcFSInode::refresh_process_property_data(OpenFileDescription& description)
337{
338 // For process-specific inodes, hold the process's ptrace lock across refresh
339 // and refuse to load data if the process is not dumpable.
340 // Without this, files opened before a process went non-dumpable could still be used for dumping.
341 VERIFY(m_type == Type::ProcessProperty);
342 VERIFY(m_associated_pid.has_value());
343 auto process = Process::from_pid_in_same_jail(m_associated_pid.value());
344 if (!process)
345 return Error::from_errno(ESRCH);
346 process->ptrace_lock().lock();
347 if (!process->is_dumpable()) {
348 process->ptrace_lock().unlock();
349 return EPERM;
350 }
351 ScopeGuard guard = [&] {
352 process->ptrace_lock().unlock();
353 };
354 MutexLocker locker(m_refresh_lock);
355 auto& cached_data = description.data();
356 if (!cached_data) {
357 cached_data = adopt_own_if_nonnull(new (nothrow) ProcFSInodeData);
358 if (!cached_data)
359 return ENOMEM;
360 }
361 auto builder = TRY(KBufferBuilder::try_create());
362 TRY(try_fetch_process_property_data(*process, builder));
363 return build_from_cached_data(builder, static_cast<ProcFSInodeData&>(*cached_data));
364}
365
366InodeMetadata ProcFSInode::metadata() const
367{
368 InodeMetadata metadata;
369 switch (m_type) {
370 case Type::SelfProcessLink: {
371 metadata.inode = { fsid(), 2 };
372 metadata.mode = S_IFLNK | 0777;
373 metadata.uid = 0;
374 metadata.gid = 0;
375 metadata.size = 0;
376 metadata.mtime = TimeManagement::boot_time();
377 break;
378 }
379 case Type::RootDirectory: {
380 metadata.inode = { fsid(), 1 };
381 metadata.mode = S_IFDIR | 0555;
382 metadata.uid = 0;
383 metadata.gid = 0;
384 metadata.size = 0;
385 metadata.mtime = TimeManagement::boot_time();
386 break;
387 }
388 case Type::ProcessProperty: {
389 VERIFY(m_associated_pid.has_value());
390 auto process = Process::from_pid_in_same_jail(m_associated_pid.value());
391 if (!process)
392 return {};
393 metadata.inode = identifier();
394 metadata.mode = determine_procfs_process_inode_mode(m_subdirectory, m_property);
395 auto credentials = process->credentials();
396 metadata.uid = credentials->uid();
397 metadata.gid = credentials->gid();
398 metadata.size = 0;
399 metadata.mtime = TimeManagement::now();
400 break;
401 }
402 case Type::ProcessDirectory: {
403 VERIFY(m_associated_pid.has_value());
404 auto process = Process::from_pid_in_same_jail(m_associated_pid.value());
405 if (!process)
406 return {};
407 metadata.inode = identifier();
408 metadata.mode = S_IFDIR | 0555;
409 auto credentials = process->credentials();
410 metadata.uid = credentials->uid();
411 metadata.gid = credentials->gid();
412 metadata.size = 0;
413 metadata.mtime = TimeManagement::now();
414 break;
415 }
416 case Type::ProcessSubdirectory: {
417 VERIFY(m_associated_pid.has_value());
418 auto process = Process::from_pid_in_same_jail(m_associated_pid.value());
419 if (!process)
420 return {};
421 metadata.inode = identifier();
422 metadata.mode = S_IFDIR | 0555;
423 auto credentials = process->credentials();
424 metadata.uid = credentials->uid();
425 metadata.gid = credentials->gid();
426 metadata.size = 0;
427 metadata.mtime = TimeManagement::now();
428 break;
429 }
430 }
431 return metadata;
432}
433
434}