Zsh Mailing List Archive
Messages sorted by: Reverse Date, Date, Thread, Author

PATCH v2: socket: unmetafy the socket name



This also led me to discover that if you type a leading nul in zsh, it
passes through correctly to linux and it gives us an abstract socket
instead, so document that that works, and print them properly. Also add
support for reporting the autobind socket name if you pass an empty path.

The more specific size in the bind and connect calls is due to abstract
sockets using the entire specified length, regardless of any embedded 0
bytes.

---

On Thu, Aug 27, 2026 at 7:00 PM Bart Schaefer <schaefer@xxxxxxxxxxxxxxxx> wrote:
>
> On Thu, Aug 27, 2026 at 7:29 AM Mikael Magnusson <mikachu@xxxxxxxxx> wrote:
> >
> > +In the invocations below, var(filename) may start with a NUL byte to use
> > +the abstract socket namespace where no actual socket file is used. It
> > +can also be passed as an empty string to ask the kernel to autoassign a
> > +name, in which case that name will be returned in tt(REPLY2) and printed
> > +when using the tt(-v) option. This feature may be limited to Linux.
>
> Should this perhaps use the $reply array instead of creating a new
> "reserved" scalar?
> reply=(sfd sockname)  # for pseudo-example
> This could be set even when a name was supplied by the caller.

I thought about this, and remembered that some distributed shell scripts use REPLY2
and it was slightly easier to just make another scalar parameter, but it's probably
neater to use reply, yeah.
> Functions/Zle/split-shell-arguments:# Hence ${reply[$REPLY][$REPLY2]} is the character under the cursor.
You can even use all three for a single thing :).

Here's the incremental diff for this, and followed by the full patch.

