The Pedigree Project 0.1
process-accounting.cc
1/* Copyright (c) 2026, Pedigree Developers. */
2#include "pedigree/kernel/LockGuard.h"
3#include "pedigree/kernel/process/Scheduler.h"
4#include "pedigree/kernel/process/TerminationDeferral.h"
5#include "pedigree/kernel/process/Thread.h"
6#include "pedigree/kernel/processor/PhysicalMemoryManager.h"
7#include "pedigree/kernel/processor/Processor.h"
8#include "pedigree/kernel/processor/ProcessorInformation.h"
9#include "pedigree/kernel/syscallError.h"
10
11#include <limits.h>
12
13#include "PosixProcess.h"
14#include "PosixSubsystem.h"
15#include "ResolvedPath.h"
16#include "accounting-record.h"
17#include "file-syscalls.h"
18#include "modules/system/console/Console.h"
19#include "modules/system/vfs/VFS.h"
20#include "process-accounting.h"
21#include <sys/acct.h>
22
23namespace {
24static_assert(sizeof(PosixAccounting::Record) == sizeof(acct_v3));
25static_assert(offsetof(PosixAccounting::Record, elapsedFloat) == offsetof(acct_v3, ac_etime));
26static_assert(offsetof(PosixAccounting::Record, command) == offsetof(acct_v3, ac_comm));
27
28class AccountingFile {
29 public:
30 ~AccountingFile() {
31 TerminationDeferral lifetime;
32 if (m_File) {
33 m_File->decreaseRefCount(true);
34 m_File->releaseVfsReference();
35 }
36 m_Filesystem.reset();
37 }
38
39 bool prepare(File* file) {
40 if (!Process::currentFilesystemCredentials(m_Credentials))
41 return false;
42 if (!VFS::instance().pinFilesystem(file->getFilesystem(), m_Filesystem) ||
43 !file->retainVfsReference())
44 return false;
45 m_File = file;
46 file->increaseRefCount(true);
47 return true;
48 }
49
50 void append(const PosixAccounting::Record& record) {
51 Process::FilesystemAccessScope credentials(m_Credentials);
52 uint64_t offset = 0;
53 const uint64_t written =
54 m_File->append(sizeof(record), reinterpret_cast<uintptr_t>(&record), offset);
55 if (written != sizeof(record)) {
56 // Exit must remain possible when the logging device fills or fails.
57 // Report loss without turning process teardown into an unbounded retry.
58 if (!(m_Dropped++ % 64))
59 WARNING("Process accounting could not append a complete record");
60 }
61 }
62
63 private:
64 VFS::FilesystemPin m_Filesystem;
65 File* m_File = nullptr;
66 FilesystemCredentials m_Credentials;
67 size_t m_Dropped = 0;
68};
69
70Mutex accountingLock;
72
73struct AccountingResult {
74 AccountingResult(int result)
75 : value(result),
76 error(result < 0 ? Processor::information().getCurrentThread()->getErrno() : 0) {}
77 int value;
78 int error;
79};
80
81AccountingResult configure(const char* path) {
82 TerminationDeferral lifetime;
83 auto* process = Processor::information().getCurrentThread()->getParent();
84 if (process->getEffectiveUserId() != 0) {
85 SYSCALL_ERROR(NotEnoughPermissions);
86 return -1;
87 }
88 UniquePointer<AccountingFile> prepared, retired;
89 if (path) {
90 String copied, normalised;
91 const auto result = PosixSubsystem::copyUserString(path, copied, PATH_MAX);
92 if (result != PosixSubsystem::UserStringSuccess) {
93 syscallError(result == PosixSubsystem::UserStringBadAddress ? Error::BadAddress
94 : Error::NameTooLong);
95 return -1;
96 }
97 if (!copied.length()) {
98 SYSCALL_ERROR(DoesNotExist);
99 return -1;
100 }
101 normalisePath(normalised, copied.cstr());
102 ResolvedPath fileLease;
103 File* file = findFilePath(normalised, fileLease, FilesystemPathRef(), true);
104 if (!file) {
105 if (!Processor::information().getCurrentThread()->getErrno())
106 SYSCALL_ERROR(DoesNotExist);
107 return -1;
108 }
109 if (!file->supportsRegularFileOperations()) {
110 SYSCALL_ERROR(PermissionDenied);
111 return -1;
112 }
113 if (file->getFilesystem()->isReadOnly()) {
114 SYSCALL_ERROR(ReadOnlyFilesystem);
115 return -1;
116 }
117 if (!VFS::checkAccess(file, false, true, false))
118 return -1;
120 if (!prepared || !prepared.get()->prepare(file)) {
121 SYSCALL_ERROR(OutOfMemory);
122 return -1;
123 }
124 }
125 {
126 // The same lock admits append and destination changes. Returning from
127 // acct(NULL) therefore also means all writes to the retired file drained.
128 LockGuard<Mutex> guard(accountingLock);
129 retired = pedigree_std::move(accountingFile);
130 accountingFile = pedigree_std::move(prepared);
131 }
132 return 0;
133}
134
135uint32_t parentId(PosixProcess& process) {
136 while (Process* expected = process.getParent()) {
138 if (!Scheduler::instance().acquireProcess(parent, expected)) {
139 if (process.getParent() != expected)
140 continue;
141 return 0;
142 }
143 if (process.getParent() == parent.get())
144 return parent->getUserspaceId();
145 }
146 return 0;
147}
148} // namespace
149
150int posix_acct(const char* path) {
151 const AccountingResult result = configure(path);
152 syscallError(result.error);
153 return result.value;
154}
155
156void posix_account_process_exit(PosixProcess& process, const ProcessAccountingLifetime& lifetime) {
158#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
159 record.version |= 128;
160#endif
161 const auto credentials = process.snapshotCredentials();
162 record.uid = credentials.ruid;
163 record.gid = credentials.rgid;
164 record.pid = process.getUserspaceId();
165 record.parentPid = parentId(process);
166 const uint64_t birth = lifetime.birth / Time::Multiplier::Second;
167 record.birthSeconds = birth > UINT32_MAX ? UINT32_MAX : static_cast<uint32_t>(birth);
168 const uint64_t now = Time::getTicks();
169 record.elapsedFloat = PosixAccounting::floatBits(
170 (now > lifetime.started ? now - lifetime.started : 0) / PosixAccounting::TickNanoseconds);
171 record.userTicks =
172 PosixAccounting::compressed(process.getUserTime() / PosixAccounting::TickNanoseconds);
173 record.systemTicks =
174 PosixAccounting::compressed(process.getKernelTime() / PosixAccounting::TickNanoseconds);
175 record.memory = PosixAccounting::compressed(lifetime.virtualKilobytes);
176 {
177 Process::FileContextLease terminalLease;
178 File* terminal = process.acquireCtty(terminalLease);
179 if (terminal && ConsoleManager::instance().isConsole(terminal)) {
180 const size_t number = static_cast<ConsoleFile*>(terminal)->getConsoleNumber();
181 if (number <= 255)
182 record.terminal = static_cast<uint16_t>(0x8800 | number);
183 }
184 }
185 record.exitStatus = process.getExitStatus();
186 if (record.exitStatus & 0x7f)
187 record.flags |= AXSIG;
188 if (lifetime.forked && !process.hasExecCommitted())
189 record.flags |= AFORK;
190 const auto& description = process.description();
191 size_t start = 0;
192 for (size_t i = 0; i < description.length(); ++i) {
193 if (description[i] == '/')
194 start = i + 1;
195 }
196 for (size_t i = 0; i < sizeof(record.command) - 1 && start + i < description.length(); ++i)
197 record.command[i] = description[start + i];
198 const int previousError = Processor::information().getCurrentThread()->getErrno();
199 {
200 LockGuard<Mutex> guard(accountingLock);
201 if (accountingFile)
202 accountingFile.get()->append(record);
203 }
204 syscallError(previousError);
205}
206
207void posix_stop_accounting() {
209 {
210 LockGuard<Mutex> guard(accountingLock);
211 retired = pedigree_std::move(accountingFile);
212 }
213}
214
215void PosixProcess::snapshotAccountingMemory() {
216 // The sole exit owner calls this after peer retirement and before unmapping.
217 const ssize_t pages = getVirtualPageCount();
218 const size_t kilobytesPerPage = PhysicalMemoryManager::getPageSize() / 1024;
219 m_AccountingLifetime.virtualKilobytes =
220 pages > 0 ? static_cast<uint64_t>(pages) * kilobytesPerPage : 0;
221}
Definition File.h:75
virtual bool retainVfsReference()
Definition File.cc:910
bool supportsRegularFileOperations()
Definition File.cc:824
bool isReadOnly()
Definition Filesystem.h:150
Definition Mutex.h:56
static UserStringResult copyUserString(const char *userString, String &copy, size_t maxLength)
size_t getUserspaceId() const
Definition Process.h:504
int getExitStatus()
Definition Process.h:560
Process * getParent()
Definition Process.h:620
LargeStaticString & description()
Definition Process.h:525
Time::Timestamp getUserTime() const
Definition Process.h:829
static ProcessorInformation & information()
static Scheduler & instance()
Definition Scheduler.h:96
static bool checkAccess(File *pFile, bool bRead, bool bWrite, bool bExecute)
Definition VFS.cc:1502
static VFS & instance()
Definition VFS.cc:311