From 5d09364edc1816b3e1e4b5ee0db222027881a1b5 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 10 Jan 2024 15:31:55 +0200 Subject: [PATCH] gh-66515: Fix locking of an MH mailbox without ".mh_sequences" file (GH-113482) Guarantee that it either open an existing ".mh_sequences" file or create a new ".mh_sequences" file, but do not replace existing ".mh_sequences" file. --- Lib/mailbox.py | 19 +++++++++++++++++-- Lib/test/test_mailbox.py | 9 +++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/Lib/mailbox.py b/Lib/mailbox.py index 0e1d49b399d0776..81ea210cf815a48 100644 --- a/Lib/mailbox.py +++ b/Lib/mailbox.py @@ -1141,10 +1141,24 @@ def __len__(self): """Return a count of messages in the mailbox.""" return len(list(self.iterkeys())) + def _open_mh_sequences_file(self, text): + mode = '' if text else 'b' + kwargs = {'encoding': 'ASCII'} if text else {} + path = os.path.join(self._path, '.mh_sequences') + while True: + try: + return open(path, 'r+' + mode, **kwargs) + except FileNotFoundError: + pass + try: + return open(path, 'x+' + mode, **kwargs) + except FileExistsError: + pass + def lock(self): """Lock the mailbox.""" if not self._locked: - self._file = open(os.path.join(self._path, '.mh_sequences'), 'rb+') + self._file = self._open_mh_sequences_file(text=False) _lock_file(self._file) self._locked = True @@ -1225,8 +1239,9 @@ def get_sequences(self): def set_sequences(self, sequences): """Set sequences using the given name-to-key-list dictionary.""" - f = open(os.path.join(self._path, '.mh_sequences'), 'w', encoding='ASCII') + f = self._open_mh_sequences_file(text=True) try: + os.close(os.open(f.name, os.O_WRONLY | os.O_TRUNC)) for name, keys in sequences.items(): if len(keys) == 0: continue diff --git a/Lib/test/test_mailbox.py b/Lib/test/test_mailbox.py index 8c350eb02ccc179..d84faad0eb34069 100644 --- a/Lib/test/test_mailbox.py +++ b/Lib/test/test_mailbox.py @@ -1360,6 +1360,15 @@ def test_no_dot_mh_sequences_file(self): box.set_sequences({}) self.assertEqual(os.listdir(path), ['.mh_sequences']) + def test_lock_unlock_no_dot_mh_sequences_file(self): + path = os.path.join(self._path, 'foo.bar') + os.mkdir(path) + box = self._factory(path) + self.assertEqual(os.listdir(path), []) + box.lock() + box.unlock() + self.assertEqual(os.listdir(path), ['.mh_sequences']) + def test_issue2625(self): msg0 = mailbox.MHMessage(self._template % 0) msg0.add_sequence('foo')