WinTer 0.1.1
Windows Terminal Emulator
Loading...
Searching...
No Matches
pty.c
Go to the documentation of this file.
1
23
24#include "pty.h"
25#include <stdio.h>
26
31
40INTERNAL void
42{
43 if (h != NULL && *h != NULL) {
44 CloseHandle(*h);
45 *h = NULL;
46 }
47}
48
49b32
50pty_init(struct pty_state_t *state, u16 columns, u16 rows)
51{
52 ASSERT_NOT_NULL(state);
53
54 HANDLE hInputRead = NULL;
55 HANDLE hInputWrite = NULL;
56 HANDLE hOutputRead = NULL;
57 HANDLE hOutputWrite = NULL;
58
59 /*
60 * Mark pipe handles as inheritable so ConPTY can duplicate them
61 * internally. bInheritHandle = TRUE opts these handles into the
62 * inheritance system, but inheritance itself is disabled in
63 * pty_spawn() via bInheritHandles = FALSE.
64 */
65 SECURITY_ATTRIBUTES saAttr = {0};
66 saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
67 saAttr.bInheritHandle = TRUE;
68 saAttr.lpSecurityDescriptor = NULL;
69
70 if (!CreatePipe(&hInputRead, &hInputWrite, &saAttr, 0)) {
71 (void)fprintf(
72 stderr,
73 "[PTY] Failed to create input pipe. Error: %lu\n",
74 GetLastError());
75 return false;
76 }
77
78 if (!CreatePipe(&hOutputRead, &hOutputWrite, &saAttr, 0)) {
79 (void)fprintf(
80 stderr,
81 "[PTY] Failed to create output pipe. Error: %lu\n",
82 GetLastError());
83 safe_close_handle(&hInputRead);
84 safe_close_handle(&hInputWrite);
85 return false;
86 }
87
88 COORD terminalSize = {(SHORT)columns, (SHORT)rows};
89 HRESULT hr = CreatePseudoConsole(terminalSize, hInputRead, hOutputWrite, 0, &state->hPC);
90
91 if (FAILED(hr)) {
92 (void)fprintf(stderr, "[PTY] Failed to create ConPTY. HRESULT: 0x%08lX\n", hr);
93 safe_close_handle(&hInputRead);
94 safe_close_handle(&hInputWrite);
95 safe_close_handle(&hOutputRead);
96 safe_close_handle(&hOutputWrite);
97 return false;
98 }
99
100 /*
101 * Close the ConPTY-owned pipe ends in our process. ConPTY has
102 * duplicated these handles internally. Retaining our copies would
103 * prevent the output pipe from ever signalling EOF our ReadFile
104 * loop would block forever after the shell exits.
105 */
106 safe_close_handle(&hInputRead);
107 safe_close_handle(&hOutputWrite);
108
109 state->hInputWrite = hInputWrite;
110 state->hOutputRead = hOutputRead;
111
112 return true;
113}
114
115b32
116pty_spawn(struct pty_state_t *state, LPCWSTR command_line)
117{
118 ASSERT_NOT_NULL(state);
119 ASSERT_NOT_NULL((void *)command_line);
120
121 /*
122 * Phase 1: Query the required size of the process thread attribute list.
123 *
124 * The attribute list is an opaque, variable-length blob. Its size
125 * depends on the number of attributes we want to attach (1 in our case).
126 * We perform a dry-run call with a NULL buffer to have Windows fill in
127 * the required byte count. The function will return FALSE with
128 * GetLastError() == ERROR_INSUFFICIENT_BUFFER this is expected.
129 */
130 SIZE_T attr_list_size = 0;
131 InitializeProcThreadAttributeList(NULL, 1, 0, &attr_list_size);
132
133 /*
134 * Phase 2: Allocate and initialize the attribute list.
135 *
136 * HeapAlloc is used rather than malloc because this memory is
137 * handed directly to Win32. Using the process default heap
138 * (GetProcessHeap) signals that this allocation is OS-facing.
139 */
140 LPPROC_THREAD_ATTRIBUTE_LIST attr_list =
141 (LPPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), 0, attr_list_size);
142
143 if (attr_list == NULL) {
144 (void)fprintf(stderr, "[PTY] Failed to allocate process thread attribute list.\n");
145 return false;
146 }
147
148 if (!InitializeProcThreadAttributeList(attr_list, 1, 0, &attr_list_size)) {
149 (void)fprintf(
150 stderr,
151 "[PTY] Failed to initialize attribute list. Error: %lu\n",
152 GetLastError());
153 HeapFree(GetProcessHeap(), 0, attr_list);
154 return false;
155 }
156
157 /*
158 * Phase 3: Wire the ConPTY handle into the attribute list.
159 *
160 * PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE is the key that tells Windows
161 * to attach the new process to our ConPTY session. This is the only
162 * supported mechanism there is no way to attach a ConPTY after the
163 * process has already been created.
164 */
165 if (!UpdateProcThreadAttribute(
166 attr_list,
167 0,
168 PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE,
169 state->hPC,
170 sizeof(HPCON),
171 NULL,
172 NULL)) {
173 (void)fprintf(
174 stderr,
175 "[PTY] Failed to set ConPTY process attribute. Error: %lu\n",
176 GetLastError());
177 DeleteProcThreadAttributeList(attr_list);
178 HeapFree(GetProcessHeap(), 0, attr_list);
179 return false;
180 }
181
182 /*
183 * Phase 4: Configure STARTUPINFOEXW.
184 *
185 * cb MUST be sizeof(STARTUPINFOEXW), not sizeof(STARTUPINFOW).
186 * Windows reads cb as a version tag to determine which struct it is
187 * looking at. Passing the wrong size causes Windows to silently
188 * ignore lpAttributeList and spawn the process with no ConPTY.
189 *
190 * Zero-initialization clears all other STARTUPINFOW fields (window
191 * position, console title, standard handle overrides), preventing
192 * garbage values from affecting process creation.
193 */
194 STARTUPINFOEXW si = {0};
195 si.StartupInfo.cb = sizeof(STARTUPINFOEXW);
196 si.lpAttributeList = attr_list;
197
198 /*
199 * Phase 5: Spawn the shell process.
200 *
201 * Key flags and parameters:
202 *
203 * - bInheritHandles = FALSE: the pipe handles are attached via the
204 * ConPTY attribute list, not via inheritance. Enabling inheritance
205 * would give the child duplicate pipe handles it doesn't know about,
206 * causing subtle pipe-lifetime bugs and potential deadlocks.
207 *
208 * - EXTENDED_STARTUPINFO_PRESENT: tells CreateProcessW to treat the
209 * startup info pointer as STARTUPINFOEXW and read lpAttributeList.
210 * This flag AND the correct cb size are both required. One without
211 * the other will silently skip the attribute list.
212 *
213 * - NULL for lpApplicationName: Windows resolves the executable by
214 * searching the system PATH, so bare names like L"cmd.exe" work.
215 */
216 PROCESS_INFORMATION pi = {0};
217 BOOL success = CreateProcessW(
218 NULL,
219 (LPWSTR)command_line,
220 NULL,
221 NULL,
222 FALSE,
223 EXTENDED_STARTUPINFO_PRESENT,
224 NULL,
225 NULL,
226 &si.StartupInfo,
227 &pi);
228
229 /*
230 * Phase 6: Tear down the attribute list immediately.
231 *
232 * DeleteProcThreadAttributeList performs internal kernel-side cleanup.
233 * It must be called BEFORE HeapFree skipping it can leak memory
234 * inside the kernel. The attribute list is no longer needed once
235 * CreateProcessW has returned.
236 */
237 DeleteProcThreadAttributeList(attr_list);
238 HeapFree(GetProcessHeap(), 0, attr_list);
239
240 if (!success) {
241 (void)fprintf(stderr, "[PTY] CreateProcessW failed. Error: %lu\n", GetLastError());
242 return false;
243 }
244
245 /*
246 * Phase 7: Store the process handle; discard the thread handle.
247 *
248 * We retain hProcess for lifecycle management (waiting for exit,
249 * querying exit code, etc.). We have no use for the main thread handle
250 * at this stage, so it is closed immediately to avoid a handle leak.
251 * Closing it does NOT affect the running thread.
252 */
253 state->hProcess = pi.hProcess;
254 safe_close_handle(&pi.hThread);
255
256 return true;
257}
258
259void
261{
262 if (!state) {
263 return;
264 }
265
266 if (state->hPC) {
267 ClosePseudoConsole(state->hPC);
268 state->hPC = NULL;
269 }
270
274}
275 // end pty_module
uint16_t u16
Definition base_types.h:23
#define ASSERT_NOT_NULL(ptr)
Asserts that a given pointer is not NULL.
#define INTERNAL
Marks a function or variable as strictly internal to its translation unit (static).
b32 pty_init(struct pty_state_t *state, u16 columns, u16 rows)
Initializes a Pseudo Console and establishes bidirectional communication pipes.
Definition pty.c:50
INTERNAL void safe_close_handle(HANDLE *h)
Safely closes a Win32 HANDLE and sets the pointer to NULL.
Definition pty.c:41
void pty_cleanup(struct pty_state_t *state)
Terminates the Pseudo Console session and releases all active system handles.
Definition pty.c:260
b32 pty_spawn(struct pty_state_t *state, LPCWSTR command_line)
Spawns a shell process and attaches it to an initialized Pseudo Console.
Definition pty.c:116
int32_t b32
Definition base_types.h:75
Pseudo Console (PTY) lifecycle, stream management, and shell process spawning.
Encapsulates the OS-level state of an active Pseudo Console session.
Definition pty.h:86
HPCON hPC
Definition pty.h:87
HANDLE hProcess
Definition pty.h:90
HANDLE hOutputRead
Definition pty.h:89
HANDLE hInputWrite
Definition pty.h:88