You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
postgres/src/test/modules/test_cloexec/test_cloexec.c

262 lines
6.1 KiB

/*-------------------------------------------------------------------------
*
* 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
}