Merge branch 'nate-i50189-unix' into 'master'

See merge request SchedMD/dev/slurm!1691
diff --git a/doc/man/man8/slurmrestd.8 b/doc/man/man8/slurmrestd.8
index d97b0e9..7fad583 100644
--- a/doc/man/man8/slurmrestd.8
+++ b/doc/man/man8/slurmrestd.8
@@ -1,4 +1,4 @@
-.TH slurmrestd "8" "Slurm REST Daemon" "June 2025" "Slurm REST Daemon"
+.TH slurmrestd "8" "Slurm REST Daemon" "August 2025" "Slurm REST Daemon"
 
 .SH "NAME"
 slurmrestd \- Interface to Slurm via REST API.
@@ -32,7 +32,7 @@
 .IP
 
 .TP
-\fBunix:/path/to/socket\fR
+\fBunix:///path/to/socket\fR
 Listen on local UNIX socket. Must have permission to create socket in
 filesystem.
 .IP
diff --git a/src/common/http.c b/src/common/http.c
index dee446e..73e03c8 100644
--- a/src/common/http.c
+++ b/src/common/http.c
@@ -146,7 +146,8 @@
 	T(URL_SCHEME_INVALID, "INVALID"),
 	T(URL_SCHEME_HTTP, "http"),
 	T(URL_SCHEME_HTTPS, "https"),
-	T(URL_SCHEME_INVALID_MAX, "INVALID_MAX")
+	T(URL_SCHEME_UNIX, "unix"),
+	T(URL_SCHEME_INVALID_MAX, "INVALID_MAX"),
 };
 #undef T
 
diff --git a/src/common/http.h b/src/common/http.h
index 2c134f9..8705a2a 100644
--- a/src/common/http.h
+++ b/src/common/http.h
@@ -147,6 +147,7 @@
 	URL_SCHEME_INVALID = 0,
 	URL_SCHEME_HTTP,
 	URL_SCHEME_HTTPS,
+	URL_SCHEME_UNIX, /* UNIX Socket - Not IANA registered */
 	URL_SCHEME_INVALID_MAX /* place holder */
 } url_scheme_t;
 
diff --git a/src/conmgr/con.c b/src/conmgr/con.c
index 84a126f..fdaaa44 100644
--- a/src/conmgr/con.c
+++ b/src/conmgr/con.c
@@ -995,80 +995,58 @@
 	return false;
 }
 
