xref: /reactos/sdk/lib/ucrt/conio/popen.cpp (revision e98e9000)
1 //
2 // popen.cpp
3 //
4 //      Copyright (c) Microsoft Corporation.  All rights reserved.
5 //
6 // The _popen() and _pclose() functions, which open a pipe to a child process.
7 //
8 #include <corecrt_internal_stdio.h>
9 #include <process.h>
10 
11 
12 
13 #define STDIN     0
14 #define STDOUT    1
15 
16 
17 
18 namespace {
19 
20     template <typename Character>
21     struct fdopen_mode
22     {
23         Character mode[3];
24     };
25 
26     // This is the entry type for the stream pointer / process handle pairs that
27     // are stored for each outstanding popen.
28     struct process_handle_pair
29     {
30         FILE*    stream;
31         intptr_t process_handle;
32     };
33 
34     struct stream_traits
35     {
36         typedef FILE* type;
37 
close__anon7a101e1d0111::stream_traits38         static bool close(_In_ type h) throw()
39         {
40             fclose(h);
41             return true;
42         }
43 
get_invalid_value__anon7a101e1d0111::stream_traits44         static type get_invalid_value() throw()
45         {
46             return nullptr;
47         }
48     };
49 
50     struct process_handle_pair_traits
51     {
52         typedef process_handle_pair* type;
53 
close__anon7a101e1d0111::process_handle_pair_traits54         static bool close(_In_ type h) throw()
55         {
56             h->process_handle = 0;
57             h->stream         = nullptr;
58             return true;
59         }
60 
get_invalid_value__anon7a101e1d0111::process_handle_pair_traits61         static type get_invalid_value() throw()
62         {
63             return nullptr;
64         }
65     };
66 
67     typedef __crt_unique_handle_t<stream_traits>              unique_stream;
68     typedef __crt_unique_handle_t<process_handle_pair_traits> unique_process_handle_pair;
69 }
70 
71 
72 
73 // The global table of stream pointer / process handle pairs.  Access to this
74 // global tbale is only done via the idtab function.  The table is expanded as
75 // necessary (by idtab), and free table entries are reused.  (An entry is free
76 // if its stream is null.)  The table is never contracted.
77 static unsigned             __idtabsiz;
78 static process_handle_pair* __idpairs;
79 
80 
81 
82 // Finds the entry for the given stream in the global table.  If the stream is
83 // found, a pointer to it is returned; if the stream is not found, null is
84 // returned.
85 //
86 // If the stream is null, a new entry is allocated and a pointer to it is
87 // returned.  If no entries are available and expansion of the table fails,
88 // null is returned.
89 //
90 // This function assumes the caller has acquired the lock on the table already.
idtab(FILE * const stream)91 static process_handle_pair* __cdecl idtab(FILE* const stream) throw()
92 {
93     // Search the table, and return the matching entry if one is found:
94     process_handle_pair* const first = __idpairs;
95     process_handle_pair* const last  = first + __idtabsiz;
96     for (process_handle_pair* it = first; it != last; ++it)
97     {
98         if (it->stream == stream)
99             return it;
100     }
101 
102     // We did not find an entry in the table.  If the stream is null, then we
103     // try creating or expanding the table.  Otherwise, we return null.  Note
104     // that when the table is created or expanded, exactly one new entry is
105     // produced.  This must not be changed unless code is added to mark the
106     // extra entries as being free (e.g., by setting their stream fields to null.
107     if (stream != nullptr)
108         return nullptr;
109 
110     if (__idtabsiz + 1 < __idtabsiz)
111         return nullptr;
112 
113     if (__idtabsiz + 1 >= SIZE_MAX / sizeof(process_handle_pair))
114         return nullptr;
115 
116     process_handle_pair* const newptr = _recalloc_crt_t(process_handle_pair, __idpairs, __idtabsiz + 1).detach();
117     if (newptr == nullptr)
118         return nullptr;
119 
120     __idpairs = newptr;
121     process_handle_pair* const pairptr = newptr + __idtabsiz;
122     ++__idtabsiz;
123 
124     return pairptr;
125 }
126 
127 
128 
129 template <typename Character>
convert_popen_type_to_fdopen_mode(Character const * const type)130 static fdopen_mode<Character> __cdecl convert_popen_type_to_fdopen_mode(
131     Character const* const type
132     ) throw()
133 {
134     fdopen_mode<Character> result = fdopen_mode<Character>();
135 
136     Character const* type_it = type;
137 
138     while (*type_it == ' ')
139         ++type_it;
140 
141     _VALIDATE_RETURN(*type_it == 'w' || *type_it == 'r', EINVAL, result);
142     result.mode[0] = *type_it++;
143 
144     while (*type_it == ' ')
145         ++type_it;
146 
147     _VALIDATE_RETURN(*type_it == '\0' || *type_it == 't' || *type_it == 'b', EINVAL, result);
148     result.mode[1] = *type_it;
149 
150     return result;
151 }
152 
153 
154 
155 template <typename Character>
get_comspec()156 static Character const* __cdecl get_comspec() throw()
157 {
158     typedef __acrt_stdio_char_traits<Character> stdio_traits;
159 
160     static Character const comspec_name[] = { 'C', 'O', 'M', 'S', 'P', 'E', 'C', '\0' };
161 
162     Character* comspec_value = nullptr;
163     if (_ERRCHECK_EINVAL(stdio_traits::tdupenv_s_crt(&comspec_value, nullptr, comspec_name)) != 0)
164         return nullptr;
165 
166     return comspec_value;
167 }
168 
169 
170 
171 template <typename Character>
get_path()172 static Character const* __cdecl get_path() throw()
173 {
174     typedef __acrt_stdio_char_traits<Character> stdio_traits;
175 
176     static Character const path_name[] = { 'P', 'A', 'T', 'H', '\0' };
177 
178     Character* path_value = nullptr;
179     if (_ERRCHECK_EINVAL(stdio_traits::tdupenv_s_crt(&path_value, nullptr, path_name)) != 0)
180         return nullptr;
181 
182     return path_value;
183 }
184 
185 
186 
187 template <typename Character>
get_executable_path(Character const * const executable)188 static Character const* __cdecl get_executable_path(
189     Character const* const executable
190     ) throw()
191 {
192     typedef __acrt_stdio_char_traits<Character> stdio_traits;
193 
194     // If we can access the given path, just use it:
195     if (stdio_traits::taccess_s(executable, 0) == 0)
196         return executable;
197 
198     // Otherwise, we need to search the PATH:
199     __crt_unique_heap_ptr<Character> buffer(_calloc_crt_t(Character, MAX_PATH));
200     if (buffer.get() == nullptr)
201         return nullptr;
202 
203     __crt_unique_heap_ptr<Character const> path(get_path<Character>());
204 
205     Character const* current = path.get();
206     while ((current = stdio_traits::tgetpath(current, buffer.get(), MAX_PATH - 1)) != 0)
207     {
208         if (__crt_stdio_path_requires_backslash(buffer.get()))
209         {
210             static Character const backslash[] = { '\\', '\0' };
211             _ERRCHECK(stdio_traits::tcscat_s(buffer.get(), MAX_PATH, backslash));
212         }
213 
214         if (stdio_traits::tcslen(buffer.get()) + stdio_traits::tcslen(executable) >= MAX_PATH)
215             return nullptr;
216 
217         _ERRCHECK(stdio_traits::tcscat_s(buffer.get(), MAX_PATH, executable));
218 
219         if (stdio_traits::taccess_s(buffer.get(), 0) == 0)
220             return buffer.detach();
221     }
222 
223     return nullptr;
224 }
225 
226 
227 
228 template <typename Character>
common_popen_nolock(Character const * const command,Character const * const fdopen_mode,int const std_fh,int (& pipe_handles)[2])229 static FILE* __cdecl common_popen_nolock(
230     Character const* const command,
231     Character const* const fdopen_mode,
232     int              const std_fh,
233     int (&pipe_handles)[2]
234     ) throw()
235 {
236     typedef __acrt_stdio_char_traits<Character> stdio_traits;
237 
238     HANDLE const process_handle = GetCurrentProcess();
239 
240     // We only return the second pipe handle to the caller; for the first pipe,
241     // we just need to use the HANDLE:
242     __crt_unique_handle new_pipe_handle;
243     if (!DuplicateHandle(
244             process_handle,
245             reinterpret_cast<HANDLE>(_osfhnd(pipe_handles[0])),
246             process_handle,
247             new_pipe_handle.get_address_of(),
248             0,
249             TRUE,
250             DUPLICATE_SAME_ACCESS))
251     {
252         return nullptr;
253     }
254 
255     _close(pipe_handles[0]);
256     pipe_handles[0] = -1;
257 
258     // Associate a stream with the pipe handle to be returned to the caller:
259     unique_stream pipe_stream(stdio_traits::tfdopen(pipe_handles[1], fdopen_mode));
260     if (!pipe_stream)
261         return nullptr;
262 
263     // Obtain a proces handle pair in which to store the process handle:
264     unique_process_handle_pair id_pair(idtab(nullptr));
265     if (!id_pair)
266         return nullptr;
267 
268     // Determine which command processor to use:  command.com or cmd.exe:
269     static Character const default_cmd_exe[] = { 'c', 'm', 'd', '.', 'e', 'x', 'e', '\0' };
270 
271     __crt_unique_heap_ptr<Character const> const comspec_variable(get_comspec<Character>());
272     Character const* const cmd_exe = comspec_variable.get() != nullptr
273         ? comspec_variable.get()
274         : default_cmd_exe;
275 
276     STARTUPINFOW startup_info = { 0 };
277     startup_info.cb = sizeof(startup_info);
278 
279     // The following arguments are used by the OS for duplicating the handles:
280     startup_info.dwFlags = STARTF_USESTDHANDLES;
281     startup_info.hStdInput  = std_fh == STDIN  ? new_pipe_handle.get() : reinterpret_cast<HANDLE>(_osfhnd(0));
282     startup_info.hStdOutput = std_fh == STDOUT ? new_pipe_handle.get() : reinterpret_cast<HANDLE>(_osfhnd(1));
283     startup_info.hStdError  = reinterpret_cast<HANDLE>(_osfhnd(2));
284 
285     static Character const slash_c[] = { ' ', '/', 'c', ' ', '\0' };
286 
287     size_t const command_line_count =
288         stdio_traits::tcslen(cmd_exe) +
289         stdio_traits::tcslen(slash_c) +
290         stdio_traits::tcslen(command) +
291         1;
292 
293     __crt_unique_heap_ptr<Character> const command_line(_calloc_crt_t(Character, command_line_count));
294     if (command_line.get() == nullptr)
295         return nullptr;
296 
297     _ERRCHECK(stdio_traits::tcscpy_s(command_line.get(), command_line_count, cmd_exe));
298     _ERRCHECK(stdio_traits::tcscat_s(command_line.get(), command_line_count, slash_c));
299     _ERRCHECK(stdio_traits::tcscat_s(command_line.get(), command_line_count, command));
300 
301     // Find the path at which the executable is accessible:
302     Character const* const selected_cmd_exe(get_executable_path(cmd_exe));
303     if (selected_cmd_exe == nullptr)
304         return nullptr;
305 
306     // If get_executable_path() returned a path other than the one we gave it,
307     // we must be sure to free the string when we return:
308     __crt_unique_heap_ptr<Character const> const owned_final_exe_path(selected_cmd_exe != cmd_exe
309         ? selected_cmd_exe
310         : nullptr);
311 
312     PROCESS_INFORMATION process_info = PROCESS_INFORMATION();
313     BOOL const child_status = stdio_traits::create_process(
314         selected_cmd_exe,
315         command_line.get(),
316         nullptr,
317         nullptr,
318         TRUE,
319         0,
320         nullptr,
321         nullptr,
322         &startup_info,
323         &process_info);
324 
325     if (!child_status)
326         return nullptr;
327 
328     FILE* const result_stream = pipe_stream.detach();
329 
330     CloseHandle(process_info.hThread);
331     id_pair.get()->process_handle = reinterpret_cast<intptr_t>(process_info.hProcess);
332     id_pair.get()->stream         = result_stream;
333     id_pair.detach();
334     return result_stream;
335 }
336 
337 
338 
339 template <typename Character>
common_popen(Character const * const command,Character const * const type)340 static FILE* __cdecl common_popen(
341     Character const* const command,
342     Character const* const type
343     ) throw()
344 {
345     _VALIDATE_RETURN(command != nullptr, EINVAL, nullptr);
346     _VALIDATE_RETURN(type    != nullptr, EINVAL, nullptr);
347 
348     fdopen_mode<Character> const fdopen_mode = convert_popen_type_to_fdopen_mode(type);
349     if (fdopen_mode.mode[0] == '\0')
350         return nullptr;
351 
352     // Do the _pipe().  Note that neither of the resulting handles is inheritable.
353     int pipe_mode = _O_NOINHERIT;
354     if (fdopen_mode.mode[1] == 't') { pipe_mode |= _O_TEXT;   }
355     if (fdopen_mode.mode[1] == 'b') { pipe_mode |= _O_BINARY; }
356 
357     int pipe_handles[2];
358     if (_pipe(pipe_handles, 1024, pipe_mode) == -1)
359         return nullptr;
360 
361     int const std_fh = fdopen_mode.mode[0] == 'w'
362         ? STDIN
363         : STDOUT;
364 
365     int ordered_pipe_handles[] =
366     {
367         std_fh == STDIN ? pipe_handles[0] : pipe_handles[1],
368         std_fh == STDIN ? pipe_handles[1] : pipe_handles[0]
369     };
370 
371     FILE* return_value = nullptr;
372 
373     __acrt_lock(__acrt_popen_lock);
374     __try
375     {
376         errno_t const saved_errno = errno;
377 
378         return_value = common_popen_nolock(
379             command,
380             fdopen_mode.mode,
381             std_fh,
382             ordered_pipe_handles);
383 
384         errno = saved_errno;
385 
386         if (return_value != nullptr)
387             __leave;
388 
389         // If the implementation function returned successfully, everything was
390         // cleaned up except the lock.
391         int* const first = ordered_pipe_handles;
392         int* const last  = first + _countof(ordered_pipe_handles);
393         for (int* it = first; it != last; ++it)
394         {
395             if (*it != -1)
396                 _close(*it);
397         }
398     }
399     __finally
400     {
401         __acrt_unlock(__acrt_popen_lock);
402     }
403     __endtry
404 
405     return return_value;
406 }
407 
408 
409 
410 // Starts a child process using the given 'command' and opens a pipe to it, as
411 // requested via the 'type'.  If the 'type' string contains an 'r', the calling
412 // process can read the child command's standard output via the returned stream.
413 // If the 'type' string contains a 'w', the calling process can write to the
414 // child command's standard input via the returned stream.
415 //
416 // Returns a usable stream on success; returns null on failure.
_popen(char const * const command,char const * const type)417 extern "C" FILE* __cdecl _popen(
418     char const* const command,
419     char const* const type
420     )
421 {
422     return common_popen(command, type);
423 }
424 
425 
426 
_wpopen(wchar_t const * const command,wchar_t const * const type)427 extern "C" FILE* __cdecl _wpopen(
428     wchar_t const* const command,
429     wchar_t const* const type
430     )
431 {
432     return common_popen(command, type);
433 }
434 
435 
436 
437 // Waits on the child command with which the 'stream' is associated, then closes
438 // the stream and its associated pipe.  The 'stream' must have been returned from
439 // a previous call to _popen().  This function looks up the process handle in the
440 // global table, waits on it, then closes the stream.
441 //
442 // On success, the exit status of the child command is returned.  The format of
443 // the return value is the same as for cwait(), except that the low order and
444 // high order bytes are swapped.  If an error occurs, -1 is returned.
_pclose(FILE * const stream)445 extern "C" int __cdecl _pclose(FILE* const stream)
446 {
447     _VALIDATE_RETURN(stream != nullptr, EINVAL, -1);
448 
449     int return_value = -1;
450 
451     __acrt_lock(__acrt_popen_lock);
452     __try
453     {
454         process_handle_pair* const id_pair = idtab(stream);
455         if (id_pair == nullptr)
456         {
457             errno = EBADF;
458             __leave;
459         }
460 
461         fclose(stream);
462 
463         intptr_t const process_handle = id_pair->process_handle;
464 
465         // Mark the id pair as free (we will close the handle in the call to _cwait):
466         id_pair->stream         = nullptr;
467         id_pair->process_handle = 0;
468 
469         // Wait on the child copy of the command processor and its children:
470         errno_t const saved_errno = errno;
471         errno = 0;
472 
473         int status = 0;
474         if (_cwait(&status, process_handle, _WAIT_GRANDCHILD) != -1 || errno == EINTR)
475         {
476             errno = saved_errno;
477             return_value = status;
478             __leave;
479         }
480 
481         errno = saved_errno;
482     }
483     __finally
484     {
485         __acrt_unlock(__acrt_popen_lock);
486     }
487     __endtry
488 
489     return return_value;
490 }
491