Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion clientserver.c
Original file line number Diff line number Diff line change
Expand Up @@ -1722,9 +1722,30 @@ static void become_daemon(void)
}
}

/* Inetd supplies a connected IP stream on stdin. Other launchers may use a
* local socket for process I/O, which must not select inetd mode. */
static int is_inetd_socket(int fd)
{
int type;
struct sockaddr_storage peer = {0};
socklen_t type_len = sizeof type;
socklen_t peer_len = sizeof peer;

if (getsockopt(fd, SOL_SOCKET, SO_TYPE, (char *)&type, &type_len) != 0
|| type != SOCK_STREAM
|| getpeername(fd, (struct sockaddr *)&peer, &peer_len) != 0)
return 0;

return peer.ss_family == AF_INET
#ifdef INET6
|| peer.ss_family == AF_INET6
#endif
;
}

int daemon_main(void)
{
if (is_a_socket(STDIN_FILENO)) {
if (is_inetd_socket(STDIN_FILENO)) {
int i;

/* we are running via inetd - close off stdout and
Expand Down
35 changes: 35 additions & 0 deletions testsuite/daemon-stdin-local-socket_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/usr/bin/env python3
"""A local stdin socket must not make a standalone daemon enter inetd mode."""

import socket

from rsyncfns import (
RSYNC, SCRATCHDIR, claim_free_port, rmtree, start_rsyncd,
test_skipped, write_daemon_conf,
)

if not hasattr(socket, 'AF_UNIX') or not hasattr(socket, 'socketpair'):
test_skipped('Unix-domain socket pairs are unavailable')

base = SCRATCHDIR / 'daemon-stdin-local-socket'
rmtree(base)
module = base / 'module'
module.mkdir(parents=True)
conf = write_daemon_conf([
('module', {'path': str(module), 'read only': 'yes'}),
])
port = claim_free_port(12979)

try:
parent, child = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
except (OSError, ValueError) as e:
test_skipped(f'Unix-domain socket pairs are unavailable: {e}')
try:
# ADB shell without a PTY similarly presents a local socket as fd 0. The
# local socket must not be mistaken for an inetd connection.
start_rsyncd(conf, port, rsync_cmd=RSYNC, stdin=child)
finally:
child.close()
parent.close()

print('daemon ignores a local stdin socket when selecting inetd mode')
8 changes: 5 additions & 3 deletions testsuite/rsyncfns.py
Original file line number Diff line number Diff line change
Expand Up @@ -767,7 +767,8 @@ def _cleanup_rsyncd(proc, port: int) -> 'None':
_record_port_proc(port, 0, 0)


def start_rsyncd(conf_path, port: int, rsync_cmd: str = None) -> 'subprocess.Popen':
def start_rsyncd(conf_path, port: int, rsync_cmd: str = None,
stdin=subprocess.DEVNULL) -> 'subprocess.Popen':
"""Spawn `rsync --daemon --no-detach --address=127.0.0.1 --port=N
--config=conf` and return the Popen handle after the port is accepting
connections.
Expand All @@ -784,7 +785,8 @@ def start_rsyncd(conf_path, port: int, rsync_cmd: str = None) -> 'subprocess.Pop
RSYNC_PEER (the peer side of a two-sided run), so ordinary daemon tests
get current-client <-> peer-daemon. The reverse-direction test passes
rsync_cmd=RSYNC to put the current build on the daemon side and drive with
the old client.
the old client. stdin may override the default /dev/null input when a test
needs to exercise daemon launch detection.

This is only ever reached from start_test_daemon() in --use-tcp mode; the
default (pipe) mode never starts a listening daemon.
Expand All @@ -797,7 +799,7 @@ def start_rsyncd(conf_path, port: int, rsync_cmd: str = None) -> 'subprocess.Pop
]
proc = subprocess.Popen(
argv,
stdin=subprocess.DEVNULL,
stdin=stdin,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
preexec_fn=_set_pdeathsig,
Expand Down
1 change: 1 addition & 0 deletions testsuite/skiplist/cygwin.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ daemon-config-symlink
daemon-module-chdir-symlink
daemon-module-private-parent
daemon-secrets-file-symlink
daemon-stdin-local-socket # Cygwin Python has no AF_UNIX socketpair
devices
dir-sgid
early-input-symlink
Expand Down
17 changes: 14 additions & 3 deletions testsuite/stdio_daemon.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Launch one rsync daemon session over a local socketpair."""
"""Launch one rsync daemon session over a loopback TCP connection."""

import socket
import subprocess
Expand Down Expand Up @@ -28,7 +28,19 @@ def _client_for_socket(sock, timeout=10):

def start_stdio_daemon(conf, timeout=10, env=None):
"""Return ``(DaemonClient, Popen)`` for one daemon connection."""
parent, child = socket.socketpair()
listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
parent = None
try:
listener.bind(('127.0.0.1', 0))
listener.listen(1)
parent = socket.create_connection(listener.getsockname(), timeout)
child, _ = listener.accept()
except OSError:
if parent is not None:
parent.close()
raise
finally:
listener.close()
try:
proc = subprocess.Popen(
rsync_argv('--daemon', '--no-detach', f'--config={conf}'),
Expand Down Expand Up @@ -60,4 +72,3 @@ def finish_stdio_daemon(client, proc, timeout=5):
proc.kill()
proc.wait(timeout=timeout)
return proc.stderr.read().decode('utf-8', 'replace') if proc.stderr else ''

Loading