-extern int conmgr_create_listen_socket(conmgr_con_type_t type,
-				       conmgr_con_flags_t flags,
-				       const char *listen_on,
-				       const conmgr_events_t *events, void *arg)
+static int _add_unix_listener(conmgr_con_type_t type, conmgr_con_flags_t flags,
+			      const char *listen_on, const char *unixsock,
+			      const conmgr_events_t *events, void *arg)
 {
-	static const char UNIX_PREFIX[] = "unix:";
-	const char *unixsock = xstrstr(listen_on, UNIX_PREFIX);
+	slurm_addr_t addr = { 0 };
+	int fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
+	int rc = EINVAL;
+
+	if (fd < 0)
+		fatal("%s: socket() failed: %m", __func__);
+
+	addr = sockaddr_from_unix_path(unixsock);
+
+	if (addr.ss_family != AF_UNIX)
+		fatal("%s: [%s] Invalid Unix socket path: %s",
+		      __func__, listen_on, unixsock);
+
+	log_flag(CONMGR, "%s: [%pA] attempting to bind() and listen() UNIX socket",
+		 __func__, &addr);
+
+	if (unlink(unixsock) && (errno != ENOENT))
+		error("Error unlink(%s): %m", unixsock);
+
+	/* bind() will EINVAL if socklen=sizeof(addr) */
+	if ((rc = bind(fd, (const struct sockaddr *) &addr,
+		       sizeof(struct sockaddr_un))))
+		fatal("%s: [%s] Unable to bind UNIX socket: %m",
+		      __func__, listen_on);
+
+	fd_set_oob(fd, 0);
+
+	rc = listen(fd, SLURM_DEFAULT_LISTEN_BACKLOG);
+	if (rc < 0)
+		fatal("%s: [%s] unable to listen(): %m",
+		      __func__, listen_on);
+
+	return add_connection(type, NULL, fd, -1, events, flags, &addr,
+			      sizeof(addr), true, unixsock, NULL, arg);
+}
+
+static int _add_socket_listener(conmgr_con_type_t type,
+				conmgr_con_flags_t flags, const char *listen_on,
+				url_t *url, const conmgr_events_t *events,
+				void *arg)
+{
 	int rc = SLURM_SUCCESS;
 	struct addrinfo *addrlist = NULL;
 
-	/* check for name local sockets */
-	if (unixsock) {
-		slurm_addr_t addr = {0};
-		int fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
-
-		if (fd < 0)
-			fatal("%s: socket() failed: %m", __func__);
-
-		unixsock += sizeof(UNIX_PREFIX) - 1;
-		if (unixsock[0] == '\0')
-			fatal("%s: [%s] Invalid UNIX socket",
-			      __func__, listen_on);
-
-		addr = sockaddr_from_unix_path(unixsock);
-
-		if (addr.ss_family != AF_UNIX)
-			fatal("%s: [%s] Invalid Unix socket path: %s",
-			      __func__, listen_on, unixsock);
-
-		log_flag(CONMGR, "%s: [%pA] attempting to bind() and listen() UNIX socket",
-			 __func__, &addr);
-
-		if (unlink(unixsock) && (errno != ENOENT))
-			error("Error unlink(%s): %m", unixsock);
-
-		/* bind() will EINVAL if socklen=sizeof(addr) */
-		if ((rc = bind(fd, (const struct sockaddr *) &addr,
-			       sizeof(struct sockaddr_un))))
-			fatal("%s: [%s] Unable to bind UNIX socket: %m",
-			      __func__, listen_on);
-
-		fd_set_oob(fd, 0);
-
-		rc = listen(fd, SLURM_DEFAULT_LISTEN_BACKLOG);
-		if (rc < 0)
-			fatal("%s: [%s] unable to listen(): %m",
-			      __func__, listen_on);
-
-		return add_connection(type, NULL, fd, -1, events, flags, &addr,
-				      sizeof(addr), true, unixsock, NULL, arg);
-	} else {
-		url_t url = URL_INITIALIZER;
-		buf_t buffer = {
-			.magic = BUF_MAGIC,
-		};
-
-		buffer.head = (void *) listen_on;
-		buffer.processed = buffer.size = strlen(listen_on);
-
-		/* split up host and port */
-		if ((rc = url_parser_g_parse("listener", &buffer, &url)))
-			fatal("%s: Unable to parse %s: %s",
-			      __func__, listen_on, slurm_strerror(rc));
-
-		if (url.scheme == URL_SCHEME_HTTPS)
-			flags |= CON_FLAG_TLS_SERVER;
-
-		/* resolve out the host and port if provided */
-		if (!(addrlist = xgetaddrinfo(url.host, url.port)))
-			fatal("%s: Unable to listen on %s:%s(%s): %m",
-			      __func__, url.host, url.port, listen_on);
-
-		url_free_members(&url);
-	}
+	/* resolve out the host and port if provided */
+	if (!(addrlist = xgetaddrinfo(url->host, url->port)))
+		fatal("%s: Unable to listen on %s:%s(%s): %m",
+		      __func__, url->host, url->port, listen_on);
 
 	/*
 	 * Create a socket for every address returned
@@ -1121,7 +1099,46 @@
 	}
 
 	freeaddrinfo(addrlist);
+	return rc;
+}
 
+extern int conmgr_create_listen_socket(conmgr_con_type_t type,
+				       conmgr_con_flags_t flags,
+				       const char *listen_on,
+				       const conmgr_events_t *events, void *arg)
+{
+	int rc = SLURM_SUCCESS;
+	url_t url = URL_INITIALIZER;
+	buf_t buffer = {
+		.magic = BUF_MAGIC,
+		.head = (void *) listen_on,
+		.processed = strlen(listen_on),
+		.size = strlen(listen_on),
+		.shadow = true,
+	};
+
+	if ((rc = url_parser_g_parse(__func__, &buffer, &url)))
+		fatal("%s: Unable to parse %s: %s",
+		      __func__, listen_on, slurm_strerror(rc));
+
+	switch (url.scheme) {
+	case URL_SCHEME_UNIX:
+		rc = _add_unix_listener(type, flags, listen_on, url.path,
+					events, arg);
+		break;
+	case URL_SCHEME_HTTPS:
+		flags |= CON_FLAG_TLS_SERVER;
+		/* fall through */
+	case URL_SCHEME_HTTP:
+	case URL_SCHEME_INVALID:
+		rc = _add_socket_listener(type, flags, listen_on, &url, events,
+					  arg);
+		break;
+	case URL_SCHEME_INVALID_MAX:
+		fatal_abort("should never happen");
+	}
+
+	url_free_members(&url);
 	return rc;
 }
 
