86 lines · 3.0 KB · Python
RawDownload
#!/usr/bin/env python3"""Daemon coverage: pre-xfer exec and post-xfer exec. A module's pre-xfer/post-xfer exec hooks must run with the documentedenvironment (RSYNC_MODULE_NAME, RSYNC_EXIT_STATUS, ...), and a non-zeropre-xfer exec must abort the transfer.""" import shleximport subprocessimport time from rsyncfns import (    FROMDIR, SCRATCHDIR,    make_tree, makepath, rmtree, rsync_argv, start_test_daemon, test_fail,    write_daemon_conf,)  def wait_for(path, want, secs=5):    """Poll for a marker file to contain `want`. post-xfer exec runs on the    daemon side after the client disconnects (a real race under --use-tcp),    so we must wait for it rather than check immediately."""    deadline = time.monotonic() + secs    while time.monotonic() < deadline:        if path.is_file() and path.read_text().strip() == want:            return True        time.sleep(0.05)    return False DAEMON_PORT = 12889 src = FROMDIRrmtree(src)make_tree(src, depth=3) markers = SCRATCHDIR / 'markers'rmtree(markers)makepath(markers)hookdir = SCRATCHDIR / 'hookdest'faildir = SCRATCHDIR / 'faildest'makepath(hookdir, faildir)  def script(name, body):    p = SCRATCHDIR / name    p.write_text('#!/bin/sh\n' + body)    p.chmod(0o755)    return p  pre = script('pre.sh', f'echo "$RSYNC_MODULE_NAME" > {shlex.quote(str(markers / "pre.out"))}\nexit 0\n')post = script('post.sh', f'echo "$RSYNC_EXIT_STATUS" > {shlex.quote(str(markers / "post.out"))}\n'                         'exit 0\n')prefail = script('prefail.sh', 'exit 1\n') conf = write_daemon_conf([    ('hook', {'path': hookdir, 'read only': 'no',              'pre-xfer exec': shlex.quote(str(pre)),               'post-xfer exec': shlex.quote(str(post))}),    ('failhook', {'path': faildir, 'read only': 'no',                  'pre-xfer exec': shlex.quote(str(prefail))}),])url = start_test_daemon(conf, DAEMON_PORT) # --- pre/post hooks run with the documented environment ---------------------proc = subprocess.run(rsync_argv('-a', f'{src}/', f'{url}hook/'),                      stdout=subprocess.DEVNULL, stderr=subprocess.PIPE,                      text=True)if proc.returncode not in (0, 23):    test_fail(f"transfer through exec-hook module failed: {proc.stderr}")if not wait_for(markers / 'pre.out', 'hook'):    test_fail("pre-xfer exec did not run with RSYNC_MODULE_NAME=hook")if not wait_for(markers / 'post.out', '0'):    test_fail("post-xfer exec did not run with RSYNC_EXIT_STATUS=0") # --- a failing pre-xfer exec aborts the transfer ----------------------------proc = subprocess.run(rsync_argv('-a', f'{src}/', f'{url}failhook/'),                      stdout=subprocess.DEVNULL, stderr=subprocess.PIPE,                      text=True)if proc.returncode == 0:    test_fail("a failing pre-xfer exec did not abort the transfer")if list(faildir.iterdir()):    test_fail("transfer wrote files despite a failing pre-xfer exec") print("daemon-exec: pre-xfer/post-xfer exec env + abort-on-failure verified")