Skip to content

Commit 7ae926b

Browse files
committed
ansible: prevent tempfile.mkstemp() leaks.
This avoids a leak present in Ansible 2.7.0..current HEAD, and all similar leaks. See ansible/ansible#57327.
1 parent 2f05b93 commit 7ae926b

File tree

1 file changed

+41
-0
lines changed

1 file changed

+41
-0
lines changed

ansible_mitogen/runner.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,45 @@ def shlex_split(s, comments=False):
112112
for token in shlex.split(str(s), comments=comments)]
113113

114114

115+
class TempFileWatcher(object):
116+
"""
117+
Since Ansible 2.7.0, lineinfile leaks file descriptors returned by
118+
:func:`tempfile.mkstemp` (ansible/ansible#57327). Handle this and all
119+
similar cases by recording descriptors produced by mkstemp during module
120+
execution, and cleaning up any leaked descriptors on completion.
121+
"""
122+
def __init__(self):
123+
self._real_mkstemp = tempfile.mkstemp
124+
# (fd, st.st_dev, st.st_ino)
125+
self._fd_dev_inode = []
126+
tempfile.mkstemp = self._wrap_mkstemp
127+
128+
def _wrap_mkstemp(self, *args, **kwargs):
129+
fd, path = self._real_mkstemp(*args, **kwargs)
130+
st = os.fstat(fd)
131+
self._fd_dev_inode.append((fd, st.st_dev, st.st_ino))
132+
return fd, path
133+
134+
def revert(self):
135+
tempfile.mkstemp = self._real_mkstemp
136+
for tup in self._fd_dev_inode:
137+
self._revert_one(*tup)
138+
139+
def _revert_one(self, fd, st_dev, st_ino):
140+
try:
141+
st = os.fstat(fd)
142+
except OSError:
143+
# FD no longer exists.
144+
return
145+
146+
if not (st.st_dev == st_dev and st.st_ino == st_ino):
147+
# FD reused.
148+
return
149+
150+
LOG.info("a tempfile.mkstemp() FD was leaked during the last task")
151+
os.close(fd)
152+
153+
115154
class EnvironmentFileWatcher(object):
116155
"""
117156
Usually Ansible edits to /etc/environment and ~/.pam_environment are
@@ -803,6 +842,7 @@ def setup(self):
803842
# module, but this has never been a bug report. Instead act like an
804843
# interpreter that had its script piped on stdin.
805844
self._argv = TemporaryArgv([''])
845+
self._temp_watcher = TempFileWatcher()
806846
self._importer = ModuleUtilsImporter(
807847
context=self.service_context,
808848
module_utils=self.module_map['custom'],
@@ -818,6 +858,7 @@ def _revert_excepthook(self):
818858

819859
def revert(self):
820860
self.atexit_wrapper.revert()
861+
self._temp_watcher.revert()
821862
self._argv.revert()
822863
self._stdio.revert()
823864
self._revert_excepthook()

0 commit comments

Comments
 (0)