[PATCH 4/4] alfred: handle short reads when waiting for daemon answer

Sven Eckelmann <[email protected]> Fri, 31 Jul 2026 09:40:16 +0200
Newsgroups org.open-mesh.lists.batman
Message-ID <[email protected]>
The answer of the alfred daemon is received over a SOCK_STREAM unix socket
which doesn't preserve message boundaries. read() can therefore return less
than the requested number of bytes. Such a short read was treated like an
end of stream and the processing was stopped.

Retry the read until the requested amount of data, end of stream or a real
error was received (ignoring temporary interruptions EINTR).

Fixes: 23359bbf72aa ("alfred: initial commit")
Signed-off-by: Sven Eckelmann <[email protected]>
---
 alfred.h    |  1 +
 client.c    | 66 +++++++++++++++++++++++++++++++++++--------------------------
 unix_sock.c | 26 ++++++++++++------------
 util.c      | 25 +++++++++++++++++++++++
 4 files changed, 77 insertions(+), 41 deletions(-)

diff --git a/alfred.h b/alfred.h
index df47d54..1d915a8 100644
--- a/alfred.h
+++ b/alfred.h
@@ -224,6 +224,7 @@ void netsock_reopen(struct globals *globals);
 int netsock_own_address(const struct globals *globals,
 			const alfred_addr *address);
 /* util.c */
+ssize_t read_full(int fd, void *buf, size_t count);
 int time_diff(struct timespec *tv1, struct timespec *tv2,
 	      struct timespec *tvdiff);
 void time_random_seed(void);
diff --git a/client.c b/client.c
index 1967f69..1e1f1ef 100644
--- a/client.c
+++ b/client.c
@@ -53,7 +53,7 @@ int alfred_client_request_data(struct globals *globals)
 
 	push = (struct alfred_push_data_v0 *)buf;
 	tlv = (struct alfred_tlv *)buf;
