LCOV - code coverage report
Current view: top level - glib - gspawn-win32-helper.c (source / functions) Coverage Total Hit
Test: unnamed Lines: 79.5 % 176 140
Test Date: 2026-08-11 05:12:37 Functions: 60.0 % 5 3
Branches: - 0 0

             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                 :             : }
        

Generated by: LCOV version 2.0-1