Skip to content

Commit 84f7020

Browse files
committed
emit a warning if system-wide temporary directory is used
1 parent 453fc7b commit 84f7020

File tree

2 files changed

+70
-28
lines changed

2 files changed

+70
-28
lines changed

Lib/multiprocessing/util.py

Lines changed: 67 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from . import process
2020

2121
__all__ = [
22-
'sub_debug', 'debug', 'info', 'sub_warning', 'get_logger',
22+
'sub_debug', 'debug', 'info', 'sub_warning', 'warn', 'get_logger',
2323
'log_to_stderr', 'get_temp_dir', 'register_after_fork',
2424
'is_exiting', 'Finalize', 'ForkAwareThreadLock', 'ForkAwareLocal',
2525
'close_all_fds_except', 'SUBDEBUG', 'SUBWARNING',
@@ -34,6 +34,7 @@
3434
DEBUG = 10
3535
INFO = 20
3636
SUBWARNING = 25
37+
WARNING = 30
3738

3839
LOGGER_NAME = 'multiprocessing'
3940
DEFAULT_LOGGING_FORMAT = '[%(levelname)s/%(processName)s] %(message)s'
@@ -53,6 +54,10 @@ def info(msg, *args):
5354
if _logger:
5455
_logger.log(INFO, msg, *args, stacklevel=2)
5556

57+
def warn(msg, *args):
58+
if _logger:
59+
_logger.log(WARNING, msg, *args, stacklevel=2)
60+
5661
def sub_warning(msg, *args):
5762
if _logger:
5863
_logger.log(SUBWARNING, msg, *args, stacklevel=2)
@@ -121,17 +126,17 @@ def is_abstract_socket_namespace(address):
121126
# Function returning a temp directory which will be removed on exit
122127
#
123128

124-
# Maximum length of a socket file path is usually between 92 and 108 [1].
125-
# BSD-based operating systems usually use 104 (OpenBSD, FreeBSD, macOS)
126-
# and Linux uses 108 [2].
129+
# Maximum length of a socket file path is usually between 92 and 108 [1],
130+
# but Linux is known to use a size of 108 [2]. BSD-based systems usually
131+
# use a size of 104 or 108 and Windows does not create AF_UNIX sockets.
127132
#
128133
# [1]: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/sys_un.h.html
129134
# [2]: https://man7.org/linux/man-pages/man7/unix.7.html.
130135

131136
if sys.platform == 'linux':
132-
_SUN_PATH_MAX = 104
133-
elif sys.platform.startswith(('openbsd', 'freebsd')):
134137
_SUN_PATH_MAX = 108
138+
elif sys.platform.startswith(('openbsd', 'freebsd')):
139+
_SUN_PATH_MAX = 104
135140
else:
136141
# On Windows platforms, we do not create AF_UNIX sockets.
137142
_SUN_PATH_MAX = None if os.name == 'nt' else 92
@@ -145,31 +150,67 @@ def _remove_temp_dir(rmtree, tempdir):
145150
if current_process is not None:
146151
current_process._config['tempdir'] = None
147152

153+
def _get_base_temp_dir(tempfile):
154+
"""Get a temporary directory where socket files will be created.
155+
156+
To prevent additional imports, pass a pre-imported 'tempfile' module.
157+
"""
158+
if os.name == 'nt':
159+
return None
160+
# Most of the time, the default temporary directory is /tmp. Thus,
161+
# listener sockets files "$TMPDIR/pymp-XXXXXXXX/sock-XXXXXXXX" do
162+
# not have a path length exceeding SUN_PATH_MAX.
163+
#
164+
# If users specify their own temporary directory, we may be unable
165+
# to create those files. Therefore, we fall back to the system-wide
166+
# temporary directory /tmp, assumed to exist on POSIX systems.
167+
#
168+
# See https://github.com/python/cpython/issues/132124.
169+
base_tempdir = tempfile.gettempdir()
170+
# Files created in a temporary directory are suffixed by a string
171+
# generated by tempfile._RandomNameSequence, which, by design,
172+
# is 8 characters long.
173+
#
174+
# Thus, the length of socket filename will be:
175+
#
176+
# len(base_tempdir + '/pymp-XXXXXXXX' + '/sock-XXXXXXXX')
177+
sun_path_len = len(base_tempdir) + 14 + 14
178+
if sun_path_len <= _SUN_PATH_MAX:
179+
return base_tempdir
180+
# Fallback to the default system-wide temporary directory.
181+
# This ignores user-defined environment variables.
182+
#
183+
# On POSIX systems, /tmp MUST be writable by any application [1].
184+
# We however emit a warning if this is not the case to prevent
185+
# obscure errors later in the execution.
186+
#
187+
# On some legacy systems, /var/tmp and /usr/tmp can be present
188+
# and will be used instead.
189+
#
190+
# [1]: https://refspecs.linuxfoundation.org/FHS_3.0/fhs/ch03s18.html
191+
dirlist = ['/tmp', '/var/tmp', '/usr/tmp']
192+
try:
193+
base_system_tempdir = tempfile._get_default_tempdir(dirlist)
194+
except FileNotFoundError:
195+
warn("Process-wide temporary directory %r will not be usable for "
196+
"creating socket files and no usable system-wide temporary "
197+
"directory was found in %r", base_tempdir, dirlist)
198+
# At this point, the system-wide temporary directory is not usable
199+
# but we may assume that the user-defined one is, even if we will
200+
# not be able to write socket files out there.
201+
return base_tempdir
202+
warn("Ignoring user-defined temporary directory: %s", base_tempdir)
203+
# at most max(map(len, dirlist)) + 14 + 14 = 36 characters
204+
assert len(base_system_tempdir) + 14 + 14 <= _SUN_PATH_MAX
205+
return base_system_tempdir
206+
148207
def get_temp_dir():
149208
# get name of a temp directory which will be automatically cleaned up
150209
tempdir = process.current_process()._config.get('tempdir')
151210
if tempdir is None:
152211
import shutil, tempfile
153-
if os.name == 'nt':
154-
tempdir = tempfile.mkdtemp(prefix='pymp-')
155-
else:
156-
# Most of the time, the root temporary directory is /tmp, and thus
157-
# listener sockets files "$TMPDIR/pymp-XXXXXXXX/sock-XXXXXXXX"
158-
# do not have a path length exceeding SUN_PATH_MAX.
159-
#
160-
# If users specify their own temporary directory, we may be unable
161-
# to create those files. Therefore, we fall back to the system-wide
162-
# temporary directory /tmp, assumed to exist on POSIX systems.
163-
#
164-
# See https://github.com/python/cpython/issues/132124.
165-
base_tempdir = tempfile.gettempdir()
166-
# len(base_tempdir) + len('/pymp-XXXXXXXX') + len('/sock-XXXXXXXX')
167-
sun_path_len = len(base_tempdir) + 14 + 14
168-
if sun_path_len > _SUN_PATH_MAX:
169-
# fallback to the system-wide temporary directory,
170-
# ignoring environment variables.
171-
base_tempdir = '/tmp'
172-
tempdir = tempfile.mkdtemp(prefix='pymp-', dir=base_tempdir)
212+
base_tempdir = _get_base_temp_dir(tempfile)
213+
tempdir = tempfile.mkdtemp(prefix='pymp-', dir=base_tempdir)
173214
info('created temp directory %s', tempdir)
174215
# keep a strong reference to shutil.rmtree(), since the finalizer
175216
# can be called late during Python shutdown

Lib/tempfile.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ def _candidate_tempdir_list():
180180

181181
return dirlist
182182

183-
def _get_default_tempdir():
183+
def _get_default_tempdir(dirlist=None):
184184
"""Calculate the default directory to use for temporary files.
185185
This routine should be called exactly once.
186186
@@ -190,7 +190,8 @@ def _get_default_tempdir():
190190
service, the name of the test file must be randomized."""
191191

192192
namer = _RandomNameSequence()
193-
dirlist = _candidate_tempdir_list()
193+
if dirlist is None:
194+
dirlist = _candidate_tempdir_list()
194195

195196
for dir in dirlist:
196197
if dir != _os.curdir:

0 commit comments

Comments
 (0)