(root)/
glib-2.79.0/
glib/
gspawn-win32-helper.c
       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  write_err_and_exit (gint    fd,
      35  		    gintptr msg)
      36  {
      37    gintptr en = errno;
      38    
      39    write (fd, &msg, sizeof(gintptr));
      40    write (fd, &en, sizeof(gintptr));
      41    
      42    _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  protect_wargv (gint       argc,
      61  	       wchar_t  **wargv,
      62  	       wchar_t ***new_wargv)
      63  {
      64    gint i;
      65    
      66    *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    for (i = 0; i < argc; i++)
      80      {
      81        wchar_t *p = wargv[i];
      82        wchar_t *q;
      83        gint len = 0;
      84        gint pre_bslash = 0;
      85        gboolean need_dblquotes = FALSE;
      86        while (*p)
      87  	{
      88  	  if (*p == ' ' || *p == '\t')
      89  	    need_dblquotes = TRUE;
      90  	  /* estimate max len, assuming that all escapable characters will be escaped */
      91  	  if (*p == '"' || *p == '\\')
      92  	    len += 2;
      93  	  else
      94  	    len += 1;
      95  	  p++;
      96  	}
      97  
      98        q = (*new_wargv)[i] = g_new (wchar_t, len + need_dblquotes*2 + 1);
      99        p = wargv[i];
     100  
     101        if (need_dblquotes)
     102  	*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        while (*p)
     109  	{
     110  	  if (*p == '"')
     111  	    {
     112  	      /* Add backslash for escaping quote itself */
     113  	      *q++ = '\\';
     114  	      /* Add backslash for every preceding backslash for escaping it */
     115  	      for (;pre_bslash > 0; --pre_bslash)
     116  		*q++ = '\\';
     117  	    }
     118  
     119  	  /* Count length of continuous sequence of preceding backslashes. */
     120  	  if (*p == '\\')
     121  	    ++pre_bslash;
     122  	  else
     123  	    pre_bslash = 0;
     124  
     125  	  *q++ = *p;
     126  	  p++;
     127  	}
     128  
     129        if (need_dblquotes)
     130  	{
     131  	  /* Add backslash for every preceding backslash for escaping it,
     132  	   * do NOT escape quote itself.
     133  	   */
     134  	  for (;pre_bslash > 0; --pre_bslash)
     135  	    *q++ = '\\';
     136  	  *q++ = '"';
     137  	}
     138        *q++ = '\0';
     139      }
     140    (*new_wargv)[argc] = NULL;
     141  
     142    return argc;
     143  }
     144  
     145  static int
     146  checked_dup2 (int oldfd, int newfd, int report_fd)
     147  {
     148    if (oldfd == newfd)
     149      return newfd;
     150  
     151    if (dup2 (oldfd, newfd) == -1)
     152      write_err_and_exit (report_fd, CHILD_DUP_FAILED);
     153  
     154    return newfd;
     155  }
     156  
     157  #ifndef HELPER_CONSOLE
     158  int _stdcall
     159  WinMain (struct HINSTANCE__ *hInstance,
     160  	 struct HINSTANCE__ *hPrevInstance,
     161  	 char               *lpszCmdLine,
     162  	 int                 nCmdShow)
     163  #else
     164  int
     165  main (int ignored_argc, char **ignored_argv)
     166  #endif
     167  {
     168    GHashTable *fds;  /* (element-type int int) */
     169    int child_err_report_fd = -1;
     170    int helper_sync_fd = -1;
     171    int saved_stderr_fd = -1;
     172    int i;
     173    int fd;
     174    int mode;
     175    int maxfd = 2;
     176    gintptr handle;
     177    int saved_errno;
     178    gintptr no_error = CHILD_NO_ERROR;
     179    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    wargv = CommandLineToArgvW (GetCommandLineW(), &argc);
     189  
     190    g_assert (argc >= ARG_COUNT);
     191  
     192    /* Convert unicode wargs to utf8 */
     193    argv = g_new(char *, argc + 1);
     194    for (i = 0; i < argc; i++)
     195      argv[i] = g_utf16_to_utf8(wargv[i], -1, NULL, NULL, NULL);
     196    argv[i] = NULL;
     197  
     198    /* argv[ARG_CHILD_ERR_REPORT] is the file descriptor number onto
     199     * which write error messages.
     200     */
     201    child_err_report_fd = atoi (argv[ARG_CHILD_ERR_REPORT]);
     202    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    if (argv[ARG_CHILD_ERR_REPORT][strlen (argv[ARG_CHILD_ERR_REPORT]) - 1] == '#')
     209      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    helper_sync_fd = atoi (argv[ARG_HELPER_SYNC]);
     218    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    if (argv[ARG_STDIN][0] == '-')
     226      ; /* Nothing */
     227    else if (argv[ARG_STDIN][0] == 'z')
     228      {
     229        fd = open ("NUL:", O_RDONLY);
     230        checked_dup2 (fd, 0, child_err_report_fd);
     231      }
     232    else
     233      {
     234        fd = atoi (argv[ARG_STDIN]);
     235        checked_dup2 (fd, 0, child_err_report_fd);
     236      }
     237  
     238    if (argv[ARG_STDOUT][0] == '-')
     239      ; /* Nothing */
     240    else if (argv[ARG_STDOUT][0] == 'z')
     241      {
     242        fd = open ("NUL:", O_WRONLY);
     243        checked_dup2 (fd, 1, child_err_report_fd);
     244      }
     245    else
     246      {
     247        fd = atoi (argv[ARG_STDOUT]);
     248        checked_dup2 (fd, 1, child_err_report_fd);
     249      }
     250  
     251    /* GUI application do not necessarily have a stderr */
     252    if (_fileno (stderr) == 2)
     253      {
     254        saved_stderr_fd = GLIB_PRIVATE_CALL (g_win32_reopen_noninherited) (
     255          dup (2), _O_WRONLY, NULL);
     256        if (saved_stderr_fd == -1)
     257          write_err_and_exit (child_err_report_fd, CHILD_DUP_FAILED);
     258      }
     259  
     260    maxfd = MAX (saved_stderr_fd, maxfd);
     261    if (argv[ARG_STDERR][0] == '-')
     262      ; /* Nothing */
     263    else if (argv[ARG_STDERR][0] == 'z')
     264      {
     265        fd = open ("NUL:", O_WRONLY);
     266        checked_dup2 (fd, 2, child_err_report_fd);
     267      }
     268    else
     269      {
     270        fd = atoi (argv[ARG_STDERR]);
     271        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    if (argv[ARG_WORKING_DIRECTORY][0] == '-' &&
     278        argv[ARG_WORKING_DIRECTORY][1] == 0)
     279      ; /* Nothing */
     280    else if (_wchdir (wargv[ARG_WORKING_DIRECTORY]) < 0)
     281      write_err_and_exit (child_err_report_fd, CHILD_CHDIR_FAILED);
     282  
     283    fds = g_hash_table_new (NULL, NULL);
     284    if (argv[ARG_FDS][0] != '-')
     285      {
     286        gchar **fdsv = g_strsplit (argv[ARG_FDS], ",", -1);
     287        gsize i;
     288  
     289        for (i = 0; fdsv[i]; i++)
     290          {
     291            char *endptr = NULL;
     292            int sourcefd, targetfd;
     293            gint64 val;
     294  
     295            val = g_ascii_strtoll (fdsv[i], &endptr, 10);
     296            g_assert (val <= G_MAXINT32);
     297            sourcefd = val;
     298            g_assert (endptr != fdsv[i]);
     299            g_assert (*endptr == ':');
     300            val = g_ascii_strtoll (endptr + 1, &endptr, 10);
     301            targetfd = val;
     302            g_assert (val <= G_MAXINT32);
     303            g_assert (*endptr == '\0');
     304  
     305            maxfd = MAX (maxfd, sourcefd);
     306            maxfd = MAX (maxfd, targetfd);
     307  
     308            g_hash_table_insert (fds, GINT_TO_POINTER (targetfd), GINT_TO_POINTER (sourcefd));
     309          }
     310  
     311        g_strfreev (fdsv);
     312      }
     313  
     314    maxfd++;
     315    child_err_report_fd = checked_dup2 (child_err_report_fd, maxfd, child_err_report_fd);
     316    maxfd++;
     317    helper_sync_fd = checked_dup2 (helper_sync_fd, maxfd, child_err_report_fd);
     318    if (saved_stderr_fd >= 0)
     319      {
     320        maxfd++;
     321        saved_stderr_fd = checked_dup2 (saved_stderr_fd, maxfd, child_err_report_fd);
     322      }
     323  
     324    {
     325      GHashTableIter iter;
     326      gpointer sourcefd, targetfd;
     327  
     328      g_hash_table_iter_init (&iter, fds);
     329      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          maxfd++;
     337          checked_dup2 (GPOINTER_TO_INT (sourcefd), maxfd, child_err_report_fd);
     338          g_hash_table_iter_replace (&iter, GINT_TO_POINTER (maxfd));
     339        }
     340  
     341      g_hash_table_iter_init (&iter, fds);
     342      while (g_hash_table_iter_next (&iter, &targetfd, &sourcefd))
     343        checked_dup2 (GPOINTER_TO_INT (sourcefd), GPOINTER_TO_INT (targetfd), child_err_report_fd);
     344    }
     345  
     346    g_hash_table_add (fds, GINT_TO_POINTER (child_err_report_fd));
     347    g_hash_table_add (fds, GINT_TO_POINTER (helper_sync_fd));
     348    if (saved_stderr_fd >= 0)
     349      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    GLIB_PRIVATE_CALL (g_win32_push_empty_invalid_parameter_handler) (&handler);
     355    if (argv[ARG_CLOSE_DESCRIPTORS][0] == 'y')
     356      for (i = 3; i < 1000; i++)	/* FIXME real limit? */
     357        if (!g_hash_table_contains (fds, GINT_TO_POINTER (i)))
     358          if (_get_osfhandle (i) != -1)
     359            close (i);
     360    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    child_err_report_fd = GLIB_PRIVATE_CALL (g_win32_reopen_noninherited) (
     366      child_err_report_fd, _O_WRONLY, NULL);
     367    helper_sync_fd = GLIB_PRIVATE_CALL (g_win32_reopen_noninherited) (
     368      helper_sync_fd, _O_RDONLY, NULL);
     369    if (helper_sync_fd == -1)
     370      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    if (argv[ARG_WAIT][0] == 'w')
     374      mode = P_WAIT;
     375    else
     376      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    protect_wargv (argc - argv_zero_offset, wargv + argv_zero_offset, &new_wargv);
     390  
     391    if (argv[ARG_USE_PATH][0] == 'y')
     392      handle = _wspawnvp (mode, wargv[ARG_PROGRAM], (const wchar_t **) new_wargv);
     393    else
     394      handle = _wspawnv (mode, wargv[ARG_PROGRAM], (const wchar_t **) new_wargv);
     395  
     396    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    if (saved_stderr_fd >= 0)
     403      dup2 (saved_stderr_fd, 2);
     404  
     405    if (handle == -1 && saved_errno != 0)
     406      {
     407        int ec = (saved_errno == ENOENT)
     408            ? CHILD_SPAWN_NOENT
     409            : CHILD_SPAWN_FAILED;
     410        write_err_and_exit (child_err_report_fd, ec);
     411      }
     412  
     413    write (child_err_report_fd, &no_error, sizeof (no_error));
     414    write (child_err_report_fd, &handle, sizeof (handle));
     415  
     416    read (helper_sync_fd, &c, 1);
     417  
     418    LocalFree (wargv);
     419    g_strfreev (argv);
     420    g_hash_table_unref (fds);
     421  
     422    return 0;
     423  }