diff mbox

CYGWIN file input redirection

Message ID 8360ot3kzq.fsf@gnu.org
State New
Headers show

Commit Message

Eli Zaretskii Oct. 15, 2016, 11:33 a.m. UTC
> Date: Fri, 30 Sep 2016 12:22:13 +0300
> From: Eli Zaretskii <eliz@gnu.org>
> CC: gdb-patches@sourceware.org
> 
> > How can we fix this bug?
> 
> Someone needs to write and submit the code to (a) parse the command
> line, like "run < foo >> bar 2> baz" and glean the redirections from
> it; and (b) actually implement the redirection in the CreateProcess
> call.

Guess what? someone just did.

The patches below implement redirection support for native debugging
of MinGW programs.  (I reused some of the code I wrote years ago for
the DJGPP project.)

OK to push to master?
diff mbox

Patch

--- gdb/windows-nat.c~1	2016-10-09 12:37:04.538125000 +0300
+++ gdb/windows-nat.c	2016-10-15 14:27:51.966125000 +0300
@@ -2054,6 +2054,166 @@  clear_win32_environment (char **env)
 }
 #endif
 
+#ifndef __CYGWIN__
+
+/* Support routines for redirecting standard handles of the inferior.  */
+
+static int
+redir_open (const char *redir_string, HANDLE *inp, HANDLE *out, HANDLE *err)
+{
+  HANDLE *hdl;
+  int mode, fd;
+  const char *fname = redir_string + 1;;
+
+  switch (*redir_string)
+    {
+    case '<':
+      hdl = inp;
+      mode = O_RDONLY;
+      break;
+    case '1': case '2':
+      fname++;
+      /* FALLTHROUGH */
+    case '>':
+      hdl = (*redir_string == '2') ? err : out;
+      mode = O_WRONLY | O_CREAT;
+      if (*fname == '>')
+	{
+	  fname++;
+	  mode |= O_APPEND;
+	}
+      else
+	mode |= O_TRUNC;
+      break;
+    default:
+      return -1;
+    }
+  fname++;	/* skip the separator space */
+  fd = _open (fname, mode, _S_IREAD | _S_IWRITE);
+  if (fd < 0)
+    return -1;
+  /* _open just sets a flag for O_APPEND, which won't be passed to the
+     inferior, so we need to actually move the file pointer.  */
+  if ((mode & O_APPEND) != 0)
+    _lseek (fd, 0L, SEEK_END);
+  *hdl = (HANDLE)_get_osfhandle (fd);
+  return 0;
+}
+
+static int
+redir_set_redirection (const char *s, HANDLE *inp, HANDLE *out, HANDLE *err)
+{
+  char buf[__PMAX + 5];	/* extra space for redirection string */
+  char *d = buf;
+  const char *start = s;
+  int quote = 0;
+
+  *d++ = *s++;	/* copy the 1st character, < or > or a digit */
+  if ((*start == '>' || *start == '1' || *start == '2')
+      && *s == '>')
+    {
+      *d++ = *s++;
+      if (*s == '>' && *start != '>')
+	*d++ = *s++;
+    }
+  while (isspace (*s))	/* skip whitespace before file name */
+    s++;
+  *d++ = ' ';		/* separate file name with a single space */
+
+  /* Copy the file name.  */
+  while (*s)
+    {
+      /* Remove quoting characters from the file name in buf[].  */
+      if (*s == '"')	/* could support '..' quoting here */
+	{
+	  if (!quote)
+	    quote = *s++;
+	  else if (*s == quote)
+	    {
+	      quote = 0;
+	      s++;
+	    }
+	  else
+	    *d++ = *s++;
+	}
+      else if (*s == '\\')
+	{
+	  if (s[1] == '"')	/* could support '..' here */
+	    s++;
+	  *d++ = *s++;
+	}
+      else if (isspace (*s) && !quote)
+	break;
+      else
+	*d++ = *s++;
+      if (d - buf >= sizeof (buf) - 1)
+	{
+	  errno = ENAMETOOLONG;
+	  return 0;
+	}
+    }
+  *d = '\0';
+
+  /* Windows doesn't allow redirection characters in file names, so we
+     can bail out early if they use them, or if there's no target file
+     name after the redirection symbol.  */
+  if (d[-1] == '>' || d[-1] == '<')
+    {
+      errno = ENOENT;
+      return 0;
+    }
+  if (redir_open (buf, inp, out, err) == 0)
+    return s - start;
+  return 0;
+}
+
+static bool
+redirect_inferior_handles (const char *cmd_orig, char *cmd,
+			   HANDLE *inp, HANDLE *out, HANDLE *err)
+{
+  const char *s = cmd_orig;
+  char *d = cmd;
+  int quote = 0;
+  bool retval = false;
+
+  while (isspace (*s))
+    *d++ = *s++;
+
+  while (*s)
+    {
+      if (*s == '"')	/* could also support '..' quoting here */
+	{
+	  if (!quote)
+	    quote = *s;
+	  else if (*s == quote)
+	    quote = 0;
+	}
+      else if (*s == '\\')
+	{
+	  if (s[1] == '"')	/* escaped quote char */
+	    s++;
+	}
+      else if (!quote)
+	{
+	  if (*s == '<' || *s == '>'
+	      || ((*s == '1' || *s == '2') && s[1] == '>'))
+	    {
+	      int skip = redir_set_redirection (s, inp, out, err);
+
+	      if (skip <= 0)
+		return false;
+	      retval = true;
+	      s += skip;
+	    }
+	}
+      if (*s)
+	*d++ = *s++;
+    }
+  *d = '\0';
+  return retval;
+}
+#endif	/* !__CYGWIN__ */
+
 /* Start an inferior windows child process and sets inferior_ptid to its pid.
    EXEC_FILE is the file to run.
    ALLARGS is a string containing the arguments to the program.
@@ -2076,20 +2236,24 @@  windows_create_inferior (struct target_o
   size_t len;
   int tty;
   int ostdin, ostdout, ostderr;
-#else
+#else  /* !__CYGWIN__ */
   char real_path[__PMAX];
   char shell[__PMAX]; /* Path to shell */
   char *toexec;