diff --git a/src/plugins/http_parser/libhttp_parser/libhttp_parser.c b/src/plugins/http_parser/libhttp_parser/libhttp_parser.c
index f49bafe..36cb6e2 100644
--- a/src/plugins/http_parser/libhttp_parser/libhttp_parser.c
+++ b/src/plugins/http_parser/libhttp_parser/libhttp_parser.c
@@ -54,6 +54,9 @@
 const char plugin_type[] = HTTP_PARSER_PREFIX LIBHTTP_PARSER_PLUGIN;
 const uint32_t plugin_version = SLURM_VERSION_NUMBER;
 
+#define UNIX_PREFIX "unix:"
+#define UNIX_PREFIX_BYTES strlen("unix:")
+
 #define LOG_PARSE(state, fmt, ...) \
 	_log_parse(state, NULL, 0, __func__, fmt, ##__VA_ARGS__)
 #define LOG_PARSE_AT(state, at, bytes, fmt, ...) \
@@ -455,6 +458,33 @@
 /*
  * Parse URL where only the port is given.
  * Examples:
+ *	unix:/path/to/socket
+ *
+ * RET
+ *	SLURM_SUCCESS: parsed port successfully
+ *	ESLURM_URL_UNSUPPORTED_FORMAT: not a port only URL
+ *	*: error
+ */
+static int _parse_unix_url(const char *name, const buf_t *buffer, url_t *dst)
+{
+	const char *data = get_buf_data(buffer);
+	const size_t bytes = get_buf_offset(buffer);
+
+	if (xstrncmp(UNIX_PREFIX, data, UNIX_PREFIX_BYTES))
+		return ESLURM_URL_UNSUPPORTED_FORMAT;
+
+	if (data[UNIX_PREFIX_BYTES] == '\0')
+		return ESLURM_URL_EMPTY;
+
+	dst->scheme = URL_SCHEME_UNIX;
+	dst->path = xstrndup((data + UNIX_PREFIX_BYTES),
+			     (bytes - UNIX_PREFIX_BYTES));
+	return SLURM_SUCCESS;
+}
+
+/*
+ * Parse URL where only the port is given.
+ * Examples:
  *	:8080
  *	:ssh
  *
@@ -576,9 +606,14 @@
 	 * Try using libhttp_parser's builtin URL parser and then try additional
 	 * parsers for formats it doesn't support
 	 */
-	if ((rc = _library_url_parse(name, buffer, dst))) {
-		if (rc == ESLURM_URL_UNSUPPORTED_FORMAT)
-			rc = _parse_only_port(name, buffer, dst);
+	rc = _library_url_parse(name, buffer, dst);
+	if (rc == ESLURM_URL_UNSUPPORTED_FORMAT) {
+		url_free_members(dst);
+		rc = _parse_only_port(name, buffer, dst);
+	}
+	if (rc == ESLURM_URL_UNSUPPORTED_FORMAT) {
+		url_free_members(dst);
+		rc = _parse_unix_url(name, buffer, dst);
 	}
 
 	if (rc) {