From e35b571196facf6c30c6c9c05b1f4eea2817d336 Mon Sep 17 00:00:00 2001 From: Ken Dreyer Date: Aug 27 2020 11:36:27 +0000 Subject: util: handle ENOENT in _stripcwd() listdir loop After we call listdir(), another thread or process might delete files from the current directory. We cannot always expect lstat() to succeed in a loop over the initial listdir entries. If lstat() raises ENOENT, consider this file deleted and move on. --- diff --git a/koji/util.py b/koji/util.py index 1d4da3b..c7203d1 100644 --- a/koji/util.py +++ b/koji/util.py @@ -23,6 +23,7 @@ from __future__ import absolute_import, division import base64 import calendar import datetime +import errno import hashlib import logging import os @@ -472,7 +473,12 @@ def _stripcwd(dev): """Unlink all files in cwd and return list of subdirs""" dirs = [] for fn in os.listdir('.'): - st = os.lstat(fn) + try: + st = os.lstat(fn) + except OSError as e: + if e.errno == errno.ENOENT: + continue + raise if st.st_dev != dev: # don't cross fs boundary continue diff --git a/tests/test_lib/test_utils.py b/tests/test_lib/test_utils.py index c252ee0..553b44c 100644 --- a/tests/test_lib/test_utils.py +++ b/tests/test_lib/test_utils.py @@ -1,6 +1,7 @@ # coding=utf-8 from __future__ import absolute_import import calendar +import errno import locale import mock import optparse @@ -1424,6 +1425,24 @@ class TestRmtree(unittest.TestCase): isdir.assert_has_calls([call('mode'), call('mode')]) lstat.assert_has_calls([call('a'), call('b')]) + @patch('os.listdir') + @patch('os.lstat') + @patch('stat.S_ISDIR') + @patch('os.unlink') + def test_stripcwd_stat_fail(dev, unlink, isdir, lstat, listdir): + # something else deletes a file in the middle of _stripcwd() + dev = 'dev' + listdir.return_value = ['will-not-exist.txt'] + lstat.side_effect = OSError(errno.ENOENT, 'No such file or directory') + + koji.util._stripcwd(dev) + + listdir.assert_called_once_with('.') + lstat.assert_called_once_with('will-not-exist.txt') + unlink.assert_not_called() + isdir.assert_not_called() + + class TestMoveAndSymlink(unittest.TestCase): @mock.patch('koji.ensuredir') @mock.patch('koji.util.safer_move')