-  char *args;
-  size_t args_len;
-  HANDLE tty;
+  char *args, *allargs_copy;
+  size_t args_len, allargs_len;
+  HANDLE tty = INVALID_HANDLE_VALUE;
+  HANDLE inf_stdin = INVALID_HANDLE_VALUE;
+  HANDLE inf_stdout = INVALID_HANDLE_VALUE;
+  HANDLE inf_stderr = INVALID_HANDLE_VALUE;
+  bool redirected = false;
   char *w32env;
   char *temp;
   size_t envlen;
   int i;
   size_t envsize;
   char **env;
-#endif
+#endif	/* !__CYGWIN__ */
   PROCESS_INFORMATION pi;
   BOOL ret;
   DWORD flags = 0;
@@ -2121,7 +2285,7 @@  windows_create_inferior (struct target_o
 	error (_("Error starting executable: %d"), errno);
       cygallargs = (wchar_t *) alloca (len * sizeof (wchar_t));
       mbstowcs (cygallargs, allargs, len);
-#else
+#else  /* !__USEWIDE */
       cygallargs = allargs;
 #endif
     }
@@ -2137,12 +2301,12 @@  windows_create_inferior (struct target_o
 	    + mbstowcs (NULL, allargs, 0) + 2;
       cygallargs = (wchar_t *) alloca (len * sizeof (wchar_t));
       swprintf (cygallargs, len, L" -c 'exec %s %s'", exec_file, allargs);
-#else
+#else  /* !__USEWIDE */
       len = (sizeof (" -c 'exec  '") + strlen (exec_file)
 	     + strlen (allargs) + 2);
       cygallargs = (char *) alloca (len);
       xsnprintf (cygallargs, len, " -c 'exec %s %s'", exec_file, allargs);
-#endif
+#endif	/* __USEWIDE */
       toexec = shell;
       flags |= DEBUG_PROCESS;
     }
