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
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) 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(): def default_env():
try: try:
return default_env.env return default_env.env
@@ -132,6 +136,13 @@ def set_default_env(val=None):
default_env.env = env 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: class Child:
child_fd = pid = None child_fd = pid = None
@@ -178,9 +189,7 @@ class Child:
if self.forked: if self.forked:
return return
self.forked = True self.forked = True
master, slave = os.openpty() # Note that master and slave are in blocking mode master, slave = openpty()
remove_cloexec(slave)
fast_data_types.set_iutf8_fd(master, True)
stdin, self.stdin = self.stdin, None stdin, self.stdin = self.stdin, None
ready_read_fd, ready_write_fd = os.pipe() ready_read_fd, ready_write_fd = os.pipe()
remove_cloexec(ready_read_fd) remove_cloexec(ready_read_fd)
@@ -206,7 +215,7 @@ class Child:
fast_data_types.thread_write(stdin_write_fd, stdin) fast_data_types.thread_write(stdin_write_fd, stdin)
os.close(ready_read_fd) os.close(ready_read_fd)
self.terminal_ready_fd = ready_write_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 return pid
def mark_terminal_ready(self): def mark_terminal_ready(self):

View File

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

View File

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