PostgreSQL Source Code git master
test_cloexec.c
Go to the documentation of this file.
1/*-------------------------------------------------------------------------
2 *
3 * test_cloexec.c
4 * Test O_CLOEXEC flag handling on Windows
5 *
6 * This program tests that:
7 * 1. File handles opened with O_CLOEXEC are NOT inherited by child processes
8 * 2. File handles opened without O_CLOEXEC ARE inherited by child processes
9 *
10 * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
11 *
12 *-------------------------------------------------------------------------
13 */
14
15#include "postgres.h"
16
17#include <fcntl.h>
18#include <sys/stat.h>
19
20#ifdef WIN32
21#include <windows.h>
22#endif
23
24#include "common/file_utils.h"
25#include "port.h"
26
27static void run_parent_tests(const char *testfile1, const char *testfile2);
28static void run_child_tests(const char *handle1_str, const char *handle2_str);
29static bool try_write_to_handle(HANDLE h, const char *label);
30
31int
32main(int argc, char *argv[])
33{
34 char testfile1[MAXPGPATH];
35 char testfile2[MAXPGPATH];
36
37 /* Windows-only test */
38#ifndef WIN32
39 fprintf(stderr, "This test only runs on Windows\n");
40 return 0;
41#endif
42
43 if (argc == 3)
44 {
45 /*
46 * Child mode: receives two handle values as hex strings and attempts
47 * to write to them.
48 */
49 run_child_tests(argv[1], argv[2]);
50 return 0;
51 }
52 else if (argc == 1)
53 {
54 /* Parent mode: opens files and spawns child */
55 snprintf(testfile1, sizeof(testfile1), "test_cloexec_1_%d.tmp", (int) getpid());
56 snprintf(testfile2, sizeof(testfile2), "test_cloexec_2_%d.tmp", (int) getpid());
57
58 run_parent_tests(testfile1, testfile2);
59
60 /* Clean up test files */
61 unlink(testfile1);
62 unlink(testfile2);
63
64 return 0;
65 }
66 else
67 {
68 fprintf(stderr, "Usage: %s [handle1_hex handle2_hex]\n", argv[0]);
69 return 1;
70 }
71}
72
73static void
74run_parent_tests(const char *testfile1, const char *testfile2)
75{
76#ifdef WIN32
77 int fd1,
78 fd2;
79 HANDLE h1,
80 h2;
81 char cmdline[1024];
82 STARTUPINFO si;
83 PROCESS_INFORMATION pi;
84 DWORD exit_code;
85
86 printf("Parent: Opening test files...\n");
87
88 /*
89 * Open first file WITH O_CLOEXEC - should NOT be inherited
90 */
91 fd1 = open(testfile1, O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC, 0600);
92 if (fd1 < 0)
93 {
94 fprintf(stderr, "Failed to open %s: %s\n", testfile1, strerror(errno));
95 exit(1);
96 }
97
98 /*
99 * Open second file WITHOUT O_CLOEXEC - should be inherited
100 */
101 fd2 = open(testfile2, O_RDWR | O_CREAT | O_TRUNC, 0600);
102 if (fd2 < 0)
103 {
104 fprintf(stderr, "Failed to open %s: %s\n", testfile2, strerror(errno));
105 close(fd1);
106 exit(1);
107 }
108
109 /* Get Windows HANDLEs from file descriptors */
110 h1 = (HANDLE) _get_osfhandle(fd1);
111 h2 = (HANDLE) _get_osfhandle(fd2);
112
113 if (h1 == INVALID_HANDLE_VALUE || h2 == INVALID_HANDLE_VALUE)
114 {
115 fprintf(stderr, "Failed to get OS handles\n");
116 close(fd1);
117 close(fd2);
118 exit(1);
119 }
120
121 printf("Parent: fd1=%d (O_CLOEXEC) -> HANDLE=%p\n", fd1, h1);
122 printf("Parent: fd2=%d (no O_CLOEXEC) -> HANDLE=%p\n", fd2, h2);
123
124 /*
125 * Spawn child process with bInheritHandles=TRUE, passing handle values as
126 * hex strings
127 */
128 snprintf(cmdline, sizeof(cmdline), "\"%s\" %p %p",
129 GetCommandLine(), h1, h2);
130
131 /*
132 * Find the actual executable path by removing any arguments from
133 * GetCommandLine().
134 */
135 {
136 char exe_path[MAX_PATH];
137 char *space_pos;
138
139 GetModuleFileName(NULL, exe_path, sizeof(exe_path));
140 snprintf(cmdline, sizeof(cmdline), "\"%s\" %p %p",
141 exe_path, h1, h2);
142 }
143
144 memset(&si, 0, sizeof(si));
145 si.cb = sizeof(si);
146 memset(&pi, 0, sizeof(pi));
147
148 printf("Parent: Spawning child process...\n");
149 printf("Parent: Command line: %s\n", cmdline);
150
151 if (!CreateProcess(NULL, /* application name */
152 cmdline, /* command line */
153 NULL, /* process security attributes */
154 NULL, /* thread security attributes */
155 TRUE, /* bInheritHandles - CRITICAL! */
156 0, /* creation flags */
157 NULL, /* environment */
158 NULL, /* current directory */
159 &si, /* startup info */
160 &pi)) /* process information */
161 {
162 fprintf(stderr, "CreateProcess failed: %lu\n", GetLastError());
163 close(fd1);
164 close(fd2);
165 exit(1);
166 }
167
168 printf("Parent: Waiting for child process...\n");
169
170 /* Wait for child to complete */
171 WaitForSingleObject(pi.hProcess, INFINITE);
172 GetExitCodeProcess(pi.hProcess, &exit_code);
173
174 CloseHandle(pi.hProcess);
175 CloseHandle(pi.hThread);
176
177 close(fd1);
178 close(fd2);
179
180 printf("Parent: Child exit code: %lu\n", exit_code);
181
182 if (exit_code == 0)
183 printf("Parent: SUCCESS - O_CLOEXEC behavior verified\n");
184 else
185 {
186 printf("Parent: FAILURE - O_CLOEXEC not working correctly\n");
187 exit(1);
188 }
189#endif
190}
191
192static void
193run_child_tests(const char *handle1_str, const char *handle2_str)
194{
195#ifdef WIN32
196 HANDLE h1,
197 h2;
198 bool h1_worked,
199 h2_worked;
200
201 /* Parse handle values from hex strings */
202 if (sscanf(handle1_str, "%p", &h1) != 1 ||
203 sscanf(handle2_str, "%p", &h2) != 1)
204 {
205 fprintf(stderr, "Child: Failed to parse handle values\n");
206 exit(1);
207 }
208
209 printf("Child: Received HANDLE1=%p (should fail - O_CLOEXEC)\n", h1);
210 printf("Child: Received HANDLE2=%p (should work - no O_CLOEXEC)\n", h2);
211
212 /* Try to write to both handles */
213 h1_worked = try_write_to_handle(h1, "HANDLE1");
214 h2_worked = try_write_to_handle(h2, "HANDLE2");
215
216 printf("Child: HANDLE1 (O_CLOEXEC): %s\n",
217 h1_worked ? "ACCESSIBLE (BAD!)" : "NOT ACCESSIBLE (GOOD!)");
218 printf("Child: HANDLE2 (no O_CLOEXEC): %s\n",
219 h2_worked ? "ACCESSIBLE (GOOD!)" : "NOT ACCESSIBLE (BAD!)");
220
221 /*
222 * For O_CLOEXEC to work correctly, h1 should NOT be accessible (h1_worked
223 * == false) and h2 SHOULD be accessible (h2_worked == true).
224 */
225 if (!h1_worked && h2_worked)
226 {
227 printf("Child: Test PASSED - O_CLOEXEC working correctly\n");
228 exit(0);
229 }
230 else
231 {
232 printf("Child: Test FAILED - O_CLOEXEC not working correctly\n");
233 exit(1);
234 }
235#endif
236}
237
238static bool
239try_write_to_handle(HANDLE h, const char *label)
240{
241#ifdef WIN32
242 const char *test_data = "test\n";
243 DWORD bytes_written;
244 BOOL result;
245
246 result = WriteFile(h, test_data, strlen(test_data), &bytes_written, NULL);
247
248 if (result && bytes_written == strlen(test_data))
249 {
250 printf("Child: Successfully wrote to %s\n", label);
251 return true;
252 }
253 else
254 {
255 printf("Child: Failed to write to %s (error %lu)\n",
256 label, GetLastError());
257 return false;
258 }
259#else
260 return false;
261#endif
262}
#define fprintf(file, fmt, msg)
Definition: cubescan.l:21
#define close(a)
Definition: win32.h:12
static char * label
#define MAXPGPATH
#define strerror
Definition: port.h:273
#define snprintf
Definition: port.h:260
#define printf(...)
Definition: port.h:266
static void run_child_tests(const char *handle1_str, const char *handle2_str)
Definition: test_cloexec.c:193
int main(int argc, char *argv[])
Definition: test_cloexec.c:32
static bool try_write_to_handle(HANDLE h, const char *label)
Definition: test_cloexec.c:239
static void run_parent_tests(const char *testfile1, const char *testfile2)
Definition: test_cloexec.c:74
#define O_CLOEXEC
Definition: win32_port.h:344