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