summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ChangeLog3
-rw-r--r--Doc/Zsh/mod_tcp.yo31
-rw-r--r--Src/Modules/tcp.c48
-rw-r--r--Src/Modules/tcp.h5
4 files changed, 79 insertions, 8 deletions
diff --git a/ChangeLog b/ChangeLog
index 10001b2ab..f74417fc6 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -26,6 +26,9 @@
* 54494: Doc/Zsh/mod_socket.yo, Src/Modules/socket.c: Add zsocket
-s to shutdown() a socket fd
+ * 54495: Doc/Zsh/mod_tcp.yo, Src/Modules/tcp.c, Src/Modules/tcp.h:
+ Add ztcp -s to shutdown() a tcp fd
+
2026-05-10 Oliver Kiddle <opk@zsh.org>
* unposted: Completion/Linux/Command/_selinux: complete files
diff --git a/Doc/Zsh/mod_tcp.yo b/Doc/Zsh/mod_tcp.yo
index d19e8cc92..5dd1d5da1 100644
--- a/Doc/Zsh/mod_tcp.yo
+++ b/Doc/Zsh/mod_tcp.yo
@@ -7,7 +7,7 @@ startitem()
findex(ztcp)
cindex(TCP)
cindex(sockets, TCP)
-item(tt(ztcp) [ tt(-acflLtv) ] [ tt(-d) var(fd) ] [ var(args) ])(
+item(tt(ztcp) [ tt(-acflLstv) ] [ tt(-d) var(fd) ] [ var(args) ])(
tt(ztcp) is implemented as a builtin to allow full use of shell
command line editing, file I/O, and job control mechanisms.
@@ -24,7 +24,7 @@ startitem()
item(File descriptor)(
The file descriptor in use for the connection. For normal inbound (tt(I))
and outbound (tt(O)) connections this may be read and written by the usual
-shell mechanisms. However, it should only be close with `tt(ztcp -c)'.
+shell mechanisms. However, it should only be closed with `tt(ztcp -c)'.
)
item(Connection type)(
A letter indicating how the session was created:
@@ -42,6 +42,9 @@ An inbound connection accepted with `tt(ztcp -a)'.
item(tt(O))(
An outbound connection created with `tt(ztcp) var(host) var(...)'.
)
+item(tt(S))(
+A connection that has been shut down with `tt(ztcp -s)'.
+)
enditem()
)
@@ -122,6 +125,24 @@ subsect(Closing Connections)
cindex(sockets, closing TCP)
startitem()
+item(tt(ztcp) tt(-s) var(fd))(
+tt(ztcp -s) will shut down the socket associated
+with var(fd). This means a graceful termination
+of the connection is initiated, flushing unsent
+data (due to Nagle's algorithm, for example). To
+complete it, you should also drain any remaining
+data on the fd by reading it. Afterwards you should
+use tt(ztcp -c) to close the fd. If you close it
+without first using tt(ztcp -s) and draining the
+data, the kernel may reset the connection, causing
+data you sent to not be received by the other end.
+
+Note that this is only useful for inbound and
+outbound connections, not for listening sockets.
+)
+enditem()
+
+startitem()
xitem(tt(ztcp) tt(-cf) [ tt(-v) ] [ var(fd) ])
item(tt(ztcp) tt(-c) [ tt(-v) ] [ var(fd) ])(
tt(ztcp -c) will close the socket associated
@@ -129,6 +150,8 @@ with var(fd). The socket will be removed from the
session table. If var(fd) is not specified,
tt(ztcp) will close everything in the session table.
+See also the previous tt(ztcp -s) entry.
+
Normally, sockets registered by zftp (see
sectref(The zsh/zftp Module)(zshmodules))
cannot be closed this way. In order
@@ -166,6 +189,8 @@ prints `tt(This is a message)'.
To tidy up, on tt(host1):
example(ztcp -c $listenfd
+ztcp -s $fd
ztcp -c $fd)
and on tt(host2)
-example(ztcp -c $fd)
+example(ztcp -s $fd
+ztcp -c $fd)
diff --git a/Src/Modules/tcp.c b/Src/Modules/tcp.c
index 0bbce5d49..4366f3c02 100644
--- a/Src/Modules/tcp.c
+++ b/Src/Modules/tcp.c
@@ -279,6 +279,29 @@ zts_byfd(int fd)
return NULL;
}
+/**/
+mod_export int
+tcp_shutdown(Tcp_session sess)
+{
+ if (sess && sess->fd != -1)
+ {
+ if (sess->flags & ZTCP_LISTEN) {
+ zwarn("can't shutdown a listening socket");
+ return 1;
+ }
+ if (sess->flags & ZTCP_SHUTDOWN) {
+ zwarn("session is already shutdown");
+ return 1;
+ }
+ int err = shutdown(sess->fd, SHUT_WR);
+ if (err)
+ zwarn("shutdown failed: %e", errno);
+ sess->flags |= ZTCP_SHUTDOWN;
+ return 0;
+ }
+ return -1;
+}
+
static void
tcp_cleanup(void)
{
@@ -366,6 +389,22 @@ bin_ztcp(char *nam, char **args, Options ops, UNUSED(int func))
}
}
+ if (OPT_ISSET(ops,'s')) {
+ targetfd = atoi(args[0]);
+ if(!targetfd) {
+ zwarnnam(nam, "%s is an invalid argument to -s", args[0]);
+ return 1;
+ }
+
+ sess = zts_byfd(targetfd);
+ if (sess) {
+ tcp_shutdown(sess);
+ return 0;
+ } else {
+ zwarnnam(nam, "fd %s not found in tcp table", args[0]);
+ return 1;
+ }
+ }
if (OPT_ISSET(ops,'c')) {
if (!args[0]) {
@@ -373,12 +412,12 @@ bin_ztcp(char *nam, char **args, Options ops, UNUSED(int func))
}
else {
targetfd = atoi(args[0]);
- sess = zts_byfd(targetfd);
if(!targetfd) {
zwarnnam(nam, "%s is an invalid argument to -c", args[0]);
return 1;
}
+ sess = zts_byfd(targetfd);
if (sess)
{
if ((sess->flags & ZTCP_ZFTP) && !force)
@@ -594,6 +633,8 @@ bin_ztcp(char *nam, char **args, Options ops, UNUSED(int func))
schar = 'Z';
else if (sess->flags & ZTCP_LISTEN)
schar = 'L';
+ else if (sess->flags & ZTCP_SHUTDOWN)
+ schar = 'S';
else if (sess->flags & ZTCP_INBOUND)
schar = 'I';
else
@@ -606,7 +647,8 @@ bin_ztcp(char *nam, char **args, Options ops, UNUSED(int func))
printf("%s:%d %s %s:%d is on fd %d%s\n",
localname, ntohs(sess->sock.in.sin_port),
((sess->flags & ZTCP_LISTEN) ? "-<" :
- ((sess->flags & ZTCP_INBOUND) ? "<-" : "->")),
+ ((sess->flags & ZTCP_SHUTDOWN) ? "<>" :
+ ((sess->flags & ZTCP_INBOUND) ? "<-" : "->"))),
remotename, ntohs(sess->peer.in.sin_port),
sess->fd,
(sess->flags & ZTCP_ZFTP) ? " ZFTP" : "");
@@ -696,7 +738,7 @@ bin_ztcp(char *nam, char **args, Options ops, UNUSED(int func))
}
static struct builtin bintab[] = {
- BUILTIN("ztcp", 0, bin_ztcp, 0, 3, 0, "acd:flLtv", NULL),
+ BUILTIN("ztcp", 0, bin_ztcp, 0, 3, 0, "acd:flLstv", NULL),
};
static struct features module_features = {
diff --git a/Src/Modules/tcp.h b/Src/Modules/tcp.h
index f69e246e0..a9ea7df2d 100644
--- a/Src/Modules/tcp.h
+++ b/Src/Modules/tcp.h
@@ -81,8 +81,9 @@ union tcp_sockaddr {
typedef struct tcp_session *Tcp_session;
-#define ZTCP_LISTEN 1
-#define ZTCP_INBOUND 2
+#define ZTCP_LISTEN 1
+#define ZTCP_INBOUND 2
+#define ZTCP_SHUTDOWN 4
#define ZTCP_ZFTP 16
struct tcp_session {