Skip to content

Commit 17a320a

Browse files
authored
Merge pull request #1079 from JAi-SATHVIK/master
fix(stdlib_system): use STARTUPINFO for windows stdin redirection
2 parents af58730 + d2fdd50 commit 17a320a

File tree

2 files changed

+79
-37
lines changed

2 files changed

+79
-37
lines changed

src/stdlib_system_subprocess.c

Lines changed: 49 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -34,19 +34,17 @@ void process_create_windows(const char* cmd, const char* stdin_stream,
3434

3535
STARTUPINFO si;
3636
PROCESS_INFORMATION pi;
37-
HANDLE hStdout = NULL, hStderr = NULL;
37+
HANDLE hStdout = NULL, hStderr = NULL, hStdin = NULL;
3838
SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES), NULL, TRUE };
3939
FILE* stdin_fp = NULL;
40+
char* full_cmd = NULL;
4041

4142
// Initialize null handle
4243
(*pid) = 0;
4344

4445
ZeroMemory(&si, sizeof(si));
4546
si.cb = sizeof(STARTUPINFO);
4647

47-
// If possible, we redirect stdout/stderr to file handles directly.
48-
// This will override any cmd redirection settings (<>). For stdin
49-
5048
// Write stdin_stream to stdin_file if provided
5149
if (stdin_stream && stdin_file) {
5250
stdin_fp = fopen(stdin_file, "w");
@@ -58,57 +56,68 @@ void process_create_windows(const char* cmd, const char* stdin_stream,
5856
fclose(stdin_fp);
5957
}
6058

59+
// Open stdin file if provided, otherwise use the null device
60+
if (stdin_file) {
61+
hStdin = CreateFile(stdin_file, GENERIC_READ, 0, &sa, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
62+
} else {
63+
hStdin = CreateFile("NUL", GENERIC_READ, 0, &sa, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
64+
}
65+
66+
if (hStdin == INVALID_HANDLE_VALUE) {
67+
fprintf(stderr, "Failed to open input source (file or null)\n");
68+
// No handles to close yet
69+
return;
70+
}
71+
72+
si.hStdInput = hStdin;
73+
si.dwFlags |= STARTF_USESTDHANDLES;
74+
6175
// Open stdout file if provided, otherwise use the null device
6276
if (stdout_file) {
6377
hStdout = CreateFile(stdout_file, GENERIC_WRITE, 0, &sa, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
64-
if (hStdout == INVALID_HANDLE_VALUE) {
65-
fprintf(stderr, "Failed to open stdout file\n");
66-
return;
67-
}
6878
} else {
69-
hStdout = CreateFile("NUL", GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
70-
if (hStdout == INVALID_HANDLE_VALUE) {
71-
fprintf(stderr, "Failed to open null device for stdout\n");
72-
return;
73-
}
79+
hStdout = CreateFile("NUL", GENERIC_WRITE, 0, &sa, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
80+
}
81+
82+
if (hStdout == INVALID_HANDLE_VALUE) {
83+
fprintf(stderr, "Failed to open stdout sink\n");
84+
CloseHandle(hStdin);
85+
return;
7486
}
87+
7588
si.hStdOutput = hStdout;
76-
si.dwFlags |= STARTF_USESTDHANDLES;
7789

7890
// Open stderr file if provided, otherwise use the null device
7991
if (stderr_file) {
8092
hStderr = CreateFile(stderr_file, GENERIC_WRITE, 0, &sa, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
81-
if (hStderr == INVALID_HANDLE_VALUE) {
82-
fprintf(stderr, "Failed to open stderr file\n");
83-
return;
84-
}
8593
} else {
86-
hStderr = CreateFile("NUL", GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
87-
if (hStderr == INVALID_HANDLE_VALUE) {
88-
fprintf(stderr, "Failed to open null device for stderr\n");
89-
return;
90-
}
94+
hStderr = CreateFile("NUL", GENERIC_WRITE, 0, &sa, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
95+
}
96+
97+
if (hStderr == INVALID_HANDLE_VALUE) {
98+
fprintf(stderr, "Failed to open stderr sink\n");
99+
CloseHandle(hStdin);
100+
CloseHandle(hStdout);
101+
return;
91102
}
103+
92104
si.hStdError = hStderr;
93-
si.dwFlags |= STARTF_USESTDHANDLES;
94105

95-
// Prepare the command line with redirected stdin
96-
char* full_cmd;
106+
// Prepare the command line
97107
size_t cmd_len = strlen(cmd);
98-
size_t stdin_len = stdin_file ? strlen(stdin_file) : 0;
99-
size_t full_cmd_len = cmd_len + stdin_len + 5;
108+
size_t full_cmd_len = cmd_len + 1;
100109
full_cmd = (char*)malloc(full_cmd_len);
101110
if (!full_cmd) {
102111
fprintf(stderr, "Failed to allocate memory for full_cmd\n");
112+
CloseHandle(hStdin);
113+
CloseHandle(hStdout);
114+
CloseHandle(hStderr);
103115
return;
104116
}
105117

106118
// Use full_cmd as needed (e.g., pass to CreateProcess)
107-
if (stdin_file) {
108-
snprintf(full_cmd, full_cmd_len, "%s < %s", cmd, stdin_file);
109-
} else {
110-
snprintf(full_cmd, full_cmd_len, "%s", cmd);
111-
}
119+
snprintf(full_cmd, full_cmd_len, "%s", cmd);
120+
112121

113122
// Create the process
114123
BOOL success = CreateProcess(
@@ -129,12 +138,16 @@ void process_create_windows(const char* cmd, const char* stdin_stream,
129138

130139
if (!success) {
131140
fprintf(stderr, "CreateProcess failed (%lu).\n", GetLastError());
141+
CloseHandle(hStdin);
142+
CloseHandle(hStdout);
143+
CloseHandle(hStderr);
132144
return;
133145
}
134146

135-
// Close unneeded handles
136-
if (hStdout) CloseHandle(hStdout);
137-
if (hStderr) CloseHandle(hStderr);
147+
// Close unneeded handles (the child has its own duplicates now)
148+
CloseHandle(hStdin);
149+
CloseHandle(hStdout);
150+
CloseHandle(hStderr);
138151

139152
// Return the process handle for status queries
140153
CloseHandle(pi.hThread); // Close the thread handle

test/system/test_subprocess.f90

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ subroutine collect_suite(testsuite)
1515
new_unittest('test_run_synchronous', test_run_synchronous), &
1616
new_unittest('test_run_asynchronous', test_run_asynchronous), &
1717
new_unittest('test_process_kill', test_process_kill), &
18-
new_unittest('test_process_state', test_process_state) &
18+
new_unittest('test_process_state', test_process_state), &
19+
new_unittest('test_input_redirection', test_input_redirection) &
1920
]
2021
end subroutine collect_suite
2122

@@ -116,6 +117,34 @@ subroutine test_process_state(error)
116117
if (allocated(error)) return
117118
end subroutine test_process_state
118119

120+
!> Test input redirection
121+
subroutine test_input_redirection(error)
122+
type(error_type), allocatable, intent(out) :: error
123+
type(process_type) :: process
124+
character(len=*), parameter :: input_string = "Hello Stdin"
125+
126+
if (is_windows()) then
127+
! findstr "^" echoes input lines.
128+
! Note: We need complex quoting because of how arguments are parsed.
129+
! Actually, sticking to something simpler if possible.
130+
! "more" implies paging which might hang. "sort" is usually safe.
131+
process = run("sort", stdin=input_string, want_stdout=.true.)
132+
else
133+
process = run("cat", stdin=input_string, want_stdout=.true.)
134+
endif
135+
136+
call check(error, process%completed, "Process did not complete")
137+
if (allocated(error)) return
138+
139+
call check(error, process%exit_code == 0, "Process failed with non-zero exit code")
140+
if (allocated(error)) return
141+
142+
! Check if output matches input (sort of "Hello Stdin" is "Hello Stdin")
143+
call check(error, index(process%stdout, input_string) > 0, &
144+
"Output <"//trim(process%stdout)//"> should contain <"//input_string//">")
145+
146+
end subroutine test_input_redirection
147+
119148
end module test_subprocess
120149

121150
program tester

0 commit comments

Comments
 (0)