summaryrefslogtreecommitdiff
path: root/src/test/modules/test_cloexec/test_cloexec.c
blob: 9f64545168440bd5b886869b4baa72a73d9be542 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
/*-------------------------------------------------------------------------
 *
 * test_cloexec.c
 *		Test O_CLOEXEC flag handling on Windows
 *
 * This program tests that:
 * 1. File handles opened with O_CLOEXEC are NOT inherited by child processes
 * 2. File handles opened without O_CLOEXEC ARE inherited by child processes
 *
 * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include <fcntl.h>
#include <sys/stat.h>

#ifdef WIN32
#include <windows.h>
#endif

#include "common/file_utils.h"
#include "port.h"

static void run_parent_tests(const char *testfile1, const char *testfile2);
static void run_child_tests(const char *handle1_str, const char *handle2_str);
static bool try_write_to_handle(HANDLE h, const char *label);

int
main(int argc, char *argv[])
{
	char		testfile1[MAXPGPATH];
	char		testfile2[MAXPGPATH];

	/* Windows-only test */
#ifndef WIN32
	fprintf(stderr, "This test only runs on Windows\n");
	return 0;
#endif

	if (argc == 3)
	{
		/*
		 * Child mode: receives two handle values as hex strings and attempts
		 * to write to them.
		 */
		run_child_tests(argv[1], argv[2]);
		return 0;
	}
	else if (argc == 1)
	{
		/* Parent mode: opens files and spawns child */
		snprintf(testfile1, sizeof(testfile1), "test_cloexec_1_%d.tmp", (int) getpid());
		snprintf(testfile2, sizeof(testfile2), "test_cloexec_2_%d.tmp", (int) getpid());

		run_parent_tests(testfile1, testfile2);

		/* Clean up test files */
		unlink(testfile1);
		unlink(testfile2);

		return 0;
	}
	else
	{
		fprintf(stderr, "Usage: %s [handle1_hex handle2_hex]\n", argv[0]);
		return 1;
	}
}

static void
run_parent_tests(const char *testfile1, const char *testfile2)
{
#ifdef WIN32
	int			fd1,
				fd2;
	HANDLE		h1,
				h2;
	char		cmdline[1024];
	STARTUPINFO si;
	PROCESS_INFORMATION pi;
	DWORD		exit_code;

	printf("Parent: Opening test files...\n");

	/*
	 * Open first file WITH O_CLOEXEC - should NOT be inherited
	 */
	fd1 = open(testfile1, O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC, 0600);
	if (fd1 < 0)
	{
		fprintf(stderr, "Failed to open %s: %s\n", testfile1, strerror(errno));
		exit(1);
	}

	/*
	 * Open second file WITHOUT O_CLOEXEC - should be inherited
	 */
	fd2 = open(testfile2, O_RDWR | O_CREAT | O_TRUNC, 0600);
	if (fd2 < 0)
	{
		fprintf(stderr, "Failed to open %s: %s\n", testfile2, strerror(errno));
		close(fd1);
		exit(1);
	}

	/* Get Windows HANDLEs from file descriptors */
	h1 = (HANDLE) _get_osfhandle(fd1);
	h2 = (HANDLE) _get_osfhandle(fd2);

	if (h1 == INVALID_HANDLE_VALUE || h2 == INVALID_HANDLE_VALUE)
	{
		fprintf(stderr, "Failed to get OS handles\n");
		close(fd1);
		close(fd2);
		exit(1);
	}

	printf("Parent: fd1=%d (O_CLOEXEC) -> HANDLE=%p\n", fd1, h1);
	printf("Parent: fd2=%d (no O_CLOEXEC) -> HANDLE=%p\n", fd2, h2);

	/*
	 * Spawn child process with bInheritHandles=TRUE, passing handle values as
	 * hex strings
	 */
	snprintf(cmdline, sizeof(cmdline), "\"%s\" %p %p",
			 GetCommandLine(), h1, h2);

	/*
	 * Find the actual executable path by removing any arguments from
	 * GetCommandLine().
	 */
	{
		char		exe_path[MAX_PATH];
		char	   *space_pos;

		GetModuleFileName(NULL, exe_path, sizeof(exe_path));
		snprintf(cmdline, sizeof(cmdline), "\"%s\" %p %p",
				 exe_path, h1, h2);
	}

	memset(&si, 0, sizeof(si));
	si.cb = sizeof(si);
	memset(&pi, 0, sizeof(pi));

	printf("Parent: Spawning child process...\n");
	printf("Parent: Command line: %s\n", cmdline);

	if (!CreateProcess(NULL,	/* application name */
					   cmdline, /* command line */
					   NULL,	/* process security attributes */
					   NULL,	/* thread security attributes */
					   TRUE,	/* bInheritHandles - CRITICAL! */
					   0,		/* creation flags */
					   NULL,	/* environment */
					   NULL,	/* current directory */
					   &si,		/* startup info */
					   &pi))	/* process information */
	{
		fprintf(stderr, "CreateProcess failed: %lu\n", GetLastError());
		close(fd1);
		close(fd2);
		exit(1);
	}

	printf("Parent: Waiting for child process...\n");

	/* Wait for child to complete */
	WaitForSingleObject(pi.hProcess, INFINITE);
	GetExitCodeProcess(pi.hProcess, &exit_code);

	CloseHandle(pi.hProcess);
	CloseHandle(pi.hThread);

	close(fd1);
	close(fd2);

	printf("Parent: Child exit code: %lu\n", exit_code);

	if (exit_code == 0)
		printf("Parent: SUCCESS - O_CLOEXEC behavior verified\n");
	else
	{
		printf("Parent: FAILURE - O_CLOEXEC not working correctly\n");
		exit(1);
	}
#endif
}

