git: 79c342aaf86f - main - tftpd: Address flaky tests

From: Mark Johnston <markj_at_FreeBSD.org>
Date: Sun, 03 Nov 2024 16:48:11 UTC
The branch main has been updated by markj:

URL: https://cgit.FreeBSD.org/src/commit/?id=79c342aaf86feb4efbd15383f54e4fe7bdc9da7b

commit 79c342aaf86feb4efbd15383f54e4fe7bdc9da7b
Author:     Mark Johnston <markj@FreeBSD.org>
AuthorDate: 2024-11-03 14:43:38 +0000
Commit:     Mark Johnston <markj@FreeBSD.org>
CommitDate: 2024-11-03 16:47:38 +0000

    tftpd: Address flaky tests
    
    The tftpd tests all follow the same pattern:
    1. open a UDP socket,
    2. fork a child to exec tftpd, which subsequently handles requests on
       the socket,
    3. use a client socket to send some message to the tftpd daemon.
    
    However, tftpd's first action is to mark its socket as non-blocking and
    then read a request from it.  If no data is present in the socket, tftpd
    exits immediately with an error.  So, there is a race; we often see
    tftpd test timeouts when running tests in parallel.  These timeouts also
    arise periodically in CI runs.
    
    One solution is to restructure each test to create the server socket,
    then write the request to the client socket, then fork tftpd.  This
    closes the race.  However, this involves a lot of churn.
    
    This patch fixes the problem a different way, by adding a new -b flag to
    tftpd which makes it block to read the initial request.  Each test is
    modified to use -b, closing the race.
    
    Reviewed by:    imp, asomers
    MFC after:      2 weeks
    Differential Revision:  https://reviews.freebsd.org/D47404
---
 libexec/tftpd/tests/functional.c |  2 ++
 libexec/tftpd/tftpd.8            | 14 ++++++++++++--
 libexec/tftpd/tftpd.c            | 19 +++++++++++--------
 3 files changed, 25 insertions(+), 10 deletions(-)

diff --git a/libexec/tftpd/tests/functional.c b/libexec/tftpd/tests/functional.c
index 54ace29d718c..791aa9190a2f 100644
--- a/libexec/tftpd/tests/functional.c
+++ b/libexec/tftpd/tests/functional.c
@@ -323,6 +323,7 @@ setup(struct sockaddr_storage *to, uint16_t idx)
 {
 	int client_s, server_s, pid, argv_idx;
 	char execname[] = "/usr/libexec/tftpd";
+	char b_flag_str[] = "-b";
 	char s_flag_str[] = "-s";
 	char w_flag_str[] = "-w";
 	char pwd[MAXPATHLEN];
@@ -382,6 +383,7 @@ setup(struct sockaddr_storage *to, uint16_t idx)
 		bzero(argv, sizeof(argv));
 		argv[0] = execname;
 		argv_idx = 1;
+		argv[argv_idx++] = b_flag_str;
 		if (w_flag)
 			argv[argv_idx++] = w_flag_str;
 		if (s_flag)
diff --git a/libexec/tftpd/tftpd.8 b/libexec/tftpd/tftpd.8
index 558f7bdf809a..0118198da53d 100644
--- a/libexec/tftpd/tftpd.8
+++ b/libexec/tftpd/tftpd.8
@@ -25,7 +25,7 @@
 .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 .\" SUCH DAMAGE.
 .\"
-.Dd May 8, 2024
+.Dd November 3, 2024
 .Dt TFTPD 8
 .Os
 .Sh NAME
@@ -33,7 +33,7 @@
 .Nd Internet Trivial File Transfer Protocol server
 .Sh SYNOPSIS
 .Nm tftpd
-.Op Fl CcdlnoSw
+.Op Fl bCcdlnoSw
 .Op Fl F Ar strftime-format
 .Op Fl s Ar directory
 .Op Fl U Ar umask
@@ -119,6 +119,16 @@ option is specified.
 .Pp
 The options are:
 .Bl -tag -width Ds
+.It Fl b
+By default,
+.Nm
+expects an initial message to be available on its input socket.
+If no data is available, the server exits immediately.
+If
+.Fl b
+is specified,
+.Nm
+will block waiting for the initial message.
 .It Fl c
 Changes the default root directory of a connecting host via
 .Xr chroot 2
diff --git a/libexec/tftpd/tftpd.c b/libexec/tftpd/tftpd.c
index a51fb4742985..f8f9bd549a2e 100644
--- a/libexec/tftpd/tftpd.c
+++ b/libexec/tftpd/tftpd.c
@@ -121,15 +121,18 @@ main(int argc, char *argv[])
 	struct passwd	*nobody;
 	const char	*chuser = "nobody";
 	char		recvbuffer[MAXPKTSIZE];
-	int		allow_ro = 1, allow_wo = 1, on = 1;
+	int		allow_ro = 1, allow_wo = 1, block = 0, on = 1;
 	pid_t		pid;
 
 	tzset();			/* syslog in localtime */
 	acting_as_client = 0;
 
 	tftp_openlog("tftpd", LOG_PID | LOG_NDELAY, LOG_FTP);
-	while ((ch = getopt(argc, argv, "cCd::F:lnoOp:s:Su:U:wW")) != -1) {
+	while ((ch = getopt(argc, argv, "bcCd::F:lnoOp:s:Su:U:wW")) != -1) {
 		switch (ch) {
+		case 'b':
+			block = 1;
+			break;
 		case 'c':
 			ipchroot = 1;
 			break;
@@ -213,14 +216,9 @@ main(int argc, char *argv[])
 
 	umask(mask);
 
-	if (ioctl(0, FIONBIO, &on) < 0) {
-		tftp_log(LOG_ERR, "ioctl(FIONBIO): %s", strerror(errno));
-		exit(1);
-	}
-
 	/* Find out who we are talking to and what we are going to do */
 	peerlen = sizeof(peer_sock);
-	n = recvfrom(0, recvbuffer, MAXPKTSIZE, 0,
+	n = recvfrom(0, recvbuffer, MAXPKTSIZE, block ? 0 : MSG_DONTWAIT,
 	    (struct sockaddr *)&peer_sock, &peerlen);
 	if (n < 0) {
 		tftp_log(LOG_ERR, "recvfrom: %s", strerror(errno));
@@ -234,6 +232,11 @@ main(int argc, char *argv[])
 		exit(1);
 	}
 
+	if (ioctl(0, FIONBIO, &on) < 0) {
+		tftp_log(LOG_ERR, "ioctl(FIONBIO): %s", strerror(errno));
+		exit(1);
+	}
+
 	/*
 	 * Now that we have read the message out of the UDP
 	 * socket, we fork and exit.  Thus, inetd will go back