mirror of
https://github.com/RsyncProject/rsync.git
synced 2026-09-12 21:28:25 -04:00
testsuite: quote the rsync command everywhere a shell re-parses it
Second layer of the space-in-build-path work. Quoting the Makefile got the
runner started; these are the places that then hand the binary's path to
something that splits on whitespace.
- RSYNC_CONNECT_PROG is run by a shell. This was the big one: an unquoted
daemon command turned every daemon-mode test into
"sh: 1: /path/to/ws: Permission denied".
- RSYNC_RSH / --rsh is tokenised by rsync itself (do_cmd() in main.c, which
honours ' and "), so support/lsh.sh needs quoting when srcdir has a space.
- --rsync-path is a command line run by the REMOTE shell, so rsync passes it
through unsplit and lsh.sh's eval re-parses it.
- The generated rsync-shim scripts interpolate RSYNC into "#!/bin/sh\nexec
...", where it is shell syntax rather than an argv entry.
rsync_path_arg() and rsh_cmd() build those strings by splitting the command and
re-joining with shlex, so a plain path with a space comes back quoted while a
wrapper command ("valgrind ... /build/rsync") stays several words.
split_rsync_cmd() also has to cope with RSYNC once a test has appended options
to it -- chown-fake and friends do -- where the string is no longer a filename.
It now takes the longest leading run that names an existing file as the program
and splits only what follows.
In a directory with a space: 231 pass, 22 fail, from 0 able to run before the
first commit. Unchanged in a normal path: 257 passed, 0 failed.
This commit is contained in:
1 parent
b1d2c32b1d
commit
0789709aeb
27 files changed
+128
-81
No files matched your search
@@ -9,12 +9,12 @@ import os
|
||||
|
||||
from rsyncfns import (
|
||||
FROMDIR, RSYNC, RSYNC_PEER, SRCDIR, TODIR,
|
||||
checkit, run_rsync, test_fail,
|
||||
checkit, run_rsync, test_fail, rsync_path_arg, rsh_cmd,
|
||||
)
|
||||
|
||||
|
||||
# Set RSYNC_RSH so rsync picks up lsh.sh for the "lh:" hosts below.
|
||||
os.environ['RSYNC_RSH'] = str(SRCDIR / 'support' / 'lsh.sh')
|
||||
os.environ['RSYNC_RSH'] = rsh_cmd()
|
||||
|
||||
# Basic help dumps must not crash.
|
||||
if run_rsync('--version', check=False).returncode != 0:
|
||||
@@ -39,7 +39,7 @@ def append_line(line: str) -> None:
|
||||
|
||||
def copy_weird(args: list, src_host: str, dst_host: str) -> None:
|
||||
checkit(
|
||||
[*args, f'--rsync-path={RSYNC_PEER}',
|
||||
[*args, f'--rsync-path={rsync_path_arg()}',
|
||||
f'{src_host}{weird_dir}/',
|
||||
f'{dst_host}{TODIR / weird_name}'],
|
||||
FROMDIR, TODIR,
|
||||
@@ -70,7 +70,7 @@ print('test6')
|
||||
saved = os.getcwd()
|
||||
os.chdir(FROMDIR)
|
||||
try:
|
||||
run_rsync('-ai', '--old-args', f'--rsync-path={RSYNC_PEER}',
|
||||
run_rsync('-ai', '--old-args', f'--rsync-path={rsync_path_arg()}',
|
||||
'lh:one two', f'{TODIR}/')
|
||||
finally:
|
||||
os.chdir(saved)
|
||||
@@ -91,7 +91,7 @@ from rsyncfns import rsync_argv
|
||||
os.chdir(FROMDIR)
|
||||
try:
|
||||
subprocess.run(
|
||||
rsync_argv('-ai', f'--rsync-path={RSYNC_PEER}',
|
||||
rsync_argv('-ai', f'--rsync-path={rsync_path_arg()}',
|
||||
'lh:one two', f'{TODIR}/'),
|
||||
env=env, check=True,
|
||||
)
|
||||
|
||||
@@ -12,7 +12,7 @@ import time
|
||||
|
||||
from rsyncfns import (
|
||||
CHKDIR, FROMDIR, RSYNC, RSYNC_PEER, SCRATCHDIR, SRCDIR, TMPDIR, TODIR,
|
||||
checkit, hands_setup, rmtree, run_rsync, test_fail,
|
||||
checkit, hands_setup, rmtree, run_rsync, test_fail, rsync_path_arg, rsh_cmd,
|
||||
)
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ alt1dir = TMPDIR / 'alt1'
|
||||
alt2dir = TMPDIR / 'alt2'
|
||||
alt3dir = TMPDIR / 'alt3'
|
||||
|
||||
SSH = str(SRCDIR / 'support' / 'lsh.sh')
|
||||
SSH = rsh_cmd()
|
||||
|
||||
hands_setup()
|
||||
|
||||
@@ -73,7 +73,7 @@ for maybe_inplace in ([], ['--inplace']):
|
||||
for srchost in ('', 'localhost:'):
|
||||
desthost = 'localhost:' if not srchost else ''
|
||||
rmtree(TODIR)
|
||||
checkit(['-ave', SSH, f'--rsync-path={RSYNC_PEER}', *maybe_inplace,
|
||||
checkit(['-ave', SSH, f'--rsync-path={rsync_path_arg()}', *maybe_inplace,
|
||||
f'--copy-dest={alt3dir}',
|
||||
f'{srchost}{FROMDIR}/', f'{desthost}{TODIR}/'],
|
||||
FROMDIR, TODIR)
|
||||
@@ -35,6 +35,7 @@
|
||||
# - neither flag -> the injection never took effect -> FAIL (vacuous)
|
||||
|
||||
import os
|
||||
import shlex
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
@@ -154,7 +155,7 @@ def attempt():
|
||||
conf = write_daemon_conf(
|
||||
[('m', {'path': str(serversrc), 'read only': 'yes', 'use chroot': 'no'})],
|
||||
name='mal-xname-rsyncd.conf')
|
||||
os.environ['RSYNC_CONNECT_PROG'] = f'{mal_rsync} --config={conf} --daemon'
|
||||
os.environ['RSYNC_CONNECT_PROG'] = f'{shlex.quote(str(mal_rsync))} --config={shlex.quote(str(conf))} --daemon'
|
||||
os.environ['RSYNC_MAL_XNAME'] = '../secret' # from basis_dir[0] == linkdest
|
||||
proc = subprocess.run(
|
||||
rsync_argv('-a', f'--link-dest={linkdest}',
|
||||
|
||||
@@ -16,14 +16,14 @@ import subprocess
|
||||
from rsyncfns import (
|
||||
CHKFILE, FROMDIR, OUTFILE, RSYNC, RSYNC_PEER, SCRATCHDIR, SRCDIR, TODIR,
|
||||
build_rsyncd_conf, get_rootuid, get_testuid, makepath,
|
||||
rsync_argv, run_rsync, start_test_daemon, test_fail,
|
||||
rsync_argv, run_rsync, start_test_daemon, test_fail, rsh_cmd,
|
||||
)
|
||||
|
||||
|
||||
DAEMON_PORT = 12877
|
||||
|
||||
|
||||
SSH = f"{SRCDIR / 'support' / 'lsh.sh'} --no-cd"
|
||||
SSH = rsh_cmd(None, '--no-cd')
|
||||
|
||||
|
||||
def listed_paths(text: str) -> set:
|
||||
|
||||
+13
-13
@@ -17,7 +17,7 @@ import sys
|
||||
from rsyncfns import (
|
||||
CHKDIR, FROMDIR, RSYNC, RSYNC_PEER, SCRATCHDIR, SRCDIR, TMPDIR, TODIR,
|
||||
all_plus, allspace, dots,
|
||||
checkdiff, checkit, makepath, rsync_argv, run_rsync, test_fail,
|
||||
checkdiff, checkit, makepath, rsync_argv, run_rsync, test_fail, rsync_path_arg, rsh_cmd,
|
||||
)
|
||||
|
||||
|
||||
@@ -25,8 +25,8 @@ os.environ['CVSIGNORE'] = '*.junk'
|
||||
|
||||
script_name = os.path.basename(sys.argv[0] if sys.argv[0] else __file__)
|
||||
if 'lsh' in script_name:
|
||||
os.environ['RSYNC_RSH'] = str(SRCDIR / 'support' / 'lsh.sh')
|
||||
rpath = [f'--rsync-path={RSYNC_PEER}']
|
||||
os.environ['RSYNC_RSH'] = rsh_cmd()
|
||||
rpath = [f'--rsync-path={rsync_path_arg()}']
|
||||
host = 'lh:'
|
||||
else:
|
||||
rpath = []
|
||||
@@ -116,7 +116,7 @@ excl.write_text(
|
||||
# --- main checks ------------------------------------------------------------
|
||||
|
||||
# Start with a check of --prune-empty-dirs.
|
||||
run_rsync('-av', f'--rsync-path={RSYNC_PEER}',
|
||||
run_rsync('-av', f'--rsync-path={rsync_path_arg()}',
|
||||
'-f', '-_foo/too/', '-f', '-_foo/down/',
|
||||
'-f', '-_foo/and/', '-f', '-_new/',
|
||||
f'{host}{FROMDIR}/', f'{CHKDIR}/')
|
||||
@@ -162,7 +162,7 @@ for f in (CHKDIR / 'bar' / 'down' / 'to' / 'foo').glob('file[235-9]'):
|
||||
(up2 / 'dst-newness').touch()
|
||||
|
||||
# Un-tweak the directory times in our first (weak) exclude test.
|
||||
run_rsync('-av', f'--rsync-path={RSYNC_PEER}',
|
||||
run_rsync('-av', f'--rsync-path={rsync_path_arg()}',
|
||||
'--existing', '--include=*/', '--exclude=*',
|
||||
f'{host}{FROMDIR}/', f'{CHKDIR}/')
|
||||
|
||||
@@ -181,7 +181,7 @@ for f in (CHKDIR / 'bar' / 'down' / 'to' / 'foo').glob('*.junk'):
|
||||
(CHKDIR / 'bar' / 'down' / 'to' / 'home-cvs-exclude').unlink()
|
||||
(CHKDIR / 'mid' / 'one-in-one-out').unlink()
|
||||
|
||||
run_rsync('-av', f'--rsync-path={RSYNC_PEER}',
|
||||
run_rsync('-av', f'--rsync-path={rsync_path_arg()}',
|
||||
'--existing', '--filter=exclude,! */',
|
||||
f'{host}{FROMDIR}/', f'{CHKDIR}/')
|
||||
|
||||
@@ -205,7 +205,7 @@ from rsyncfns import cp_touch
|
||||
cp_touch(FROMDIR / 'bar' / 'down' / 'to' / 'foo' / 'to',
|
||||
CHKDIR / 'bar' / 'down' / 'to' / 'foo')
|
||||
|
||||
run_rsync('-av', f'--rsync-path={RSYNC_PEER}',
|
||||
run_rsync('-av', f'--rsync-path={rsync_path_arg()}',
|
||||
'--existing', '-f', 'show .filt*', '-f', 'hide,! */', '--del',
|
||||
f'{host}{FROMDIR}/', f'{TODIR}/')
|
||||
|
||||
@@ -213,7 +213,7 @@ run_rsync('-av', f'--rsync-path={RSYNC_PEER}',
|
||||
cp_touch(TODIR / 'bar' / 'down' / 'to' / 'bar' / 'baz' / 'nodel.deep',
|
||||
CHKDIR / 'bar' / 'down' / 'to' / 'bar' / 'baz')
|
||||
|
||||
run_rsync('-av', f'--rsync-path={RSYNC_PEER}',
|
||||
run_rsync('-av', f'--rsync-path={rsync_path_arg()}',
|
||||
'--existing', '--filter=-! */',
|
||||
f'{host}{FROMDIR}/', f'{CHKDIR}/')
|
||||
|
||||
@@ -255,7 +255,7 @@ verify_dirs(CHKDIR, TODIR, label="dir-merge + merge-from-stdin")
|
||||
(CHKDIR / 'bar' / 'down' / 'to' / 'bar' / '.filt2').unlink()
|
||||
(CHKDIR / 'mid' / '.filt').unlink()
|
||||
|
||||
run_rsync('-av', f'--rsync-path={RSYNC_PEER}',
|
||||
run_rsync('-av', f'--rsync-path={rsync_path_arg()}',
|
||||
'--existing', '--include=*/', '--exclude=*',
|
||||
f'{host}{FROMDIR}/', f'{CHKDIR}/')
|
||||
|
||||
@@ -275,15 +275,15 @@ verify_dirs(CHKDIR, TODIR, label="delete-before with merge")
|
||||
"+ file3\n*.bak\n"
|
||||
)
|
||||
|
||||
run_rsync('-av', f'--rsync-path={RSYNC_PEER}',
|
||||
run_rsync('-av', f'--rsync-path={rsync_path_arg()}',
|
||||
'--del', f'{host}{FROMDIR}/', f'{CHKDIR}/')
|
||||
(CHKDIR / 'bar' / 'down' / 'to' / 'foo' / 'file1.bak').unlink()
|
||||
(CHKDIR / 'bar' / 'down' / 'to' / 'foo' / 'file3').unlink()
|
||||
(CHKDIR / 'bar' / 'down' / 'to' / 'foo' / '+ file3').unlink()
|
||||
run_rsync('-av', f'--rsync-path={RSYNC_PEER}',
|
||||
run_rsync('-av', f'--rsync-path={rsync_path_arg()}',
|
||||
'--existing', '--filter=-! */',
|
||||
f'{host}{FROMDIR}/', f'{CHKDIR}/')
|
||||
run_rsync('-av', f'--rsync-path={RSYNC_PEER}',
|
||||
run_rsync('-av', f'--rsync-path={rsync_path_arg()}',
|
||||
'--delete-excluded', '--exclude=*',
|
||||
f'{host}{FROMDIR}/', f'{TODIR}/')
|
||||
|
||||
@@ -293,7 +293,7 @@ checkit(['-avv', *rpath, '-f', 'dir-merge,-_.excl',
|
||||
|
||||
# Combine with --relative.
|
||||
relative_opts = ['--relative', '--chmod=Du+w', '--copy-unsafe-links']
|
||||
run_rsync('-av', f'--rsync-path={RSYNC_PEER}', *relative_opts,
|
||||
run_rsync('-av', f'--rsync-path={rsync_path_arg()}', *relative_opts,
|
||||
f'{host}{FROMDIR}/foo', f'{CHKDIR}/')
|
||||
shutil.rmtree(str(CHKDIR) + str(FROMDIR) + '/foo/down', ignore_errors=True)
|
||||
run_rsync('-av', *relative_opts, '--existing', '--filter=-! */',
|
||||
|
||||
@@ -7,11 +7,11 @@
|
||||
|
||||
from rsyncfns import (
|
||||
CHKDIR, FROMDIR, RSYNC, RSYNC_PEER, SCRATCHDIR, SRCDIR, TODIR,
|
||||
checkit, hands_setup, rmtree, run_rsync,
|
||||
checkit, hands_setup, rmtree, run_rsync, rsync_path_arg, rsh_cmd,
|
||||
)
|
||||
|
||||
|
||||
SSH = str(SRCDIR / 'support' / 'lsh.sh')
|
||||
SSH = rsh_cmd()
|
||||
|
||||
hands_setup()
|
||||
|
||||
@@ -44,7 +44,7 @@ for filehost in ('', 'localhost:'):
|
||||
|
||||
rmtree(TODIR)
|
||||
checkit(
|
||||
['-avse', SSH, f'--rsync-path={RSYNC_PEER}',
|
||||
['-avse', SSH, f'--rsync-path={rsync_path_arg()}',
|
||||
f'--files-from={filehost}{filelist}',
|
||||
f'{srchost}{SCRATCHDIR}', f'{desthost}{TODIR}/'],
|
||||
CHKDIR, TODIR,
|
||||
|
||||
@@ -13,11 +13,11 @@ import subprocess
|
||||
|
||||
from rsyncfns import (
|
||||
CHKDIR, FROMDIR, OUTFILE, RSYNC, RSYNC_PEER, SRCDIR, TODIR,
|
||||
checkit, makepath, rsync_argv, test_fail, test_skipped,
|
||||
checkit, makepath, rsync_argv, test_fail, test_skipped, rsync_path_arg, rsh_cmd,
|
||||
)
|
||||
|
||||
|
||||
SSH = str(SRCDIR / 'support' / 'lsh.sh')
|
||||
SSH = rsh_cmd()
|
||||
|
||||
FROMDIR.mkdir(parents=True, exist_ok=True)
|
||||
name1 = FROMDIR / 'name1'
|
||||
@@ -64,7 +64,7 @@ for x in chars:
|
||||
os.link(name1, FROMDIR / 'subdir' / 'down' / 'deep' / 'new-file')
|
||||
(TODIR / 'text').unlink()
|
||||
|
||||
checkit(['-aHivve', SSH, '--debug=HLINK5', f'--rsync-path={RSYNC_PEER}',
|
||||
checkit(['-aHivve', SSH, '--debug=HLINK5', f'--rsync-path={rsync_path_arg()}',
|
||||
f'{FROMDIR}/', f'localhost:{TODIR}/'], FROMDIR, TODIR)
|
||||
|
||||
# --link-dest and --copy-dest should also keep hard-linked entries.
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
# diagnostic).
|
||||
|
||||
import os
|
||||
import shlex
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
@@ -126,7 +127,7 @@ conf = write_daemon_conf(
|
||||
'read only': 'yes',
|
||||
'use chroot': 'no'})],
|
||||
name='mal-rsyncd.conf')
|
||||
os.environ['RSYNC_CONNECT_PROG'] = f'{mal_rsync} --config={conf} --daemon'
|
||||
os.environ['RSYNC_CONNECT_PROG'] = f'{shlex.quote(str(mal_rsync))} --config={shlex.quote(str(conf))} --daemon'
|
||||
|
||||
|
||||
# -- The attack pull: -rR --delete from the malicious daemon -----------------
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
from rsyncfns import SCRATCHDIR, SRCDIR, rmtree, rsync_argv, test_fail
|
||||
from rsyncfns import SCRATCHDIR, SRCDIR, rmtree, rsync_argv, test_fail, rsh_cmd
|
||||
|
||||
base = SCRATCHDIR / 'remote-shell-newline'
|
||||
rmtree(base)
|
||||
@@ -19,7 +19,7 @@ src.mkdir(parents=True)
|
||||
sentinel = base / 'pwned'
|
||||
|
||||
env = os.environ.copy()
|
||||
env['RSYNC_RSH'] = f"{SRCDIR / 'support' / 'lsh.sh'} --no-cd"
|
||||
env['RSYNC_RSH'] = rsh_cmd(None, '--no-cd')
|
||||
dest = f"lh:{base}/dest\ntouch {sentinel}\n#"
|
||||
subprocess.run(rsync_argv('-a', f'{src}/', dest),
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, env=env)
|
||||
|
||||
@@ -10,6 +10,7 @@ out-of-tree content lands where it can be read back.
|
||||
"""
|
||||
|
||||
import os
|
||||
import shlex
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
@@ -25,7 +26,8 @@ if '--shell' in sys.argv:
|
||||
os.execve(wrapper, [wrapper, '-wo', root], env)
|
||||
|
||||
from rsyncfns import (RSYNC, SCRATCHDIR, makepath, patched_rrsync,
|
||||
proc_self_fd_pins, rmtree, rsync_argv)
|
||||
proc_self_fd_pins, rmtree, rsync_argv, rsync_path_arg,
|
||||
)
|
||||
|
||||
T = 1234567890
|
||||
|
||||
@@ -59,10 +61,10 @@ os.utime(outside / 'f0', (T, T))
|
||||
# while rrsync hands its RSYNC to execlp() as a single executable name, so it
|
||||
# has to be wrapped before patched_rrsync() sees it.
|
||||
shim = base / 'rsync-shim'
|
||||
shim.write_text('#!/bin/sh\nexec ' + RSYNC + ' "$@"\n')
|
||||
shim.write_text('#!/bin/sh\nexec ' + rsync_path_arg(RSYNC) + ' "$@"\n')
|
||||
shim.chmod(0o755)
|
||||
wrapper = patched_rrsync(base, rsync_path=str(shim))
|
||||
rsh = f'{sys.executable} {os.path.abspath(__file__)} --shell'
|
||||
rsh = f'{shlex.quote(sys.executable)} {shlex.quote(os.path.abspath(__file__))} --shell'
|
||||
env = {**os.environ, 'RRSYNC_WRAPPER': str(wrapper), 'RRSYNC_ROOT': str(root)}
|
||||
|
||||
got = subprocess.run(
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"""rrsync must not let one transfer create and consume an option-path symlink."""
|
||||
|
||||
import os
|
||||
import shlex
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
@@ -18,7 +19,8 @@ if '--shell' in sys.argv:
|
||||
os.execve(wrapper, [wrapper, '-wo', root], env)
|
||||
|
||||
from rsyncfns import (RSYNC, SCRATCHDIR, makepath, patched_rrsync,
|
||||
proc_self_fd_pins, rmtree, rsync_argv)
|
||||
proc_self_fd_pins, rmtree, rsync_argv, rsync_path_arg,
|
||||
)
|
||||
|
||||
# Handing rsync an inode instead of a name needs the /proc/self/fd primitive.
|
||||
# Where it is missing there is no safe way to pass a peer-suppliable option
|
||||
@@ -43,10 +45,10 @@ os.symlink('../outside', src / 'a')
|
||||
# while rrsync hands its RSYNC to execlp() as a single executable name, so it
|
||||
# has to be wrapped before patched_rrsync() sees it.
|
||||
shim = base / 'rsync-shim'
|
||||
shim.write_text('#!/bin/sh\nexec ' + RSYNC + ' "$@"\n')
|
||||
shim.write_text('#!/bin/sh\nexec ' + rsync_path_arg(RSYNC) + ' "$@"\n')
|
||||
shim.chmod(0o755)
|
||||
wrapper = patched_rrsync(base, rsync_path=str(shim))
|
||||
rsh = f'{sys.executable} {os.path.abspath(__file__)} --shell'
|
||||
rsh = f'{shlex.quote(sys.executable)} {shlex.quote(os.path.abspath(__file__))} --shell'
|
||||
env = {**os.environ, 'RRSYNC_WRAPPER': str(wrapper), 'RRSYNC_ROOT': str(root)}
|
||||
|
||||
seed = subprocess.run(
|
||||
|
||||
@@ -25,7 +25,7 @@ import shlex
|
||||
import subprocess
|
||||
|
||||
from rsyncfns import (
|
||||
RSYNC, SCRATCHDIR, makepath, patched_rrsync, rmtree, rsync_argv, test_fail,
|
||||
RSYNC, SCRATCHDIR, makepath, patched_rrsync, rmtree, rsync_argv, test_fail, rsync_path_arg,
|
||||
)
|
||||
|
||||
DATA = 'FILES-FROM-CONTENT\n'
|
||||
@@ -47,7 +47,7 @@ listfile.write_text('f1\nf2\n')
|
||||
# $RSYNC may carry a wrapper (valgrind, --protocol=N); rrsync wants a single
|
||||
# executable, so hand it a shim that re-expands the whole command line.
|
||||
shim = base / 'rsync-shim'
|
||||
shim.write_text('#!/bin/sh\nexec ' + RSYNC + ' "$@"\n')
|
||||
shim.write_text('#!/bin/sh\nexec ' + rsync_path_arg(RSYNC) + ' "$@"\n')
|
||||
shim.chmod(0o755)
|
||||
|
||||
rrsync = patched_rrsync(base, rsync_path=str(shim))
|
||||
|
||||
@@ -8,6 +8,7 @@ file can land on a protected name that the source itself also carries.
|
||||
"""
|
||||
|
||||
import os
|
||||
import shlex
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
@@ -23,7 +24,7 @@ if '--shell' in sys.argv:
|
||||
flags = env.get('RRSYNC_FLAGS', '-wo -no-overwrite -no-lock').split()
|
||||
os.execve(wrapper, [wrapper, *flags, root], env)
|
||||
|
||||
from rsyncfns import RSYNC, SCRATCHDIR, makepath, patched_rrsync, rmtree, rsync_argv
|
||||
from rsyncfns import RSYNC, SCRATCHDIR, makepath, patched_rrsync, rmtree, rsync_argv, rsync_path_arg
|
||||
|
||||
base = SCRATCHDIR / 'rrsync-no-overwrite-backup-collision'
|
||||
rmtree(base)
|
||||
@@ -46,10 +47,10 @@ plant(root)
|
||||
# while rrsync hands its RSYNC to execlp() as a single executable name, so it
|
||||
# has to be wrapped before patched_rrsync() sees it.
|
||||
shim = base / 'rsync-shim'
|
||||
shim.write_text('#!/bin/sh\nexec ' + RSYNC + ' "$@"\n')
|
||||
shim.write_text('#!/bin/sh\nexec ' + rsync_path_arg(RSYNC) + ' "$@"\n')
|
||||
shim.chmod(0o755)
|
||||
wrapper = patched_rrsync(base, rsync_path=str(shim))
|
||||
rsh = f'{sys.executable} {os.path.abspath(__file__)} --shell'
|
||||
rsh = f'{shlex.quote(sys.executable)} {shlex.quote(os.path.abspath(__file__))} --shell'
|
||||
env = {**os.environ, 'RRSYNC_WRAPPER': str(wrapper), 'RRSYNC_ROOT': str(root)}
|
||||
|
||||
# The stock client sends -b inside the remote short-option bundle, so the
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"""rrsync -no-overwrite must protect implicit delay-update partial files."""
|
||||
|
||||
import os
|
||||
import shlex
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
@@ -17,7 +18,7 @@ if '--shell' in sys.argv:
|
||||
flags = env.get('RRSYNC_FLAGS', '-wo -no-overwrite -no-lock').split()
|
||||
os.execve(wrapper, [wrapper, *flags, root], env)
|
||||
|
||||
from rsyncfns import RSYNC, SCRATCHDIR, makepath, patched_rrsync, rmtree, rsync_argv
|
||||
from rsyncfns import RSYNC, SCRATCHDIR, makepath, patched_rrsync, rmtree, rsync_argv, rsync_path_arg
|
||||
|
||||
base = SCRATCHDIR / 'rrsync-no-overwrite-delay-updates'
|
||||
rmtree(base)
|
||||
@@ -32,10 +33,10 @@ protected.write_bytes(b'POLICY')
|
||||
# while rrsync hands its RSYNC to execlp() as a single executable name, so it
|
||||
# has to be wrapped before patched_rrsync() sees it.
|
||||
shim = base / 'rsync-shim'
|
||||
shim.write_text('#!/bin/sh\nexec ' + RSYNC + ' "$@"\n')
|
||||
shim.write_text('#!/bin/sh\nexec ' + rsync_path_arg(RSYNC) + ' "$@"\n')
|
||||
shim.chmod(0o755)
|
||||
wrapper = patched_rrsync(base, rsync_path=str(shim))
|
||||
rsh = f'{sys.executable} {os.path.abspath(__file__)} --shell'
|
||||
rsh = f'{shlex.quote(sys.executable)} {shlex.quote(os.path.abspath(__file__))} --shell'
|
||||
env = {**os.environ, 'RRSYNC_WRAPPER': str(wrapper), 'RRSYNC_ROOT': str(root)}
|
||||
got = subprocess.run(
|
||||
rsync_argv('-rI', '--delay-updates', '-e', rsh,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"""rrsync -no-overwrite must not let the peer append to an existing file."""
|
||||
|
||||
import os
|
||||
import shlex
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
@@ -17,7 +18,7 @@ if '--shell' in sys.argv:
|
||||
flags = env.get('RRSYNC_FLAGS', '-wo -no-overwrite -no-lock').split()
|
||||
os.execve(wrapper, [wrapper, *flags, root], env)
|
||||
|
||||
from rsyncfns import RSYNC, SCRATCHDIR, makepath, patched_rrsync, rmtree, rsync_argv
|
||||
from rsyncfns import RSYNC, SCRATCHDIR, makepath, patched_rrsync, rmtree, rsync_argv, rsync_path_arg
|
||||
|
||||
base = SCRATCHDIR / 'rrsync-no-overwrite-logfile'
|
||||
rmtree(base)
|
||||
@@ -32,10 +33,10 @@ protected.write_bytes(b'POLICY\n')
|
||||
# while rrsync hands its RSYNC to execlp() as a single executable name, so it
|
||||
# has to be wrapped before patched_rrsync() sees it.
|
||||
shim = base / 'rsync-shim'
|
||||
shim.write_text('#!/bin/sh\nexec ' + RSYNC + ' "$@"\n')
|
||||
shim.write_text('#!/bin/sh\nexec ' + rsync_path_arg(RSYNC) + ' "$@"\n')
|
||||
shim.chmod(0o755)
|
||||
wrapper = patched_rrsync(base, rsync_path=str(shim))
|
||||
rsh = f'{sys.executable} {os.path.abspath(__file__)} --shell'
|
||||
rsh = f'{shlex.quote(sys.executable)} {shlex.quote(os.path.abspath(__file__))} --shell'
|
||||
env = {**os.environ, 'RRSYNC_WRAPPER': str(wrapper), 'RRSYNC_ROOT': str(root)}
|
||||
got = subprocess.run(
|
||||
rsync_argv('-rI', '-M--log-file=protected',
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"""rrsync -no-overwrite must protect existing partial-dir files too."""
|
||||
|
||||
import os
|
||||
import shlex
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
@@ -17,7 +18,7 @@ if '--shell' in sys.argv:
|
||||
flags = env.get('RRSYNC_FLAGS', '-wo -no-overwrite -no-lock').split()
|
||||
os.execve(wrapper, [wrapper, *flags, root], env)
|
||||
|
||||
from rsyncfns import RSYNC, SCRATCHDIR, makepath, patched_rrsync, rmtree, rsync_argv
|
||||
from rsyncfns import RSYNC, SCRATCHDIR, makepath, patched_rrsync, rmtree, rsync_argv, rsync_path_arg
|
||||
|
||||
base = SCRATCHDIR / 'rrsync-no-overwrite-partial-dir'
|
||||
rmtree(base)
|
||||
@@ -32,10 +33,10 @@ protected.write_bytes(b'POLICY')
|
||||
# while rrsync hands its RSYNC to execlp() as a single executable name, so it
|
||||
# has to be wrapped before patched_rrsync() sees it.
|
||||
shim = base / 'rsync-shim'
|
||||
shim.write_text('#!/bin/sh\nexec ' + RSYNC + ' "$@"\n')
|
||||
shim.write_text('#!/bin/sh\nexec ' + rsync_path_arg(RSYNC) + ' "$@"\n')
|
||||
shim.chmod(0o755)
|
||||
wrapper = patched_rrsync(base, rsync_path=str(shim))
|
||||
rsh = f'{sys.executable} {os.path.abspath(__file__)} --shell'
|
||||
rsh = f'{shlex.quote(sys.executable)} {shlex.quote(os.path.abspath(__file__))} --shell'
|
||||
env = {**os.environ, 'RRSYNC_WRAPPER': str(wrapper), 'RRSYNC_ROOT': str(root)}
|
||||
got = subprocess.run(
|
||||
rsync_argv('-rI', '--partial', '--partial-dir=protected',
|
||||
|
||||
@@ -23,7 +23,7 @@ import subprocess
|
||||
|
||||
from rsyncfns import (
|
||||
RSYNC, SCRATCHDIR, forced_protocol, makepath, patched_rrsync, rmtree,
|
||||
rsync_argv, test_fail,
|
||||
rsync_argv, test_fail, rsync_path_arg,
|
||||
)
|
||||
|
||||
base = SCRATCHDIR / 'rrsync-arg-shapes'
|
||||
@@ -53,7 +53,7 @@ xonly.mkdir()
|
||||
xonly.chmod(0o111)
|
||||
|
||||
shim = base / 'rsync-shim'
|
||||
shim.write_text('#!/bin/sh\nexec ' + RSYNC + ' "$@"\n')
|
||||
shim.write_text('#!/bin/sh\nexec ' + rsync_path_arg(RSYNC) + ' "$@"\n')
|
||||
shim.chmod(0o755)
|
||||
|
||||
rrsync = patched_rrsync(base, rsync_path=str(shim))
|
||||
|
||||
@@ -28,7 +28,7 @@ import shlex
|
||||
import subprocess
|
||||
|
||||
from rsyncfns import (
|
||||
RSYNC, SCRATCHDIR, makepath, patched_rrsync, rmtree, rsync_argv, test_fail,
|
||||
RSYNC, SCRATCHDIR, makepath, patched_rrsync, rmtree, rsync_argv, test_fail, rsync_path_arg,
|
||||
)
|
||||
|
||||
PULL_DATA = 'REAL-FILE-CONTENT\n'
|
||||
@@ -50,7 +50,7 @@ makepath(restricted / 'inbox')
|
||||
# $RSYNC may carry a wrapper (valgrind, --protocol=N); rrsync wants a single
|
||||
# executable, so hand it a shim that re-expands the whole command line.
|
||||
shim = base / 'rsync-shim'
|
||||
shim.write_text('#!/bin/sh\nexec ' + RSYNC + ' "$@"\n')
|
||||
shim.write_text('#!/bin/sh\nexec ' + rsync_path_arg(RSYNC) + ' "$@"\n')
|
||||
shim.chmod(0o755)
|
||||
|
||||
rrsync = patched_rrsync(base, rsync_path=str(shim))
|
||||
|
||||
@@ -38,7 +38,7 @@ import time
|
||||
from rsyncfns import (
|
||||
RACE_TIMEOUT, RSYNC, SCRATCHDIR, makepath, patched_rrsync,
|
||||
proc_self_fd_pins, rmtree, rsync_argv, start_path_flipper, stop_flipper,
|
||||
test_fail, test_skipped,
|
||||
test_fail, test_skipped, rsync_path_arg,
|
||||
)
|
||||
|
||||
if not proc_self_fd_pins():
|
||||
@@ -58,7 +58,7 @@ makepath(restricted, outside / 'dir', dest)
|
||||
(outside / 'dir' / 'loot').write_text(MARKER + '\n')
|
||||
|
||||
shim = base / 'rsync-shim'
|
||||
shim.write_text('#!/bin/sh\nexec ' + RSYNC + ' "$@"\n')
|
||||
shim.write_text('#!/bin/sh\nexec ' + rsync_path_arg(RSYNC) + ' "$@"\n')
|
||||
shim.chmod(0o755)
|
||||
|
||||
rrsync = patched_rrsync(base, rsync_path=str(shim))
|
||||
|
||||
@@ -34,7 +34,7 @@ import subprocess
|
||||
|
||||
from rsyncfns import (
|
||||
RSYNC, RSYNC_PREFIX, SCRATCHDIR, patched_rrsync, rmtree, rsync_argv,
|
||||
test_fail, xattr_set, xattrs_supported,
|
||||
test_fail, xattr_set, xattrs_supported, rsync_path_arg,
|
||||
)
|
||||
|
||||
base = SCRATCHDIR / 'rrsync-specials'
|
||||
@@ -113,7 +113,7 @@ served.mkdir()
|
||||
pushwrap = base / 'pushwrap'
|
||||
pushwrap.mkdir()
|
||||
shim = base / 'rsync-shim'
|
||||
shim.write_text('#!/bin/sh\nexec ' + RSYNC + ' "$@"\n')
|
||||
shim.write_text('#!/bin/sh\nexec ' + rsync_path_arg(RSYNC) + ' "$@"\n')
|
||||
shim.chmod(0o755)
|
||||
rrsync_push = patched_rrsync(pushwrap, rsync_path=str(shim))
|
||||
rsh = base / 'fake-rsh'
|
||||
@@ -175,7 +175,7 @@ if xattrs_supported():
|
||||
devwrap = base / 'devwrap'
|
||||
devwrap.mkdir()
|
||||
devshim = base / 'rsync-shim-fake'
|
||||
devshim.write_text('#!/bin/sh\nexec ' + RSYNC + ' --fake-super "$@"\n')
|
||||
devshim.write_text('#!/bin/sh\nexec ' + rsync_path_arg(RSYNC) + ' --fake-super "$@"\n')
|
||||
devshim.chmod(0o755)
|
||||
rrsync_dev = patched_rrsync(devwrap, rsync_path=str(devshim))
|
||||
devrsh = base / 'fake-rsh-dev'
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"""A forced rrsync -wo endpoint must not return a local --files-from file."""
|
||||
|
||||
import os
|
||||
import shlex
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
@@ -16,7 +17,7 @@ if '--shell' in sys.argv:
|
||||
wrapper, root = env['RRSYNC_WRAPPER'], env['RRSYNC_ROOT']
|
||||
os.execve(wrapper, [wrapper, '-wo', '-no-lock', root], env)
|
||||
|
||||
from rsyncfns import RSYNC, SCRATCHDIR, makepath, patched_rrsync, rmtree, rsync_argv
|
||||
from rsyncfns import RSYNC, SCRATCHDIR, makepath, patched_rrsync, rmtree, rsync_argv, rsync_path_arg
|
||||
|
||||
base = SCRATCHDIR / 'rrsync-write-only-files-from'
|
||||
rmtree(base)
|
||||
@@ -30,10 +31,10 @@ secret_name = 'SERVER-ONLY-SECRET-NAME'
|
||||
# while rrsync hands its RSYNC to execlp() as a single executable name, so it
|
||||
# has to be wrapped before patched_rrsync() sees it.
|
||||
shim = base / 'rsync-shim'
|
||||
shim.write_text('#!/bin/sh\nexec ' + RSYNC + ' "$@"\n')
|
||||
shim.write_text('#!/bin/sh\nexec ' + rsync_path_arg(RSYNC) + ' "$@"\n')
|
||||
shim.chmod(0o755)
|
||||
wrapper = patched_rrsync(base, rsync_path=str(shim))
|
||||
rsh = f'{sys.executable} {os.path.abspath(__file__)} --shell'
|
||||
rsh = f'{shlex.quote(sys.executable)} {shlex.quote(os.path.abspath(__file__))} --shell'
|
||||
env = {**os.environ, 'RRSYNC_WRAPPER': str(wrapper), 'RRSYNC_ROOT': str(root)}
|
||||
got = subprocess.run(
|
||||
rsync_argv('-r', '--files-from=:protected-list',
|
||||
|
||||
+38
-2
@@ -23,6 +23,7 @@ import filecmp
|
||||
import errno
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import shlex
|
||||
import shutil
|
||||
import signal
|
||||
@@ -95,6 +96,14 @@ def split_rsync_cmd(cmd: str) -> list:
|
||||
"""
|
||||
if os.path.isfile(cmd):
|
||||
return [cmd]
|
||||
# The path may be followed by options -- chown-fake and friends append
|
||||
# ' --fake-super' to RSYNC -- so the whole string is no longer a filename.
|
||||
# Take the longest leading run that names an existing file as the program
|
||||
# and split only what follows.
|
||||
for m in reversed(list(re.finditer(r'\s+', cmd))):
|
||||
head = cmd[:m.start()]
|
||||
if os.path.isfile(head):
|
||||
return [head] + shlex.split(cmd[m.start():])
|
||||
return shlex.split(cmd)
|
||||
|
||||
|
||||
@@ -658,7 +667,10 @@ def start_test_daemon(conf_path, port: int, rsync_cmd: str = None) -> str:
|
||||
port = claim_free_port(port)
|
||||
start_rsyncd(conf_path, port, daemon_cmd)
|
||||
return f'rsync://localhost:{port}/'
|
||||
os.environ['RSYNC_CONNECT_PROG'] = f'{daemon_cmd} --config={conf_path} --daemon'
|
||||
# RSYNC_CONNECT_PROG is run by a shell, so every word has to survive
|
||||
# re-parsing: a build path with a space would otherwise exec its prefix.
|
||||
os.environ['RSYNC_CONNECT_PROG'] = (
|
||||
f'{rsync_path_arg(daemon_cmd)} --config={shlex.quote(str(conf_path))} --daemon')
|
||||
return 'rsync://localhost/'
|
||||
|
||||
|
||||
@@ -677,7 +689,7 @@ def require_asan(reason: str, which: str = None) -> 'None':
|
||||
pass RSYNC to check the client side. Detection runs the binary with
|
||||
ASAN_OPTIONS=help=1, which makes an instrumented binary print the ASan
|
||||
flag help banner to stderr."""
|
||||
cmd = shlex.split(which or RSYNC_PEER)
|
||||
cmd = split_rsync_cmd(which or RSYNC_PEER)
|
||||
try:
|
||||
r = subprocess.run(cmd + ['--version'],
|
||||
env={**os.environ, 'ASAN_OPTIONS': 'help=1'},
|
||||
@@ -690,6 +702,30 @@ def require_asan(reason: str, which: str = None) -> 'None':
|
||||
test_skipped(reason)
|
||||
|
||||
|
||||
def rsh_cmd(cmd: str = None, *opts: str) -> str:
|
||||
"""Build an RSYNC_RSH / --rsh value, quoted for rsync's own tokenizer.
|
||||
|
||||
rsync splits this string on spaces itself -- honouring ' and ", see do_cmd()
|
||||
in main.c -- so a remote-shell path containing a space must be quoted or
|
||||
rsync execs only the first word. The testsuite's srcdir can contain one.
|
||||
"""
|
||||
if cmd is None:
|
||||
cmd = str(SRCDIR / 'support' / 'lsh.sh')
|
||||
return ' '.join([shlex.quote(cmd), *opts])
|
||||
|
||||
|
||||
def rsync_path_arg(cmd: str = None) -> str:
|
||||
"""Value for --rsync-path, quoted for the shell that will re-parse it.
|
||||
|
||||
--rsync-path is a command line run by the remote shell, not a filename, so
|
||||
rsync hands it over unquoted and the far side word-splits it. A build path
|
||||
containing a space therefore needs quoting here, while a wrapper command
|
||||
('valgrind ... /build/rsync') must stay several words. Splitting and
|
||||
re-joining with shlex gives both: each word is quoted only if it needs it.
|
||||
"""
|
||||
return shlex.join(split_rsync_cmd(RSYNC_PEER if cmd is None else cmd))
|
||||
|
||||
|
||||
def rsync_argv(*args: str) -> list:
|
||||
"""Return the argv for invoking rsync with the given extra arguments.
|
||||
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
import os
|
||||
import subprocess
|
||||
from rsyncfns import (
|
||||
FROMDIR, RSYNC_PEER, SRCDIR, SCRATCHDIR, makepath, rmtree, rsync_argv, test_fail,
|
||||
FROMDIR, RSYNC_PEER, SRCDIR, SCRATCHDIR, makepath, rmtree, rsync_argv, test_fail, rsync_path_arg, rsh_cmd,
|
||||
)
|
||||
|
||||
LSH = str(SRCDIR / 'support' / 'lsh.sh')
|
||||
LSH = rsh_cmd()
|
||||
os.environ['RSYNC_RSH'] = LSH
|
||||
|
||||
src = FROMDIR
|
||||
@@ -27,7 +27,7 @@ def run(label, extra, local=False):
|
||||
argv = rsync_argv('-a', *extra, f'{src}/', f'{dest}/')
|
||||
else:
|
||||
argv = rsync_argv('-a', *extra, '-e', LSH,
|
||||
f'--rsync-path={RSYNC_PEER}', f'{src}/', f'localhost:{dest}/')
|
||||
f'--rsync-path={rsync_path_arg()}', f'{src}/', f'localhost:{dest}/')
|
||||
r = subprocess.run(argv, capture_output=True, text=True)
|
||||
if r.returncode < 0 or r.returncode >= 128:
|
||||
test_fail(f'{label}: rsync crashed (rc={r.returncode}): {r.stderr.strip()[:200]}')
|
||||
|
||||
@@ -12,11 +12,11 @@ import subprocess
|
||||
|
||||
from rsyncfns import (
|
||||
FROMDIR, SRCDIR, TODIR,
|
||||
checkit, hands_setup, runtest, test_skipped,
|
||||
checkit, hands_setup, runtest, test_skipped, rsync_path_arg, rsh_cmd,
|
||||
)
|
||||
|
||||
|
||||
SSH = str(SRCDIR / 'support' / 'lsh.sh')
|
||||
SSH = rsh_cmd()
|
||||
|
||||
# Allow opting into real ssh via the shconfig variable, like the shell test.
|
||||
if os.environ.get('rsync_enable_ssh_tests') == 'yes':
|
||||
@@ -42,13 +42,13 @@ from rsyncfns import RSYNC, RSYNC_PEER
|
||||
|
||||
|
||||
def _basic():
|
||||
checkit(['-avH', '-e', SSH, f'--rsync-path={RSYNC_PEER}',
|
||||
checkit(['-avH', '-e', SSH, f'--rsync-path={rsync_path_arg()}',
|
||||
f'{FROMDIR}/', f'localhost:{TODIR}'], FROMDIR, TODIR)
|
||||
|
||||
|
||||
def _delete_after_rename():
|
||||
shutil.move(str(TODIR / 'text'), str(TODIR / 'ThisShouldGo'))
|
||||
checkit(['--delete', '-avH', '-e', SSH, f'--rsync-path={RSYNC_PEER}',
|
||||
checkit(['--delete', '-avH', '-e', SSH, f'--rsync-path={rsync_path_arg()}',
|
||||
f'{FROMDIR}/', f'localhost:{TODIR}'], FROMDIR, TODIR)
|
||||
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ import time
|
||||
|
||||
from rsyncfns import (
|
||||
RSYNC, SCRATCHDIR, RSYNC_PEER, SRCDIR, TMPDIR,
|
||||
make_data_file, rsync_argv, test_fail,
|
||||
make_data_file, rsync_argv, test_fail, rsync_path_arg, rsh_cmd,
|
||||
)
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ from rsyncfns import (
|
||||
# target off held dirfds (issue #715), so this transfer through a dir-symlinked
|
||||
# basis succeeds uniformly.
|
||||
|
||||
os.environ['RSYNC_RSH'] = str(SRCDIR / 'support' / 'lsh.sh')
|
||||
os.environ['RSYNC_RSH'] = rsh_cmd()
|
||||
# HOME -> SCRATCHDIR is set up by rsyncfns import.
|
||||
|
||||
srcbase = TMPDIR / 'src_files' # avoid clash with the runner's $scratchdir/src symlink
|
||||
@@ -50,7 +50,7 @@ def push(*args, label: str) -> None:
|
||||
os.chdir(srcbase)
|
||||
try:
|
||||
proc = subprocess.run(
|
||||
rsync_argv(f'--rsync-path={RSYNC_PEER}', *args),
|
||||
rsync_argv(f'--rsync-path={rsync_path_arg()}', *args),
|
||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True,
|
||||
)
|
||||
print(proc.stdout, end='')
|
||||
|
||||
@@ -26,10 +26,10 @@ from rsyncfns import (
|
||||
RSYNC, RSYNC_PEER, SRCDIR, SCRATCHDIR,
|
||||
make_variety_tree, compare_trees, rmtree,
|
||||
xattrs_supported, acls_supported, devices_supported, owners_supported,
|
||||
test_skipped, test_fail, split_rsync_cmd,
|
||||
test_skipped, test_fail, split_rsync_cmd, rsh_cmd,
|
||||
)
|
||||
|
||||
SSH = str(SRCDIR / 'support' / 'lsh.sh')
|
||||
SSH = rsh_cmd()
|
||||
WITH_X = xattrs_supported()
|
||||
WITH_A = acls_supported()
|
||||
|
||||
|
||||
@@ -34,10 +34,10 @@ from rsyncfns import (
|
||||
make_variety_tree, compare_trees, rmtree,
|
||||
xattrs_supported, acls_supported, devices_supported, owners_supported,
|
||||
write_daemon_conf, start_test_daemon,
|
||||
test_fail, split_rsync_cmd,
|
||||
test_fail, split_rsync_cmd, rsh_cmd,
|
||||
)
|
||||
|
||||
SSH = str(SRCDIR / 'support' / 'lsh.sh')
|
||||
SSH = rsh_cmd()
|
||||
DAEMON_PORT_OLD = 12931
|
||||
DAEMON_PORT_NEW = 12932
|
||||
|
||||
|
||||
Reference in new issue
Block a user