static void
run_child_tests(const char *handle1_str, const char *handle2_str)
{
#ifdef WIN32
	HANDLE		h1,
				h2;
	bool		h1_worked,
				h2_worked;

	/* Parse handle values from hex strings */
	if (sscanf(handle1_str, "%p", &h1) != 1 ||
		sscanf(handle2_str, "%p", &h2) != 1)
	{
		fprintf(stderr, "Child: Failed to parse handle values\n");
		exit(1);
	}

	printf("Child: Received HANDLE1=%p (should fail - O_CLOEXEC)\n", h1);
	printf("Child: Received HANDLE2=%p (should work - no O_CLOEXEC)\n", h2);

	/* Try to write to both handles */
	h1_worked = try_write_to_handle(h1, "HANDLE1");
	h2_worked = try_write_to_handle(h2, "HANDLE2");

	printf("Child: HANDLE1 (O_CLOEXEC): %s\n",
		   h1_worked ? "ACCESSIBLE (BAD!)" : "NOT ACCESSIBLE (GOOD!)");
	printf("Child: HANDLE2 (no O_CLOEXEC): %s\n",
		   h2_worked ? "ACCESSIBLE (GOOD!)" : "NOT ACCESSIBLE (BAD!)");

	/*
	 * For O_CLOEXEC to work correctly, h1 should NOT be accessible (h1_worked
	 * == false) and h2 SHOULD be accessible (h2_worked == true).
	 */
	if (!h1_worked && h2_worked)
	{
		printf("Child: Test PASSED - O_CLOEXEC working correctly\n");
		exit(0);
	}
	else
	{
		printf("Child: Test FAILED - O_CLOEXEC not working correctly\n");
		exit(1);
	}
#endif
}

static bool
try_write_to_handle(HANDLE h, const char *label)
{
#ifdef WIN32
	const char *test_data = "test\n";
	DWORD		bytes_written;
	BOOL		result;

	result = WriteFile(h, test_data, strlen(test_data), &bytes_written, NULL);

	if (result && bytes_written == strlen(test_data))
	{
		printf("Child: Successfully wrote to %s\n", label);
		return true;
	}
	else
	{
		printf("Child: Failed to write to %s (error %lu)\n",
			   label, GetLastError());
		return false;
	}
#else
	return false;
#endif
}