@@ -2153,12 +2317,12 @@  windows_create_inferior (struct target_o
   wcscpy (args, toexec);
   wcscat (args, L" ");
   wcscat (args, cygallargs);
-#else
+#else  /* !__USEWIDE */
   args = (cygwin_buf_t *) alloca (strlen (toexec) + strlen (cygallargs) + 2);
   strcpy (args, toexec);
   strcat (args, " ");
   strcat (args, cygallargs);
-#endif
+#endif	/* !__USEWIDE */
 
 #ifdef CW_CVT_ENV_TO_WINENV
   /* First try to create a direct Win32 copy of the POSIX environment. */
@@ -2167,7 +2331,7 @@  windows_create_inferior (struct target_o
     flags |= CREATE_UNICODE_ENVIRONMENT;
   else
     /* If that fails, fall back to old method tweaking GDB's environment. */
-#endif
+#endif	/* CW_CVT_ENV_TO_WINENV */
     {
       /* Reset all Win32 environment variables to avoid leftover on next run. */
       clear_win32_environment (environ);
@@ -2232,21 +2396,26 @@  windows_create_inferior (struct target_o
       close (ostdout);
       close (ostderr);
     }
-#else
-  toexec = exec_file;
-  /* Build the command line, a space-separated list of tokens where
-     the first token is the name of the module to be executed.
-     To avoid ambiguities introduced by spaces in the module name,
-     we quote it.  */
-  args_len = strlen (toexec) + 2 /* quotes */ + strlen (allargs) + 2;
-  args = (char *) alloca (args_len);
-  xsnprintf (args, args_len, "\"%s\" %s", toexec, allargs);
-
-  flags |= DEBUG_ONLY_THIS_PROCESS;
-
-  if (!inferior_io_terminal)
-    tty = INVALID_HANDLE_VALUE;
-  else
+#else  /* !__CYGWIN__ */
+  allargs_len = strlen (allargs);
+  allargs_copy = strcpy ((char *)alloca (allargs_len + 1), allargs);
+  if (strpbrk (allargs_copy, "<>"))
+    {
+      int e = errno;
+      errno = 0;
+      redirected =
+	redirect_inferior_handles (allargs, allargs_copy,
+				   &inf_stdin, &inf_stdout, &inf_stderr);
+      if (errno)
+	warning (_("Warning: Error in redirection: %s."), strerror (errno));
+      else
+	errno = e;
+      allargs_len = strlen (allargs_copy);
+    }
+  if (inferior_io_terminal
+      && !(inf_stdin != INVALID_HANDLE_VALUE
+	   && inf_stdout != INVALID_HANDLE_VALUE
+	   && inf_stderr != INVALID_HANDLE_VALUE))
     {
       SECURITY_ATTRIBUTES sa;
       sa.nLength = sizeof(sa);
@@ -2257,15 +2426,32 @@  windows_create_inferior (struct target_o
       if (tty == INVALID_HANDLE_VALUE)
 	warning (_("Warning: Failed to open TTY %s, error %#x."),
 		 inferior_io_terminal, (unsigned) GetLastError ());
-      else
-	{
-	  si.hStdInput = tty;
-	  si.hStdOutput = tty;
-	  si.hStdError = tty;
-	  si.dwFlags |= STARTF_USESTDHANDLES;
-	}
+    }
+  if (redirected || tty != INVALID_HANDLE_VALUE)
+    {
+      si.hStdInput = inf_stdin == INVALID_HANDLE_VALUE ? tty : inf_stdin;
+      if (si.hStdInput == INVALID_HANDLE_VALUE)
+	si.hStdInput = GetStdHandle (STD_INPUT_HANDLE);
+      si.hStdOutput = inf_stdout == INVALID_HANDLE_VALUE ? tty : inf_stdout;
+      if (si.hStdOutput == INVALID_HANDLE_VALUE)
+	si.hStdOutput = GetStdHandle (STD_OUTPUT_HANDLE);
+      si.hStdError = inf_stderr == INVALID_HANDLE_VALUE ? tty : inf_stderr;
+      if (si.hStdError == INVALID_HANDLE_VALUE)
+	si.hStdError = GetStdHandle (STD_ERROR_HANDLE);
+      si.dwFlags |= STARTF_USESTDHANDLES;
     }
 
+  toexec = exec_file;
+  /* Build the command line, a space-separated list of tokens where
+     the first token is the name of the module to be executed.
+     To avoid ambiguities introduced by spaces in the module name,
+     we quote it.  */
+  args_len = strlen (toexec) + 2 /* quotes */ + allargs_len + 2;
+  args = (char *) alloca (args_len);
+  xsnprintf (args, args_len, "\"%s\" %s", toexec, allargs_copy);
+
+  flags |= DEBUG_ONLY_THIS_PROCESS;
+
   /* CreateProcess takes the environment list as a null terminated set of
      strings (i.e. two nulls terminate the list).  */
 
@@ -2304,7 +2490,13 @@  windows_create_inferior (struct target_o
 			&pi);
   if (tty != INVALID_HANDLE_VALUE)
     CloseHandle (tty);
-#endif
+  if (inf_stdin != INVALID_HANDLE_VALUE)
+    CloseHandle (inf_stdin);
+  if (inf_stdout != INVALID_HANDLE_VALUE)
+    CloseHandle (inf_stdout);
+  if (inf_stderr != INVALID_HANDLE_VALUE)
+    CloseHandle (inf_stderr);
+#endif	/* !__CYGWIN__ */
 
   if (!ret)
     error (_("Error creating process %s, (error %u)."),