ssh kitten: Send data without a roundtrip
Send data to the remote side without waiting for a data request. Avoids an extra roundtrip during initialization.
This commit is contained in:
parent
434ef97952
commit
2b06ca5e1a
@ -11,14 +11,17 @@ import re
|
|||||||
import secrets
|
import secrets
|
||||||
import shlex
|
import shlex
|
||||||
import stat
|
import stat
|
||||||
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tarfile
|
import tarfile
|
||||||
import tempfile
|
import tempfile
|
||||||
|
import termios
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
from base64 import standard_b64decode, standard_b64encode
|
from base64 import standard_b64decode, standard_b64encode
|
||||||
from contextlib import contextmanager, suppress
|
from contextlib import contextmanager, suppress
|
||||||
from getpass import getuser
|
from getpass import getuser
|
||||||
|
from select import select
|
||||||
from typing import (
|
from typing import (
|
||||||
Callable, Dict, Iterator, List, NoReturn, Optional, Sequence, Set, Tuple,
|
Callable, Dict, Iterator, List, NoReturn, Optional, Sequence, Set, Tuple,
|
||||||
Union
|
Union
|
||||||
@ -216,7 +219,7 @@ def prepare_exec_cmd(remote_args: Sequence[str], is_python: bool) -> str:
|
|||||||
def bootstrap_script(
|
def bootstrap_script(
|
||||||
ssh_opts: SSHOptions, script_type: str = 'sh', remote_args: Sequence[str] = (),
|
ssh_opts: SSHOptions, script_type: str = 'sh', remote_args: Sequence[str] = (),
|
||||||
test_script: str = '', request_id: Optional[str] = None, cli_hostname: str = '', cli_uname: str = '',
|
test_script: str = '', request_id: Optional[str] = None, cli_hostname: str = '', cli_uname: str = '',
|
||||||
request_data: str = '1', echo_on: bool = True
|
request_data: bool = False, echo_on: bool = True
|
||||||
) -> Tuple[str, Dict[str, str], SharedMemory]:
|
) -> Tuple[str, Dict[str, str], SharedMemory]:
|
||||||
if request_id is None:
|
if request_id is None:
|
||||||
request_id = os.environ['KITTY_PID'] + '-' + os.environ['KITTY_WINDOW_ID']
|
request_id = os.environ['KITTY_PID'] + '-' + os.environ['KITTY_WINDOW_ID']
|
||||||
@ -233,7 +236,7 @@ def bootstrap_script(
|
|||||||
atexit.register(shm.unlink)
|
atexit.register(shm.unlink)
|
||||||
replacements = {
|
replacements = {
|
||||||
'DATA_PASSWORD': pw, 'PASSWORD_FILENAME': shm.name, 'EXEC_CMD': exec_cmd, 'TEST_SCRIPT': test_script,
|
'DATA_PASSWORD': pw, 'PASSWORD_FILENAME': shm.name, 'EXEC_CMD': exec_cmd, 'TEST_SCRIPT': test_script,
|
||||||
'REQUEST_ID': request_id, 'REQUEST_DATA': request_data, 'ECHO_ON': '1' if echo_on else '0',
|
'REQUEST_ID': request_id, 'REQUEST_DATA': '1' if request_data else '0', 'ECHO_ON': '1' if echo_on else '0',
|
||||||
}
|
}
|
||||||
return prepare_script(ans, replacements), replacements, shm
|
return prepare_script(ans, replacements), replacements, shm
|
||||||
|
|
||||||
@ -477,7 +480,6 @@ def connection_sharing_args(opts: SSHOptions, kitty_pid: int) -> List[str]:
|
|||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def restore_terminal_state() -> Iterator[bool]:
|
def restore_terminal_state() -> Iterator[bool]:
|
||||||
import termios
|
|
||||||
with open(os.ctermid()) as f:
|
with open(os.ctermid()) as f:
|
||||||
val = termios.tcgetattr(f.fileno())
|
val = termios.tcgetattr(f.fileno())
|
||||||
try:
|
try:
|
||||||
@ -486,6 +488,34 @@ def restore_terminal_state() -> Iterator[bool]:
|
|||||||
termios.tcsetattr(f.fileno(), termios.TCSAFLUSH, val)
|
termios.tcsetattr(f.fileno(), termios.TCSAFLUSH, val)
|
||||||
|
|
||||||
|
|
||||||
|
def dcs_to_kitty(payload: Union[bytes, str], type: str = 'ssh') -> bytes:
|
||||||
|
if isinstance(payload, str):
|
||||||
|
payload = payload.encode('utf-8')
|
||||||
|
payload = standard_b64encode(payload)
|
||||||
|
return b'\033P@kitty-' + type.encode('ascii') + b'|' + payload + b'\033\\'
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def drain_potential_tty_garbage(p: 'subprocess.Popen[bytes]', data_request: str) -> Iterator[None]:
|
||||||
|
with open(os.open(os.ctermid(), os.O_CLOEXEC | os.O_RDWR | os.O_NOCTTY), 'wb') as tty:
|
||||||
|
tty.write(dcs_to_kitty(data_request))
|
||||||
|
tty.flush()
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
if p.returncode:
|
||||||
|
# discard queued data on tty in case data transmission was
|
||||||
|
# interrupted due to SSH failure, avoids spewing garbage to
|
||||||
|
# screen
|
||||||
|
termios.tcflush(tty.fileno(), termios.TCIOFLUSH)
|
||||||
|
with open(tty.fileno(), 'rb', closefd=False) as tf:
|
||||||
|
os.set_blocking(tf.fileno(), False)
|
||||||
|
from tty import setraw
|
||||||
|
setraw(tf.fileno(), termios.TCSANOW)
|
||||||
|
while select([tf], [], [], 0)[0]:
|
||||||
|
tf.read()
|
||||||
|
|
||||||
|
|
||||||
def run_ssh(ssh_args: List[str], server_args: List[str], found_extra_args: Tuple[str, ...], echo_on: bool) -> NoReturn:
|
def run_ssh(ssh_args: List[str], server_args: List[str], found_extra_args: Tuple[str, ...], echo_on: bool) -> NoReturn:
|
||||||
cmd = ['ssh'] + ssh_args
|
cmd = ['ssh'] + ssh_args
|
||||||
hostname, remote_args = server_args[0], server_args[1:]
|
hostname, remote_args = server_args[0], server_args[1:]
|
||||||
@ -524,13 +554,16 @@ def run_ssh(ssh_args: List[str], server_args: List[str], found_extra_args: Tuple
|
|||||||
os.environ['SSH_ASKPASS_REQUIRE'] = 'force'
|
os.environ['SSH_ASKPASS_REQUIRE'] = 'force'
|
||||||
if not os.environ.get('SSH_ASKPASS'):
|
if not os.environ.get('SSH_ASKPASS'):
|
||||||
os.environ['SSH_ASKPASS'] = os.path.join(shell_integration_dir, 'ssh', 'askpass.py')
|
os.environ['SSH_ASKPASS'] = os.path.join(shell_integration_dir, 'ssh', 'askpass.py')
|
||||||
import subprocess
|
|
||||||
with suppress(FileNotFoundError):
|
|
||||||
try:
|
try:
|
||||||
raise SystemExit(subprocess.run(cmd).returncode)
|
p = subprocess.Popen(cmd)
|
||||||
|
except FileNotFoundError:
|
||||||
|
raise SystemExit('Could not find the ssh executable, is it in your PATH?')
|
||||||
|
else:
|
||||||
|
with drain_potential_tty_garbage(p, 'id={REQUEST_ID}:pwfile={PASSWORD_FILENAME}:pw={DATA_PASSWORD}'.format(**replacements)):
|
||||||
|
try:
|
||||||
|
raise SystemExit(p.wait())
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
raise SystemExit(1)
|
raise SystemExit(1)
|
||||||
raise SystemExit('Could not find the ssh executable, is it in your PATH?')
|
|
||||||
|
|
||||||
|
|
||||||
def main(args: List[str]) -> NoReturn:
|
def main(args: List[str]) -> NoReturn:
|
||||||
|
|||||||
@ -243,7 +243,8 @@ copy --exclude */w.* d1
|
|||||||
test_script = f'echo "UNTAR_DONE"; {test_script}'
|
test_script = f'echo "UNTAR_DONE"; {test_script}'
|
||||||
ssh_opts['shell_integration'] = SHELL_INTEGRATION_VALUE or 'disabled'
|
ssh_opts['shell_integration'] = SHELL_INTEGRATION_VALUE or 'disabled'
|
||||||
script, replacements, shm = bootstrap_script(
|
script, replacements, shm = bootstrap_script(
|
||||||
SSHOptions(ssh_opts), script_type='py' if 'python' in sh else 'sh', request_id="testing", test_script=test_script
|
SSHOptions(ssh_opts), script_type='py' if 'python' in sh else 'sh', request_id="testing", test_script=test_script,
|
||||||
|
request_data=True
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
env = basic_shell_env(home_dir)
|
env = basic_shell_env(home_dir)
|
||||||
|
|||||||
@ -27,6 +27,7 @@ with SharedMemory(
|
|||||||
shm.flush()
|
shm.flush()
|
||||||
with open(os.ctermid(), 'wb') as f:
|
with open(os.ctermid(), 'wb') as f:
|
||||||
f.write(f'\x1bP@kitty-ask|{shm.name}\x1b\\'.encode('ascii'))
|
f.write(f'\x1bP@kitty-ask|{shm.name}\x1b\\'.encode('ascii'))
|
||||||
|
f.flush()
|
||||||
while True:
|
while True:
|
||||||
# TODO: Replace sleep() with a mutex and condition variable created in the shared memory
|
# TODO: Replace sleep() with a mutex and condition variable created in the shared memory
|
||||||
time.sleep(0.05)
|
time.sleep(0.05)
|
||||||
|
|||||||
@ -73,8 +73,7 @@ login_cwd=""
|
|||||||
|
|
||||||
request_data="REQUEST_DATA"
|
request_data="REQUEST_DATA"
|
||||||
trap "cleanup_on_bootstrap_exit" EXIT
|
trap "cleanup_on_bootstrap_exit" EXIT
|
||||||
dcs_to_kitty "ssh" "id="REQUEST_ID":pwfile="PASSWORD_FILENAME":pw="DATA_PASSWORD""
|
[ "$request_data" = "1" ] && dcs_to_kitty "ssh" "id="REQUEST_ID":pwfile="PASSWORD_FILENAME":pw="DATA_PASSWORD""
|
||||||
record_separator=$(printf "\036")
|
|
||||||
|
|
||||||
mv_files_and_dirs() {
|
mv_files_and_dirs() {
|
||||||
cwd="$PWD"
|
cwd="$PWD"
|
||||||
@ -135,16 +134,6 @@ untar_and_read_env() {
|
|||||||
tdir=""
|
tdir=""
|
||||||
}
|
}
|
||||||
|
|
||||||
read_record() {
|
|
||||||
record=""
|
|
||||||
while :; do
|
|
||||||
read_one_byte_from_tty || die "Reading a byte from the TTY failed"
|
|
||||||
[ "$n" = "$record_separator" ] && break
|
|
||||||
record="$record$n"
|
|
||||||
done
|
|
||||||
printf "%s" "$record"
|
|
||||||
}
|
|
||||||
|
|
||||||
get_data() {
|
get_data() {
|
||||||
started="n"
|
started="n"
|
||||||
while IFS= read -r line; do
|
while IFS= read -r line; do
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user