> diff --git i/Doc/Zsh/mod_socket.yo w/Doc/Zsh/mod_socket.yo
> index c74c91e976..91f01b10ee 100644
> --- i/Doc/Zsh/mod_socket.yo
> +++ w/Doc/Zsh/mod_socket.yo
> @@ -14,7 +14,7 @@ command line editing, file I/O, and job control mechanisms.
>  In the invocations below, var(filename) may start with a NUL byte to use
>  the abstract socket namespace where no actual socket file is used. It
>  can also be passed as an empty string to ask the kernel to autoassign a
> -name, in which case that name will be returned in tt(REPLY2) and printed
> +name, in which case that name will be returned in tt(reply) and printed
>  when using the tt(-v) option. This feature may be limited to Linux.
>  )
>  enditem()
> @@ -40,7 +40,7 @@ needed, for example:
>  
>  example(exec {REPLY}>&-)
>  
> -The tt(zsocket -s var(fd)) command may also be used to shut down a
> +The tt(zsocket -s) var(fd) command may also be used to shut down a
>  socket fd before closing it. This will send an EOF to the other side,
>  while this side can still read their subsequent response before closing
>  the connection fully, for example.
> @@ -55,13 +55,19 @@ item(tt(zsocket) tt(-l) [ tt(-v) ] [ tt(-d) var(fd) ] var(filename))(
>  tt(zsocket -l) will open a socket listening on var(filename).
>  The shell parameter tt(REPLY) will be set to the file descriptor
>  associated with that listener.  The file descriptor remains open in subshells
> -and forked external executables.
> +and forked external executables. The parameter tt(reply) will return both
> +that file descriptor and the socket name used, which will be different
> +from var(filename) when autobind sockets are used (eg, when
> +var(filename) was empty). Note also that the name returned will start
> +with a NUL byte in that case.
>  
>  If tt(-d) is specified, its argument
>  will be taken as the target file descriptor for
>  the connection.
>  
> -In order to elicit more verbose output, use tt(-v).
> +In order to elicit more verbose output, use tt(-v). (Abstract sockets
> +will be printed with an tt(abstract:) prefix instead of the literal NUL
> +byte, but this format is not accepted as var(filename) input.)
>  )
>  item(tt(zsocket) tt(-a) [ tt(-tv) ] [ tt(-d) var(targetfd) ] var(listenfd))(
>  tt(zsocket -a) will accept an incoming connection
> diff --git i/Src/Modules/socket.c w/Src/Modules/socket.c
> index 86740fafad..4d32ee85a8 100644
> --- i/Src/Modules/socket.c
> +++ w/Src/Modules/socket.c
> @@ -82,7 +82,7 @@ bin_zsocket(char *nam, char **args, Options ops, UNUSED(int func))
>      }
>  
>      if (OPT_ISSET(ops,'l')) {
> -	char *sockname;
> +	char *sockname, **reply_arr, buf[DIGBUFSIZE];
>  	ZSOCKLEN_T addrlen;
>  	int socknamelen, abstract;
>  
> @@ -141,7 +141,6 @@ bin_zsocket(char *nam, char **args, Options ops, UNUSED(int func))
>  	/* allow to be closed explicitly */
>  	fdtable[sfd] = FDT_EXTERNAL;
>  
> -	setiparam_no_convert("REPLY", (zlong)sfd);
>  	if (socknamelen == 0) {
>  	    addrlen = sizeof(soun);
>  	    if (getsockname(sfd, (struct sockaddr *)&soun, &addrlen) == -1) {
> @@ -154,9 +153,16 @@ bin_zsocket(char *nam, char **args, Options ops, UNUSED(int func))
>  		zwarnnam(nam, "socket name buffer too small, needed %d", socknamelen);
>  		return 1;
>  	    }
> -	    setsparam("REPLY2", metafy(soun.sun_path, socknamelen, META_DUP));
>  	}
>  
> +	reply_arr = (char **) zalloc(3 * sizeof(char *));
> +	convbase(buf, sfd, 10);
> +	setsparam("REPLY", ztrdup(buf));
> +	reply_arr[0] = ztrdup(buf);
> +	reply_arr[1] = metafy(soun.sun_path, socknamelen, META_DUP);
> +	reply_arr[2] = NULL;
> +	setaparam("reply", reply_arr);
> +
>  	if (verbose) {
>  	    if (abstract) {
>  		/* this seems nicer than just printing ^@ */

 Doc/Zsh/mod_socket.yo | 18 ++++++++++--
 Src/Modules/socket.c  | 66 ++++++++++++++++++++++++++++++++++---------
 2 files changed, 67 insertions(+), 17 deletions(-)

diff --git a/Doc/Zsh/mod_socket.yo b/Doc/Zsh/mod_socket.yo
index 2c699b1d12..91f01b10ee 100644
--- a/Doc/Zsh/mod_socket.yo
+++ b/Doc/Zsh/mod_socket.yo
@@ -10,6 +10,12 @@ cindex(sockets, Unix domain)
 item(tt(zsocket) [ tt(-alstv) ] [ tt(-d) var(fd) ] [ var(args) ])(
 tt(zsocket) is implemented as a builtin to allow full use of shell
 command line editing, file I/O, and job control mechanisms.
+
+In the invocations below, var(filename) may start with a NUL byte to use
+the abstract socket namespace where no actual socket file is used. It
+can also be passed as an empty string to ask the kernel to autoassign a
+name, in which case that name will be returned in tt(reply) and printed
+when using the tt(-v) option. This feature may be limited to Linux.
 )
 enditem()
 
@@ -34,7 +40,7 @@ needed, for example:
 
 example(exec {REPLY}>&-)
 
-The tt(zsocket -s var(fd)) command may also be used to shut down a
+The tt(zsocket -s) var(fd) command may also be used to shut down a
 socket fd before closing it. This will send an EOF to the other side,
 while this side can still read their subsequent response before closing
 the connection fully, for example.
@@ -49,13 +55,19 @@ item(tt(zsocket) tt(-l) [ tt(-v) ] [ tt(-d) var(fd) ] var(filename))(
 tt(zsocket -l) will open a socket listening on var(filename).
 The shell parameter tt(REPLY) will be set to the file descriptor
 associated with that listener.  The file descriptor remains open in subshells
-and forked external executables.
+and forked external executables. The parameter tt(reply) will return both
+that file descriptor and the socket name used, which will be different
+from var(filename) when autobind sockets are used (eg, when
+var(filename) was empty). Note also that the name returned will start
+with a NUL byte in that case.
 
 If tt(-d) is specified, its argument
 will be taken as the target file descriptor for
 the connection.
 
-In order to elicit more verbose output, use tt(-v).
+In order to elicit more verbose output, use tt(-v). (Abstract sockets
+will be printed with an tt(abstract:) prefix instead of the literal NUL
+byte, but this format is not accepted as var(filename) input.)
 )
 item(tt(zsocket) tt(-a) [ tt(-tv) ] [ tt(-d) var(targetfd) ] var(listenfd))(
 tt(zsocket -a) will accept an incoming connection
diff --git a/Src/Modules/socket.c b/Src/Modules/socket.c
index 95235453e4..4d32ee85a8 100644
--- a/Src/Modules/socket.c
+++ b/Src/Modules/socket.c
@@ -82,16 +82,19 @@ bin_zsocket(char *nam, char **args, Options ops, UNUSED(int func))
     }
 
     if (OPT_ISSET(ops,'l')) {
-	char *localfn;
+	char *sockname, **reply_arr, buf[DIGBUFSIZE];
+	ZSOCKLEN_T addrlen;
+	int socknamelen, abstract;
 
 	if (!args[0]) {
 	    zwarnnam(nam, "-l requires an argument");
 	    return 1;
 	}
 
-	localfn = args[0];
-	if (strlen(localfn) >= sizeof(soun.sun_path)) {
-	    zwarnnam(nam, "socket path too long: %d > %d", strlen(localfn), sizeof(soun.sun_path) -1 );
+	sockname = unmetafy(args[0], &socknamelen);
+	abstract = (sockname[0] == '\0');
+	if (socknamelen > sizeof(soun.sun_path) - !abstract) {
+	    zwarnnam(nam, "socket path too long: %d > %d", socknamelen, sizeof(soun.sun_path) - !abstract);
 	    return 1;
 	}
 
@@ -103,11 +106,12 @@ bin_zsocket(char *nam, char **args, Options ops, UNUSED(int func))
 	}
 
 	soun.sun_family = AF_UNIX;
-	strncpy(soun.sun_path, localfn, sizeof(soun.sun_path)-1);
+	memcpy(soun.sun_path, sockname, socknamelen);
+	addrlen = offsetof(struct sockaddr_un, sun_path) + socknamelen;
 
-	if (bind(sfd, (struct sockaddr *)&soun, sizeof(struct sockaddr_un)))
+	if (bind(sfd, (struct sockaddr *)&soun, addrlen))
 	{
-	    zwarnnam(nam, "could not bind to %s: %e", soun.sun_path, errno);
+	    zwarnnam(nam, "could not bind to %s: %e", args[0], errno);
 	    close(sfd);
 	    return 1;
 	}
@@ -137,10 +141,37 @@ bin_zsocket(char *nam, char **args, Options ops, UNUSED(int func))
 	/* allow to be closed explicitly */
 	fdtable[sfd] = FDT_EXTERNAL;
 
-	setiparam_no_convert("REPLY", (zlong)sfd);
+	if (socknamelen == 0) {
+	    addrlen = sizeof(soun);
+	    if (getsockname(sfd, (struct sockaddr *)&soun, &addrlen) == -1) {
+		zwarnnam(nam, "could not retrieve autobind socket name: %e", errno);
+		return 1;
+	    }
+	    socknamelen = addrlen - offsetof(struct sockaddr_un, sun_path);
+	    if (socknamelen > sizeof(soun.sun_path)) {
+		/* this should not be possible */
+		zwarnnam(nam, "socket name buffer too small, needed %d", socknamelen);
+		return 1;
+	    }
+	}
 
-	if (verbose)
-	    printf("%s listener is on fd %d\n", soun.sun_path, sfd);
+	reply_arr = (char **) zalloc(3 * sizeof(char *));
+	convbase(buf, sfd, 10);
+	setsparam("REPLY", ztrdup(buf));
+	reply_arr[0] = ztrdup(buf);
+	reply_arr[1] = metafy(soun.sun_path, socknamelen, META_DUP);
+	reply_arr[2] = NULL;
+	setaparam("reply", reply_arr);
+
+	if (verbose) {
+	    if (abstract) {
+		/* this seems nicer than just printing ^@ */
+		fputs("abstract:", stdout);
+	    }
+	    sockname = metafy(soun.sun_path + abstract, socknamelen - abstract, META_STATIC);
+	    nicezputs(sockname, stdout);
+	    printf(" listener is on fd %d\n", sfd);
+	}
 
 	return 0;
 
@@ -242,13 +273,19 @@ bin_zsocket(char *nam, char **args, Options ops, UNUSED(int func))
     }
     else
     {
+	char *sockname;
+	ZSOCKLEN_T addrlen;
+	int socknamelen, abstract;
+
 	if (!args[0]) {
 	    zwarnnam(nam, "zsocket requires an argument");
 	    return 1;
 	}
 
-	if (strlen(args[0]) >= sizeof(soun.sun_path)) {
-	    zwarnnam(nam, "socket path too long: %d > %d", strlen(args[0]), sizeof(soun.sun_path) -1 );
+	sockname = unmetafy(args[0], &socknamelen);
+	abstract = (sockname[0] == '\0');
+	if (socknamelen > sizeof(soun.sun_path) - !abstract) {
+	    zwarnnam(nam, "socket path too long: %d > %d", socknamelen, sizeof(soun.sun_path) - !abstract);
 	    return 1;
 	}
 
@@ -260,9 +297,10 @@ bin_zsocket(char *nam, char **args, Options ops, UNUSED(int func))
 	}
 
 	soun.sun_family = AF_UNIX;
-	strncpy(soun.sun_path, args[0], sizeof(soun.sun_path)-1);
+	memcpy(soun.sun_path, sockname, socknamelen);
+	addrlen = offsetof(struct sockaddr_un, sun_path) + socknamelen;
 	
-	if (connect(sfd, (struct sockaddr *)&soun, sizeof(struct sockaddr_un))) {
+	if (connect(sfd, (struct sockaddr *)&soun, addrlen)) {
 	    zwarnnam(nam, "connection failed: %e", errno);
 	    close(sfd);
 	    return 1;
-- 
2.38.1





Messages sorted by: Reverse Date, Date, Thread, Author