The Pedigree Project 0.1
termios-syscall-regressions.cc
1/*
2 * Copyright (c) 2026, Pedigree Developers
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted.
6 */
7
8#include "pedigree/kernel/Atomic.h"
9#include "pedigree/kernel/Log.h"
10#include "pedigree/kernel/errors.h"
11#include "pedigree/kernel/process/Process.h"
12#include "pedigree/kernel/process/Thread.h"
13#include "pedigree/kernel/processor/PhysicalMemoryManager.h"
14#include "pedigree/kernel/processor/Processor.h"
15#include "pedigree/kernel/utilities/utility.h"
16
17#include <fcntl.h>
18#include <stddef.h>
19#include <termios.h>
20
21#include "modules/subsys/posix/FileDescriptor.h"
22#include "modules/subsys/posix/PosixSubsystem.h"
23#include "modules/subsys/posix/file-syscalls.h"
24#include "modules/system/console/Console.h"
26#include <sys/ioctl.h>
27
28namespace {
29constexpr size_t TerminalDescriptor = 96;
30constexpr size_t LinuxTermiosExtent = 36;
31constexpr size_t LinuxControlCount = 19;
32
33// Keep the guest musl layout independent of the hosted compiler's libc.
34struct MuslAmd64Termios {
35 uint32_t c_iflag;
36 uint32_t c_oflag;
37 uint32_t c_cflag;
38 uint32_t c_lflag;
39 uint8_t c_line;
40 uint8_t c_cc[32];
41 uint32_t c_ispeed;
42 uint32_t c_ospeed;
43};
44
45struct LegacyTermios {
46 tcflag_t c_iflag;
47 tcflag_t c_oflag;
48 tcflag_t c_cflag;
49 tcflag_t c_lflag;
50 cc_t c_cc[20];
51 speed_t c_ispeed;
52 speed_t c_ospeed;
53};
54
55static_assert(sizeof(MuslAmd64Termios) == 60, "musl amd64 termios extent");
56static_assert(offsetof(MuslAmd64Termios, c_cc) == 17, "musl amd64 control characters");
57
58struct TermiosContext {
59 Process* process;
60 ConsoleSlaveFile* console;
61 bool getter = false;
62 bool setter = false;
63 bool extent = false;
64 bool legacy = false;
65 Atomic<size_t> returned = 0;
66};
67
68int termiosWorker(void* parameter) {
69 TermiosContext* context = reinterpret_cast<TermiosContext*>(parameter);
70 PosixSubsystem* subsystem = static_cast<PosixSubsystem*>(context->process->getSubsystem());
71 Thread* thread = Processor::information().getCurrentThread();
72 const size_t pageSize = PhysicalMemoryManager::getPageSize();
73 const size_t reservedLength = pageSize * 2;
74 uintptr_t address = 0;
75 if (!context->process->allocateUserRange(Process::UserRegion::Normal, reservedLength, address)) {
76 return 1;
77 }
78 uintptr_t mappedAddress = address;
80 mappedAddress, pageSize, MemoryMappedObject::Read | MemoryMappedObject::Write);
81 if (!mapping || mappedAddress != address) {
82 MemoryMapManager::instance().remove(address, pageSize);
83 context->process->freeUserRange(Process::UserRegion::Normal, address, reservedLength);
84 return 1;
85 }
86
87 subsystem->setAbi(PosixSubsystem::LinuxAbi);
88 MuslAmd64Termios* user = reinterpret_cast<MuslAmd64Termios*>(address);
89 ByteSet(user, 0xA5, sizeof(*user) + 16);
90 context->getter = posix_ioctl(TerminalDescriptor, TCGETS, user) == 0 && user->c_line == 0;
91 for (size_t i = 0; i < LinuxControlCount; ++i) {
92 context->getter &= user->c_cc[i] == static_cast<uint8_t>(defaultControl[i]);
93 }
94 for (size_t i = LinuxTermiosExtent; i < sizeof(*user) + 16; ++i) {
95 context->getter &= reinterpret_cast<uint8_t*>(user)[i] == 0xA5;
96 }
97
98 context->setter = true;
99 const size_t commands[] = {TCSETS, TCSETSW, TCSETSF};
100 for (size_t command : commands) {
101 user->c_line = 0x7F;
102 for (size_t i = 0; i < LinuxControlCount; ++i) {
103 user->c_cc[i] = static_cast<uint8_t>(0x21 + i);
104 }
105 context->setter &= posix_ioctl(TerminalDescriptor, command, user) == 0;
106 char control[MAX_CONTROL_CHAR] = {};
107 ConsoleManager::instance().getControlChars(context->console, control);
108 // Inspect the console independently: a shifted setter/getter pair can
109 // otherwise round-trip while corrupting VINTR, VEOF, VMIN and VSUSP.
110 for (size_t i = 0; i < LinuxControlCount; ++i) {
111 context->setter &= static_cast<uint8_t>(control[i]) == static_cast<uint8_t>(0x21 + i);
112 }
113 context->setter &= control[VINTR] == user->c_cc[VINTR] && control[VEOF] == user->c_cc[VEOF] &&
114 control[VMIN] == user->c_cc[VMIN] && control[VSUSP] == user->c_cc[VSUSP];
115 }
116
117 void* exact = reinterpret_cast<void*>(address + pageSize - LinuxTermiosExtent);
118 MemoryCopy(exact, user, LinuxTermiosExtent);
119 context->extent = posix_ioctl(TerminalDescriptor, TCSETS, exact) == 0 &&
120 posix_ioctl(TerminalDescriptor, TCGETS, exact) == 0;
121 void* shortBuffer = reinterpret_cast<void*>(address + pageSize - LinuxTermiosExtent + 1);
122 char before[MAX_CONTROL_CHAR] = {};
123 char after[MAX_CONTROL_CHAR] = {};
124 ConsoleManager::instance().getControlChars(context->console, before);
125 thread->setErrno(0);
126 context->extent &= posix_ioctl(TerminalDescriptor, TCSETS, shortBuffer) == -1 &&
127 thread->getErrno() == Error::BadAddress;
128 ConsoleManager::instance().getControlChars(context->console, after);
129 context->extent &= !MemoryCompare(before, after, sizeof(before));
130 thread->setErrno(0);
131 context->extent &= posix_ioctl(TerminalDescriptor, TCGETS, shortBuffer) == -1 &&
132 thread->getErrno() == Error::BadAddress;
133
134 subsystem->setAbi(PosixSubsystem::PosixAbi);
135 LegacyTermios* legacy = reinterpret_cast<LegacyTermios*>(address);
136 ByteSet(legacy, 0, sizeof(*legacy));
137 legacy->c_lflag = ICANON;
138 for (size_t i = 0; i < sizeof(legacy->c_cc); ++i) {
139 legacy->c_cc[i] = static_cast<cc_t>(0x41 + i);
140 }
141 context->legacy = posix_ioctl(TerminalDescriptor, TCSETS, legacy) == 0;
142 ConsoleManager::instance().getControlChars(context->console, after);
143 for (size_t i = 0; i < sizeof(legacy->c_cc); ++i) {
144 context->legacy &= static_cast<cc_t>(after[i]) == legacy->c_cc[i];
145 }
146 ByteSet(legacy, 0, sizeof(*legacy));
147 context->legacy &= posix_ioctl(TerminalDescriptor, TCGETS, legacy) == 0 &&
148 legacy->c_lflag == ICANON && legacy->c_ispeed == 115200 &&
149 legacy->c_ospeed == 115200;
150 for (size_t i = 0; i < sizeof(legacy->c_cc); ++i) {
151 context->legacy &= legacy->c_cc[i] == static_cast<cc_t>(0x41 + i);
152 }
153
154 MemoryMapManager::instance().remove(address, pageSize);
155 context->process->freeUserRange(Process::UserRegion::Normal, address, reservedLength);
156 context->returned += 1;
157 return 0;
158}
159} // namespace
160
161bool runHostedTermiosSyscallRegressions(Process* kernelProcess) {
162 Process* process = new Process(kernelProcess);
163 PosixSubsystem* subsystem = new PosixSubsystem;
164 process->setSubsystem(subsystem);
165 ConsoleSlaveFile* console = new ConsoleSlaveFile(0, String("termios-test"), nullptr);
166 subsystem->addFileDescriptor(TerminalDescriptor,
167 new FileDescriptor(console, 0, TerminalDescriptor, 0, O_RDWR));
168 TermiosContext context{process, console};
169 Thread* worker = new Thread(process, termiosWorker, &context, nullptr, false, true, true);
170 worker->setName("hosted termios ABI");
171 const bool started = worker->start();
172 const bool joined = started && worker->joinForCompletion();
173 if (!started) {
174 delete worker;
175 }
176 const bool passed = started && joined && context.returned == 1 && context.getter &&
177 context.setter && context.extent && context.legacy;
178 delete process;
179 delete console;
180 if (!passed) {
181 ERROR("HOSTED-SYSCALL-TEST: FAIL termios-linux-amd64-abi: getter="
182 << context.getter << ", setter=" << context.setter << ", extent=" << context.extent
183 << ", legacy=" << context.legacy);
184 return false;
185 }
186 NOTICE("HOSTED-SYSCALL-TEST: PASS termios-linux-amd64-abi");
187 return true;
188}
Memory-mapped file interface.
MemoryMappedObject * mapAnon(uintptr_t &address, size_t length, MemoryMappedObject::Permissions perms)
size_t remove(uintptr_t base, size_t length)
static MemoryMapManager & instance()
void setAbi(Abi which)
void addFileDescriptor(size_t fd, FileDescriptor *pFd)
static ProcessorInformation & information()
void setErrno(size_t err)
Definition Thread.h:478
size_t getErrno()
Definition Thread.h:473