Repository: xonsh/xonsh

Issue:
env /bin/sh results in "sh: cannot set terminal process group (-1): Inappropriate ioctl for device
When using emacs TRAMP remote shell functionality, it connects over `ssh` in the following way, in an attempt to make it easier to find and parse the shell:

`ssh <server> "env 'TERM=dumb' 'PROMPT_COMMAND=' 'PS1=#$ ' /bin/sh"`

When using xonsh, this unfortunately errors because sh complains about being unable to set the terminal process group.

I did some digging, and it seems to come from `tcgetpgrp(shell_tty)` returning ENOTTY [here](https://github.com/bminor/bash/blob/64447609994bfddeef1061948022c074093e9a9f/jobs.c#L4201), with shell_tty = stderr. This is explained as:

```
ENOTTY
The calling process does not have a controlling terminal, or it has one but it is not described by fd
```

So I made a small program to reproduce this:

```c
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
int main() {
    pid_t = tcgetpgrp(2);
    printf("tcgetpgrp(2) = %d, errno = %d\n", p, errno);
    return 0;
}
```

- sh results: `tcgetpgrp(2) = 10837, errno = 0`
- xonsh results: `tcgetpgrp(2) = -1, errno = 25`

There is clearly a gap in behavior between sh and xonsh here. I'm unsure where to go from here. I believe xonsh is missing a `tcsetpgrp` call somewhere, but I'm not quite sure where it needs to be added. I notice bash calls it in `initialize_job_control`, in addition to `give_terminal_to`. Maybe this is what xonsh is missing?

## xonfig
```
+------------------+-----------------+
| xonsh            | 0.6.0           |
| Python           | 3.6.6           |
| PLY              | 3.11            |
| have readline    | True            |
| prompt toolkit   | 1.0.15          |
| shell type       | readline        |
| pygments         | 2.2.0           |
| on posix         | True            |
| on linux         | True            |
| distro           | arch            |
| on darwin        | False           |
| on windows       | False           |
| on cygwin        | False           |
| is superuser     | False           |
| default encoding | utf-8           |
| xonsh encoding   | utf-8           |
| encoding errors  | surrogateescape |
+------------------+-----------------+
```

## Expected Behavior

I expect sh to not complain about the failing ioctl. 

## Current Behavior

sh complains about the failing ioctl.

## Steps to Reproduce

From xonsh: `env sh`. Note that running the same command from another shell, such as sh, will not produce an error in sh.


Discussion:
(Just in case, I updated to 0.7.2, it has the same bug).
I can reproduce this issue when `/bin/sh` is a symlink to `/bin/bash` (on a system where it's a symlink to `/bin/dash` the error does not appear, though it does with `env /bin/bash`).  I've also seen the error plenty of times in other situations as well (can't remember the details atm though at least often related to starting sub-shells of various types; I'll try to add details if it turns up again and it's related/useful).

There's a possibly related issue: #3103

Marking `env` as not threadable in my `.xonshrc` seems to fix this issue (as well as #3103) for me.

Resolving patch:
diff --git a/xonsh/commands_cache.py b/xonsh/commands_cache.py
--- a/xonsh/commands_cache.py
+++ b/xonsh/commands_cache.py
@@ -380,6 +380,19 @@ def predict_hg(args):
         return not ns.interactive
 
 
+def predict_env(args):
+    """Predict if env is launching a threadable command or not.
+    The launched command is extracted from env args, and the predictor of
+    lauched command is used."""
+
+    for i in range(len(args)):
+        if args[i] and args[i][0] != "-" and "=" not in args[i]:
+            # args[i] is the command and the following is its arguments
+            # so args[i:] is used to predict if the command is threadable
+            return builtins.__xonsh__.commands_cache.predict_threadable(args[i:])
+    return True
+
+
 def default_threadable_predictors():
     """Generates a new defaultdict for known threadable predictors.
     The default is to predict true.
@@ -395,6 +408,7 @@ def default_threadable_predictors():
         "cmd": predict_shell,
         "cryptop": predict_false,
         "curl": predict_true,
+        "env": predict_env,
         "ex": predict_false,
         "emacsclient": predict_false,
         "fish": predict_shell,


Test patch: