Use a new controlling terminal when reading shell environment

This is because some people do things in their rc files based
on checking the name of the controlling terminal.
This commit is contained in:
Kovid Goyal 2019-07-31 11:34:54 +05:30
parent 6958ded4c4
commit bdade7e151
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
3 changed files with 43 additions and 20 deletions

View File

@ -118,6 +118,10 @@ def remove_cloexec(fd):
fcntl.fcntl(fd, fcntl.F_SETFD, fcntl.fcntl(fd, fcntl.F_GETFD) & ~fcntl.FD_CLOEXEC)
def remove_blocking(fd):
os.set_blocking(fd, False)
def default_env():
try:
return default_env.env
@ -132,6 +136,13 @@ def set_default_env(val=None):
default_env.env = env
def openpty():
master, slave = os.openpty() # Note that master and slave are in blocking mode
remove_cloexec(slave)
fast_data_types.set_iutf8_fd(master, True)
return master, slave
class Child:
child_fd = pid = None
@ -178,9 +189,7 @@ class Child:
if self.forked:
return
self.forked = True
master, slave = os.openpty() # Note that master and slave are in blocking mode
remove_cloexec(slave)
fast_data_types.set_iutf8_fd(master, True)
master, slave = openpty()
stdin, self.stdin = self.stdin, None
ready_read_fd, ready_write_fd = os.pipe()
remove_cloexec(ready_read_fd)
@ -206,7 +215,7 @@ class Child:
fast_data_types.thread_write(stdin_write_fd, stdin)
os.close(ready_read_fd)
self.terminal_ready_fd = ready_write_fd
fcntl.fcntl(self.child_fd, fcntl.F_SETFL, fcntl.fcntl(self.child_fd, fcntl.F_GETFL) | os.O_NONBLOCK)
remove_blocking(self.child_fd)
return pid
def mark_terminal_ready(self):

View File

@ -10,7 +10,7 @@ from contextlib import contextmanager, suppress
from .borders import load_borders_program
from .boss import Boss
from .child import set_default_env
from .child import set_default_env, openpty, remove_blocking
from .cli import create_opts, parse_args
from .config import cached_values_for, initial_window_size_func
from .constants import (
@ -203,23 +203,37 @@ def macos_cmdline(argv_args):
return ans
def read_shell_environment(opts):
def read_shell_environment(opts=None):
if not hasattr(read_shell_environment, 'ans'):
import subprocess
from .session import resolved_shell
shell = resolved_shell(opts)
p = subprocess.Popen(shell + ['-l', '-c', 'env'], stdout=subprocess.PIPE)
raw = p.stdout.read()
if p.wait() == 0:
raw = raw.decode('utf-8', 'replace')
ans = read_shell_environment.ans = {}
for line in raw.splitlines():
k, v = line.partition('=')[::2]
if k and v:
ans[k] = v
else:
log_error('Failed to run shell to read its environment')
read_shell_environment.ans = {}
master, slave = openpty()
remove_blocking(master)
p = subprocess.Popen(shell + ['-l', '-c', 'env'], stdout=slave, stdin=slave, stderr=slave, start_new_session=True, close_fds=True)
with os.fdopen(master, 'rb') as stdout, os.fdopen(slave, 'wb'):
raw = b''
while p.wait(0.01) is None:
with suppress(Exception):
raw += stdout.read()
if p.returncode == 0:
while True:
try:
x = stdout.read()
except Exception:
break
if not x:
break
raw += x
raw = raw.decode('utf-8', 'replace')
ans = read_shell_environment.ans = {}
for line in raw.splitlines():
k, v = line.partition('=')[::2]
if k and v:
ans[k] = v
else:
log_error('Failed to run shell to read its environment')
read_shell_environment.ans = {}
return read_shell_environment.ans

View File

@ -74,8 +74,8 @@ class Session:
self.tabs[-1].cwd = val
def resolved_shell(opts):
ans = opts.shell
def resolved_shell(opts=None):
ans = getattr(opts, 'shell', '.')
if ans == '.':
ans = [shell_path]
else: