(root)/
glib-2.79.0/
glib/
tests/
spawn-test.c
       1  /* GLIB - Library of useful routines for C programming
       2   * Copyright (C) 1995-1997  Peter Mattis, Spencer Kimball and Josh MacDonald
       3   *
       4   * SPDX-License-Identifier: LGPL-2.1-or-later
       5   *
       6   * This library is free software; you can redistribute it and/or
       7   * modify it under the terms of the GNU Lesser General Public
       8   * License as published by the Free Software Foundation; either
       9   * version 2.1 of the License, or (at your option) any later version.
      10   *
      11   * This library is distributed in the hope that it will be useful,
      12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
      13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      14   * Lesser General Public License for more details.
      15   *
      16   * You should have received a copy of the GNU Lesser General Public
      17   * License along with this library; if not, see <http://www.gnu.org/licenses/>.
      18   */
      19  
      20  /*
      21   * Modified by the GLib Team and others 1997-2000.  See the AUTHORS
      22   * file for a list of people on the GLib Team.  See the ChangeLog
      23   * files for a list of changes.  These files are distributed with
      24   * GLib at ftp://ftp.gtk.org/pub/gtk/.
      25   */
      26  
      27  #include <glib.h>
      28  #include <glib/gstdio.h>
      29  
      30  #ifdef G_OS_UNIX
      31  #include <unistd.h>
      32  #endif
      33  
      34  #ifdef G_OS_WIN32
      35  #define WIN32_LEAN_AND_MEAN
      36  #include <windows.h>
      37  #include <fcntl.h>
      38  #include <io.h>
      39  #define pipe(fds) _pipe(fds, 4096, _O_BINARY)
      40  #endif
      41  
      42  #ifdef G_OS_WIN32
      43  static gchar *dirname = NULL;
      44  #endif
      45  
      46  #ifdef G_OS_WIN32
      47  static char *
      48  get_system_directory (void)
      49  {
      50    wchar_t path_utf16[MAX_PATH] = {0};
      51    char *path = NULL;
      52  
      53    if (!GetSystemDirectoryW (path_utf16, G_N_ELEMENTS (path_utf16)))
      54      g_error ("%s failed with error code %u", "GetSystemWindowsDirectory",
      55               (unsigned int) GetLastError ());
      56  
      57    path = g_utf16_to_utf8 (path_utf16, -1, NULL, NULL, NULL);
      58    g_assert_nonnull (path);
      59  
      60    return path;
      61  }
      62  
      63  static wchar_t *
      64  g_wcsdup (const wchar_t *wcs_string)
      65  {
      66    size_t length = wcslen (wcs_string);
      67  
      68    return g_memdup2 (wcs_string, (length + 1) * sizeof (wchar_t));
      69  }
      70  
      71  static wchar_t *
      72  g_wcsndup (const wchar_t *wcs_string,
      73             size_t         length)
      74  {
      75    wchar_t *result = NULL;
      76  
      77    g_assert_true (length < SIZE_MAX);
      78  
      79    result = g_new (wchar_t, length + 1);
      80    memcpy (result, wcs_string, length * sizeof (wchar_t));
      81    result[length] = L'\0';
      82  
      83    return result;
      84  }
      85  
      86  /**
      87   * parse_environment_string:
      88   *
      89   * @string: source environment string in the form <VARIABLE>=<VALUE>
      90   *          (e.g as returned by GetEnvironmentStrings)
      91   * @name: (out) (optional) (utf-16) name of the variable
      92   * @value: (out) (optional) (utf-16) value of the variable
      93   *
      94   * Parse environment string in the form <VARIABLE>=<VALUE>, for example
      95   * the strings in the environment block returned by GetEnvironmentStrings.
      96   *
      97   * Returns: %TRUE on success
      98   */
      99  static gboolean
     100  parse_environment_string (const wchar_t  *string,
     101                            wchar_t       **name,
     102                            wchar_t       **value)
     103  {
     104    const wchar_t *equal_sign;
     105  
     106    g_assert_nonnull (string);
     107    g_assert_true (name || value);
     108  
     109    /* On Windows environment variables may have an equal-sign
     110     * character as part of their name, but only as the first
     111     * character */
     112    equal_sign = wcschr (string[0] == L'=' ? (string + 1) : string, L'=');
     113  
     114    if (name)
     115      *name = equal_sign ? g_wcsndup (string, equal_sign - string) : NULL;
     116  
     117    if (value)
     118      *value = equal_sign ? g_wcsdup (equal_sign + 1) : NULL;
     119  
     120    return (equal_sign != NULL);
     121  }
     122  
     123  /**
     124   * find_cmd_shell_environment_variables:
     125   *
     126   * Finds all the environment variables related to cmd.exe, which are
     127   * usually (but not always) present in a process environment block.
     128   * Those environment variables are named "=X:", where X is a drive /
     129   * volume letter and are used by cmd.exe to track per-drive current
     130   * directories.
     131   *
     132   * See "What are these strange =C: environment variables?"
     133   * https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133
     134   *
     135   * This is used to test a work around for an UCRT issue
     136   * https://developercommunity.visualstudio.com/t/UCRT-Crash-in-_wspawne-functions/10262748
     137   */
     138  static GList *
     139  find_cmd_shell_environment_variables (void)
     140  {
     141    wchar_t *block = NULL;
     142    wchar_t *iter = NULL;
     143    GList *variables = NULL;
     144    size_t len = 0;
     145  
     146    block = GetEnvironmentStringsW ();
     147    if (!block)
     148      {
     149        DWORD code = GetLastError ();
     150        g_error ("%s failed with error code %u",
     151                 "GetEnvironmentStrings", (unsigned int) code);
     152      }
     153  
     154    iter = block;
     155  
     156    while ((len = wcslen (iter)))
     157      {
     158        if (iter[0] == L'=')
     159          {
     160            wchar_t *variable = NULL;
     161  
     162            g_assert_true (parse_environment_string (iter, &variable, NULL));
     163            g_assert_nonnull (variable);
     164  
     165            variables = g_list_prepend (variables, variable);
     166          }
     167  
     168        iter += len + 1;
     169      }
     170  
     171    FreeEnvironmentStringsW (block);
     172  
     173    return variables;
     174  }
     175  
     176  static void
     177  remove_environment_variables (GList *list)
     178  {
     179    for (GList *l = list; l; l = l->next)
     180      {
     181        const wchar_t *variable = (const wchar_t*) l->data;
     182  
     183        if (!SetEnvironmentVariableW (variable, NULL))
     184          {
     185            DWORD code = GetLastError ();
     186            g_error ("%s failed with error code %u",
     187                     "SetEnvironmentVariable", (unsigned int) code);
     188          }
     189      }
     190  }
     191  #endif /* G_OS_WIN32 */
     192  
     193  static void
     194  test_spawn_basics (void)
     195  {
     196    gboolean result;
     197    GError *err = NULL;
     198    gchar *output = NULL;
     199    gchar *erroutput = NULL;
     200  #ifdef G_OS_WIN32
     201    int n;
     202    char buf[100];
     203    int pipedown[2], pipeup[2];
     204    gchar **argv = NULL;
     205    gchar **envp = g_get_environ ();
     206    gchar *system_directory;
     207    gchar spawn_binary[1000] = {0};
     208    gchar full_cmdline[1000] = {0};
     209    GList *cmd_shell_env_vars = NULL;
     210    const LCID old_lcid = GetThreadUILanguage ();
     211    const unsigned int initial_cp = GetConsoleOutputCP ();
     212  
     213    SetConsoleOutputCP (437); /* 437 means en-US codepage */
     214    SetThreadUILanguage (MAKELCID (MAKELANGID (LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT));
     215    system_directory = get_system_directory ();
     216  
     217    g_snprintf (spawn_binary, sizeof (spawn_binary),
     218                "%s\\spawn-test-win32-gui.exe", dirname);
     219  #endif
     220  
     221    err = NULL;
     222    result =
     223      g_spawn_command_line_sync ("nonexistent_application foo 'bar baz' blah blah",
     224                                 NULL, NULL, NULL, &err);
     225    g_assert_false (result);
     226    g_assert_error (err, G_SPAWN_ERROR, G_SPAWN_ERROR_NOENT);
     227    g_clear_error (&err);
     228  
     229    err = NULL;
     230    result =
     231      g_spawn_command_line_async ("nonexistent_application foo bar baz \"blah blah\"",
     232                                  &err);
     233    g_assert_false (result);
     234    g_assert_error (err, G_SPAWN_ERROR, G_SPAWN_ERROR_NOENT);
     235    g_clear_error (&err);
     236  
     237    err = NULL;
     238  #ifdef G_OS_UNIX
     239    result = g_spawn_command_line_sync ("/bin/sh -c 'echo hello'",
     240                                        &output, NULL, NULL, &err);
     241    g_assert_no_error (err);
     242    g_assert_true (result);
     243    g_assert_cmpstr (output, ==, "hello\n");
     244  
     245    g_free (output);
     246    output = NULL;
     247  #endif
     248  
     249    /* Running sort synchronously, collecting its output. 'sort' command
     250     * is selected because it is non-builtin command on both unix and
     251     * win32 with well-defined stdout behaviour.
     252     * On win32 we use an absolute path to the system-provided sort.exe
     253     * because a different sort.exe may be available in PATH. This is
     254     * important e.g for the MSYS2 environment, which provides coreutils
     255     * sort.exe
     256     */
     257    g_file_set_contents ("spawn-test-created-file.txt",
     258                         "line first\nline 2\nline last\n", -1, &err);
     259    g_assert_no_error(err);
     260  
     261  #ifndef G_OS_WIN32
     262    result = g_spawn_command_line_sync ("sort spawn-test-created-file.txt",
     263                                        &output, &erroutput, NULL, &err);
     264  #else
     265    g_snprintf (full_cmdline, sizeof (full_cmdline),
     266                "'%s\\sort.exe' spawn-test-created-file.txt", system_directory);
     267    result = g_spawn_command_line_sync (full_cmdline, &output, &erroutput, NULL, &err);
     268  #endif
     269    g_assert_no_error (err);
     270    g_assert_true (result);
     271    g_assert_nonnull (output);
     272    if (strchr (output, '\r') != NULL)
     273      g_assert_cmpstr (output, ==, "line 2\r\nline first\r\nline last\r\n");
     274    else
     275      g_assert_cmpstr (output, ==, "line 2\nline first\nline last\n");
     276    g_assert_cmpstr (erroutput, ==, "");
     277  
     278    g_free (output);
     279    output = NULL;
     280    g_free (erroutput);
     281    erroutput = NULL;
     282  
     283  #ifndef G_OS_WIN32
     284    result = g_spawn_command_line_sync ("sort non-existing-file.txt",
     285                                        NULL, &erroutput, NULL, &err);
     286  #else
     287    g_snprintf (full_cmdline, sizeof (full_cmdline),
     288                "'%s\\sort.exe' non-existing-file.txt", system_directory);
     289    result = g_spawn_command_line_sync (full_cmdline, NULL, &erroutput, NULL, &err);
     290  #endif
     291    g_assert_no_error (err);
     292    g_assert_true (result);
     293  #ifndef G_OS_WIN32
     294    /* Test against output of coreutils sort */
     295    g_assert_true (g_str_has_prefix (erroutput, "sort: "));
     296    g_assert_nonnull (strstr (erroutput, g_strerror (ENOENT)));
     297  #else
     298    /* Test against output of windows sort */
     299    {
     300      gchar *file_not_found_message = g_win32_error_message (ERROR_FILE_NOT_FOUND);
     301      g_test_message ("sort output: %s\nExpected message: %s", erroutput, file_not_found_message);
     302      g_assert_nonnull (strstr (erroutput, file_not_found_message));
     303      g_free (file_not_found_message);
     304    }
     305  #endif
     306    g_free (erroutput);
     307    erroutput = NULL;
     308    g_unlink ("spawn-test-created-file.txt");
     309  
     310  #ifdef G_OS_WIN32
     311    g_test_message ("Running spawn-test-win32-gui in various ways.");
     312  
     313    g_test_message ("First asynchronously (without wait).");
     314    g_snprintf (full_cmdline, sizeof (full_cmdline), "'%s' 1", spawn_binary);
     315    result = g_spawn_command_line_async (full_cmdline, &err);
     316    g_assert_no_error (err);
     317    g_assert_true (result);
     318  
     319    g_test_message ("Now synchronously, collecting its output.");
     320    g_snprintf (full_cmdline, sizeof (full_cmdline), "'%s' 2", spawn_binary);
     321    result =
     322      g_spawn_command_line_sync (full_cmdline, &output, &erroutput, NULL, &err);
     323  
     324    g_assert_no_error (err);
     325    g_assert_true (result);
     326    g_assert_cmpstr (output, ==, "# This is stdout\r\n");
     327    g_assert_cmpstr (erroutput, ==, "This is stderr\r\n");
     328  
     329    g_free (output);
     330    output = NULL;
     331    g_free (erroutput);
     332    erroutput = NULL;
     333  
     334    g_test_message ("Now with G_SPAWN_FILE_AND_ARGV_ZERO.");
     335    g_snprintf (full_cmdline, sizeof (full_cmdline),
     336                "'%s' this-should-be-argv-zero print_argv0", spawn_binary);
     337    result = g_shell_parse_argv (full_cmdline, NULL, &argv, &err);
     338    g_assert_no_error (err);
     339    g_assert_true (result);
     340  
     341    result = g_spawn_sync (NULL, argv, NULL, G_SPAWN_FILE_AND_ARGV_ZERO,
     342                           NULL, NULL, &output, NULL, NULL, &err);
     343    g_assert_no_error (err);
     344    g_assert_true (result);
     345    g_assert_cmpstr (output, ==, "this-should-be-argv-zero");
     346  
     347    g_free (output);
     348    output = NULL;
     349    g_free (argv);
     350    argv = NULL;
     351  
     352    g_test_message ("Now talking to it through pipes.");
     353    g_assert_cmpint (pipe (pipedown), >=, 0);
     354    g_assert_cmpint (pipe (pipeup), >=, 0);
     355  
     356    g_snprintf (full_cmdline, sizeof (full_cmdline), "'%s' pipes %d %d",
     357                spawn_binary, pipedown[0], pipeup[1]);
     358  
     359    result = g_shell_parse_argv (full_cmdline, NULL, &argv, &err);
     360    g_assert_no_error (err);
     361    g_assert_true (result);
     362  
     363    result = g_spawn_async (NULL, argv, NULL,
     364                            G_SPAWN_LEAVE_DESCRIPTORS_OPEN |
     365                            G_SPAWN_DO_NOT_REAP_CHILD,
     366                            NULL, NULL, NULL,
     367                            &err);
     368    g_assert_no_error (err);
     369    g_assert_true (result);
     370    g_free (argv);
     371    argv = NULL;
     372  
     373    g_assert_cmpint (read (pipeup[0], &n, sizeof (n)), ==, sizeof (n));
     374    g_assert_cmpint (read (pipeup[0], buf, n), ==, n);
     375  
     376    n = strlen ("Bye then");
     377    g_assert_cmpint (write (pipedown[1], &n, sizeof (n)), !=, -1);
     378    g_assert_cmpint (write (pipedown[1], "Bye then", n), !=, -1);
     379  
     380    g_assert_cmpint (read (pipeup[0], &n, sizeof (n)), ==, sizeof (n));
     381    g_assert_cmpint (n, ==, strlen ("See ya"));
     382  
     383    g_assert_cmpint (read (pipeup[0], buf, n), ==, n);
     384  
     385    buf[n] = '\0';
     386    g_assert_cmpstr (buf, ==, "See ya");
     387  
     388    /* Test workaround for:
     389     *
     390     * https://developercommunity.visualstudio.com/t/UCRT-Crash-in-_wspawne-functions/10262748
     391     */
     392    cmd_shell_env_vars = find_cmd_shell_environment_variables ();
     393    remove_environment_variables (cmd_shell_env_vars);
     394  
     395    g_snprintf (full_cmdline, sizeof (full_cmdline),
     396                "'%s\\sort.exe' non-existing-file.txt", system_directory);
     397    g_assert_true (g_shell_parse_argv (full_cmdline, NULL, &argv, NULL));
     398    g_assert_nonnull (argv);
     399    g_spawn_sync (NULL, argv, envp, G_SPAWN_DEFAULT,
     400                  NULL, NULL, NULL, NULL, NULL, NULL);
     401    g_free (argv);
     402    argv = NULL;
     403  #endif
     404  
     405  #ifdef G_OS_WIN32
     406    SetThreadUILanguage (old_lcid);
     407    SetConsoleOutputCP (initial_cp); /* 437 means en-US codepage */
     408    g_list_free_full (cmd_shell_env_vars, g_free);
     409    g_strfreev (envp);
     410    g_free (system_directory);
     411  #endif
     412  }
     413  
     414  #ifdef G_OS_UNIX
     415  static void
     416  test_spawn_stdio_overwrite (void)
     417  {
     418    gboolean result;
     419    int ret;
     420    GError *error = NULL;
     421    int old_stdin_fd = -1;
     422    int old_stdout_fd = -1;
     423    int old_stderr_fd = -1;
     424    char **envp = g_get_environ ();
     425    enum OpenState { OPENED = 0, CLOSED = 1, DONE = 2 } stdin_state, stdout_state, stderr_state, output_return_state, error_return_state;
     426  
     427    g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/16");
     428  
     429    old_stdin_fd = dup (STDIN_FILENO);
     430    old_stdout_fd = dup (STDOUT_FILENO);
     431    old_stderr_fd = dup (STDERR_FILENO);
     432  
     433    for (output_return_state = OPENED; output_return_state != DONE; output_return_state++)
     434      for (error_return_state = OPENED; error_return_state != DONE; error_return_state++)
     435        for (stdin_state = OPENED; stdin_state != DONE; stdin_state++)
     436          for (stdout_state = OPENED; stdout_state != DONE; stdout_state++)
     437            for (stderr_state = OPENED; stderr_state != DONE; stderr_state++)
     438      {
     439          char *command_line = NULL;
     440          char **argv = NULL;
     441          gchar *standard_output = NULL;
     442          gchar *standard_error = NULL;
     443  
     444          g_test_message ("Fetching GSpawn result %s%s%s with stdin %s, stdout %s, stderr %s",
     445                          output_return_state == OPENED? "output" : "",
     446                          output_return_state == OPENED && error_return_state == OPENED? " and " : "",
     447                          error_return_state == OPENED? "error output" : "",
     448                          stdin_state == CLOSED? "already closed" : "open",
     449                          stdout_state == CLOSED? "already closed" : "open",
     450                          stderr_state == CLOSED? "already closed" : "open");
     451  
     452          if (stdin_state == CLOSED)
     453            {
     454              g_close (STDIN_FILENO, &error);
     455              g_assert_no_error (error);
     456            }
     457  
     458          if (stdout_state == CLOSED)
     459            {
     460              g_close (STDOUT_FILENO, &error);
     461              g_assert_no_error (error);
     462            }
     463  
     464          if (stderr_state == CLOSED)
     465            {
     466              g_close (STDERR_FILENO, &error);
     467              g_assert_no_error (error);
     468            }
     469  
     470          command_line = g_strdup_printf ("/bin/sh -c '%s%s%s'",
     471                                          output_return_state == OPENED? "echo stdout": "",
     472                                          output_return_state == OPENED && error_return_state == OPENED? ";" : "",
     473                                          error_return_state == OPENED? "echo stderr >&2": "");
     474          g_shell_parse_argv (command_line, NULL, &argv, &error);
     475          g_assert_no_error (error);
     476  
     477          g_clear_pointer (&command_line, g_free);
     478  
     479          result = g_spawn_sync (NULL,
     480                                 argv, envp, G_SPAWN_SEARCH_PATH_FROM_ENVP,
     481                                 NULL, NULL,
     482                                 output_return_state == OPENED? &standard_output : NULL,
     483                                 error_return_state == OPENED? &standard_error: NULL,
     484                                 NULL,
     485                                 &error);
     486          g_clear_pointer (&argv, g_strfreev);
     487  
     488          ret = dup2 (old_stderr_fd, STDERR_FILENO);
     489          g_assert_cmpint (ret, ==, STDERR_FILENO);
     490  
     491          ret = dup2 (old_stdout_fd, STDOUT_FILENO);
     492          g_assert_cmpint (ret, ==, STDOUT_FILENO);
     493  
     494          ret = dup2 (old_stdin_fd, STDIN_FILENO);
     495          g_assert_cmpint (ret, ==, STDIN_FILENO);
     496  
     497          g_assert_no_error (error);
     498          g_assert_true (result);
     499  
     500          if (output_return_state == OPENED)
     501            {
     502              g_assert_cmpstr (standard_output, ==, "stdout\n");
     503              g_clear_pointer (&standard_output, g_free);
     504            }
     505  
     506          if (error_return_state == OPENED)
     507            {
     508              g_assert_cmpstr (standard_error, ==, "stderr\n");
     509              g_clear_pointer (&standard_error, g_free);
     510            }
     511      }
     512  
     513    g_clear_fd (&old_stdin_fd, &error);
     514    g_assert_no_error (error);
     515  
     516    g_clear_fd (&old_stdout_fd, &error);
     517    g_assert_no_error (error);
     518  
     519    g_clear_fd (&old_stderr_fd, &error);
     520    g_assert_no_error (error);
     521  
     522    g_clear_pointer (&envp, g_strfreev);
     523  }
     524  #endif
     525  
     526  int
     527  main (int   argc,
     528        char *argv[])
     529  {
     530    int ret_val;
     531  
     532  #ifdef G_OS_WIN32
     533    dirname = g_path_get_dirname (argv[0]);
     534  #endif
     535  
     536    g_test_init (&argc, &argv, NULL);
     537  
     538    g_test_add_func ("/spawn/basics", test_spawn_basics);
     539  #ifdef G_OS_UNIX
     540    g_test_add_func ("/spawn/stdio-overwrite", test_spawn_stdio_overwrite);
     541  #endif
     542  
     543    ret_val = g_test_run ();
     544  
     545  #ifdef G_OS_WIN32
     546    g_free (dirname);
     547  #endif
     548    return ret_val;
     549  }