-	while ((ret = read(globals->unix_sock, buf, sizeof(*tlv))) > 0) {
+	while ((ret = read_full(globals->unix_sock, buf, sizeof(*tlv))) > 0) {
 		if (ret < (int)sizeof(*tlv))
 			break;
 
@@ -64,16 +64,16 @@ int alfred_client_request_data(struct globals *globals)
 			break;
 
 		/* read the rest of the header */
-		ret = read(globals->unix_sock, buf + sizeof(*tlv),
-			   sizeof(*push) - sizeof(*tlv));
+		ret = read_full(globals->unix_sock, buf + sizeof(*tlv),
+				sizeof(*push) - sizeof(*tlv));
 
 		/* too short */
 		if (ret < (int)(sizeof(*push) - (int)sizeof(*tlv)))
 			break;
 
 		/* read the rest of the header */
-		ret = read(globals->unix_sock, buf + sizeof(*push),
-			   sizeof(*data));
+		ret = read_full(globals->unix_sock, buf + sizeof(*push),
+				sizeof(*data));
 
 		if (ret < (ssize_t)sizeof(*data))
 			break;
@@ -86,8 +86,8 @@ int alfred_client_request_data(struct globals *globals)
 			break;
 
 		/* read the data */
-		ret = read(globals->unix_sock,
-			   buf + sizeof(*push) + sizeof(*data), data_len);
+		ret = read_full(globals->unix_sock,
+				buf + sizeof(*push) + sizeof(*data), data_len);
 
 		/* again too short */
 		if (ret < data_len)
@@ -124,8 +124,8 @@ int alfred_client_request_data(struct globals *globals)
 
 recv_err:
 	/* read the rest of the status message */
-	ret = read(globals->unix_sock, buf + sizeof(*tlv),
-		   sizeof(*status) - sizeof(*tlv));
+	ret = read_full(globals->unix_sock, buf + sizeof(*tlv),
+			sizeof(*status) - sizeof(*tlv));
 
 	/* too short */
 	if (ret < (int)(sizeof(*status) - sizeof(*tlv)))
@@ -377,42 +377,49 @@ int alfred_client_server_status(struct globals *globals)
 		fprintf(stderr, "%s: only wrote %d of %d bytes: %s\n",
 			__func__, ret, len, strerror(errno));
 
-	len = read(globals->unix_sock, buf, sizeof(buf));
-	if (len <= 0) {
+	ret = -1;
+	status_rep = (struct alfred_server_status_rep_v0 *)buf;
+	headsize = sizeof(status_rep->header);
+
+	/* drop too small packets */
+	len = read_full(globals->unix_sock, buf, headsize);
+	if (len < 0) {
 		perror("read from unix socket failed");
 		goto err;
 	}
 
-	ret = -1;
-	status_rep = (struct alfred_server_status_rep_v0 *)buf;
-
-	/* drop too small packets */
-	headsize = sizeof(status_rep->header);
 	if (len < headsize) {
-		perror("unexpected header size received from unix socket");
-		goto err;
-	}
-
-	if ((len - headsize) < ((int)ntohs(status_rep->header.length))) {
-		perror("unexpected packet size received from unix socket");
+		fprintf(stderr, "unexpected header size received from unix socket\n");
 		goto err;
 	}
 
 	if (status_rep->header.type != ALFRED_SERVER_STATUS) {
-		perror("alfred server_status type mismatch");
+		fprintf(stderr, "alfred server_status type mismatch\n");
 		goto err;
 	}
 
 	if (status_rep->header.version != ALFRED_VERSION) {
-		perror("alfred version mismatch");
+		fprintf(stderr, "alfred version mismatch\n");
 		goto err;
 	}
 
-	headsize = ntohs(status_rep->header.length);
+	tlvsize = ntohs(status_rep->header.length);
 
-	if (headsize < (int)(sizeof(*status_rep) - sizeof(status_rep->header)))
+	if (tlvsize < (int)(sizeof(*status_rep) - sizeof(status_rep->header)))
 		goto err;
 
+	if (tlvsize > (int)(sizeof(buf) - headsize)) {
+		fprintf(stderr, "unexpected packet size received from unix socket\n");
+		goto err;
+	}
+
+	/* read the announced rest of the reply */
+	if (read_full(globals->unix_sock, buf + headsize, tlvsize) < tlvsize) {
+		fprintf(stderr, "unexpected packet size received from unix socket\n");
+		goto err;
+	}
+
+	len = headsize + tlvsize;
 	consumed = sizeof(*status_rep);
 
 	while (len - consumed > 0) {
@@ -473,9 +480,11 @@ int alfred_client_server_status(struct globals *globals)
 		consumed += tlvsize;
 	}
 
+	ret = 0;
+
 err:
 	unix_sock_close(globals);
-	return 0;
+	return ret;
 }
 
 int alfred_client_event_monitor(struct globals *globals)
@@ -502,7 +511,8 @@ int alfred_client_event_monitor(struct globals *globals)
 	}
 
 	while (true) {
-		len = read(globals->unix_sock, &event_notify, sizeof(event_notify));
+		len = read_full(globals->unix_sock, &event_notify,
+				sizeof(event_notify));
 		if (len == 0) {
 			fprintf(stdout, "Server closed the connection\n");
 			goto err;
diff --git a/unix_sock.c b/unix_sock.c
index d4261ba..e5ccc35 100644
--- a/unix_sock.c
+++ b/unix_sock.c
@@ -542,26 +542,26 @@ static void unix_sock_read(struct globals *globals,
 		return;
 	}
 
-	/* we assume that we can instantly read here. */
-	length = read(client_sock, buf, sizeof(buf));
-	if (length <= 0) {
+	headsize = sizeof(*packet);
+	packet = (struct alfred_tlv *)buf;
+
+	/* drop too small packets */
+	if (read_full(client_sock, buf, headsize) < headsize) {
 		perror("read from unix socket failed");
 		goto err;
 	}
 
-	/* drop too small packets */
-	headsize = sizeof(*packet);
-	if (length < headsize)
-		goto err;
-
-	packet = (struct alfred_tlv *)buf;
-
-	if ((length - headsize) < ((int)ntohs(packet->length)))
-		goto err;
-
 	if (packet->version != ALFRED_VERSION)
 		goto err;
 
+	length = ntohs(packet->length);
+	if (length > (int)(sizeof(buf) - headsize))
+		goto err;
+
+	/* read the announced rest of the request */
+	if (read_full(client_sock, buf + headsize, length) < length)
+		goto err;
+
 	switch (packet->type) {
 	case ALFRED_PUSH_DATA:
 		unix_sock_add_data(globals,
diff --git a/util.c b/util.c
index b00b953..1a552ea 100644
--- a/util.c
+++ b/util.c
@@ -6,6 +6,7 @@
  * License-Filename: LICENSES/preferred/GPL-2.0
  */
 
+#include <errno.h>
 #include <netinet/ether.h>
 #include <stdbool.h>
 #include <stddef.h>
@@ -19,6 +20,30 @@
 #include <unistd.h>
 #include "alfred.h"
 
+ssize_t read_full(int fd, void *buf, size_t count)
+{
+	size_t read_len = 0;
+	uint8_t *pos = buf;
+	ssize_t ret;
+
+	while (read_len < count) {
+		ret = read(fd, pos + read_len, count - read_len);
+		if (ret < 0) {
+			if (errno == EINTR)
+				continue;
+
+			return ret;
+		}
+
+		if (ret == 0)
+			break;
+
+		read_len += ret;
+	}
+
+	return read_len;
+}
+
 int time_diff(struct timespec *tv1, struct timespec *tv2,
 	      struct timespec *tvdiff) {
 	tvdiff->tv_sec = tv1->tv_sec - tv2->tv_sec;

-- 
2.47.3