Branch data Line data Source code
1 : : /* gspawn-win32-helper.c - Helper program for process launching on Win32.
2 : : *
3 : : * Copyright 2000 Red Hat, Inc.
4 : : * Copyright 2000 Tor Lillqvist
5 : : *
6 : : * SPDX-License-Identifier: LGPL-2.1-or-later
7 : : *
8 : : * This library is free software; you can redistribute it and/or
9 : : * modify it under the terms of the GNU Lesser General Public
10 : : * License as published by the Free Software Foundation; either
11 : : * version 2.1 of the License, or (at your option) any later version.
12 : : *
13 : : * This library is distributed in the hope that it will be useful,
14 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 : : * Lesser General Public License for more details.
17 : : *
18 : : * You should have received a copy of the GNU Lesser General Public License
19 : : * along with this library; if not, see <http://www.gnu.org/licenses/>.
20 : : */
21 : :
22 : : #include "config.h"
23 : :
24 : : #include <fcntl.h>
25 : :
26 : : #undef G_LOG_DOMAIN
27 : : #include "glib.h"
28 : : #define GSPAWN_HELPER
29 : : #include "gspawn-win32.c" /* For shared definitions */
30 : : #include "glib/glib-private.h"
31 : :
32 : :
33 : : static void
34 : 0 : write_err_and_exit (gint fd,
35 : : gintptr msg)
36 : : {
37 : 0 : gintptr en = errno;
38 : :
39 : 0 : write (fd, &msg, sizeof(gintptr));
40 : 0 : write (fd, &en, sizeof(gintptr));
41 : :
42 : 0 : _exit (1);
43 : : }
44 : :
45 : : #ifdef __GNUC__
46 : : # ifndef _stdcall
47 : : # define _stdcall __attribute__((stdcall))
48 : : # endif
49 : : #endif
50 : :
51 : : /* We build gspawn-win32-helper.exe as a Windows GUI application
52 : : * to avoid any temporarily flashing console windows in case
53 : : * the gspawn function is invoked by a GUI program. Thus, no main()
54 : : * but a WinMain().
55 : : */
56 : :
57 : : /* Copy of protect_argv that handles wchar_t strings */
58 : :
59 : : static gint
60 : 422 : protect_wargv (gint argc,
61 : : wchar_t **wargv,
62 : : wchar_t ***new_wargv)
63 : : {
64 : : gint i;
65 : :
66 : 422 : *new_wargv = g_new (wchar_t *, argc+1);
67 : :
68 : : /* Quote each argv element if necessary, so that it will get
69 : : * reconstructed correctly in the C runtime startup code. Note that
70 : : * the unquoting algorithm in the C runtime is really weird, and
71 : : * rather different than what Unix shells do. See stdargv.c in the C
72 : : * runtime sources (in the Platform SDK, in src/crt).
73 : : *
74 : : * Note that a new_wargv[0] constructed by this function should
75 : : * *not* be passed as the filename argument to a _wspawn* or _wexec*
76 : : * family function. That argument should be the real file name
77 : : * without any quoting.
78 : : */
79 : 2255 : for (i = 0; i < argc; i++)
80 : : {
81 : 1833 : wchar_t *p = wargv[i];
82 : : wchar_t *q;
83 : 1833 : size_t len = 0;
84 : 1833 : size_t pre_bslash = 0;
85 : 1833 : gboolean need_dblquotes = FALSE;
86 : 49426 : while (*p)
87 : : {
88 : 47593 : if (*p == ' ' || *p == '\t')
89 : 36 : need_dblquotes = TRUE;
90 : : /* estimate max len, assuming that all escapable characters will be escaped */
91 : 47593 : if (*p == '"' || *p == '\\')
92 : 187 : len += 2;
93 : : else
94 : 47406 : len += 1;
95 : 47593 : p++;
96 : : }
97 : :
98 : 1833 : q = (*new_wargv)[i] = g_new (wchar_t, len + need_dblquotes*2 + 1);
99 : 1833 : p = wargv[i];
100 : :
101 : 1833 : if (need_dblquotes)
102 : 30 : *q++ = '"';
103 : :
104 : : /* Only quotes and backslashes preceding quotes are escaped:
105 : : * see "Parsing C Command-Line Arguments" at
106 : : * https://docs.microsoft.com/en-us/cpp/c-language/parsing-c-command-line-arguments
107 : : */
108 : 49426 : while (*p)
109 : : {
110 : 47593 : if (*p == '"')
111 : : {
112 : : /* Add backslash for escaping quote itself */
113 : 11 : *q++ = '\\';
114 : : /* Add backslash for every preceding backslash for escaping it */
115 : 17 : for (;pre_bslash > 0; --pre_bslash)
116 : 6 : *q++ = '\\';
117 : 11 : }
118 : :
119 : : /* Count length of continuous sequence of preceding backslashes. */
120 : 47593 : if (*p == '\\')
121 : 176 : ++pre_bslash;
122 : : else
123 : 47417 : pre_bslash = 0;
124 : :
125 : 47593 : *q++ = *p;
126 : 47593 : p++;
127 : : }
128 : :
129 : 1833 : if (need_dblquotes)
130 : : {
131 : : /* Add backslash for every preceding backslash for escaping it,
132 : : * do NOT escape quote itself.
133 : : */
134 : 31 : for (;pre_bslash > 0; --pre_bslash)
135 : 1 : *q++ = '\\';
136 : 30 : *q++ = '"';
137 : 30 : }
138 : 1833 : *q++ = '\0';
139 : 1833 : }
140 : 422 : (*new_wargv)[argc] = NULL;
141 : :
142 : 422 : return argc;
143 : : }
144 : :
145 : : static int
146 : 2418 : checked_dup2 (int oldfd, int newfd, int report_fd)
147 : : {
148 : 2418 : if (oldfd == newfd)
149 : 0 : return newfd;
150 : :
151 : 2418 : if (dup2 (oldfd, newfd) == -1)
152 : 0 : write_err_and_exit (report_fd, CHILD_DUP_FAILED);
153 : :
154 : 2418 : return newfd;
155 : 2418 : }
156 : :
157 : : #ifndef HELPER_CONSOLE
158 : : int _stdcall
159 : 0 : WinMain (struct HINSTANCE__ *hInstance,
160 : : struct HINSTANCE__ *hPrevInstance,
161 : : char *lpszCmdLine,
162 : : int nCmdShow)
163 : : #else
164 : : int
165 : 422 : main (int ignored_argc, char **ignored_argv)
166 : : #endif
167 : : {
168 : : GHashTable *fds; /* (element-type int int) */
169 : 422 : int child_err_report_fd = -1;
170 : 422 : int helper_sync_fd = -1;
171 : 422 : int saved_stderr_fd = -1;
172 : : int i;
173 : : int fd;
174 : : int mode;
175 : 422 : int maxfd = 2;
176 : : gintptr handle;
177 : : int saved_errno;
178 : 422 : gintptr no_error = CHILD_NO_ERROR;
179 : 422 : gint argv_zero_offset = ARG_PROGRAM;
180 : : wchar_t **new_wargv;
181 : : int argc;
182 : : char **argv;
183 : : wchar_t **wargv;
184 : : char c;
185 : : GWin32InvalidParameterHandler handler;
186 : :
187 : : /* Fetch the wide-char argument vector */
188 : 422 : wargv = CommandLineToArgvW (GetCommandLineW(), &argc);
189 : :
190 : 422 : g_assert (argc >= ARG_COUNT);
191 : :
192 : : /* Convert unicode wargs to utf8 */
193 : 422 : argv = g_new(char *, argc + 1);
194 : 6898 : for (i = 0; i < argc; i++)
195 : 6476 : argv[i] = g_utf16_to_utf8(wargv[i], -1, NULL, NULL, NULL);
196 : 422 : argv[i] = NULL;
197 : :
198 : : /* argv[ARG_CHILD_ERR_REPORT] is the file descriptor number onto
199 : : * which write error messages.
200 : : */
201 : 422 : child_err_report_fd = atoi (argv[ARG_CHILD_ERR_REPORT]);
202 : 422 : maxfd = MAX (child_err_report_fd, maxfd);
203 : :
204 : : /* Hack to implement G_SPAWN_FILE_AND_ARGV_ZERO. If
205 : : * argv[ARG_CHILD_ERR_REPORT] is suffixed with a '#' it means we get
206 : : * the program to run and its argv[0] separately.
207 : : */
208 : 422 : if (argv[ARG_CHILD_ERR_REPORT][strlen (argv[ARG_CHILD_ERR_REPORT]) - 1] == '#')
209 : 1 : argv_zero_offset++;
210 : :
211 : : /* argv[ARG_HELPER_SYNC] is the file descriptor number we read a
212 : : * byte that tells us it is OK to exit. We have to wait until the
213 : : * parent allows us to exit, so that the parent has had time to
214 : : * duplicate the process handle we sent it. Duplicating a handle
215 : : * from another process works only if that other process exists.
216 : : */
217 : 422 : helper_sync_fd = atoi (argv[ARG_HELPER_SYNC]);
218 : 422 : maxfd = MAX (helper_sync_fd, maxfd);
219 : :
220 : : /* argv[ARG_STDIN..ARG_STDERR] are the file descriptor numbers that
221 : : * should be dup2'd to 0, 1 and 2. '-' if the corresponding fd
222 : : * should be left alone, and 'z' if it should be connected to the
223 : : * bit bucket NUL:.
224 : : */
225 : 422 : if (argv[ARG_STDIN][0] == '-')
226 : : ; /* Nothing */
227 : 419 : else if (argv[ARG_STDIN][0] == 'z')
228 : : {
229 : 363 : fd = open ("NUL:", O_RDONLY);
230 : 363 : checked_dup2 (fd, 0, child_err_report_fd);
231 : 363 : }
232 : : else
233 : : {
234 : 56 : fd = atoi (argv[ARG_STDIN]);
235 : 56 : checked_dup2 (fd, 0, child_err_report_fd);
236 : : }
237 : :
238 : 422 : if (argv[ARG_STDOUT][0] == '-')
239 : : ; /* Nothing */
240 : 391 : else if (argv[ARG_STDOUT][0] == 'z')
241 : : {
242 : 28 : fd = open ("NUL:", O_WRONLY);
243 : 28 : checked_dup2 (fd, 1, child_err_report_fd);
244 : 28 : }
245 : : else
246 : : {
247 : 363 : fd = atoi (argv[ARG_STDOUT]);
248 : 363 : checked_dup2 (fd, 1, child_err_report_fd);
249 : : }
250 : :
251 : : /* GUI application do not necessarily have a stderr */
252 : 422 : if (_fileno (stderr) == 2)
253 : : {
254 : 844 : saved_stderr_fd = GLIB_PRIVATE_CALL (g_win32_reopen_noninherited) (
255 : 422 : dup (2), _O_WRONLY, NULL);
256 : 422 : if (saved_stderr_fd == -1)
257 : 0 : write_err_and_exit (child_err_report_fd, CHILD_DUP_FAILED);
258 : 422 : }
259 : :
260 : 422 : maxfd = MAX (saved_stderr_fd, maxfd);
261 : 422 : if (argv[ARG_STDERR][0] == '-')
262 : : ; /* Nothing */
263 : 342 : else if (argv[ARG_STDERR][0] == 'z')
264 : : {
265 : 65 : fd = open ("NUL:", O_WRONLY);
266 : 65 : checked_dup2 (fd, 2, child_err_report_fd);
267 : 65 : }
268 : : else
269 : : {
270 : 277 : fd = atoi (argv[ARG_STDERR]);
271 : 277 : checked_dup2 (fd, 2, child_err_report_fd);
272 : : }
273 : :
274 : : /* argv[ARG_WORKING_DIRECTORY] is the directory in which to run the
275 : : * process. If "-", don't change directory.
276 : : */
277 : 422 : if (argv[ARG_WORKING_DIRECTORY][0] == '-' &&
278 : 164 : argv[ARG_WORKING_DIRECTORY][1] == 0)
279 : : ; /* Nothing */
280 : 258 : else if (_wchdir (wargv[ARG_WORKING_DIRECTORY]) < 0)
281 : 0 : write_err_and_exit (child_err_report_fd, CHILD_CHDIR_FAILED);
282 : :
283 : 422 : fds = g_hash_table_new (NULL, NULL);
284 : 422 : if (argv[ARG_FDS][0] != '-')
285 : : {
286 : 0 : gchar **fdsv = g_strsplit (argv[ARG_FDS], ",", -1);
287 : : gsize i;
288 : :
289 : 0 : for (i = 0; fdsv[i]; i++)
290 : : {
291 : 0 : char *endptr = NULL;
292 : : int sourcefd, targetfd;
293 : : gint64 val;
294 : :
295 : 0 : val = g_ascii_strtoll (fdsv[i], &endptr, 10);
296 : 0 : g_assert (val <= G_MAXINT32);
297 : 0 : sourcefd = val;
298 : 0 : g_assert (endptr != fdsv[i]);
299 : 0 : g_assert (*endptr == ':');
300 : 0 : val = g_ascii_strtoll (endptr + 1, &endptr, 10);
301 : 0 : targetfd = val;
302 : 0 : g_assert (val <= G_MAXINT32);
303 : 0 : g_assert (*endptr == '\0');
304 : :
305 : 0 : maxfd = MAX (maxfd, sourcefd);
306 : 0 : maxfd = MAX (maxfd, targetfd);
307 : :
308 : 0 : g_hash_table_insert (fds, GINT_TO_POINTER (targetfd), GINT_TO_POINTER (sourcefd));
309 : 0 : }
310 : :
311 : 0 : g_strfreev (fdsv);
312 : 0 : }
313 : :
314 : 422 : maxfd++;
315 : 422 : child_err_report_fd = checked_dup2 (child_err_report_fd, maxfd, child_err_report_fd);
316 : 422 : maxfd++;
317 : 422 : helper_sync_fd = checked_dup2 (helper_sync_fd, maxfd, child_err_report_fd);
318 : 422 : if (saved_stderr_fd >= 0)
319 : : {
320 : 422 : maxfd++;
321 : 422 : saved_stderr_fd = checked_dup2 (saved_stderr_fd, maxfd, child_err_report_fd);
322 : 422 : }
323 : :
324 : : {
325 : : GHashTableIter iter;
326 : : gpointer sourcefd, targetfd;
327 : :
328 : 422 : g_hash_table_iter_init (&iter, fds);
329 : 422 : while (g_hash_table_iter_next (&iter, &targetfd, &sourcefd))
330 : : {
331 : : /* If we're doing remapping fd assignments, we need to handle
332 : : * the case where the user has specified e.g. 5 -> 4, 4 -> 6.
333 : : * We do this by duping all source fds, taking care to ensure the new
334 : : * fds are larger than any target fd to avoid introducing new conflicts.
335 : : */
336 : 0 : maxfd++;
337 : 0 : checked_dup2 (GPOINTER_TO_INT (sourcefd), maxfd, child_err_report_fd);
338 : 0 : g_hash_table_iter_replace (&iter, GINT_TO_POINTER (maxfd));
339 : : }
340 : :
341 : 422 : g_hash_table_iter_init (&iter, fds);
342 : 422 : while (g_hash_table_iter_next (&iter, &targetfd, &sourcefd))
343 : 0 : checked_dup2 (GPOINTER_TO_INT (sourcefd), GPOINTER_TO_INT (targetfd), child_err_report_fd);
344 : : }
345 : :
346 : 422 : g_hash_table_add (fds, GINT_TO_POINTER (child_err_report_fd));
347 : 422 : g_hash_table_add (fds, GINT_TO_POINTER (helper_sync_fd));
348 : 422 : if (saved_stderr_fd >= 0)
349 : 422 : g_hash_table_add (fds, GINT_TO_POINTER (saved_stderr_fd));
350 : :
351 : : /* argv[ARG_CLOSE_DESCRIPTORS] is "y" if file descriptors from 3
352 : : * upwards should be closed
353 : : */
354 : 422 : GLIB_PRIVATE_CALL (g_win32_push_empty_invalid_parameter_handler) (&handler);
355 : 422 : if (argv[ARG_CLOSE_DESCRIPTORS][0] == 'y')
356 : 414170 : for (i = 3; i < 1000; i++) /* FIXME real limit? */
357 : 826265 : if (!g_hash_table_contains (fds, GINT_TO_POINTER (i)))
358 : 415831 : if (_get_osfhandle (i) != -1)
359 : 3736 : close (i);
360 : 422 : GLIB_PRIVATE_CALL (g_win32_pop_invalid_parameter_handler) (&handler);
361 : :
362 : : /* We don't want our child to inherit the error report and
363 : : * helper sync fds.
364 : : */
365 : 844 : child_err_report_fd = GLIB_PRIVATE_CALL (g_win32_reopen_noninherited) (
366 : 422 : child_err_report_fd, _O_WRONLY, NULL);
367 : 844 : helper_sync_fd = GLIB_PRIVATE_CALL (g_win32_reopen_noninherited) (
368 : 422 : helper_sync_fd, _O_RDONLY, NULL);
369 : 422 : if (helper_sync_fd == -1)
370 : 0 : write_err_and_exit (child_err_report_fd, CHILD_DUP_FAILED);
371 : :
372 : : /* argv[ARG_WAIT] is "w" to wait for the program to exit */
373 : 422 : if (argv[ARG_WAIT][0] == 'w')
374 : 76 : mode = P_WAIT;
375 : : else
376 : 346 : mode = P_NOWAIT;
377 : :
378 : : /* argv[ARG_USE_PATH] is "y" to use PATH, otherwise not */
379 : :
380 : : /* argv[ARG_PROGRAM] is executable file to run,
381 : : * argv[argv_zero_offset]... is its argv. argv_zero_offset equals
382 : : * ARG_PROGRAM unless G_SPAWN_FILE_AND_ARGV_ZERO was used, in which
383 : : * case we have a separate executable name and argv[0].
384 : : */
385 : :
386 : : /* For the program name passed to spawnv(), don't use the quoted
387 : : * version.
388 : : */
389 : 422 : protect_wargv (argc - argv_zero_offset, wargv + argv_zero_offset, &new_wargv);
390 : :
391 : 422 : if (argv[ARG_USE_PATH][0] == 'y')
392 : 4 : handle = _wspawnvp (mode, wargv[ARG_PROGRAM], (const wchar_t **) new_wargv);
393 : : else
394 : 418 : handle = _wspawnv (mode, wargv[ARG_PROGRAM], (const wchar_t **) new_wargv);
395 : :
396 : 422 : saved_errno = errno;
397 : :
398 : : /* Some coverage warnings may be printed on stderr during this process exit.
399 : : * Remove redirection so that they would go to original stderr
400 : : * instead of being treated as part of stderr of child process.
401 : : */
402 : 422 : if (saved_stderr_fd >= 0)
403 : 422 : dup2 (saved_stderr_fd, 2);
404 : :
405 : 422 : if (handle == -1 && saved_errno != 0)
406 : : {
407 : 0 : int ec = (saved_errno == ENOENT)
408 : : ? CHILD_SPAWN_NOENT
409 : : : CHILD_SPAWN_FAILED;
410 : 0 : write_err_and_exit (child_err_report_fd, ec);
411 : 0 : }
412 : :
413 : 422 : write (child_err_report_fd, &no_error, sizeof (no_error));
414 : 422 : write (child_err_report_fd, &handle, sizeof (handle));
415 : :
416 : 422 : read (helper_sync_fd, &c, 1);
417 : :
418 : 422 : LocalFree (wargv);
419 : 422 : g_strfreev (argv);
420 : 422 : g_hash_table_unref (fds);
421 : :
422 : 422 : return 0;
423 : : }
|