https://github.com/python/cpython/commit/1617cbe1dc16db302d708c321b7b0a1579a382c9
commit: 1617cbe1dc16db302d708c321b7b0a1579a382c9
branch: main
author: Sam Bull <[email protected]>
committer: kumaraditya303 <[email protected]>
date: 2026-08-03T21:12:16+05:30
summary:
gh-94984: Add mode parameter to asyncio create_unix_server() (#155086)
files:
A Misc/NEWS.d/next/Library/2026-07-27-12-00-00.gh-issue-94984.Xr3vFq.rst
M Doc/library/asyncio-eventloop.rst
M Doc/library/asyncio-stream.rst
M Doc/whatsnew/3.16.rst
M Lib/asyncio/events.py
M Lib/asyncio/unix_events.py
M Lib/test/test_asyncio/test_unix_events.py
diff --git a/Doc/library/asyncio-eventloop.rst
b/Doc/library/asyncio-eventloop.rst
index d24c8420ef8920..41abb2d7d0a53e 100644
--- a/Doc/library/asyncio-eventloop.rst
+++ b/Doc/library/asyncio-eventloop.rst
@@ -838,7 +838,7 @@ Creating network servers
*, sock=None, backlog=100, ssl=None, \
ssl_handshake_timeout=None, \
ssl_shutdown_timeout=None, \
- start_serving=True, cleanup_socket=True)
+ start_serving=True, cleanup_socket=True, mode=None)
:async:
Similar to :meth:`loop.create_server` but works with the
@@ -853,6 +853,13 @@ Creating network servers
be removed from the filesystem when the server is closed, unless the
socket has been replaced after the server has been created.
+ If *mode* is not ``None``, the permissions of the socket file created
+ for *path* are changed to *mode* (as accepted by :func:`os.chmod`)
+ right after binding, before the server starts accepting connections,
+ so a connection can never be accepted while the default,
+ umask-derived permissions are still in effect. *mode* cannot be
+ combined with *sock* and is not supported for abstract Unix sockets.
+
See the documentation of the :meth:`loop.create_server` method
for information about arguments to this method.
@@ -871,6 +878,10 @@ Creating network servers
Added the *cleanup_socket* parameter.
+ .. versionchanged:: 3.16
+
+ Added the *mode* parameter.
+
.. method:: loop.connect_accepted_socket(protocol_factory, \
sock, *, ssl=None, ssl_handshake_timeout=None, \
diff --git a/Doc/library/asyncio-stream.rst b/Doc/library/asyncio-stream.rst
index 05445219510ca5..4092f440f66ad3 100644
--- a/Doc/library/asyncio-stream.rst
+++ b/Doc/library/asyncio-stream.rst
@@ -171,7 +171,8 @@ and work with streams:
.. function:: start_unix_server(client_connected_cb, path=None, \
*, limit=None, sock=None, backlog=100, ssl=None, \
ssl_handshake_timeout=None, \
- ssl_shutdown_timeout=None, start_serving=True,
cleanup_socket=True)
+ ssl_shutdown_timeout=None, start_serving=True, \
+ cleanup_socket=True, mode=None)
:async:
Start a Unix socket server.
@@ -182,6 +183,9 @@ and work with streams:
be removed from the filesystem when the server is closed, unless the
socket has been replaced after the server has been created.
+ If *mode* is not ``None``, the permissions of the Unix socket file
+ are set to *mode* before the server starts accepting connections.
+
See also the documentation of :meth:`loop.create_unix_server`.
.. note::
@@ -205,6 +209,9 @@ and work with streams:
.. versionchanged:: 3.13
Added the *cleanup_socket* parameter.
+ .. versionchanged:: 3.16
+ Added the *mode* parameter.
+
StreamReader
============
diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst
index 6e69737768d5e1..c607e3c620572f 100644
--- a/Doc/whatsnew/3.16.rst
+++ b/Doc/whatsnew/3.16.rst
@@ -95,6 +95,15 @@ New modules
Improved modules
================
+asyncio
+-------
+
+* Add the *mode* parameter to :meth:`asyncio.loop.create_unix_server` and
+ :func:`asyncio.start_unix_server` to set the permissions of the Unix
+ socket file created for *path*.
+ (Contributed by Sam Bull in :gh:`94984`.)
+
+
codecs
------
diff --git a/Lib/asyncio/events.py b/Lib/asyncio/events.py
index 807c70bc775aa2..6b2d34e733a6b1 100644
--- a/Lib/asyncio/events.py
+++ b/Lib/asyncio/events.py
@@ -451,7 +451,7 @@ async def create_unix_server(
sock=None, backlog=100, ssl=None,
ssl_handshake_timeout=None,
ssl_shutdown_timeout=None,
- start_serving=True):
+ start_serving=True, mode=None):
"""A coroutine which creates a UNIX Domain Socket server.
The return value is a Server object, which can be used to stop
@@ -480,6 +480,10 @@ async def create_unix_server(
the user should await Server.start_serving() or
Server.serve_forever() to make the server to start accepting
connections.
+
+ mode, if not None, is applied to the socket file created for
+ path with os.chmod() after binding and before the server
+ starts accepting connections.
"""
raise NotImplementedError
diff --git a/Lib/asyncio/unix_events.py b/Lib/asyncio/unix_events.py
index 94c5fb726e5933..3a66cee93da4f5 100644
--- a/Lib/asyncio/unix_events.py
+++ b/Lib/asyncio/unix_events.py
@@ -276,7 +276,7 @@ async def create_unix_server(
sock=None, backlog=100, ssl=None,
ssl_handshake_timeout=None,
ssl_shutdown_timeout=None,
- start_serving=True, cleanup_socket=True):
+ start_serving=True, cleanup_socket=True, mode=None):
if isinstance(ssl, bool):
raise TypeError('ssl argument must be an SSLContext or None')
@@ -294,6 +294,9 @@ async def create_unix_server(
'path and sock can not be specified at the same time')
path = os.fspath(path)
+ if mode is not None and path and path[0] in (0, '\x00'):
+ raise ValueError(
+ 'mode is not supported for abstract sockets')
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
# Check for abstract socket. `str` and `bytes` paths are supported.
@@ -322,11 +325,26 @@ async def create_unix_server(
except:
sock.close()
raise
+
+ if mode is not None:
+ # The socket cannot accept connections until listen() is
+ # called, which happens later in Server._start_serving(),
+ # so no connection can be accepted while the socket still
+ # has the default permissions.
+ try:
+ os.chmod(path, mode)
+ except:
+ sock.close()
+ raise
else:
if sock is None:
raise ValueError(
'path was not specified, and no sock specified')
+ if mode is not None:
+ raise ValueError(
+ 'mode is only meaningful with path')
+
if (sock.family != socket.AF_UNIX or
sock.type != socket.SOCK_STREAM):
raise ValueError(
diff --git a/Lib/test/test_asyncio/test_unix_events.py
b/Lib/test/test_asyncio/test_unix_events.py
index e88437eb2337ff..c383a3bff962d7 100644
--- a/Lib/test/test_asyncio/test_unix_events.py
+++ b/Lib/test/test_asyncio/test_unix_events.py
@@ -411,6 +411,57 @@ def test_create_unix_server_bind_error(self, m_socket):
self.loop.run_until_complete(coro)
self.assertTrue(sock.close.called)
+ @socket_helper.skip_unless_bind_unix_socket
+ def test_create_unix_server_mode(self):
+ # Two distinct modes: whatever the umask, at most one of them
+ # can coincide with the default permissions, so a no-op chmod
+ # cannot pass both subtests.
+ for mode in (0o600, 0o644):
+ with self.subTest(mode=mode):
+ with test_utils.unix_socket_path() as path:
+ srv = self.loop.run_until_complete(
+ self.loop.create_unix_server(
+ lambda: None, path, mode=mode))
+ try:
+ self.assertEqual(
+ stat.S_IMODE(os.stat(path).st_mode), mode)
+ finally:
+ srv.close()
+ self.loop.run_until_complete(srv.wait_closed())
+
+ def test_create_unix_server_mode_sock(self):
+ sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ with sock:
+ coro = self.loop.create_unix_server(lambda: None, path=None,
+ sock=sock, mode=0o600)
+ with self.assertRaisesRegex(ValueError,
+ 'mode is only meaningful with path'):
+ self.loop.run_until_complete(coro)
+
+ def test_create_unix_server_mode_abstract(self):
+ # The check is a pure string test, so it runs on all platforms.
+ for path in ('\x00spam', b'\x00spam'):
+ with self.subTest(path=path):
+ coro = self.loop.create_unix_server(lambda: None, path,
+ mode=0o600)
+ with self.assertRaisesRegex(
+ ValueError, 'mode is not supported for abstract'):
+ self.loop.run_until_complete(coro)
+
+ @mock.patch('asyncio.unix_events.socket')
+ def test_create_unix_server_chmod_error(self, m_socket):
+ # Ensure that the socket is closed when os.chmod() fails
+ sock = mock.Mock()
+ m_socket.socket.return_value = sock
+
+ with mock.patch('asyncio.unix_events.os.chmod',
+ side_effect=PermissionError):
+ coro = self.loop.create_unix_server(lambda: None, path='/test',
+ mode=0o600)
+ with self.assertRaises(PermissionError):
+ self.loop.run_until_complete(coro)
+ self.assertTrue(sock.close.called)
+
def test_create_unix_connection_path_sock(self):
coro = self.loop.create_unix_connection(
lambda: None, os.devnull, sock=object())
diff --git
a/Misc/NEWS.d/next/Library/2026-07-27-12-00-00.gh-issue-94984.Xr3vFq.rst
b/Misc/NEWS.d/next/Library/2026-07-27-12-00-00.gh-issue-94984.Xr3vFq.rst
new file mode 100644
index 00000000000000..648287680cf156
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-07-27-12-00-00.gh-issue-94984.Xr3vFq.rst
@@ -0,0 +1,4 @@
+Add the *mode* parameter to :meth:`asyncio.loop.create_unix_server` and
+:func:`asyncio.start_unix_server` to set the permissions of the Unix
+socket file created for *path*, applied before the server starts
+accepting connections.
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]