The Pedigree Project 0.1
posix.cc
1/*
2 * Copyright (c) 2008-2014, Pedigree Developers
3 *
4 * Please see the CONTRIB file in the root of the source tree for a full
5 * list of contributors.
6 *
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 */
19
20#include "pedigree/kernel/Log.h"
21#include "pedigree/kernel/linker/KernelElf.h"
22#include "pedigree/kernel/machine/Machine.h"
23#include "pedigree/kernel/panic.h"
24#include "pedigree/kernel/process/Process.h"
25#include "pedigree/kernel/process/Scheduler.h"
26#include "pedigree/kernel/process/Thread.h"
27#include "pedigree/kernel/processor/Processor.h"
28#include "pedigree/kernel/processor/SyscallManager.h"
29#include "pedigree/kernel/utilities/ZombieQueue.h"
30
31#include "DevFs.h"
32#include "PosixSubsystem.h"
33#include "PosixSyscallManager.h"
34#include "ProcFs.h"
35#include "SysFs.h"
36#include "modules/Module.h"
39#include "modules/system/vfs/MountView.h"
40#include "modules/system/vfs/VFS.h"
41#include "net-syscalls.h"
42#include "process-accounting.h"
43#include "signal-syscalls.h"
44#include "system-syscalls.h"
45
46#if X64 && PEDIGREE_VM_REMAP_TESTS
47extern "C" bool x64RemapCoreRegression();
48#endif
49
50#if !HOSTED && PEDIGREE_VM_OWNERSHIP_SMOKE_TESTS
51extern bool runVmMappedOwnershipRegressions();
52#endif
53#if !HOSTED && PEDIGREE_MEMORY_LOCK_TESTS
54extern bool runMemoryLockRegressions();
55#endif
56#if !HOSTED && PEDIGREE_REMAP_FILE_PAGES_TESTS
57extern bool runRemapFilePagesRegressions();
58#endif
59#if !HOSTED && PEDIGREE_PROCESS_MEMORY_TESTS
60extern bool processMemoryBackendRegression();
61extern bool processVmRegression();
62#endif
63#if !HOSTED && PEDIGREE_UTS_NAMESPACE_TESTS
64extern bool utsNamespaceRegression();
65#endif
66
67static PosixSyscallManager g_PosixSyscallManager;
68
69Filesystem* g_pUnixSocketBacking = nullptr;
70static RamFs* g_pRunFilesystem = 0;
71static RamFs* g_pDevShmFilesystem = nullptr;
72static SysFs* g_pSysFs = nullptr;
73
74DevFs* g_pDevFs = 0;
75ProcFs* g_pProcFs = 0;
76
77enum class PosixTerminalLifetimeState {
78 Unowned,
79 HookOwned,
80 Quiesced,
81};
82
83static PosixTerminalLifetimeState g_PosixTerminalLifetime = PosixTerminalLifetimeState::Unowned;
84
85static bool retireUnownedSyscallRegistrations(PosixSyscallManager& manager) {
86 return manager.shutdown();
87}
88
89#if THREADS
90namespace {
91struct TerminalDrainStats {
92 TerminalDrainStats()
93 : syntheticOwners(0), zeroThreadProcesses(0), threadedProcesses(0), zombies(0) {}
94
95 size_t syntheticOwners;
96 size_t zeroThreadProcesses;
97 size_t threadedProcesses;
98 size_t zombies;
99};
100
101int terminalProcessExit(void* parameter) {
102 PosixSubsystem* subsystem = reinterpret_cast<PosixSubsystem*>(parameter);
103 subsystem->exit(0);
104 return 0;
105}
106
107void drainPosixProcesses(TerminalDrainStats& stats) {
108 while (true) {
110 if (!Scheduler::instance().acquireFirstProcessOfType(process, Process::Posix)) {
111 break;
112 }
113
114 const Process::ProcessState state = process->getState();
115 if (state == Process::Active || state == Process::Suspended) {
116 // A kernel-entry peer can execute its ordinary entry before it reaches
117 // any deferred-exit boundary, even if it is only Created or Ready.
118 // Reserve a dedicated delayed owner before construction; competing
119 // exits then take only the thread path until this owner is installed.
121 if (!reservation) {
122 const Process::ProcessState currentState = process->getState();
123 if (currentState == Process::Active || currentState == Process::Suspended) {
124 panic("POSIX shutdown could not reserve its active terminal exit owner");
125 }
126 ++stats.zombies;
127 } else {
128 if (process->getNumThreads()) {
129 ++stats.threadedProcesses;
130 } else {
131 ++stats.zeroThreadProcesses;
132 }
133
134 PosixSubsystem* subsystem = static_cast<PosixSubsystem*>(process->getSubsystem());
135 if (!subsystem) {
136 panic("POSIX shutdown found a process without its subsystem");
137 }
138 Thread* ownerIdentity =
139 new Thread(process.get(), terminalProcessExit, subsystem, nullptr, false, true, true);
140 ownerIdentity->setName("POSIX terminal exit owner");
141 reservation.install(ownerIdentity);
142
144 if (!process->acquireThread(owner, ownerIdentity)) {
145 panic("POSIX shutdown lost its reserved terminal exit owner");
146 }
147 if (!owner->start()) {
148 panic("POSIX shutdown could not start its reserved terminal exit owner");
149 }
150 ++stats.syntheticOwners;
151 owner.reset();
152 }
153 } else {
154 ++stats.zombies;
155 }
156
158 panic("POSIX shutdown attempted to reap its own exit owner");
159 }
160
161 Process* processIdentity = process.get();
162 Process::ReaperClaim reaper = process->tryClaimReaper();
163 if (reaper) {
164 reaper.publish();
165 }
167 process.reset();
168
169 // Scheduler removal precedes the derived destructor. Draining the queue
170 // is the completion barrier that keeps POSIX text mapped until every
171 // IntervalTimer and PosixSubsystem destructor has returned.
172 if (!ZombieQueue::instance().drain()) {
173 panic("POSIX shutdown could not drain process destruction");
174 }
175 }
176
177 // Also covers a POSIX reaper which removed its process before the final
178 // enumeration pass but is still running the derived destructor.
179 if (!ZombieQueue::instance().drain()) {
180 panic("POSIX shutdown could not complete its final process drain");
181 }
182}
183} // namespace
184#endif
185
186static bool terminalQuiesce() {
187 if (g_PosixTerminalLifetime == PosixTerminalLifetimeState::Quiesced) {
188 return true;
189 }
190 if (g_PosixTerminalLifetime != PosixTerminalLifetimeState::HookOwned) {
191 return false;
192 }
193 if (!Processor::information().getCurrentThread() ||
194 Processor::executionContext() != ExecutionContext::WaitableThread) {
195 return false;
196 }
197
198 // New callbacks must be rejected before process termination starts, but
199 // admitted blocking callbacks retain their registration and module text
200 // until process teardown has made them return.
201 if (!g_PosixSyscallManager.closeAdmission()) {
202 return false;
203 }
204
205#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
206 NOTICE("HOSTED-POSIX-SHUTDOWN: PHASE syscall-admission-closed");
207#endif
208
209#if THREADS
210 TerminalDrainStats stats;
211 Machine::setShutdownPhase(Machine::ShutdownPhase::Userspace);
212 drainPosixProcesses(stats);
213#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
214 NOTICE("HOSTED-POSIX-SHUTDOWN: PHASE initial-process-drain-complete");
215#endif
216#endif
217
218 Machine::setShutdownPhase(Machine::ShutdownPhase::Syscalls);
219 if (!g_PosixSyscallManager.finishShutdown()) {
220 return false;
221 }
222
223#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
224 NOTICE("HOSTED-POSIX-SHUTDOWN: PHASE syscall-handler-drain-complete");
225#endif
226
227#if THREADS
228 // An already-admitted fork or clone can publish after the first empty
229 // scheduler scan. Handler retirement closes that final publication window.
230 Machine::setShutdownPhase(Machine::ShutdownPhase::Userspace);
231 drainPosixProcesses(stats);
232 auto& maps = MemoryMapManager::instance();
233 if (maps.swapSnapshot().active) {
234 const auto swapStatus = maps.deactivateSwap(SwapStore::instance().endpointId());
235 if (swapStatus != SwapStatus::Success) {
236 WARNING("POSIX shutdown could not drain swap; storage retained (status "
237 << static_cast<size_t>(swapStatus) << ")");
238 return false;
239 }
240 }
241#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
242 NOTICE("HOSTED-POSIX-SHUTDOWN: PHASE final-process-drain-complete");
243#endif
244#endif
245
246 Machine::setShutdownPhase(Machine::ShutdownPhase::Filesystems);
247 posix_stop_accounting();
248 // Bound socket names outlive their descriptors and may reside in RAMFS or
249 // disk-backed directories. Destroy them before POSIX code and statics unload.
250 if (!VFS::instance().removeEphemeralFiles()) {
251 return false;
252 }
253#if THREADS
254 // Process 0 survives the terminal halt; its bootstrap root would otherwise
255 // keep the mount namespace and storage alive past filesystem shutdown.
256 Processor::information().getCurrentThread()->getParent()->releaseFilesystemContext();
257#endif
258 if (auto* view = VFS::instance().mountView()) {
259 // A surviving attachment path keeps both its backend and this module mapped.
260 // /dev/shm must detach before its parent /dev attachment.
261 Filesystem* backings[] = {g_pProcFs, g_pDevShmFilesystem, g_pDevFs, g_pRunFilesystem, g_pSysFs};
262 for (auto* backing : backings)
263 if (backing && !view->detachBackingForShutdown(backing))
264 return false;
265 }
266 g_PosixTerminalLifetime = PosixTerminalLifetimeState::Quiesced;
267
268#if THREADS && HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
269 NOTICE("HOSTED-POSIX-SHUTDOWN: PASS terminal-drain synthetic="
270 << stats.syntheticOwners << " zero-thread=" << stats.zeroThreadProcesses
271 << " threaded=" << stats.threadedProcesses << " zombies=" << stats.zombies);
272#endif
273
274 return true;
275}
276
277#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
278extern "C" EXPORTED_PUBLIC bool posixDuplicateInitRollbackPreservesProcessForTest(
279 Process* processIdentity) {
280 PosixSyscallManager duplicate;
281 const bool duplicateRejected = !duplicate.initialise();
282 if (!retireUnownedSyscallRegistrations(duplicate)) {
283 return false;
284 }
285
287 return duplicateRejected && Scheduler::instance().acquireProcess(process, processIdentity) &&
288 process->getType() == Process::Posix && process->getState() == Process::Active;
289}
290#endif
291
292static bool init() {
293 if (!g_PosixSyscallManager.initialise()) {
294 return false;
295 }
296
297 g_pDevFs = new DevFs();
298 g_pDevFs->initialise(0);
299
300 g_pProcFs = new ProcFs();
301 g_pProcFs->initialise(0);
302
303 g_pSysFs = new SysFs();
304 if (!g_pSysFs || !g_pSysFs->initialise(nullptr))
305 return false;
306
307 g_pRunFilesystem = new RamFs;
308 g_pRunFilesystem->initialise(0);
309 g_pDevShmFilesystem = new RamFs;
310 if (!g_pDevShmFilesystem || !g_pDevShmFilesystem->initialise(nullptr))
311 return false;
312 g_pUnixSocketBacking = g_pRunFilesystem;
313 VFS::instance().registerFilesystem(g_pRunFilesystem, String("posix-runtime"));
314 VFS::instance().registerFilesystem(g_pDevShmFilesystem, String("posix-shm"));
315 VFS::instance().registerFilesystem(g_pDevFs, String("dev"));
316 VFS::instance().registerFilesystem(g_pProcFs, String("proc"));
317 VFS::instance().registerFilesystem(g_pSysFs, String("sysfs"));
318
319 Filesystem* scratchfs = VFS::instance().getFilesystemAt(String("/media/scratch"));
320
321 // Keep the conventional socket directory without giving it a special
322 // filesystem; pathname sockets work in any writable VFS directory.
323 VFS::instance().createDirectory(String("/media/posix-runtime/sockets"), 0755);
324 VFS::instance().createDirectory(String("/media/posix-runtime/lock"), 0755);
325
326 if (!KernelElf::instance().registerTerminalQuiesce(&init, &terminalQuiesce)) {
327 return false;
328 }
329 g_PosixTerminalLifetime = PosixTerminalLifetimeState::HookOwned;
330
331 if (!VFS::instance().initialiseMountView())
332 return false;
333 auto* view = VFS::instance().mountView();
334 FilesystemContextOwner bootstrap;
335 if (!view->createBootContext(bootstrap))
336 return false;
337 auto context = bootstrap.reference();
338 struct Attachment {
339 const char* path;
340 Filesystem* backing;
341 } attachments[] = {{"/dev", g_pDevFs}, {"/dev/shm", g_pDevShmFilesystem},
342 {"/run", g_pRunFilesystem}, {"/proc", g_pProcFs},
343 {"/sys", g_pSysFs}, {"/tmp", scratchfs}};
345 options.requireDirectory = true;
346 options.crossFinalMount = false;
347 for (const auto& attachment : attachments) {
348 if (!attachment.backing)
349 continue;
350 FilesystemPathRef covered;
351 if (!view->resolve(context, FilesystemPathRef(), String(attachment.path), options, covered)) {
352 ERROR("failed to resolve attachment for " << attachment.path);
353 }
354 if (!view->attach(context, covered, attachment.backing)) {
355 ERROR("failed to finalize attachment for " << attachment.path);
356 return false;
357 }
358 }
359 if (!Processor::information().getCurrentThread()->getParent()->installFilesystemContext(
360 pedigree_std::move(bootstrap)))
361 return false;
362
363#if X64 && PEDIGREE_VM_REMAP_TESTS
364 NOTICE("VM-REMAP-CORE: BEGIN");
365 if (!x64RemapCoreRegression()) {
366 FATAL("VM-REMAP-CORE: FAIL");
367 }
368 NOTICE("VM-REMAP-CORE: PASS");
369#endif
370#if !HOSTED && PEDIGREE_VM_OWNERSHIP_SMOKE_TESTS
371 if (!runVmMappedOwnershipRegressions()) {
372 FATAL("QEMU mapped ownership regression failed");
373 }
374#endif
375#if !HOSTED && PEDIGREE_MEMORY_LOCK_TESTS
376 if (!runMemoryLockRegressions()) {
377 FATAL("Memory-lock residency regression failed");
378 }
379#endif
380#if !HOSTED && PEDIGREE_REMAP_FILE_PAGES_TESTS
381 if (!runRemapFilePagesRegressions()) {
382 FATAL("File-offset remapping regression failed");
383 }
384#endif
385#if !HOSTED && PEDIGREE_PROCESS_MEMORY_TESTS
386 if (!processMemoryBackendRegression() || !processVmRegression()) {
387 FATAL("Process-memory copy regression failed");
388 }
389#endif
390#if !HOSTED && PEDIGREE_UTS_NAMESPACE_TESTS
391 if (!utsNamespaceRegression()) {
392 FATAL("UTS namespace lifecycle regression failed");
393 }
394#endif
395#if !HOSTED && PEDIGREE_AFFINITY_TESTS
396 extern bool runAffinityRegressions();
397 if (!runAffinityRegressions()) {
398 FATAL("Scheduler affinity regression failed");
399 }
400#endif
401#if !HOSTED && PEDIGREE_CHILD_WAIT_TESTS
402 extern bool runChildWaitRegressions();
403 if (!runChildWaitRegressions()) {
404 FATAL("Child wait regression failed");
405 }
406#endif
407#if !HOSTED && PEDIGREE_PTRACE_TESTS
408 extern bool runPtraceFrameRegressions();
409 if (!runPtraceFrameRegressions()) {
410 FATAL("Tracing frame regression failed");
411 }
412#endif
413 return true;
414}
415
416static void destroy() {
417 if (g_PosixTerminalLifetime == PosixTerminalLifetimeState::HookOwned) {
418 if (!KernelElf::instance().unregisterTerminalQuiesce(&init, &terminalQuiesce)) {
419 panic("POSIX terminal quiesce ownership could not be released safely.");
420 }
421 // Retain HookOwned across unregister: this module lifetime still owns all
422 // live POSIX processes and must perform the same terminal drain itself.
423 if (!terminalQuiesce()) {
424 panic("POSIX syscall handlers could not be retired safely.");
425 }
426 } else if (g_PosixTerminalLifetime == PosixTerminalLifetimeState::Unowned) {
427 // Failed or duplicate initialisation never acquired global POSIX lifetime
428 // ownership and therefore must not terminate another module's processes.
429 if (!retireUnownedSyscallRegistrations(g_PosixSyscallManager)) {
430 panic("Partial POSIX syscall handlers could not be retired safely.");
431 }
432 }
433
434 if (g_pProcFs && !VFS::instance().unregisterFilesystem(g_pProcFs, false)) {
435 panic("POSIX shutdown could not retire procfs");
436 }
437 if (g_pDevFs && !VFS::instance().unregisterFilesystem(g_pDevFs, false)) {
438 panic("POSIX shutdown could not retire devfs");
439 }
440 if (g_pSysFs && !VFS::instance().unregisterFilesystem(g_pSysFs, false)) {
441 panic("POSIX shutdown could not retire sysfs");
442 }
443 if (g_pDevShmFilesystem && !VFS::instance().unregisterFilesystem(g_pDevShmFilesystem, false)) {
444 panic("POSIX shutdown could not retire shmfs");
445 }
446 if (g_pRunFilesystem && !VFS::instance().unregisterFilesystem(g_pRunFilesystem, false)) {
447 panic("POSIX shutdown could not retire runfs");
448 }
449
450 g_pUnixSocketBacking = nullptr;
451 delete g_pRunFilesystem;
452 delete g_pDevShmFilesystem;
453 delete g_pProcFs;
454 delete g_pSysFs;
455 delete g_pDevFs;
456 g_pRunFilesystem = nullptr;
457 g_pDevShmFilesystem = nullptr;
458 g_pProcFs = nullptr;
459 g_pSysFs = nullptr;
460 g_pDevFs = nullptr;
461}
462
463MODULE_INFO_RUNTIME_PINNED("posix", &init, &destroy, "console", "network-stack", "mountroot",
464 "ramfs", "lwip");
465// DevFs mouse nodes need the registration code until their destructors finish.
466MODULE_OPTIONAL_DEPENDS("ps2mouse", "gfx-deps", "virtio-rng");
Memory-mapped file interface.
An in-RAM filesystem.
Definition DevFs.h:256
virtual bool initialise(Disk *pDisk)
Definition DevFs.cc:772
static KernelElf & instance()
Definition KernelElf.h:135
static void setShutdownPhase(ShutdownPhase phase, const char *detail=nullptr)
Definition Machine.cc:33
static MemoryMapManager & instance()
void exit(int code, ExitCause cause=ExitCause::Normal) NORETURN
virtual bool initialise(Disk *pDisk)
Definition ProcFs.cc:325
void install(Thread *owner)
Definition Process.cc:321
ProcessState
Definition Process.h:263
bool waitUntilTerminationReapableForTerminalCoordinator()
Definition Process.cc:2115
size_t getNumThreads()
Definition Process.cc:1253
MUST_USE_RESULT bool acquireThread(ThreadLease &lease, size_t n)
Definition Process.cc:1258
TerminalOwnerReservation reserveTerminalOwner()
Definition Process.cc:1405
ReaperClaim tryClaimReaper()
Definition Process.cc:1824
static ProcessorInformation & information()
static ExecutionContext executionContext()
Definition Processor.cc:109
Definition RamFs.h:118
virtual bool initialise(Disk *pDisk) override
Definition RamFs.cc:441
static Scheduler & instance()
Definition Scheduler.h:96
MUST_USE_RESULT bool acquireProcess(ProcessLease &lease, size_t n)
Definition Scheduler.cc:275
void waitUntilProcessRemoved(Process *expected)
Definition Scheduler.cc:412
Definition SysFs.h:22
bool initialise(Disk *) override
Definition SysFs.cc:212
Filesystem * getFilesystemAt(const String &path) const
Definition VFS.cc:843
String registerFilesystem(Filesystem *pFs, const String &preferredStableName)
Definition VFS.cc:534
static VFS & instance()
Definition VFS.cc:311
bool createDirectory(const String &path, uint32_t mask, File *pStartNode=0)
Definition VFS.cc:1464
void EXPORTED_PUBLIC panic(const char *msg) NORETURN
Definition panic.cc:118